# CRV-2026-001: excerpts from the internal record

Every footnote in the paper that cites an internal document is reproduced here
as the verbatim passage the footnote relies on, redacted, with enough
surrounding text that the passage cannot be read against its own meaning.

This file exists because the paper asserts, in definition D8, that an evidence
artifact is one a specified party can inspect without trusting the acting
party's narration. A footnote pointing at a private path fails that test. So do
the numbers it supports.

The full audit report and the full sprint records are NOT published. Those
documents contain account identifiers, connection state and customer detail
that redaction would either miss or render unreadable. Publishing bounded
excerpts is the compromise: it is less than a reader would ideally have, and it
is enough to check every number in the paper against the sentence that produced
it.

Passages are pulled by line range from the source files at build time, so they
are verbatim by construction rather than by transcription. Line numbers are the
positions in the repository at the time the bundle was built and are given so a
future reader can see how much of a document each excerpt represents.

Redaction policy is stated in README.md. Placeholders in square brackets are
stable across every file in this bundle.


---

## Note 1

**Source:** `audit/write-access/REPORT.md`, lines 3 to 8

**Why this passage:** Audit posture, HEAD, method, corpus size.

```
**Posture:** hostile, evidence-gated, read-only. Default verdict per capability = FAIL; a capability moves up only on `file:line` evidence.
**HEAD:** `2f1dbc36c7fa77aeb721e34a838d12f26aefd0d8` · **Commit date:** 2026-07-10 · **Audit date:** 2026-07-29
**Branch:** main · **git status:** NOT clean (pre-existing, not audit-caused): `M docs/audits/AUDIT_V2_ADVERSARIAL.md`, `?? public/hackernoon-icon-filled-256.png`. No audit file touched application code.
**Repo:** ~550k LOC TS/TSX (7,709 files) · Next 14.2.35 · React 18.3.1 · TS 5.9 · Prisma 6.16 · Node ≥20 <21
**Method:** six parallel platform evidence-sweeps (Meta, Google, Shopify, Klaviyo, TikTok, GA4), each citing `file:line`; lead-auditor reconciliation of the 7-gate scores. No `build`, no vendor writes, no commits, no migrations. Full raw traces in `EVIDENCE-<platform>.md`; every row in `matrix.json`.

```

**Source:** `audit/write-access/REPORT.md`, lines 19 to 26

**Why this passage:** The three published scores and the tier weights.

```
## 2. The three numbers (report all three; only the first may be called "real write access")

| Score | Definition | Overall | Tier-1 only (money-moving) |
|---|---|---|---|
| **Headline Real Write Score (strict, G6)** | proven live vendor 2xx | **0.0%** | **0.0%** |
| **Plausible Write Score (G4+)** | code exists, reachable, authorized, contract looks correct — but unproven | **56.6%** | **68.8%** |
| **Maturity Score** (Σ weight·gate/6) | progress metric only | **45.2%** | **55.2%** |

```

---

## Note 2

**Source:** `audit/write-access/PROGRESS.md`, lines 10 to 14

**Why this passage:** The breadth figure recorded as a paraphrase, not a computed value.

```
- [x] ActionAudit telemetry fields exist (platformResponse, executionMode, preState/postState, scopeVerified) → G6 provable-in-principle, just never exercised live.
- [x] Prior in-repo audit `docs/audits/WRITE_CAPABILITY_AUDIT.md` (2026-07-07) self-reports ~25-35% built+NLP / ~0-5% live — corroborates strict≈0%.
- [x] Claim source: docs number-scrubbed (`ln`/`n`); "96-99%" = paraphrase of "all 5 platforms closed / Sam 84→98 verbs" breadth narrative.
- [x] Lens 5 (scale): bulk `lib/executor/batch.ts` runPool bounded-concurrency + allSettled + spend-batch serialized(conc=1); pacing-scan uses runBrandBatch cursor + truncation alerts.
- [x] Lens 4 (reversibility): `lib/actions/reversibility.ts` irreversible flag + 2nd-stage "type SEND" + creates never-auto-fire + audiences reversalVerb=delete.
```

**Source:** `audit/write-access/REPORT.md`, lines 136 to 140

**Why this passage:** Claim-versus-reality rows, including the breadth claim.

```
| Claim (as it appears / is paraphrased in the repo) | Verdict | Contradicting evidence |
|---|---|---|
| "**~96-99% write capability**" / "all 5 platforms closed" / "Sam 84→98 verbs" | **FALSE** as *write access*; **OVERSTATED** as *breadth* | Strict (proven) = **0.0%**. The number conflates verb *count* / platform *breadth* with proven writes. Docs are number-scrubbed (`ln`/`n` placeholders), so the literal figure lives in the pitch, not the code. |
| "TikTok — the last ❌ platform, closed" (`WRITE_CAPABILITY_AUDIT.md:19`) | **OVERSTATED** | TikTok writers exist and reach G4, but scope enforcement is a rubber-stamp, budget rollback is a no-op, uploads lack idempotency, and **0% is proven live**. Status/budget/bid/create are built; audiences/pixel/events/catalog are absent. |
| "Google is now fully buildable" (`:25`) | **OVERSTATED** | Search/PMax build reaches G5, but ad-status has no standalone verb (G1), keyword *bid* mutate, shared negative lists, extensions, geo/language, device modifiers, and offline-conversion upload are all **absent**; experiments + customer-match self-flagged unverified (G4). |
```

