Skip to content

Commit 1a059b4

Browse files
committed
phase0/live_e2e: dual-lane e2e skills — cross-lane triage tree + lane-took-effect gate, UI-monitor watchdog subagent, UC1-UC3 buyer use-cases, final-decisions slim-down
e2e-loop: lane-neutral spine (--provider deepseek|claude, argument-hint); four-exit cross-lane triage tree (env/infra/general/lane-specific; re-run once, diagnosis only, never switch lanes mid-run); lane-took-effect ledger gate (mistyped provider env silently defaults to deepseek); AUTOBROKER_AGENT_PROVIDER + CLAUDE_CODE_OAUTH_TOKEN in the run envelope; #1244 checks lane-A-scoped with lane-B structurally-exempt counterpart (+$schema-strip surface); NEW references/ui-monitor.md (checkpoint watchdog: deterministic layout sweep + generous full-page screenshots + console harvest; whitelist/blacklist incl. mode-toggle + any mutating route); UC1 spine named, UC2 ActiveSlotConflict one-time boundary evaluation, UC3 same-metro tri-competitor cross-shop; known-correct list compressed to behavior/re-flag/commit; 06-29 incident narratives collapsed to final rules; dealer actor stays operator-subscription (stale 'Agent SDK requires api key' claim removed). e2e-evolve: argument-hint; golden rule + pointer to the runner tree; provider-aware credential + same-lane re-verify wording; seasoning first-round seed list retired; #1244 seasoning lane-A-scoped. Gates: RUN_UI_FUNCTIONAL=1 green.sh GREEN; 5 task reviews + final whole-branch review (C1 pricing_source predicate fixed) + safety-invariant audit SAFE.
1 parent b89ade8 commit 1a059b4

13 files changed

Lines changed: 479 additions & 375 deletions

File tree

.claude/skills/e2e-evolve/SKILL.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ description: The companion to `/e2e-loop`. Run it in a FRESH session AFTER one o
1010
Pass `--provider claude|deepseek` (default `deepseek`) to re-verify fixes against the
1111
same LLM lane the finding came from — `claude` runs the fresh serve-live re-verify on
1212
the Claude OAuth subscription lane (lane B), `deepseek` on the DeepSeek API-key lane.
13+
argument-hint: "[--provider deepseek|claude]"
1314
disable-model-invocation: true
1415
---
1516

