# ADR-067: Cross-PWA Change Safety Gate — Explicit Confirmation Before Shared Infrastructure Changes

## Status

**Accepted**, 2026-06-27.

## Status History

| Date | Author | Status | Notes |
|------|--------|--------|-------|
| 2026-06-27 | hkl | Accepted | Initial decision. Claude must confirm before proceeding with shared infrastructure changes. |

## Context

The 360lm platform comprises ~20 progressive web applications (PWAs) that share critical infrastructure:

- **Database:** Single PostgreSQL instance (`lm360`) with ~13 schemas (expense, installation, finance, recce, production, stores, hr, vehicle, sales, dispatch, activity, hub, custodian)
- **PostgREST API:** Single instance (`api/db`) serving all PWAs with RPC-based access control via grants
- **Session Format:** Hub PWA manages a shared `lm360-session` localStorage key read by all other PWAs
- **Service Worker Cache:** Each PWA has its own cache key, but they share the same origin
- **Docker Compose & Traefik:** Shared reverse proxy, routing labels, and container orchestration
- **Proxy Services:** Shared sidecars (maps-client, mcp-ocr-proxy, slides-proxy, tour-ai-proxy, dispatch-ai-proxy)
- **Shared DB Tables:** expense.employees (canonical identity), hub.pwa_registry, custodian.payees, sales.catalog

**The problem:** A change scoped to one PWA can silently break others via:

- Overly broad PostgREST grants (e.g., `GRANT SELECT ON expense.employees TO web_anon;` breaks access control across all PWAs)
- Traefik routing rule changes affecting shared services (e.g., wrong middleware chain)
- Hub session key structure changes (all other PWAs would fail to deserialize the session)
- Docker Compose network or service definition changes (port conflicts, shared container lifecycle)
- Cache key collisions or CACHE_VER pattern changes breaking multiple PWAs' offline functionality
- Proxy service logic changes affecting all PWAs that depend on it (e.g., maps-client failover)

**Current state (as of 2026-06-27):** No explicit gate. Claude (or a developer) can propose changes to shared infrastructure without a confirmation step, risking silent cascade failures.

**Why this matters:** Multi-PWA breakage can go undetected until QA or production, where the cost of a fix is high. The team has explicitly requested (2026-05-04) that Claude confirm before touching shared infrastructure.

## Decision

**Claude Code must explicitly ask the user for confirmation before proceeding with any change that could affect multiple PWAs or shared infrastructure.** The confirmation must list the affected PWAs or services, the exact nature of the change, and require an explicit "yes" or "no" answer before any code is written, committed, or deployed.

## Implementation Notes

### Trigger List: When to Ask for Confirmation

Claude must halt and ask the user to confirm before proceeding with any of the following:

1. **PostgREST Grants** (`04_grants.sql` or any `ALTER` permission)
   - Any new `GRANT EXECUTE`, `GRANT SELECT`, `GRANT INSERT`, `GRANT UPDATE`, `GRANT DELETE` on any table or function
   - Changes to RPC access control
   - Widening or narrowing of role permissions (web_anon, web_user, expense_admin, etc.)

2. **Docker Compose Changes** (`/root/360lm-web/docker-compose.yml`)
   - Modifications to shared networks (e.g., `lm360-net`, `lm360-external`)
   - Changes to service definitions (ports, volumes, environment, labels) of any multi-PWA service (postgres, postgrest, traefik, mcp-*, etc.)
   - Addition or removal of services

3. **Traefik Routing Rules** (docker-compose labels on any container)
   - Changes to `traefik.http.routers`, `traefik.http.services`, `traefik.http.middlewares` labels
   - Changes to routing paths (e.g., `/db/`, `/slides-proxy/`, `/hub/`)
   - Changes to middleware chain (auth, rate limiting, compression, etc.)

4. **Hub Session Format** (`/hub/index.html` or any code writing to `lm360-session` localStorage)
   - Changes to the structure of the session object (adding/removing/renaming keys)
   - Changes to the expiry format or TTL logic
   - Changes to how other PWAs deserialize or validate the session

