diff --git a/.planning/phases/09-user-profile-preferences-new/09-06-SUMMARY.md b/.planning/phases/09-user-profile-preferences-new/09-06-SUMMARY.md
new file mode 100644
index 0000000..2abb691
--- /dev/null
+++ b/.planning/phases/09-user-profile-preferences-new/09-06-SUMMARY.md
@@ -0,0 +1,177 @@
+---
+phase: 09-user-profile-preferences-new
+plan: "06"
+subsystem: admin-surfaces
+tags: [admin, notification-channels, event-keys, pipeline-executions, auth, phase-9]
+dependency_graph:
+ requires:
+ - "notification_channels.owner_user_id column (Plan 01 — migration 085)"
+ - "notify_event_keys table (Plan 01 — migration 086)"
+ - "pipeline_executions + pipeline_execution_steps tables (pre-existing pipeline engine)"
+ provides:
+ - "GET /api/notification-channels: requireAuth(), role-scoped, owner_email JOIN, ?owner=global|personal|all"
+ - "POST /api/notification-channels: requireAdmin(), owner_user_id column accepted"
+ - "GET/PUT/DELETE /api/notification-channels/[id]: requireAuth() + per-row (isAdmin || isOwner) authorization"
+ - "Owner badge on /admin/workflow/channels (Global vs Personal: {email}) + Show filter"
+ - "GET/POST /api/admin/notify-event-keys: admin-only list + create with key regex"
+ - "PUT/DELETE /api/admin/notify-event-keys/[key]: admin-only update + delete"
+ - "/admin/workflow/event-keys: full CRUD page for notify_event_keys"
+ - "GET /api/admin/pipeline-executions: admin-only, fallbacks_only/pipeline_id/limit params, has_fallback boolean"
+ - "/admin/workflow/executions: NEW admin page at ROUTE-07 locked URL with fallback filter"
+ affects:
+ - "Plans 03/04/05 — personal-channels service now has gated backing API"
+ - "Any non-admin code calling /api/notification-channels — now requires auth (previously unauthenticated)"
+tech_stack:
+ added: []
+ patterns:
+ - "Per-row authorization: isAdmin || isOwner predicate against owner_user_id"
+ - "Role-scoped GET: admin sees all + LEFT JOIN owner email; non-admin sees WHERE owner_user_id IS NULL"
+ - "Four complete parameterized SQL strings for pipeline-executions — no alias-in-WHERE (HIGH 4 fix)"
+ - "EXISTS subquery inlined in WHERE for fallback filter — avoids PostgreSQL alias-in-WHERE rejection"
+ - "URLSearchParams.set() for building query strings in client components"
+key_files:
+ created:
+ - app/api/admin/notify-event-keys/route.ts
+ - app/api/admin/notify-event-keys/[key]/route.ts
+ - app/admin/workflow/event-keys/page.tsx
+ - app/api/admin/pipeline-executions/route.ts
+ - app/admin/workflow/executions/page.tsx
+ modified:
+ - app/api/notification-channels/route.ts
+ - app/api/notification-channels/[id]/route.ts
+ - app/admin/workflow/channels/page.tsx
+decisions:
+ - "Per-row authorization uses checkAccess() helper that treats owner_user_id=null (global) as admin-only; personal rows allow isOwner OR isAdmin"
+ - "GET /api/notification-channels now requires requireAuth() — pre-existing security gap closed; non-admins see only global rows"
+ - "Four parameterized SQL strings for pipeline-executions chosen over dynamic WHERE building — eliminates alias-in-WHERE bug from original plan pseudo-SQL"
+ - "Conflict detection on notify_event_keys POST: ON CONFLICT DO NOTHING + timestamp-based check (created_at within 2s = just inserted)"
+ - "/admin/workflow/event-keys and /admin/workflow/executions not linked from /admin/workflow index page — noted as deferred wire-up (see Known Stubs)"
+metrics:
+ duration_minutes: 20
+ completed_date: "2026-05-10"
+ tasks_completed: 3
+ files_created: 5
+ files_modified: 3
+---
+
+# Phase 9 Plan 06: Admin Surfaces Summary
+
+One-liner: Three admin surfaces land — notification-channels gets owner column + role-scoped auth (closing a pre-existing unauth gap), a new event-keys CRUD page backed by two new admin API routes, and a new pipeline-executions page at the ROUTE-07 locked URL with a fallback filter using four safe parameterized SQL strings.
+
+## Tasks Completed
+
+| Task | Name | Commit | Files |
+|------|------|--------|-------|
+| 1 | Owner column + role-scoped reads on /admin/workflow/channels | 47cab78 | app/api/notification-channels/route.ts, app/api/notification-channels/[id]/route.ts, app/admin/workflow/channels/page.tsx |
+| 2 | /admin/workflow/event-keys CRUD page + API | 7f4ffa0 | app/api/admin/notify-event-keys/route.ts, app/api/admin/notify-event-keys/[key]/route.ts, app/admin/workflow/event-keys/page.tsx |
+| 3 | NEW /admin/workflow/executions page + API for ROUTE-07 fallback filter | 23a8c7c | app/api/admin/pipeline-executions/route.ts, app/admin/workflow/executions/page.tsx |
+
+## What Was Built
+
+### Owner Column Rendering Rule
+
+- `channel.owner_user_id == null` → `Global`
+- `channel.owner_user_id != null` → `Personal: {owner_email ?? owner_user_id}`
+- The `owner_email` field comes from a `LEFT JOIN "user" u ON u.id = nc.owner_user_id` in the admin GET query.
+- Owner badge renders BEFORE the channel-type badge in each channel row card.
+
+### Owner-filter URL Parameter on /api/notification-channels
+
+GET accepts `?owner=global|personal|all` (default: `all` for admin sessions).
+
+- `global` → `WHERE nc.owner_user_id IS NULL`
+- `personal` → `WHERE nc.owner_user_id IS NOT NULL`
+- `all` → no WHERE clause on ownership (full JOIN result)
+
+Non-admin sessions always get global-only rows regardless of the `owner` parameter.
+
+### Event Keys CRUD Page
+
+- URL: `/admin/workflow/event-keys`
+- Key regex for POST validation: `/^[a-z][a-z0-9_]*$/i` (max 128 chars)
+- `display_label` max 200 chars, required
+- POST uses `ON CONFLICT (key) DO NOTHING` + timestamp-based duplicate detection → 409 if key pre-existed
+- Inline edit on each row; `is_active` toggles via PUT with `COALESCE($4, is_active)`
+- PUT uses: `UPDATE notify_event_keys SET display_label = COALESCE($1, display_label), description = COALESCE($2, description), sort_order = COALESCE($3, sort_order), is_active = COALESCE($4, is_active), updated_at = NOW() WHERE key = $5`
+
+### Pipeline Executions API — JSONB Predicate
+
+JSONB containment predicate: `pes.output_data ? 'user_route_fallback'`
+
+Used in all four SQL branches as an EXISTS subquery inlined in WHERE — never as a SELECT-list alias referenced in WHERE.
+
+### has_fallback Per-Row Flag
+
+- `fallbacksOnly=true` branches: constant `true AS has_fallback`
+- `fallbacksOnly=false` branches: `EXISTS (SELECT 1 FROM pipeline_execution_steps pes WHERE pes.execution_id = pe.id AND pes.output_data ? 'user_route_fallback') AS has_fallback`
+- The page renders `fallback` on rows where `has_fallback === true`
+
+### ROUTE-07 Confirmation
+
+The filter at `/admin/workflow/executions` is the URL locked by both CONTEXT.md (D-11) and REQUIREMENTS.md (ROUTE-07). No silent rerouting — a fresh `app/admin/workflow/executions/page.tsx` was created over the pipeline-engine tables (`pipeline_executions` + `pipeline_execution_steps`). The legacy `workflow_executions` table and existing admin route are untouched.
+
+### Four Parameterized SQL Strings Confirmation
+
+The pipeline-executions route uses exactly four complete SQL strings selected by boolean flags:
+1. `fallbacksOnly && pipelineId !== null` — filters to one pipeline's fallbacks
+2. `fallbacksOnly && pipelineId === null` — all fallbacks across all pipelines
+3. `!fallbacksOnly && pipelineId !== null` — one pipeline, all executions with EXISTS-computed has_fallback
+4. `!fallbacksOnly && pipelineId === null` — all pipelines, all executions with EXISTS-computed has_fallback
+
+The alias-in-WHERE bug from the original plan pseudo-SQL (`${fallbacks_only ? 'AND has_fallback' : ''}`) is absent — the EXISTS predicate is inlined in WHERE, not referenced via alias.
+
+### POST /api/notification-channels channel_type Allowlist
+
+Unchanged — all four values remain accepted:
+`validTypes = ['teams', 'telegram', 'ntfy', 'webhook']`
+
+### Wire-up Deferred Note
+
+Links to `/admin/workflow/event-keys` and `/admin/workflow/executions` were NOT added to the `/admin/workflow` index page (`app/admin/workflow/page.tsx`). Both pages are functional at their URLs and reachable via direct navigation. A future small plan should add entries to the workflow admin index. This is noted in Known Stubs below.
+
+## Deviations from Plan
+
+None — all three tasks executed as specified. The one implementation choice (URLSearchParams.set() for building the fetch URL in the executions page) is semantically identical to string concatenation — `params.set('fallbacks_only', '1')` produces `fallbacks_only=1` in the final URL.
+
+## Known Stubs
+
+| Stub | File | Reason |
+|------|------|--------|
+| No navigation link to /admin/workflow/event-keys | app/admin/workflow/page.tsx (not in this plan) | Plan scope: build the target pages, not update the nav index. Future plan should add entries for event-keys and pipeline-executions to the workflow admin index |
+| No navigation link to /admin/workflow/executions | app/admin/workflow/page.tsx (not in this plan) | Same as above |
+
+Both pages are fully functional and reachable by URL — the stubs are navigation convenience items only, not blockers for the plan's goal.
+
+## Threat Flags
+
+No new network endpoints, auth paths, file access patterns, or schema changes at trust boundaries beyond what was declared in the plan's threat model (T-09-06-01 through T-09-06-08). All eight threats are addressed:
+
+- T-09-06-01: /api/notification-channels GET now requires requireAuth() — gap closed
+- T-09-06-02: Per-row authorization (isAdmin || isOwner) on [id] routes
+- T-09-06-03: Admin disclaimer rendered above personal-channel list
+- T-09-06-04: requireAdmin() + key regex + ON CONFLICT DO NOTHING on event keys POST
+- T-09-06-05: Admin-only writes via gated API; page HTML contains no extra secrets
+- T-09-06-06: SQL injection mitigated — pipeline_id /^\d+$/ validated, limit capped at 500, fallbacks_only is boolean flag selecting one of four static SQL strings
+- T-09-06-07: has_fallback reveals only presence of fallback, not user_id — admin-only context
+- T-09-06-08: POST channel_type allowlist preserved verbatim (teams/telegram/ntfy/webhook)
+
+## Self-Check: PASSED
+
+Files created:
+- app/api/admin/notify-event-keys/route.ts: FOUND
+- app/api/admin/notify-event-keys/[key]/route.ts: FOUND
+- app/admin/workflow/event-keys/page.tsx: FOUND
+- app/api/admin/pipeline-executions/route.ts: FOUND
+- app/admin/workflow/executions/page.tsx: FOUND
+
+Files modified:
+- app/api/notification-channels/route.ts: FOUND
+- app/api/notification-channels/[id]/route.ts: FOUND
+- app/admin/workflow/channels/page.tsx: FOUND
+
+Commits:
+- 47cab78: FOUND (Task 1 — channels owner column + auth)
+- 7f4ffa0: FOUND (Task 2 — event-keys CRUD)
+- 23a8c7c: FOUND (Task 3 — pipeline executions page + API)
+
+TypeScript: `npx tsc --noEmit` exit 0 — no errors.