---

## Note 3

**Source:** `audit/write-access/REPORT.md`, lines 27 to 56

**Why this passage:** Per-platform table and the overall gate histogram. The prose here says 129 capabilities and the bars sum to 129, while matrix.json and compute.mjs both give 130.

````
Weights: Tier1=3, Tier2=2, Tier3=1. Business-day-weighted (Meta40/Google25/Shopify10/Klaviyo10/GA4-10/TikTok5): Plausible **58.0%**, Maturity **46.8%**, Strict **0.0%**.

**Calibration guard:** strict = 0% is far below the 80% re-audit trigger — no false-high risk. The *Plausible* 56.6% is deliberately generous (it credits every reachable, contract-plausible writer as if the live flag were flipped and the connection correctly scoped); it exists precisely to isolate that the entire delta to strict is **proof**, not plausibility.

### Per platform

| Platform | Strict (G6) | Plausible (G4+) | Maturity | Tier-1 Plausible | Gate histogram |
|---|---|---|---|---|---|
| **Meta** | 0.0% | 73.7% | 59.2% | 70.0% | G0×13 · G4×5 · G5×24 |
| **Google** | 0.0% | 61.7% | 51.7% | 75.0% | G0×12 · G1×2 · G4×2 · G5×15 |
| **Shopify** | 0.0% | 45.7% | 38.1% | 80.0% | G0×12 · G5×6 |
| **TikTok** | 0.0% | 70.8% | 47.2% | 100.0% | G0×6 · G4×7 |
| **GA4** | 0.0% | 34.8% | 23.2% | 66.7% | G0×9 · G4×3 |
| **Klaviyo** | 0.0% | 15.4% | 16.7% | 0.0% | G0×11 · G3×1 · G5×2 |

### Overall gate histogram (129 capabilities)

```
G0 (not built / DB-only)   ████████████████████████████████████████████████████████████  62
G1 (writer, unreachable)   ██                                                              2
G2 (reachable, no scope)   (none)                                                          0
G3 (scoped, bad contract)  █                                                               1
G4 (contract-plausible)    █████████████████                                              17
G5 (fully guardrailed)     ███████████████████████████████████████████████              47
G6 (PROVEN LIVE)                                                                           0   ← the only bar that counts as "real write access"
```

Read this shape plainly: **48% of the canonical media-buyer write set isn't built at all** (G0+G1+G3 = 65 of 129), **50% is built to a genuinely high engineering standard** (G4+G5 = 64), and **0% is proven**. Cresva is a well-engineered write *machine* whose ignition has never been observed to turn over.

---
````

---

## Note 4

**Source:** `audit/write-access/REPORT-ADDENDUM.md`, lines 44 to 57

**Why this passage:** The enforcement-sensitivity recompute, including the baseline row that reads 57.0.

```
### (b) Enforcement-sensitivity — G3 requires *boundary enforcement*, not just scope acquisition
Now that production is live, a stale-scoped connection fails **in the real world** (silent vendor 403), so crediting a capability for merely *requesting* a scope overstates reality. Recomputing Plausible with G3 requiring the writer to **refuse a mis-scoped connection pre-flight** (only GA4 fail-closed, Meta catalog-create, and Klaviyo's keyed model qualify):

| Scope | Plausible before | Plausible with boundary-enforcement | 
|---|---|---|
| **Overall** | 57.0% | **5.4%** |
| **Tier-1 (money-moving)** | 68.8% | **6.3%** |
| Meta | 73.7% | **1.3%** |
| Google | 61.7% | **0.0%** |
| Shopify | 48.5% | **0.0%** |
| TikTok | 70.8% | **0.0%** |
| GA4 | 34.8% | **34.8%** (unchanged — fail-closed) |
| Klaviyo | 15.4% | **15.4%** (keyed, no scope drift) |

```

---

## Note 5

**Source:** `audit/write-access/REPORT.md`, lines 58 to 66

**Why this passage:** The three independent facts that cap the strict score at zero.

```
## 3. Why strict = 0% (the load-bearing evidence)

Three independent facts, any one of which alone caps G6 at zero:

1. **The live-verification harness has never been run.** `scripts/verify-live/` (`npm run verify:live`) plus `docs/audits/live-verification/RUNBOOK.md` exist, but `docs/audits/live-verification/UNVERIFIED_INVENTORY.md` is the standing record and the harness requires an interactive, human-armed session that has not occurred. The only "verification" document, `audit/deep-audit/pre-activation-verification.md` (2026-06-15), is explicitly *read-only* — "no platform writes against any account."
2. **Shadow-by-default.** Every ad platform gates the terminal vendor call behind `*_WRITE_MODE` (`META_/GOOGLE_ADS_/SHOPIFY_/KLAVIYO_/TIKTOK_WRITE_MODE`), default `"shadow"` (`lib/ads/writers/test-mode.ts`, `google-internal.ts`, `shopify-internal.ts`, `klaviyo-internal.ts`, `tiktok-token.ts:21`). Whether prod sets any to `live` is **unverifiable from the repo** (it lives in Vercel env). Shadow mode returns an explicit `{shadow:true}` envelope and logs a CRITICAL warning — it is *transparent*, not a fabricated success — but it means no vendor mutation fires under default config.
3. **No production telemetry proves a live write.** The schema *can* prove it — `ActionAudit` carries `platformResponse Json?`, `executionMode ("live"|"shadow"|"test_account")`, `preState/postState`, `scopeVerified` (`prisma/schema.prisma:2229+`) — but no accessible prod query surfaces a single row with `executionMode="live"` + a vendor 2xx, and the dev DB runs shadow. "Never executed live" and "executed and failing" are indistinguishable from the repo, so neither can earn G6.

A capability that has never once executed live cannot score G6 regardless of how good the code looks — and 64 of them look very good.
```

