# AWP Pain Invariants

**Date:** 2026-08-21
**Status:** **BINDING — every listed regression test is a GOLIVE-blocking gate for its subsystem**
**Source of pain data:** `~/Projects/0 DOCS/COMPLAINT_REGISTRY.md` + `COMPLAINT_REGISTRY.jsonl` (217 dated complaints, 2026-01-26 → 2026-08-07) and `ANNOYANCE_FATIGUE.md`
**Authority:** [`AWP-GOVERNANCE-AMENDMENT-2026-08-21.md`](AWP-GOVERNANCE-AMENDMENT-2026-08-21.md) A3 — these tests sit in the "required before the GOLIVE journey passes" bucket
**Implementation digest:** [`I0-I1-AUTHORITY.md`](I0-I1-AUTHORITY.md)

## Why This Document Exists

AWP is the successor to Overdeck. The owner's Overdeck experience is recorded, dated and categorized in a complaint registry — not as vague dissatisfaction but as repeat chains, several of which survived multiple declared fixes. `notification-spam` is the clearest: 20 rows tracing to one root (C012), spanning 2026-07-09 to 2026-08-06, surviving at least three "fixed" declarations.

A successor that reproduces those chains has failed regardless of its architecture. So each chain below is converted into a **binding design invariant** plus a **regression test the owning subsystem must ship with before it goes live**. Not after. Not "tracked as a follow-up."

```text
pain chain  -> design invariant     -> regression test -> GOLIVE gate
observed       binding in AWP          in CI              blocks go-live
```

The registry's own rule applies here: a point-fix in a chained category **must be checked against every prior row in that chain**, not just the most recent one. Two of these chains have a documented history of being declared closed and recurring.

## Chain 1 — Notification Spam