@@ -81,10 +82,12 @@ never left half-done.
8182
ISOLATED to that lane. Re-run the failing skill on the other provider
8283
(`AUTOBROKER_AGENT_PROVIDER`): if BOTH lanes fail, the cause is general / environmental and
8384
the fix is provider-agnostic (or none, when it is host thrash); only a fault that reproduces
84-
on one lane AND clears on the other earns a lane-scoped fix. **No fallback method ships
85-
without a met live case** — a merely-hypothesised failure mode is a *discovery* job (a
86-
seasoned candidate to go find it, `references/seasoning.md`), never pre-emptively-shipped
87-
code nor an "assert this someday" probe baked into the runner docs.
85+
on one lane AND clears on the other earns a lane-scoped fix. The runner's four-exit triage
86+
tree (`e2e-loop` SKILL.md) is the shared classification procedure — the re-run is a one-shot
87+
diagnostic, and a fix re-verifies on the SAME lane the finding came from. **No fallback
88+
method ships without a met live case** — a merely-hypothesised failure mode is a *discovery*
89+
job (a seasoned candidate to go find it, `references/seasoning.md`), never
90+
pre-emptively-shipped code nor an "assert this someday" probe baked into the runner docs.
8891
- **Never** set `AUTOBROKER_TEST_AUTO_APPROVE`; never touch a production DB; isolated
8992
throwaway data-dir (CLAUDE.md inv #11). The 12 safety invariants hold — your fixes
9093
strengthen the floor, never lower it.

.claude/skills/e2e-evolve/references/evolving.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@ product, ask what the runner missed and close that gap:
5757

5858
- **A blind spot** — a skill "completed but couldn't be verified" because no row / route /
5959
audit / committed `data-testid` exposed its result → add that verification surface, in
60-
the **test host** (`serve-live.mjs` control routes, committed testids), never the product
61-
`/api` layer. A new check must **generalize to the next random buyer** (the brand-picker
62-
randomness is the held-out set), not over-fit this run's metro.
60+
the **test host** (`serve-live.mjs` control routes, committed testids, or a ui-monitor
61+
checkpoint/sweep when a UI defect was missed because no checkpoint covered it), never the
62+
product `/api` layer. A new check must **generalize to the next random buyer** (the
63+
brand-picker randomness is the held-out set), not over-fit this run's metro.
6364
- **A stale detail** — a drifted testid, a removed route, a changed threshold, a persona
6465
that no longer triggers its edge → refresh the relevant `/e2e-loop` reference so the next
6566
run doesn't trip on it.

.claude/skills/e2e-evolve/references/fix-machine.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ parallel; any file overlap runs **serial** (a later subagent reads the prior edi
1212
## Fix (step 4a) — edits ONLY inside the worktree
1313

1414
Work in a fresh worktree off `origin/main` (copy `.env` in — it is gitignored, so a fresh
15-
worktree has none, and serve-live needs the DeepSeek key). The fixer applies the **minimal**
15+
worktree has none, and serve-live needs the run lane's credential — `DEEPSEEK_API_KEY` /
16+
`CLAUDE_CODE_OAUTH_TOKEN` per `--provider`). The fixer applies the **minimal**
1617
change inside the worktree. `git status` MUST show **no out-of-worktree absolute-path
1718
writes** — a subagent writing to the main checkout's absolute path is a real, observed bug.
1819
Stage explicit paths only; leave unrelated worktree changes alone.
@@ -42,7 +43,9 @@ wall-clock lever, ~5–12 min/fix): restart serve-live in the **same worktree**
4243
up the new `pnpm -r build`, reusing the worktree's `node_modules` + prebuilt
4344
`better-sqlite3` — only `git worktree add` a fresh tree if the base diverged. The verdict
4445
is a `/__e2e/rows` / `/__e2e/audit` delta, **not** a screenshot. "Needs live verify" is
45-
never a defer reason — a fresh run is.
46+
never a defer reason — a fresh run is. Re-verify on the SAME provider lane the finding came
47+
from — export `AUTOBROKER_AGENT_PROVIDER=<lane>` before the fresh serve-live (`--provider`
48+
of this session).
4649

4750
### The fix → seasoned-case PAIRING rule (deterministic-AND-seasoned, never OR)
4851

.claude/skills/e2e-evolve/references/seasoning.md

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -75,36 +75,9 @@ routes the runner uses; record the live `/__e2e` deltas / DOM against its contra
7575
Record the round's WINNER/HARDENER/DUD tally in the evolve-report's **Seasoning coverage**
7676
section (`references/evolving.md` step 7).
7777