---

## Note 6

**Source:** `audit/write-program/SPRINT-4.md`, lines 115 to 133

**Why this passage:** Sprint 4 regenerated matrix.

```
## 6. Regenerated matrix

Same 130 canonical capabilities, tiers and weights as the 2026-07-29 audit (`audit/write-access/compute.mjs`); only the gate column moved. Every change is annotated with the sprint that moved it in `audit/write-program/compute-sprint4.mjs`.

| Platform | G4+ before | **G4+ after** | Maturity before | **after** | G6 |
|---|---:|---:|---:|---:|---:|
| Meta | 73.7% | **90.8%** | 59.2% | **75.2%** | 0 |
| Google | 61.7% | **98.3%** | 51.7% | **81.1%** | 0 |
| Shopify | 45.7% | **94.3%** | 38.1% | **78.6%** | 0 |
| Klaviyo | 15.4% | **100%** | 16.7% | **82.1%** | 0 |
| TikTok | 70.8% | **100%** | 47.2% | **82.6%** | 0 |
| GA4 | 34.8% | **100%** | 23.2% | **69.6%** | 0 |
| **Overall** | **56.6%** | **95.9%** | **45.2%** | **78.1%** | **0** |
| Tier-1 only (money-moving) | 68.8% | **100%** | 55.2% | **81.8%** | **0** |
| Business-day weighted | 58.0% | **95.3%** | 46.8% | **77.5%** | **0** |

Gate histogram overall: `G0 63→9 · G1 2→0 · G3 1→0 · G4 17→13 · G5 47→108 · G6 0→0`.

**Lead reconciliation, stated because two agents graded identically-situated platforms differently:** the Klaviyo agent capped its platform at G4 because live writes refuse until an operator records the key's scopes; the TikTok agent awarded G5 despite an equally-unproven write scope. Both cannot be right. **Ruling: an unverified scope *configuration* is an arming precondition, not a gate demotion** — otherwise every shadow platform collapses to G4 and the gate stops discriminating between "code is correct" and "operator has configured it". G5 means the control stack is present and correct; configuration state is tracked in §8 instead. Klaviyo is graded on TikTok's basis.
```

**Source:** `audit/write-program/SPRINT-5.md`, lines 205 to 220

**Why this passage:** Sprint 5 regenerated matrix and the note that coverage fell because the measurement tightened.

```
## Regenerated matrix (`audit/write-program/compute-sprint5.mjs`)

| Platform | Sprint 4 G4+ | **Sprint 5 G4+** | Maturity | Note |
|---|---:|---:|---:|---|
| Meta | 90.8% | **90.8%** | 75.2% | unchanged; A-fixes are hygiene, not new capability |
| Google | 98.3% | **98.3%** | 81.1% | unchanged in score — but it now runs on a version that will still answer in September |
| Shopify | 94.3% | **94.3%** | 78.6% | unchanged |
| Klaviyo | 100% | **100%** | 82.1% | unchanged |
| **TikTok** | 100% | **0.0%** | **0.0%** | **all 13 marked `[VENDOR-BLOCKED]`** |
| GA4 | 100% | **100%** | 69.6% | unchanged |
| **Overall** | **95.9%** | **86.1%** | **69.9%** | **−9.8 points** |
| Tier-1 (money-moving) | 100% | **90.6%** | 74.0% | |
| **G6** | **0** | **0** | | unchanged |

**Coverage went down, and that is the sprint working.** 95.9% was counting 27 TikTok writers that cannot execute. 86.1% counts what can. The number got worse because it got truer.

```

---

## Note 7

**Source:** `lib/ads/writers/types.ts`, lines 148 to 168

**Why this passage:** The execution-mode type comment that distinguishes a suppressed write from a verb with no vendor call.

```
 * Sprint 5 (Part A.1) — what an ActionAudit row records about how a verb
 * executed. This is DELIBERATELY wider than `WriteMode`.
 *
 * `WriteMode` answers "what did the *_WRITE_MODE env gate resolve to" and must
 * stay exactly the three values the gate can produce — adding a fourth would
 * imply an operator could set it.
 *
 * `ExecutionMode` answers "what actually happened to this row", which is not the
 * same question. `local_config` covers verbs that have NO vendor call in their
 * definition (enable/disable_catalog_sync flip a BrandSettings flag that a cron
 * later acts on). Those previously reported "live", which stamped
 * ActionAudit.executionMode='live' on an operation that never touched the
 * platform — a false entry in the audit trail on the one armed platform.
 *
 * `local_config` is not "shadow": shadow means a real platform write was
 * SUPPRESSED, and nothing was suppressed here. deriveVendorTelemetry leaves
 * vendorOk/vendorStatusCode null for every non-"live" mode, which is right —
 * there is no vendor response to grade.
 */
export type ExecutionMode = WriteMode | "local_config";

```