5. **Service Worker Cache Keys** (any PWA's `sw.js`)
   - Changes to cache key naming that could collide with other PWAs (e.g., changing `360exp-v24` to `360-v24` risks collision with hub's `360lm-hub-v26`)
   - Changes to the `CACHE_VER` pattern used by multiple PWAs
   - Changes to which URLs are cached or pre-cached

6. **Shared Database Schemas or Tables**
   - Any modification to `expense.employees` (canonical identity table used by activity, hub, expense, finance, sales, admin, vehicle, dispatch, stores PWAs)
   - Any modification to `expense.verify_pin` (RPC used by all employee-facing PWAs)
   - Any modification to `hub.pwa_registry`, `hub.employee_pwa_access` (registry used by all PWAs on startup)
   - Any modification to `custodian.payees` (shared across finance and other payment-facing PWAs)
   - Any modification to `sales.catalog` (shared product master)
   - Any modification to tables in schemas with 3+ PWAs reading them (installation, finance, stores, sales, vehicle, recce, production, dispatch)
   - Any addition of new constraints, triggers, or functions that could cascade or break downstream code

7. **Proxy Service Code** (`/opt/sales-proxy`, `/opt/slides-proxy`, `/opt/ocr-proxy`, `/opt/mcp-maps`, `/opt/mcp-tomtom`, `/opt/mcp-ocr-proxy`, `/opt/tour-ai-proxy`, `/opt/dispatch-ai-proxy`)
   - Any change to request/response format, auth flow, or error handling
   - Any change to cache/fallback behavior affecting multiple PWAs
   - Any change to timeout, retry, or circuit-breaker logic

### Confirmation Message Format

When a trigger is detected, Claude must output a **stop-and-confirm message** in the following format:

```
⚠️ CROSS-PWA CHANGE DETECTED

This change affects the following shared infrastructure:
  • [Affected component(s)]

Affected PWAs or services:
  • [List all PWAs or services that will be impacted]

Change summary:
  [What is being changed and why]

Before I proceed, please confirm:
  "Yes, proceed with [specific change]" — or —
  "No, let's discuss further"

Your response will determine whether I write code or pause for discussion.
```

**Example:**

```
⚠️ CROSS-PWA CHANGE DETECTED

This change affects the following shared infrastructure:
  • PostgREST grants on expense.employees table
  • Access control for all employee-facing PWAs

Affected PWAs or services:
  • activity, hub, expense, finance, sales, admin, vehicle, dispatch, stores

Change summary:
  Widening SELECT grant on expense.employees from expense_admin role to web_anon.
  This would expose employee IDs, names, and PINs to the browser (SECURITY ISSUE).

Before I proceed, please confirm:
  "Yes, proceed with widening expense.employees grant to web_anon" — or —
  "No, let's discuss further"
```

### What "Proceed" Means

- **If user confirms (explicit "yes"):** Claude writes the code, commits (if requested), and updates relevant tests and documentation. Claude then runs Playwright tests in background to catch cascade failures early.
- **If user says "no" or pauses:** Claude does NOT write code. Instead, Claude opens a discussion: what alternatives exist? What would be the safer approach? Claude may suggest a refactoring that narrows the change to a single PWA.
- **If user does not respond clearly:** Claude assumes "no" and asks for clarification before proceeding.

### When NOT to Ask

Claude does NOT need to confirm changes that:

- **Touch only one PWA's files** (e.g., `/var/www/360lm/expense/index.html`, `/var/www/360lm/expense/sw.js`) AND have no DB or proxy changes
- **Are bug fixes** (e.g., fixing a typo, correcting a validation rule) in existing code, unless the bug fix changes API contracts or grants
- **Are covered by an existing ADR** that already governs that area (e.g., ADR-001 governs `?next=` redirect, ADR-005 governs cache busting)
- **Are purely cosmetic** (CSS, layout) with no logic or data flow changes

### Related Guidelines

- **ADR-009:** Each PWA owns a dedicated schema
- **ADR-010:** Cross-schema access goes through the proxy, not PostgREST directly
- **ADR-012:** Hub is the SSO gateway
- **ADR-016:** Every new PWA must register in hub.pwa_registry
- **ADR-029:** Access control is dual-written to DB and hub-access.json
- **ADR-036:** custodian.payees is the shared payee master

## Alternatives Considered

### Alternative 1: No Gate — Trust Developer Judgment

**Rejected.** Manual testing across 20 PWAs is error-prone. Cascade failures happen silently and are discovered late (QA or production). The user explicitly requested this rule (2026-05-04) because it happened in practice.

### Alternative 2: Automatic Detection + Silent Rollback

**Rejected.** Requires CI/CD infrastructure (multi-PWA smoke tests) that is not yet in place. Stop-and-confirm is simpler and works with current manual QA.

### Alternative 3: Require Unit Tests Before Confirmation

**Rejected.** Unit tests cannot catch cross-PWA breakage (e.g., session format mismatch). The gate is a human check, not a test substitute.

### Alternative 4: Only Confirm on "Likely" Changes, Not All Shared Infrastructure

**Rejected.** "Likely" is subjective and varies by developer context. Explicit trigger list is clearer and catches edge cases (e.g., a "small" schema change that breaks a hidden dependent PWA).

## Consequences

### Benefits

1. **Prevents cascade failures:** The stop-and-confirm gate catches potentially breaking changes before they propagate.
2. **Explicit traceability:** User commits to the change in writing, creating a record of intent.
3. **Forces design discussion:** Asking "which PWAs will this break?" often surfaces a better design (e.g., "refactor this into a single-PWA change instead").
4. **No infrastructure overhead:** Gate is a Claude behavior, not a new CI/CD tool or test suite.

### Trade-offs

1. **Slower iterative development:** Changes to shared infrastructure require an extra confirmation step, adding 1–2 minutes per change. Mitigated by bundling multiple related changes into one confirmation.
2. **User fatigue if misapplied:** If Claude asks for confirmation on every tiny change, the user may dismiss the gate. Trigger list must be precise.
3. **Gate can be overridden:** User can always say "yes, proceed" even for a risky change. Gate is advisory, not enforcement. Mitigation: Claude must be clear about risks in the confirmation message.

### Risks & Mitigations

| Risk | Mitigation |
|------|-----------|
| False positives (asking unnecessarily) | Trigger list includes thresholds (e.g., "3+ PWAs reading") to exclude minor changes |
| User ignores the gate and says "yes" to risky changes | Confirmation message must clearly state the risk (e.g., "SECURITY ISSUE") |
| Claude forgets to apply the gate | Rule is documented in ADR (this file) and Claude's project CLAUDE.md |
| Gate is too strict (e.g., confirming on typos in shared code) | Carve-out for bug fixes and cosmetic changes (see "When NOT to Ask") |

## Related Decisions

- **ADR-001** (Hub ?next= redirect): Establishes a cross-PWA pattern that requires coordination
- **ADR-009** (Each PWA owns a schema): Supports the principle that changes to shared schemas need confirmation
- **ADR-012** (Hub as SSO gateway): Session format is shared by all PWAs; changes require coordination
- **ADR-029** (Dual-write access control): PostgREST grants are mirrored in hub-access.json; changes to one must sync to the other
- **ADR-036** (custodian.payees as shared master): Identifies a shared table that must not be modified without coordination

## References

- **User feedback:** 2026-05-04, explicit request to "ask before touching shared infra"
- **Memory:** `/root/.claude/projects/-var-www-360lm/memory/feedback_cross_pwa_safety.md`
- **Project architecture:** `/root/.claude/projects/-var-www-360lm/memory/project_arch.md`
- **Trigger patterns from:** PostgREST grant audit, Traefik routing rules, hub session format, SW cache collision history