78-
## First candidates (seed the first discovery round with these)
79-
80-
Drawn from this round's research (the A4 / F-series seeds) — author them as seasoned cases:
81-
82-
**Messy-buyer axes** (adversarial-buyer):
83-
84-
- **trim-as-marketing-name** — the buyer names a trim by feel, not the spec name: "the one
85-
with leather and the big screen", "the Tech trim", "the sport-looking one". Stresses the
86-
intake trim-suggestion picker + `sanitizePrefillTrim` (a non-trim qualifier must NOT seed a
87-
bogus trim, the web-grounded picker must fire).
88-
- **budget-as-monthly / down / trade-equity** — "keep me under $450/mo", "I've got $5k down",
89-
"I still owe $4k on my trade". Stresses inv #9: a monthly/down/equity figure IS budget — it
90-
must NEVER render on any surface and must not silently become a profile budget field.
91-
- **contradict-mid-message** — "I want a RAV4… actually make it a CR-V, no wait the hybrid".
92-
Stresses the 2-active / mid-flow-correction path (`J3` / `E7`) and the profile-ASK picker.
93-
94-
**Adversarial-dealer behaviors** (adversarial-dealer):
95-
96-
- **wrong-vehicle-binding on the negotiation card** — a dealer reply that references a
97-
DIFFERENT model/trim than the profile's, or a shared CRM relay bound to ≥2 rooftops.
98-
Stresses same-source routing attribution (the negotiation card must not bind a quote to the
99-
wrong dealer; see the harvest-register `PIC-20260625-1/-2` routing items).
100-
- **HTML-only reply through the LIVE extract path** — a dealer reply that is HTML-only (no
101-
text/plain part) carrying an itemized OTD. Stresses the `stripHtmlToText` recovery + the
102-
`dealer_reply_extract` extraction so the OTD is not silently lost (the buyer-email-probe
103-
proved this is a real failure class; season it on the live extract path).
104-
- **budget-verbatim / self-contradicting OTD / competitor-name in the body** — a reply that
105-
embeds the buyer's budget verbatim, or quotes $42k then "$44k out the door", or names a
106-
competing dealer. Stresses the negotiation-summary `assertNoBudget` belt + the
107-
no-competing-name redaction + summary coherence (lens 14).
78+
Seed each round's generators from the CURRENT library (personas / journeys / router edges /
79+
archetypes), the new-feature surfaces this session touched, the known-correct list, and the
80+
harvest-register's realized tail. (The original first-round seed list is retired — its round ran.)
10881

10982
## Over-fit & flakiness guards (load-bearing)
11083

@@ -119,6 +92,8 @@ Drawn from this round's research (the A4 / F-series seeds) — author them as se
11992
run), prove #1244 recovery with a COUNTED forced fault rather than waiting for a rare organic
12093
malform.
12194
- **#1244 is request-shape/mixing-triggered, not content-triggered** (the 2026-06-04
122-
107-call probe): do NOT try to engineer a malformed tool call from a dealer email's content
123-
— a content payload cannot deterministically force #1244. Season the *recovery contract*
124-
(fail-closed-then-auto-recover, 2 deepseek rows, no retry button), not a content exploit.
95+
107-call probe), and is a **lane-A (deepseek) class** — lane B is structurally exempt
96+
(single structured call, Zod belt, no recovery hop). Do NOT try to engineer a malformed
97+
tool call from a dealer email's content — a content payload cannot deterministically force
98+
#1244. Season the lane-A *recovery contract* (fail-closed-then-auto-recover, 2 deepseek
99+
rows, no retry button), not a content exploit; on lane B season nothing here.

.claude/skills/e2e-loop/SKILL.md

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
---
22
name: e2e-loop
33
description: Run one manually-triggered, live end-to-end pass of the AutoBroker product
4-
as a real car buyer — drive all 17 skills against the real DeepSeek lane through
5-
serve-live + a Playwright browser, negotiate for real against resistant LLM dealers,
6-
cross-shop several cars at once, and RECORD every blocker / backlog gap / rough edge
7-
into an HTML report. The job is to reproduce the real buyer experience and expose its
8-
imperfections honestly — NOT to ship a perfect run. Fixing the recorded issues is the
9-
companion `e2e-evolve` skill's job (run it in a fresh session). Pass `--light` for a
10-
quick read-only sweep. Pass `--provider claude` to drive the WHOLE journey through the
11-
Claude OAuth subscription lane (lane B, the official Agent SDK) instead of the default
12-
`--provider deepseek` (the DeepSeek API-key lane). Use to run the live e2e / 全技能巡检 on demand.
4+
as a real car buyer — drive all 17 skills against the run's live LLM lane (default
5+
DeepSeek api-key; `--provider claude` = the Claude OAuth subscription lane, the
6+
official Agent SDK) through serve-live + a Playwright browser, negotiate for real
7+
against resistant LLM dealers, cross-shop several cars at once, and RECORD every
8+
blocker / backlog gap / rough edge into an HTML report. The job is to reproduce the
9+
real buyer experience and expose its imperfections honestly — NOT to ship a perfect
10+
run. Fixing the recorded issues is the companion `e2e-evolve` skill's job (run it in
11+
a fresh session). Pass `--light` for a quick read-only sweep. Use to run the live
12+
e2e / 全技能巡检 on demand.
13+
argument-hint: "[--light] [--provider deepseek|claude]"
1314
disable-model-invocation: true
1415
---
1516