---

## Note 8

**Source:** `audit/write-program/SPRINT-5.md`, lines 22 to 31

**Why this passage:** Part A.1, the two verbs that stamped a live execution mode with no vendor call.

```
| # | Defect | Fix |
|---|---|---|
| A.1 | `enable`/`disable_catalog_sync` stamped **`executionMode: "live"`** on an operation making zero Graph calls | New `ExecutionMode = WriteMode \| "local_config"` |
| A.2 | Five Meta verbs had **no taxonomy entry at all** | Entries added; three `none`, two `semi` |
| A.3 | `meta_create_ad_with_creative` irreversible with **no idempotency guard** | `findPriorCreate`, before the image upload |

**A.1 mattered more than it looks.** Every "how many live writes have we executed" query was counting operations that never touched Meta. The audit trail is the evidence base for this entire program, and it contained false entries on the one armed platform.

The fix widens the *audit-facing* type only. `WriteMode` deliberately stays the three values the `*_WRITE_MODE` env gate can resolve to — adding a fourth would imply an operator could set it. `ExecutionMode` answers a different question: what actually happened to this row. **`local_config` is not `shadow`**: shadow means a real platform write was *suppressed*, and nothing was suppressed here — these verbs have no vendor call in their definition. Calling them shadow would have been a different lie. The widening rippled to five call sites carrying their own narrow annotations; the type system caught every one.

```

---

## Note 9

**Source:** `lib/actions/reversibility.ts`, lines 27 to 48

**Why this passage:** The reversibility class union and the explicit-confirmation default.

```
export type ReversibilityClass = "full" | "semi" | "none";

export type AutonomyDecision = "propose" | "auto" | "blocked";

export interface ReversibilityEntry {
  verb: string;
  platform: ActionPlatform;
  reversible: ReversibilityClass;
  reversalWindowMs: number | null;
  reversalVerb: string | null;
  defaultMode: {
    suggest: AutonomyDecision;
    reversible: AutonomyDecision;
    full: AutonomyDecision;
  };
  /** Sprint 11 — irreversible verbs require an explicit second-stage "type SEND"
   *  confirmation in the approval UI; the approve route + writer both assert a
   *  sendConfirmed flag. Defaults to (reversible === "none") when unset. */
  requiresExplicitConfirmation?: boolean;
}

const HOUR_24_MS = 24 * 60 * 60 * 1000;
```

---

## Note 10

**Source:** `audit/write-program/SPRINT-6.md`, lines 15 to 34

**Why this passage:** Part A, the duplicate-suppression window, its scope, and the residual gap.

```
**Correction to the brief: this was not 15 verbs.** There are **ten** `findPrior*` implementations across six platforms guarding **sixty-six** verbs. Fixing them individually would have guaranteed the next writer got it wrong.

**What already existed, and was deliberately not rebuilt.** The write paths already stamp `executing` before the vendor call (steps 1–2), and `lib/actions/reconciler.ts` already resolves orphans — every 5 minutes, for rows older than 60s and younger than 7 days, by issuing a **read-only** platform GET and settling from **observed state** (`executed` / `failed` / `needs_review`), never by retrying the write and never by clearing on a timer. That is exactly the orphan policy step 4 asked for. **The missing piece was step 3**: making the idempotency check *see* those rows.

**The fix.** New `lib/actions/audit/idempotency.ts` returns a three-way outcome — `proceed` / `replay` / `blocked` — treating **both** `executing` (in flight or orphaned) and `needs_review` (the reconciler looked and couldn't tell) as blocking. A completed prior attempt wins over an in-flight one, because a replayable recorded result is strictly better information than "something is happening".

New `WriterError` kind **`duplicate_in_flight`**, retryable per case: `true` for `executing` (the reconciler settles it, then a re-check replays or proceeds), `false` for `needs_review` (a human must resolve it; rescheduling would hide it). Maps to **HTTP 409 Conflict** in all five mappers — the exact semantic, not a 5xx (nothing is broken) and not a 429 (retrying sooner won't help). The exhaustive switches caught every mapper that needed a decision.

The resolver **throws** rather than returning on `blocked`, so an unmigrated writer cannot silently ignore it; `executeVerb` converts that to a typed `WriterResult` at exactly one boundary instead of 66 call sites.

**A subtlety that would have silently defeated the whole fix:** the three Klaviyo helpers wrap their lookup in `catch { return null }` (fail-open on DB error). That would have swallowed the refusal and re-fired the blast — the exact bug. They now re-throw `DuplicateInFlightError` explicitly while keeping fail-open for genuine read errors.

### Residual window — honestly stated

| Window | Status |
|---|---|
| vendor call returned → `markExecuted` (a full round-trip + DB write) | **CLOSED** |
| row set `executing` → vendor call issued (sub-millisecond) | **OPEN** |

A crash in the open gap leaves a row that blocks a retry which would actually have been safe. That fails in the **correct direction** — a refused write the operator can re-approve beats a duplicate blast — and the reconciler settles it within ~5 minutes by observing the platform never changed. **The remaining exposure is "refuses when it could have proceeded", never "proceeds when it should have refused."**
```