**Registry:** `notification-spam` ×20, root C012 (2026-07-22, 79 popups/24h) → C013 (2026-08-03, 169 popups/24h, **97% fired by the guard's own test suites**) → C017 (2026-08-06). Plus `notification-fatigue` C210. Longest-running unresolved thread in the registry.

> "overnight there was TONS of FP's and just spam, there is no way that an alert system with that many alerts will get anything except of annoying me." — C049
> "this morning, like any other morning, i woke up to notification spam ... its not what the systray is for" — C017

### Binding invariants

```text
QUIET UNLESS ACTIONABLE
  a Notification is emitted only when a specific Principal can take a
  specific action that AWP cannot take itself

  informational state changes    -> UI/feed, never a push channel
  progress                       -> UI/feed, never a push channel
  a condition AWP can auto-resolve -> resolve it, do not notify
  a repeat of an unacknowledged condition -> coalesce, never re-fire

TESTS CANNOT REACH THE CHANNEL
  no test, in any suite, at any level, can deliver to a real
  CommunicationEndpoint. This is enforced at the adapter, not by
  convention or by remembering to set an env var.

EVERY ALERT MECHANISM SHIPS ITS FALSE-POSITIVE ARM
  a new alert condition is not mergeable without a test that asserts
  it does NOT fire under the near-miss conditions
```

### Regression tests required before the notifications subsystem goes live

| Test | Asserts |
|---|---|
| `notification-channel-unreachable-from-tests` | The delivery adapter throws if `NODE_ENV !== 'production'` or the run carries a test correlation ID. Under the harness, attempting real delivery is a hard failure, not a no-op. C013's root cause was the guard's own test suite generating 97% of the flood; a no-op stub would have hidden it rather than caught it. |
| `notification-requires-actionable-target` | Emitting a `Notification` without a resolvable Principal **and** a concrete action reference fails at the domain boundary. Non-actionable state changes are rejected, not downgraded. |
| `notification-coalesces-unacknowledged` | The same condition re-detected N times while unacknowledged produces exactly one delivered notification. Directly targets C012/C013's volume shape. |
| `<new-alert>-false-positive-arm` | Ships with every new alert condition. Asserts silence under the near-miss inputs. A new condition without this arm fails the CI invariant check, not code review. |
| `notification-volume-ceiling` | A synthetic 24h window of realistic system events produces a bounded delivered count. The registry's units are popups-per-24h; the test's unit should match. |

## Chain 2 — Config Drift and File Sprawl

**Registry:** `file-sprawl` ×6, root C006 (2026-07-18). `ANNOYANCE_FATIGUE.md` §5 records it as, at the time, the top complaint.

> "AGENTS TOOK OVER MY LAPTOP AND CREATING FILES EVERYWHERE" — C006
> "agents scatter one-off scripts + systemd units across `~/.claude/bin`, `~/.config/systemd/user`, `/usr/local/bin`, `/etc/systemd/system` … N scattered files, no single owner, no single log, no single off-switch." — ANNOYANCE_FATIGUE §5

The owner's own remedy, recorded in that document, is the invariant: *"One daemon, one tick loop, one log, one config surface, one off-switch."*

### Binding invariants

```text
POSTGRES IS THE SINGLE CONFIG AUTHORITY
  every runtime-tunable value resolves from the control-plane database

  files/env are BOOTSTRAP ONLY: how to reach Postgres, and nothing else
  no config is copied to a file, a sidecar, a workspace or a runner
  as its authoritative form

NO FILE-COPIED CONFIG
  an execution workload receives config as a projection, materialized
  at dispatch, expiring with the workload. It is a read-only copy
  labeled as one, never a second place to edit.

EVERY CONFIG SURFACE IS READABLE AND WRITABLE VIA UI OR API
  no tunable exists that requires editing a file on a machine
  no tunable exists that requires a restart to observe
  every tunable has one owner, one current value, one audit trail
  every mechanism has an off-switch reachable from the same surface
```

### Regression tests required before the configuration subsystem goes live

| Test | Asserts |
|---|---|
| `config-authority-is-postgres-only` | Every registered `ConfigurationDefinition` resolves from the database. A definition whose only source is a file or env var fails registration, except for the explicitly enumerated bootstrap set. |
| `bootstrap-config-allowlist-is-closed` | The bootstrap file/env allowlist is a fixed list asserted in the test. Adding an entry requires editing the test — which is the point. |
| `no-config-surface-without-api` | Enumerates every `ConfigurationDefinition` and asserts each has a working read **and** write path through the API. A tunable reachable only by direct SQL fails. |
| `workspace-config-is-projection` | A config value mutated in a running Workspace does not propagate back to authoritative state, and the workload's copy is labeled read-only and carries an expiry. |
| `every-mechanism-has-an-off-switch` | Every background/scheduled/guard mechanism registers a disable path reachable from the same UI/API surface. Directly encodes the owner's "one off-switch" remedy. |

## Chain 3 — Delivery Approval Fatigue

**Registry:** `delivery-approval-fatigue` ×21 across 17 distinct days, 2026-01-26 → 2026-08-05, root C011. Second-largest category with a chain.

> "IF I ASKED FOR IT YOUR JOB IS TO MAKE IT PART OF THE SOFTWARE I ASKED — MAKE SURE IT HAPPENS. I DONT KNOW GIT LAND SHIP ... JUST DO THE THING CALL IT WHATEVER YOU WANT." — C010
> "execution end to end including testing and verification and deployment and shipping and landing and trains and every other stupid jargon you are inventing to excuse to ask me things — without me approving every step." — C011
> "why did you stop? This needs to be completed without my interaction. verify completion before stopping. do not prompt me again for next actions." — C018
> "why do i need to 'Say the word'?" — C146

### Binding invariants

```text
A USER REQUEST PRE-APPROVES THE FULL DELIVERY CHAIN
  request -> plan -> implement -> test -> verify -> review -> merge
          -> release -> deploy
  is ONE approval, granted at request time

AUTONOMY DEFAULT IS FULL-CHAIN
  the default Policy runs the chain to completion
  HITL hooks are CONFIGURABLE and OFF unless the owner turns them on
  or a hard policy boundary (destructive/irreversible/out-of-scope)
  is crossed

NO INVENTED GATES
  a stage cannot introduce an approval prompt that the Policy does not
  define. Renaming a gate ("ready to land", "ship confirmation",
  "promotion sign-off") does not make it a new legitimate gate — C011
  names this behavior explicitly.

NOT-A-BLOCKER IS NOT A STOP
  ambiguity, a preference question, or an unknown that AWP can resolve
  by choosing safely is resolved by choosing. Only a genuine
  authority/irreversibility boundary stops the chain.
```

### Regression tests required before the policy/autonomy subsystem goes live

| Test | Asserts |
|---|---|
| `request-preapproves-full-chain` | A single Goal/Task request under default Policy drives Plan → Task → FactoryRun → ChangeSet → Review → Merge to a terminal state with **zero** Approval records created. |
| `approval-points-are-policy-declared` | **I1 active:** mechanically proves there is no post-launch `Approval` write/pause path; the explicit Plan launch is the only owner start boundary. **I3 extension:** every generic Approval/HITL path must map to a declared Policy hook. |
| `default-policy-has-no-hitl-hooks` | **I1 active:** the shipped post-launch chain exposes no interactive hooks and Review/Merge/recovery remain automatic. **I3 extension:** turning a generic hook on becomes an explicit audited Project configuration change. |
| `pause-requires-authority-boundary` | **I1 active:** there is no terminal post-launch waiting-for-human state; owner attention is limited to real pre-execution authority/prerequisite boundaries. **I3 extension:** any generic waiting-for-human state must carry a closed authority/irreversibility reason code. |
| `hitl-hooks-are-configurable-per-project` | **I3 activation gate.** When generic Policy/HITL hooks activate, enabling/disabling a hook via the API must change the next run without restart/file edit. **I1 must not fake this product surface:** its shipped post-launch chain has zero generic interactive hooks; the explicit owner Plan launch boundary is the request/start authority boundary, after which Review/Merge/recovery remain automatic. |

## Chain 4 — Git Decision Dumping

**Registry:** `git-decision-dumping` ×9, root C009 (2026-07-18), recurring through C152/C153 (2026-08-06). Six rows chain directly to the root.

> "YOU AND THE AGENTS DO GIT IN A BIG MESS AND YOU LEAVE THE DESICION FOR ME?!?!?!" — C008
> "IT IS \*\*\*YOUR\*\*\* DESICION. YOU ALWAYS NEED TO DO IT: SAFELY+ROBUSTLY+NEVER LOSE IMPORTANT CODE WE NEED+NEVER ASK ME WTF WHAT I DONT KNOW" — C009
> "why are you asking me git stuff? … i dont know git" — C093
> "DONT INVOLVE ME IN GIT MESS YOU MADE" — C153

### Binding invariants

```text
AWP OWNS ALL GIT MECHANICS
  branch naming, base selection, rebase vs merge, conflict resolution
  strategy, force-push safety, worktree lifecycle, stash/WIP handling,
  remote/ref hygiene, publication and merge

  these are AWP decisions, made by AWP, every time

NEVER SURFACE A GIT DECISION TO THE USER
  no UI affordance, no notification, no chat turn, no blocking state
  ever asks the user to choose a git mechanic

  the user's vocabulary is Task, Change, Review, Merged
  never branch, rebase, upstream, HEAD, remote or conflict marker

CONFLICTS ARE AWP'S PROBLEM
  a merge conflict is a Finding on a ChangeSet with an AWP-chosen
  resolution strategy, escalating to a NEW AGENT ATTEMPT — never to
  a user prompt

NEVER LOSE WORK
  C009's explicit conjunction is SAFELY + ROBUSTLY + NEVER LOSE CODE.
  Accepted WIP is durable until deliberate terminal cleanup. A git
  operation that could discard uncollected work does not run.
```

### Regression tests required before the VCS/changes subsystem goes live

| Test | Asserts |
|---|---|
| `no-git-vocabulary-in-user-surfaces` | Static scan of all user-facing strings (UI copy, notification templates, API error messages, approval prompts) against a banned-term list: `rebase`, `force-push`, `upstream`, `HEAD`, `detached`, `conflict marker`, `cherry-pick`, `stash`. Failing the scan fails CI. |
| `no-approval-of-git-kind` | No `Approval` or HITL reason code exists whose subject is a git mechanic. Enforced against the closed reason-code enum from Chain 3. |
| `conflict-resolves-without-user` | A seeded merge conflict drives to a new `Attempt` with a resolution strategy and terminal disposition. Zero user-facing prompts. Asserted end to end, not mocked. |
| `no-git-operation-discards-uncollected-wip` | Every mutating git operation is preceded by a durable-collection assertion. Simulated interruption at each step leaves accepted WIP recoverable. |
| `branch-strategy-is-deterministic` | Given identical Task inputs, branch naming and base selection are identical — the mechanic is a function, not a judgment call to be delegated. |

## Chain 5 — Agent Done-Claims

**Registry:** no exact category slug exists for this chain; do not invent one. The nearest recorded categories are `agent-ignores-instructions` (31 rows, largest in the registry, 22 distinct days) and `ci-ritual-fatigue` (11 rows, root C002). The pain is stated most directly in C018 and C102.

> "verify completion before stopping" — C018
> "so let me understand, i lost all context from this conversation … everything i asked for in this conversation is done and completed?" — C102
> "agents ... solve it ... make it ... work and merge ... i need it on main" — C005

The owner asks whether things are *actually* done because agent self-reports have not been reliable. The complement matters equally: `ci-ritual-fatigue` (C002, "whats the point in running typecheck 2 times? Its long, heavy, ritual") says the verification must be **real and once**, not performative and repeated.

### Binding invariants

```text
NO TASK IS DONE WITHOUT MACHINE-VERIFIED EVIDENCE
  I1 Done/Merge requires VerificationEvidence bound to the exact
  ChangeSet candidate and carrying an immutable machine-resolvable
  reference. The thin I1 references are either:
    - the attested Workspace checkpoint that produced the candidate, or
    - a trusted provider observation for repository-required checks.

  When I5 activates first-class CIExecution / acceptance-run entities,
  those entity IDs become the mandatory reference forms for the same
  invariant. I1 must not invent placeholder CIExecution rows merely to
  satisfy a later-increment type name.

SELF-REPORTS ARE NEVER STATUS
  an agent's assertion that work is complete is an INPUT to
  verification, never a state transition. There is no code path from
  agent narration to Done. A bare passed evidence string without an
  immutable reference cannot satisfy the I1 gate.

EVIDENCE IS REFERENCED, NOT SUMMARIZED
  VerificationEvidence carries a structured immutable reference plus
  exact candidate identity. A prose `source` or summary is descriptive
  metadata, not evidence authority.

VERIFY ONCE, PROPERLY
  evidence is cached/bound against the exact ChangeSet candidate. I1
  observes existing repository-required checks; it does not start CI
  again at every stage boundary. I5 CI orchestration must reuse an
  unchanged CIExecution/acceptance result and rerun only when its input
  identity changes.
```

### Regression tests required before the verification subsystem goes live

| Test | Asserts |
|---|---|
| `done-requires-verification-evidence` | **I1 active:** terminal Merge/Task completion fails unless current required evidence is passed, exact-candidate-bound and carries resolvable Workspace-checkpoint/provider references. **I5 extension:** first-class passing CIExecution + acceptance-run references become mandatory when those entity types activate. |
| `agent-self-report-cannot-transition-state` | Feeding agent completion text through every ingestion path produces zero lifecycle transitions. The narration is recorded as an Attempt artifact and nothing more. |
| `evidence-references-are-immutable-and-resolvable` | **I1 active:** every required passing evidence row has a structured immutable reference resolving to the exact candidate Workspace checkpoint or a trusted provider observation; bare/synthesized strings fail. **I5 extension:** the reference must resolve to first-class CIExecution/acceptance-run records when those entities activate. |
| `evidence-invalidated-by-changeset-revision` | **I1 active:** evidence bound to candidate N does not satisfy the gate for candidate N+1. Prevents stale-evidence Done. I5 retains the same rule for CIExecution/acceptance-run inputs. |
| `verification-not-rerun-when-inputs-unchanged` | **I1 active:** AWP observes/reuses provider results and does not initiate CI at lifecycle boundaries. **I5 activation gate:** its CI orchestrator must reuse cached CIExecution/acceptance results when the input identity is unchanged. |

## Cross-Chain Rules

```text
A FIX IN A CHAINED CATEGORY IS CHECKED AGAINST THE WHOLE CHAIN
  the registry's §1 rule 4. notification-spam and
  delivery-approval-fatigue both have documented
  fixed-then-recurred history. A new point-fix that is not tested
  against every prior row in its chain is not a fix.

THESE TESTS ARE GATES, NOT COVERAGE
  each one blocks its subsystem's go-live. A subsystem shipping
  without its arm is a governance defect of the same class the
  2026-08-21 amendment was written to repair.

NEW PAIN ENTERS HERE
  a new complaint chain that appears during dogfood gets a row in this
  document with its invariant and its regression arm, before the
  point-fix lands — not after.
```

## Interlock With the GOLIVE Journey

Every test in this document is a **GOLIVE-BLOCKING** item under [`AWP-GOVERNANCE-AMENDMENT-2026-08-21.md`](AWP-GOVERNANCE-AMENDMENT-2026-08-21.md) A3. They are not descoped by A4 — A4 reduces *provider proof* burden, which is about whether infrastructure works. These are about whether AWP reproduces the failures it exists to end, which is a different and non-negotiable question.

```text
Chain 1  notifications / communications    -> gates notification go-live
Chain 2  configuration                     -> gates config go-live
Chain 3  policy / autonomy / approvals     -> gates autonomy go-live
Chain 4  repositories / changes / VCS      -> gates merge go-live
Chain 5  verification / CI                 -> gates Done-state go-live
```