@@ -44,6 +45,19 @@ ends with a full report; it does **not** need an empty backlog.
4445
write the HTML report ◄── write-back lessons
4546
```
4647

48+
## The three buyer use-cases
49+
50+
- **UC1 — single-profile pipeline** (every run): one buyer, one search profile, the full
51+
pipeline to a negotiated quote — this IS the pinned spine (steps 2–3).
52+
- **UC2 — same vehicle, different metro** (one-time boundary evaluation): the product
53+
holds one active profile per (account, make) — a second same-make intake trips
54+
`ActiveSlotConflict` with replace/supersede as the product path. Evaluated ONCE (see
55+
`references/multi-profile-lane.md` "UC2"), then graduated to the known-correct list;
56+
NOT an every-run probe.
57+
- **UC3 — same metro, competing models** (portfolio): 3 same-segment different-make
58+
searches in ONE metro, concurrent, one approval inbox, goal = best quote across the
59+
options — this IS step 4 (`references/multi-profile-lane.md`).
60+
4761
## Modes
4862

4963
- **Provider lane** (`--provider claude|deepseek`, default `deepseek`): which real LLM
@@ -65,6 +79,7 @@ ends with a full report; it does **not** need an empty backlog.
6579
already-running) serve-live. If its DB is empty, do a minimal intake first to seed one
6680
profile (this also exercises intake live). Light still writes a report; it just
6781
expects fewer findings (a clean read-only sweep may legitimately record zero).
82+
(`--light` still runs ONE final ui-monitor checkpoint.)
6883
- **Buyer-email probe** (optional, owner-run, separate from serve-live): validates the
6984
REAL Gmail I/O layer — OAuth/refresh, real MIME parse (incl. HTML-only recovery),
7085
attachment download, historyId — that the test-mode journey can never reach. It is a
@@ -88,20 +103,34 @@ ends with a full report; it does **not** need an empty backlog.
88103
for a full run** — pick a big metro + high-volume car) — else fail closed. An empty
89104
metro means a misconfigured `location_query` (see the Irvine fallback trap in
90105
`references/harness-boundaries.md`); do not drive a vacuous sweep.
106+
4. **Lane-took-effect verification:** after the FIRST LLM-bearing skill completes, read
107+
`test_run_records` in `<dataDir>/autobroker.db` and assert the rows match the
108+
requested lane (`--provider claude` → `provider='anthropic' AND
109+
pricing_source='subscription'`; `deepseek``provider='deepseek' AND
110+
pricing_source != 'subscription'` — lane A persists the dated pricing-snapshot
111+
label, or `'unavailable'` on a usage-missing row, never `'subscription'`).
112+
Mismatch = **STOP**: a mistyped
113+
`AUTOBROKER_AGENT_PROVIDER` value silently resolves to the DeepSeek default (exact
114+
strings `claude`/`deepseek` only), and a whole run on the wrong lane poisons every
115+
cross-lane conclusion.
91116

92117
## The journey
93118

94119
| step | what you do | how you know it worked | load when you reach it |
95120
|---|---|---|---|
96121
| 0 | read the last 1–2 reports, run the safety gate, `touch .claude/.e2e-loop-active` | keys/MCP present; listening line seen | this spine |
97-
| 1 | (full) fresh worktree off `origin/main` + better-sqlite3 rebuild + `pnpm -r build`, then start `pnpm e2e:serve-live`; pick a realistic buyer (metro, car, finance mode, persona) | build OK; `dataDir` recorded; geosearch ≥1 dealer (≥10 full) | `references/harness-boundaries.md`, `references/brand-picker.md` |
122+
| 1 | (full) fresh worktree off `origin/main` + better-sqlite3 rebuild + `pnpm -r build`, then start `pnpm e2e:serve-live`; pick a realistic buyer (metro, car, finance mode, persona) | build OK; `dataDir` recorded; geosearch ≥1 dealer (≥10 full) | `references/harness-boundaries.md`, `references/brand-picker.md`, `references/ui-monitor.md` |
98123
| 2 | live the journey: drive all 17 skills as the buyer — PASS-A in natural language, PASS-B by `/slash` — verifying each skill lands its data AND its UI | terminal skill row + table delta + the right Canvas panel; data-quality coverage (not just a row count) | `references/skill-pipeline.md` (+ `references/ui-lane-personas.md`) |
99124
| 3 | (full) negotiate for real: deep, multi-thread email negotiation against resistant LLM dealers — ≥10 dealers, front-runners driven to ≥4 rounds, with ghosting and manager escalation | front-runner threads reach ≥4 buyer rounds; ghosts drop after 2 unanswered; revised OTDs extracted | `references/dealer-brain.md` |
100125
| 4 | (full) cross-shop: run several searches at once (3 different-brand profiles) on the real scheduler — concurrent negotiation, a shared dealer both want, one shared approval inbox | scheduler cap holds; every profile reaches a terminal state; exactly one profile binds each shared dealer, losers voiced + zero send; no budget leak; nothing sent for real | `references/multi-profile-lane.md` |
101126
| 5 | judge the experience: run `frontend-taste` per data tab | a ranked usability findings list | `references/ui-lane-personas.md` (→ the `frontend-taste` skill by name) |
102127
| 6 | record everything: classify each imperfection (blocker / backlog / polish), capture telemetry, fix only safety + one-line blockers, write the HTML report + harvest-register | report sections present; ledger rebuilt | `references/recording.md` |
103128
| 7 | teardown: kill serve-live, remove the worktree, write the memory pointer, `rm .claude/.e2e-loop-active` | marker gone; memory pointer ≤200c | `references/recording.md` |
104129

130+
**UI-monitor checkpoints** (dispatch per `references/ui-monitor.md`, driver idle): after
131+
step 2 PASS-A · after step 2 PASS-B · after step 3 · after step 4 · at step 5 ·
132+
pre-teardown final. `--light` = exactly ONE final checkpoint.
133+
105134
**Step 4 ordering:** the single pinned-brand journey (steps 2 and 3) runs FIRST and
106135
reaches a terminal, healthy state before cross-shop (step 4) begins. Never run them
107136
concurrently — cross-shop is layered on top of a known-good single-brand pass.
@@ -128,17 +157,22 @@ rows — and a buyer then sees "0 recommendations". For `inventory_site_scan` an
128157
`dealer_reply_extract` the verdict is the **coverage** from `/__e2e/dataquality`, not the
129158
row count. (`references/harness-boundaries.md` has the exact thresholds.)
130159

131-
**Cross-provider isolation — a one-lane failure is not a lane verdict (general rule, both
132-
lanes).** When a skill misbehaves on the run's provider lane (a 0-yield scan, a malformed
133-
extract, a dropped field), do NOT conclude "this lane is broken" from that one run — *isolate*
134-
it first. Re-run that ONE skill on the OTHER provider (`AUTOBROKER_AGENT_PROVIDER` flipped,
135-
fresh dir): the same fault on both lanes = a general / product / environmental cause (fix it
136-
provider-agnostically); a fault on only one lane = a genuine lane-specific gap (record it for
137-
`e2e-evolve`). For a browser-fed 0-yield, read `/__e2e/dataquality … rendered_empty_count`
138-
FIRST — a blank render is host thrash, provider-independent, and needs no re-run. The browse
139-
path has no LLM in it, so it is byte-identical across providers; only the EXTRACTION is the
140-
lane. (Full procedure + the decisive controlled-extraction test:
141-
`references/harness-boundaries.md`.)
160+
**Cross-lane triage (the four-exit tree — general rule, both lanes).** A one-lane failure is not a lane
161+
verdict. When ANY skill misbehaves on the run's lane, classify before you blame:
162+
163+
0. Browser-fed 0-yield? Read /__e2e/dataquality `rendered_empty_count` FIRST —
164+
>0 = host thrash (environment; browse has no LLM in it), no re-run needed.
165+
1. Infra signal (429 / timeout / lane-B subprocess failure / credential)? An
166+
availability fault, not a product fault — re-run ONCE on the SAME lane to confirm.
167+
2. Anything else: re-run that ONE skill on the OTHER provider
168+
(`AUTOBROKER_AGENT_PROVIDER` flipped, fresh dir), AT MOST ONCE, as DIAGNOSIS:
169+
- fails on BOTH lanes → a general product/environment cause — fix provider-agnostically;
170+
- fails on ONE lane only → a genuine lane-specific gap — record it with its lane tag
171+
for `e2e-evolve` (which re-verifies its fix on the SAME lane).
172+
173+
Decoupling invariants: the journey NEVER switches lanes mid-run; a missing credential
174+
is a STOP, never a provider swap; the re-run is a diagnostic, never a retry-to-green.
175+
(Procedure detail + the decisive controlled-extraction test: `references/harness-boundaries.md`.)
142176

143177
## How to classify what you find (three buckets)
144178

@@ -235,8 +269,8 @@ headline.
235269
`cold` outcome, never a fabricated quote, never a failure on its own.
236270
- **Worktree needs `.env`.** `.env` is gitignored, so a fresh worktree has none — copy
237271
the main checkout's `.env` into the worktree after `git worktree add` (it stays
238-
gitignored, never staged), or serve-live reports "add your DeepSeek key" and the
239-
router 500s.
272+
gitignored, never staged). Without it the run has no lane credential: lane A reports
273+
"add your DeepSeek key" and the router 500s; lane B fails closed on every call.
240274
- **Self-contained `YYYY-MM-DD` HTML report**; `MEMORY.md` pointer ≤200 chars (detail
241275
lives in the topic file).
242276

@@ -251,5 +285,9 @@ headline.
251285
just the row count.
252286
- After each of the 3 irreversible sends, you confirmed the **fake** adapter fired (a
253287
positive check, not just the negative "no real send" counter).
288+
- UI-monitor checkpoints ran (all six on a full run; ONE final on `--light`) and their
289+
findings are folded into the three buckets with a `monitor` provenance tag.
290+
- The lane-took-effect check was recorded (provider + pricing_source noted in the
291+
report header).
254292
- All cross-session artifacts written: the report, the run-ledger row, the memory pointer.
255293
- `.claude/.e2e-loop-active` removed (on done AND on any abort).

.claude/skills/e2e-loop/references/brand-picker.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ To exercise `multiple_active_profiles` (edge behavior E7 in
9696
Cheapest path: mid-sweep the persona says "actually I want a RAV4 not a CR-V"
9797
(journey variation 3).
9898

99+
**UC3 cross-shop picks (step 4 only):** the two EXTRA profiles use the SAME
100+
metro as the spine pick and two DIFFERENT-make, same-segment competitors from
101+
the rotation pool (e.g. Camry spine → Accord + Mazda6). Different makes = no
102+
active-slot clash; one metro = the natural shared-rooftop world
103+
(`references/multi-profile-lane.md`).
104+
99105
---
100106

101107
## 6. Record picks before PASS-A (reproducibility anchor)
@@ -109,4 +115,5 @@ finance: <finance | lease | cash>
109115
persona: <P1–P9>
110116
email: <buyer email>
111117
2nd profile (opt): <year brand model>
118+
UC3 cross-shop (opt): <make model> + <make model> # same metro, same segment
112119
```

0 commit comments

Comments
 (0)