---

## Note 11

**Source:** `audit/write-program/SPRINT-2E.md`, line 40

**Why this passage:** The grading rule for historical audit rows and the three that stay ungradeable.

```
- **Backfill** — `scripts/backfill-actionaudit-executionmode.ts`, idempotent, grades historical `executionMode=null` rows from `platformResponse`: `shadow:true` → `shadow`; a real vendor id/handle → `live`; **`{"success":true}` with no request id → left null (ungradeable by design)**. Dry-run on DEV: 8 executed-null rows, **all correctly ungradeable** (the `{"success":true}` rollback + no-platformResponse rows). The 3 May/June prod rows the task flagged are exactly this class and remain null — they cannot be graded, which the script states explicitly rather than guessing.
```

**Source:** `docs/audits/live-verification/UNVERIFIED_INVENTORY.md`, line 58

**Why this passage:** The production table size at the migration verification.

```
**Status: no action needed.** `prisma migrate status` against prod (`[db-prod]`) returns "Database schema is up to date"; all 7 columns/tables/enum values confirmed present via `information_schema`. The CI/CD deploy pipeline (`npm run build` → `prisma migrate deploy`) applied each one automatically on merge to `main` — the "apply via Path B" note in the sprint reports was written at authoring time and is superseded. `ActionAudit` had 5 rows so migration #2's FK/index build was trivial. Listed below for the record.
```

**Source:** `audit/write-program/SPRINT-2E.md`, lines 25 to 26

**Why this passage:** The write against a real campaign whose pre-state reads as vendor-returned.

```
**The actual defect (23-Jun):** the "Cresva" brand executed a write against a real Meta campaign (`preState.lifetimeBudget [budget-value]`) while having **no `AdAccount` row at all** — the write target was not resolved from the brand's linked account. With multiple brands under tokens that can reach many accounts, a write for brand A could land on brand B's account.

```

---

## Note 12

**Source:** `audit/write-program/SPRINT-2.md`, lines 27 to 31

**Why this passage:** The corrected Klaviyo endpoints, with the two-step schedule and the absent idempotency header.

```
- **Klaviyo send/schedule/cancel — FIXED (the fabricated `campaigns/{id}/jobs/send` is gone):**
  - send-now → `POST /api/campaign-send-jobs` `{data:{type:"campaign-send-job",id}}` (send_campaign OpenAPI) + app-level `findPriorKlaviyoSend` idempotency (Klaviyo documents **no** Idempotency-Key header for this endpoint; a double-send is irreversible).
  - schedule → **2-step** (the send-job body carries no `send_strategy`): `PATCH /api/campaigns/{id}` `send_strategy:{method:"static",datetime,options:{is_local:false}}` (update_campaign OpenAPI), then POST the send-job.
  - cancel → `PATCH /api/campaign-send-jobs/{id}` `{...attributes:{action:"cancel"}}` (cancel_campaign_send OpenAPI; enum `cancel|revert`) — was a DELETE on the invented sub-resource.
  - revision `2024-10-15` → `2026-07-15`.
```

---

## Note 13

**Source:** `audit/write-program/SPRINT-2C.md`, lines 47 to 51

**Why this passage:** The writer-test classification table rows for the two mirror-class Meta files.

```
| Test file | Class | Would catch an invented endpoint? | Evidence |
|---|---|---|---|
| `pixel-writers.test.ts` | **MIRROR** ✔ | **NO** — same class as bug #1 | mocks `metaFetch`→`{ok:true}` regardless of URL; asserts only `r.ok` + side-effect mocks (`store`/`send` called). Meta pixel/CAPI/test-event endpoints (`/adspixels`, `/events`) are never asserted. |
| `sprint-8-meta-create-verbs.test.ts` | **PARTIAL/MIRROR** ✔ | **WEAK** | `metaFetch.mockResolvedValue({ok:true,data:{id}})` regardless of args; tests special_ad_categories/idempotency/shadow but does not assert the `/act_{id}/campaigns` endpoint path. A wrong create path would slip. |
| `meta.test.ts` (status/budget) | **PARTIAL** ✔ | **MOSTLY** | asserts the mutate **body** (`JSON.stringify({status:"PAUSED"})`) + the preState GET `fields=` — but not the mutate URL path explicitly. Wrong body caught; a wrong path might slip. |
```

**Source:** `audit/write-program/SPRINT-2C.md`, lines 63 to 64

**Why this passage:** The actionable finding and the harness rule it produced.

```
**Actionable finding:** `pixel-writers.test.ts` (and to a lesser degree `sprint-8-meta-create-verbs.test.ts`) are **incapable of catching an invented endpoint** — the exact class that let bug #1 survive. They cover Meta pixel/CAPI/test-event + create-campaign/adset/ad. **Recommendation (not done this session — Task 4 is report-only):** rewrite them to assert the literal `metaFetch` URL/method/body against the Meta docs, using the same Proxy/branch-on-path technique as the rewritten customer-match test. The general rule for the harness: **a writer test's transport mock must be shaped to the real vendor surface (branch on the documented path / expose only real methods), never to whatever the implementation happens to call.**

```

---

## Note 14

**Source:** `audit/write-program/SPRINT-3.md`, lines 21 to 25

**Why this passage:** The deliberate-change proof for the Meta endpoints, with the revert check.

```
**DELIBERATE-CHANGE PROOF (exit criterion):** temporarily changing the writer's endpoint made the tests fail, where before they stayed green:
- `createPixel` `/adspixels` → `/pixels` ⇒ `CONTRACT VIOLATION: unexpected Meta endpoint POST …/act_1/pixels` (2 tests fail).
- `createCampaign` `/campaigns` → `/campaign_groups` ⇒ `CONTRACT VIOLATION: …/act_1/campaign_groups` (fails).
Both changes reverted; `git diff lib/ads/writers/meta.ts` is empty. **Harness rule going forward: a transport mock must be shaped to the real vendor surface, never to whatever the implementation calls.**

```

**Source:** `audit/write-program/SPRINT-4.md`, lines 147 to 148

**Why this passage:** The Shopify and Klaviyo deliberate-change proofs.

```
**D2 — endpoint assertions, with deliberate-change proofs.** Shopify rewrote `sprint-11-shopify-verbs.test.ts` to an exact `(method, endpoint)` contract mock (9 → 14 tests); breaking `updateInventoryLevel` to `inventory_levels/adjust.json` — a *real* Shopify endpoint, just the wrong one — produced 3 `CONTRACT VIOLATION` failures. Klaviyo added a 28-route contract mock (35 new tests) and **re-introduced the program's actual historical hallucination** (`campaigns/{id}/jobs/send`): it now fails loudly where before this sprint that exact change stayed green. That is the single most direct piece of evidence in this document.

```

---

## Note 15

**Source:** `audit/write-program/SPRINT-5.md`, lines 46 to 66

**Why this passage:** The renamed campaign date fields, why a clean compile missed them, and the two affected writers.

````
### The breaking change a clean `tsc` completely hid

```
google.ads.googleads.v24.resources.Campaign
  start_date (id 63)  REMOVED  ->  start_date_time (id 104)
  end_date   (id 64)  REMOVED  ->  end_date_time   (id 105)
```

`mutateResources<T>` is **generic with `T` inferred from the call site**, so `resource: T` accepts any object literal. TypeScript validates the `entity` string and nothing else — a removed protobuf field is invisible to it. **tsc passed with zero errors across a three-major upgrade while two writers were broken.**

| Writer | What v24 did to it |
|---|---|
| `google_create_campaign` | set `campaign.start_date`. On v24 that key isn't applied — a campaign created with a start date would have **silently started immediately**. The more serious of the two: silent, and in the wrong direction. |
| `google_set_campaign_end_date` | set `campaign.end_date` **and** selected it in GAQL. The SELECT fails outright on v24, so the writer was dead before reaching the mutate. |

**Format — it is a date-TIME, not a date.** Google's own create-campaign samples use a date part plus a time part, conventionally `00:00:00` for a start and `23:59:59` for an end. We send `23:59:59`: a bare date (or `00:00:00`) would end the campaign at the **start** of the chosen day, cutting a full day of delivery.

**Timezone.** The value is a **wall-clock string** Google resolves in the serving customer's timezone; it carries no offset. The correct handling is to pass the operator's chosen calendar date through **unchanged** and append the time part. We do **not** round-trip through `Date` and do **not** convert to UTC — formatting an instant in UTC or server-local time can shift the calendar date by one, which on `end_date_time` means a campaign runs an extra day or stops a day early. That is a spend bug that surfaces as a client complaint, not an API error. Neither date is derived from "now", so no conversion is needed; `AdAccount.accountTimezone` would only be required if a future caller derived a date from the current instant.

**Caller contract unchanged.** Schemas, tool inputs, proposals and stored payloads all still carry `YYYY-MM-DD`; conversion happens at the writer boundary only, so no in-flight proposal is invalidated. The v24 read-back (`"YYYY-MM-DD HH:MM:SS"`) is reduced to a calendar date before the idempotency compare, so a retry doesn't re-fire on a time-part mismatch.

````

**Source:** `audit/write-program/SPRINT-5.md`, lines 67 to 82

**Why this passage:** The methodology correction and the per-message re-verification totals.

```
### Methodology correction

My first pass **missed this**, because it checked field names against the **global v24 vocabulary** rather than per-message: `start_date`/`end_date` still exist on *other* messages (`SitelinkAsset`, `CalloutAsset`), so the global check came back clean. Worse, a bare-name lookup also lies — **three distinct messages are named `Campaign` in v24**. Every check below is per fully-qualified message. *Verify against the specific message or endpoint, never the vocabulary, and never let a clean `tsc` stand in for it.*

### Re-verification against the v24 descriptor (`google-ads-node@24.1.0`)

| Check | Result |
|---|---|
| mutate entity names | **22/22** present |
| service methods, incl. the customer-match trio whose names were already wrong once | **7/7** present |
| enum types | **26/26** present |
| enum **renumbering** across all 348 enums | **0** — no silent value breakage |
| enum members removed SDK-wide | 1 (`UserListCustomerTypeCategory.LOYALTY_SIGN_UPS`) — never referenced |
| field names our writers use | **77/77** valid v24 fields |
| `*_date` → `*_date_time` renames in the whole descriptor | **exactly 2**, both on `resources.Campaign` |
| `SitelinkAsset` / `CalloutAsset` dates | **unchanged** — Sprint-4 asset writers unaffected |
```

---

## Note 16

**Source:** `audit/write-program/SPRINT-1.md`, lines 21 to 28

**Why this passage:** Task 2, the shared scope preflight wired into the four write-preparation chokepoints.

```
## Task 2 — Scope enforcement at the writer boundary (closes the live-Meta silent-403 exposure)
- **`lib/guardrails/assert-write-scope.ts`** (new): ONE shared preflight `resolveWriteScopeRefusal(platform, brandId, opts)` → typed `permission_denied` WriterError (retryable:false) naming the missing scope, **fail-closed** on read error. Wired into all four write-prep chokepoints, gated on `mode==='live'`:
  - `lib/ads/writers/meta-internal.ts` `prepareWrite` → `ads_management` (was: only catalog enforced, at `meta.ts:1987`).
  - `lib/ads/writers/google-internal.ts` `prepareGoogleAdsWrite` → `adwords` (was: **no** write-path readback — new **`lib/oauth/google/check-write-scopes.ts`**).
  - `lib/ads/writers/shopify-internal.ts` `prepareShopifyWrite(...requiredScope)` → per-capability scope (5 writers pass `write_discounts`/`write_inventory`/`write_marketing_events`; rest default `write_products`).
  - `lib/ads/writers/tiktok-token.ts` `prepareTikTokWrite` → ads-management, now preflighted for **all 8** writers.
- `lib/oauth/shopify/check-write-scopes.ts`: `REQUIRED_SHOPIFY_WRITE_SCOPES` now includes `write_inventory` + `write_marketing_events` (they were requested by the OAuth start route but omitted → false-green banner).
- `lib/oauth/tiktok/check-write-scopes.ts`: removed the `"1"` sentinel + `g.includes(...)` substring match → **exact-match** ads-management ids (the rubber-stamp is gone).
```

---

## Note 17

**Source:** `audit/write-program/SPRINT-1.md`, lines 27 to 28

**Why this passage:** The two Shopify scopes omitted from the readback, and the removal of the substring sentinel.

```
- `lib/oauth/shopify/check-write-scopes.ts`: `REQUIRED_SHOPIFY_WRITE_SCOPES` now includes `write_inventory` + `write_marketing_events` (they were requested by the OAuth start route but omitted → false-green banner).
- `lib/oauth/tiktok/check-write-scopes.ts`: removed the `"1"` sentinel + `g.includes(...)` substring match → **exact-match** ads-management ids (the rubber-stamp is gone).
```

**Source:** `audit/write-program/SPRINT-5.md`, lines 133 to 140

**Why this passage:** The documented numeric scope identifiers, stated as unobserved.

```
TikTok's documented behaviour is that the token response carries **numeric scope ids** (`"scope": [4]`). If so, we persist `["4"]`, and `"4" === "ads.management"` is **false forever**, on every brand, regardless of app configuration.

**Two independent pieces of corroboration, both from our own history:**
1. The `.map(String)` in our callback exists because somebody was converting non-strings. You do not write that for a list of strings.
2. The historical rubber stamp Sprint 1 removed was **`g.includes("1")`** — you only write *that* if you are staring at digits and trying to make a number match a name.

So the sequence was: someone hit the numeric-vs-string mismatch, papered over it with a digit check, Sprint 1 correctly removed the rubber stamp (restoring fail-closed), and the underlying mismatch was never fixed. The preflight has refused every TikTok write ever since, correctly and permanently.

```

**Source:** `audit/write-program/SPRINT-6.md`, lines 82 to 92

**Why this passage:** Part D, why the Klaviyo key scopes are stored as an attestation.

```
## Part D — Klaviyo key scopes

Klaviyo publishes **no** endpoint returning an API key's own scopes. The options were inventing one (the failure this program exists to prevent), probing with a real write (exactly what must not happen before the scope is known), or having the operator record what Klaviyo showed them at key creation. **Only the third is sound.**

Built `lib/oauth/klaviyo/key-scope-store.ts` + `PUT/GET/DELETE /api/settings/klaviyo-key-scopes` (route-factory `{mode:"brand"}`, so `requireBrandAccess` runs first).

It is stored **as an attestation** — the scope list plus who recorded it and when — because an attestation with no author is barely better than a guess. Written to `Connection.metadata.declaredScopes`, which was **already** in the resolver's precedence chain, so recording is enforced immediately with no preflight change.

**Deliberately not written to `grantedScopes[]`.** That column means "the provider told us this" everywhere else in the codebase; laundering an operator attestation into it is the same mistake Sprint 5 refused to make with Google's backfill.

Malformed scopes are **rejected, not dropped** — silently discarding `campaigns:wirte` would leave the operator believing a verb was enabled when it is still blocked. `PUT` requires `confirmed: true` because recording *opens* live writes; `DELETE` closes them instantly. `GET` reports which verbs the record enables and blocks, which is the operator's actual question.
```

---

## Note 18

**Source:** `audit/write-program/SPRINT-2E.md`, lines 10 to 14

**Why this passage:** Task 1, the readback that refused every write, and the resolution chain that fixed it.

```
**Root cause:** the OAuth callback wrote the granted scopes into `Connection.metadata.grantedScopes` (a comma-separated *string*) but never into the top-level `grantedScopes String[]` column — which stayed `{}` on all 8 prod connections. Sprint 1's preflight read the empty array column (falling back to the also-empty `scope` column), so with `META_WRITE_MODE=live` it refused **every** Meta write as `needs_reconsent`.

**Fix (three layers):**
- **Readback** — new `resolveGrantedScopes(connection)` with precedence `grantedScopes[]` → `metadata.grantedScopes` (string) → `metadata.scope` → `scope`. Applied to `getBrandMetaWriteScopes`, `brandHasCatalogScope`, and `getMetaConnectionScopeHealth`. This makes the gate correct **even before the backfill** (it reads where the data actually is).
- **Callback** — now writes the top-level `grantedScopes` array + `scope` columns canonically for new/reconnected connections, so the source is correct going forward.
```

---

## Note 19

**Source:** `audit/write-program/SPRINT-1.md`, lines 8 to 15

**Why this passage:** The premise correction: the guardrail finding that was stale.

```
## Premise correction (verified before building — Task 1 was already shipped)

The sprint's Task 1 premise ("cumulative cap is count-only, fails OPEN, cron bypasses it") was **stale**, and so was the audit that drove it (`audit/write-access/GAPS.md` D2, `CONTAINMENT.md` C1). The real state, verified in code:
- `lib/executor/guardrailEvaluator.ts` (called at `executeVerb.ts:1114` — ALL paths) already enforces a **cumulative per-UTC-day DOLLAR cap** (`sumTodaysBudgetWritesUsd`) with a **binding $500 system default** (`DEFAULT_DAILY_MAX_SPEND_DELTA_USD`, override→env→default), reading **fail-CLOSED** (`getSpendGuardrailsStrict`).
- `lib/executor/processGoal.ts:339` and `lib/divergence-response/adapter.ts:210` already route the **cron/autonomous** path through `reserveAutoFireSpend` (CHECK 3 was stale).

Per the operator's decision, Task 1 was **not rebuilt** — only extended (bid magnitude) + proving-tests added + this correction recorded. **`GAPS.md`/`CONTAINMENT.md` should be updated to reflect that D2/C1 shipped in Road-to-100% Prompt 1 (A2).**

```

---

## Note 20

**Source:** `audit/write-access/REPORT-ADDENDUM.md`, lines 5 to 9

**Why this passage:** The auditor's own error, stated with its class.

```
## 1. What was wrong, and why

`REPORT.md` scored **strict (G6) = 0.0%** and captioned the system "built and guardrailed, activation-pending." That conclusion rested on one stated assumption: *all platforms run shadow-by-default and production arming is unverifiable from the repo.*

**That assumption was false and, importantly, was checkable — I did not check it hard enough.** `vercel env ls production` (available this session, names-only) shows **`META_WRITE_MODE` is set in production** (36 days ago, same day as `ACTIONS_ENABLED`). The original audit inferred "shadow-by-default" from *code defaults* and declared prod arming "unverifiable from the repo" — but it was verifiable from the deployment platform, which the audit did not consult. **This is the same class of error the audit itself flagged (asserting from what's in front of you instead of checking the authoritative source).** Recorded here so it survives.
```

---

## Note 21

**Source:** `audit/write-program/SPRINT-6.md`, lines 118 to 132

**Why this passage:** The final arm-readiness table, every row a human action.

```
## Final arm-readiness — non-code blockers only

Every row below is a human action. **No row requires a code change.**

| Platform | Remaining blockers (all non-code) |
|---|---|
| **Meta** | Set `VERIFY_META_AD_ACCOUNT` / `VERIFY_META_TEST_PIXEL` / `VERIFY_META_CAMPAIGN_ID` to a throwaway account. Confirm each armed brand has an `AdAccount` row. |
| **Google** | Re-consent connections flagged `UNPROVABLE`/`NO-SCOPE` by `scripts/audit-google-scope-provenance.ts`. Set `VERIFY_GOOGLE_CUSTOMER_ID` + base entity ids. Confirm each armed brand has an `AdAccount` row. |
| **Shopify** | **Every existing store must re-consent** (4 scopes added in Sprint 4; Shopify never silently expands). Set `VERIFY_SHOPIFY_DEV_STORE`. |
| **Klaviyo** | Record the API key's scopes via `PUT /api/settings/klaviyo-key-scopes`. Run the brand's Klaviyo ETL. Set `VERIFY_KLAVIYO_TEST_ACCOUNT` + `VERIFY_KLAVIYO_TEST_EMAIL`. |
| **TikTok** | **Capture one real token response** and read `data.scope`. Enable ads-management in the Business Center app if absent. Populate `BrandSettings.settings.tiktokBcId`. Set `VERIFY_TIKTOK_ADVERTISER`. |
| **GA4** | Nominate a writable test property; set `VERIFY_GA4_PROPERTY_ID`. Record the MP credential via `PUT /api/settings/ga4-measurement-protocol`. Confirm an `AnalyticsProperty` row per armed brand. |

**One code-side caveat that is not a blocker:** TikTok's scope matcher compares string literals, and TikTok is documented to return **numeric** scope ids. If the captured response confirms that, a small numeric-id mapping is needed. It is listed as evidence-gathering because the code change cannot be written correctly until the ids are known — inventing them is the failure mode this program exists to prevent.

```
