# Enhance, simplify and align plan

80/20, ordered. Every item names outcome, systems, owner, prerequisite, effort, risk, business value
and an exact acceptance test. Each carries a disposition:
**APPLY NOW** · **APPLY AFTER MORE DATA** · **KEEP AS SAFETY CONTROL** · **REMOVE OR MERGE** ·
**NEEDS MATT'S DECISION** · **FUTURE IDEA**.

## The one-paragraph reason this plan is ordered the way it is

Matt asked for a dashboard that compares creatives, placements, targeting and demographics so he can
move budget toward better leads. The blocking problem is not that the dashboard was never built. It
is that **(a)** 94% of the conversions both platforms optimise against are form *starts*, not
enquiries, **(b)** the lead register has no source column, so the 13 real enquiries cannot be
attributed to anything, and **(c)** 100% of Google spend sits in one PMax campaign and Meta has one
campaign, so there is no population to compare and no dimensional data is even fetched. Building the
dashboard first would produce a polished page of one-row tables driven by a proxy metric — the exact
"polished but wrong" outcome the review was asked to prevent. Groups 1 and 2 below cost days, not
weeks, and they are what makes a dashboard worth building.

---

## Group 1 — Immediate correctness and measurement integrity

### P1-01 · Make a verified enquiry the primary conversion, demote the form-start
**Disposition: NEEDS MATT'S DECISION** (it is a live Google Ads and Meta change, which this review is
forbidden to make and which Matt's own rule reserves for explicit approval).

- **Outcome:** PMax smart bidding and Meta delivery optimise toward submitted enquiries instead of
  form starts.
- **Evidence:** `report_data.json` → `ppc.conversion_actions`: `vhk_form_start` = 46.0 and
  `VHK_lead` = 3.0 against 49 reported conversions; `meta_ads.actions` uses
  `offsite_conversion.fb_pixel_custom.VHK_form_start`; the client register shows 13 real enquiries.
- **Systems:** Google Ads `293-822-3921` conversion actions; Meta pixel event set on
  `act_1783210591808804`; the site's form success handler.
- **Prerequisite:** confirm a server-side or thank-you-page success event exists that fires only on
  successful submission. `VHK_lead` at 3.0 suggests one exists but is under-firing — diagnose before
  switching.
- **Effort:** low to medium. **Risk:** medium — bidding will re-learn, and volume will drop on
  paper because the honest number is smaller. Say so to the client before the change, not after.
- **Business value:** highest single item in this review. Every budget decision downstream is
  currently trained on a proxy that overstates results by roughly 3.8x against the client's own
  register.
- **Acceptance test:** in the following 30-day window, Google Ads primary conversions and the lead
  register agree within a stated tolerance, and the report's platform-versus-register gap narrows
  from 49-versus-13 to a documented, explainable difference.

### P1-02 · Add source, campaign and click-ID columns to the lead register
**Disposition: APPLY NOW.**

- **Outcome:** the 13 monthly enquiries become attributable to a platform, campaign and creative.
- **Evidence:** `report_data.json` → `crm.source_column: null`; `crm.no_source_column_note`: *"this
  spreadsheet has no source or campaign column, so enquiries cannot be attributed to a traffic
  source."*
- **Systems:** the `FusionTables - Ajánlatkérések` sheet, the site's form handler, hidden form
  fields for `utm_*`, `gclid`, `fbclid`, `wbraid`.
- **Effort:** low — hidden fields plus four columns. **Risk:** low.
- **Business value:** this single change converts the whole reporting stack from "how many
  enquiries" to "which spend produced which enquiry". Nothing else unlocks as much for as little.
- **Acceptance test:** a test submission through a tagged ad URL lands in the sheet with a non-empty
  source, campaign and click ID; the next monthly report attributes at least 80% of enquiries to a
  traffic source and reports the unattributed remainder explicitly rather than dropping it.

### P1-03 · Retire or rename the page-load conversion actions
**Disposition: NEEDS MATT'S DECISION** (live Ads change).

- **Evidence:** `ppc.conversion_actions` contains
  `Purchase (Page load https://fusiontables.hu/billiardasztal/billiardasztal)` and
  `Submit lead form (Page load https://fusiontables.hu/biliard/biliard)`. Both read 0.0 this window,
  so nothing is currently corrupted — but an action named `Purchase` that fires on a page view is a
  live trap. If it ever starts firing, the account reports purchases that did not happen and the
  report inherits them.
- **Effort:** low. **Risk:** low. **Value:** removes a latent false-revenue path before it fires.
- **Acceptance test:** no enabled conversion action in the account has a page-load trigger and a
  transaction-shaped name; a documented action inventory shows primary versus secondary status for
  all 20 actions.

### P1-04 · Repair the Meta API session so the refresh is automatable
**Disposition: APPLY NOW.**

- **Evidence:** `report_data.json` → `limitations[0]`: *"The configured Meta API session is invalid
  (190/467)."* The delivered figures came from a manual authenticated Ads Manager export.
  `source_health.meta_ads.status` = `degraded`.
- **Effort:** low. **Risk:** low. **Value:** without it the "monthly refresh" is a person clicking
  export in Ads Manager, which is the thing the automation was supposed to replace.
- **Acceptance test:** a read-only API call returns the same account totals for 2026-07-25..2026-08-23
  as the archived export, reconciled row by row across all 30 daily rows.

### P1-05 · Stop publishing a revenue axis that has no revenue behind it
**Disposition: APPLY NOW.**

- **Outcome:** no surface renders ROAS, conversion value or revenue for this account until real
  revenue exists.
- **Evidence:** `report_data.json` → `ppc.totals.conversion_value` = `{"current": 49.0,
  "previous": 1.0, "unit": "huf"}`, numerically identical to `ppc.totals.conversions`
  `{"current": 49.0, "previous": 1.0, "unit": "count"}`. Every conversion carries a default value of
  1, relabelled as forint. The pipeline then derives `roas` = 0.000532056… and `cpa` = 1,879.50 HUF
  from it. The same pair appears inside `ppc.campaigns` for the PMax campaign.
- **Why it is urgent:** the delivered `report.md` does not print ROAS, so nothing is currently
  misleading a client. But the field is sitting in the data envelope labelled `huf`, and the
  dashboard Matt asked for is exactly the surface that would pick it up. This is a trap set for the
  next builder.
- **Smallest safe fix:** set `conversion_value`, `roas` and any revenue-derived field to the
  explicit `na` state the schema already supports, until a real value is imported.
- **Effort:** trivial. **Risk:** none. **Acceptance test:** a rendered dashboard cannot display a
  ROAS or revenue figure for this client; the field reads `UNAVAILABLE`, not `0` and not `49`.

### P1-06 · Align the period boundaries, or label them per source
**Disposition: APPLY NOW.**

- **Evidence:** `report_data.json` → `windows`: `google_ads` and `meta_ads` = 2026-07-25..08-23;
  `bigquery` = 2026-07-24..08-22; `crm_sheet` = 2026-07-**26**..08-**24**; `work_evidence` =
  2026-06-23..08-23 (62 days, `previous: null`). The enquiry count that forms the funnel's last step
  is measured over a different 30 days than the impressions and clicks above it.
- **Why it matters:** Matt's requested chart puts all of these on one x-axis. A funnel whose steps
  span different windows is not a funnel.
- **Smallest safe fix:** align the CRM window to the ad-platform window, or render each step's exact
  window on the chart and refuse to draw a step-to-step rate across mismatched windows.
- **Acceptance test:** every step in the funnel chart displays the same window, or displays its own
  window and no cross-step conversion rate is computed.

### P1-07 · Raise the period-comparison delivery threshold
**Disposition: APPLY NOW.**

- **Evidence:** `meta_ads.lead_generation.comparability` reports `comparable: true` while the
  previous window delivered on **11 of 30 days** (`ratio` 0.3667), because `min_delivery_day_ratio`
  is `0.3333`. Meta's headline period-over-period growth is therefore partly an artifact of more
  delivery days, not better performance.
- **Smallest safe fix:** raise the threshold, and when a comparison is made across unequal delivery
  days, show both day counts next to the change figure.
- **Acceptance test:** the 11-of-30 case renders with an explicit delivery-days caveat, or is marked
  not comparable.

---

## Group 2 — Critical broken or misleading flows

### P2-01 · Verify branch membership wherever a commit is cited as integrated
**Disposition: APPLY NOW.**

- **Evidence (ORCH-CORR-4):** `dependency-handoff-ledger.md` states the gate as *"Only a
  parent-accepted child commit integrated into `round-13-takeover` can release its downstream task"*
  and marks T1→T2 `PASS` citing `b1013882`. On the box,
  `git branch -a --contains b1013882` returns only `codex/fusion-ppc-t2-audit`. The one gate the
  architecture ever passed was validated against a commit that is not on the branch its own
  precondition names.
- **Smallest safe fix:** one `git branch --contains <sha> | grep -qx "  <branch>"` assertion in the
  release path, and the same check as an acceptance row.
- **Effort:** trivial. **Risk:** none. **Value:** the acceptance machinery currently certifies
  statements it never checks, which is worse than having no machinery.
- **Acceptance test:** re-running the release check against `b1013882` and `round-13-takeover` fails
  loudly; against the commit that actually carries the content (`4459dfe`) it passes.

### P2-02 · Assert that a child worktree contains its declared inputs before freezing its goal
**Disposition: APPLY NOW.**

- **Evidence (ORCH-CORR-1):** all eight child worktrees were based on `round-13-takeover`, which is
  missing 41 files including all of `ads-control/`, the Meta creative dashboard and the complete
  `2026-08-28-fusiontables-report/` package. `638e7524` is not an ancestor of that branch
  (`git merge-base --is-ancestor` → false). `launch_child.py` validates the title contract, the
  worktree, the prompt marker, tmux collisions, Codex login, the daemon, remote control, trust, the
  branch name and the runtime model/effort pin — and **nothing about inputs**.
- **Smallest safe fix:** each child goal already lists its input paths; add a loop that stats them in
  the worktree and refuses to launch on a miss.
- **Effort:** low. **Value:** T3 (dashboard) and T5 (report) would have failed on missing data with
  no way to work around it, exactly as T1 only survived by reading Git blobs from another branch.
- **Acceptance test:** launching a child whose goal cites `ads-control/config.yaml` from a
  `round-13-takeover` worktree exits non-zero naming the missing path.

### P2-03 · Make a stalled run visible
**Disposition: APPLY NOW.**

- **Evidence:** the architecture `run-state.md` still reads `Status: ACTIVE` and
  `Last update: 2026-08-30T12:14:00+00:00` while `tmux ls` shows no architecture or child session.
  `~/ops/sessions/fusion-ppc-*.json` carry `branch` and requested model but no status field that is
  ever updated, so liveness cannot be read from disk. Nothing alerted; the run simply stopped and the
  files kept asserting it was running.
- **Smallest safe fix:** a heartbeat written by each child and a single watcher that flips
  `run-state.md` to `STALLED` and posts one Mission Control line when no heartbeat advances for N
  minutes. The box already has `~/ops/status.sh` for exactly this.
- **Effort:** low. **Value:** this is why a full day passed with seven of eight deliverables not
  produced and no signal.
- **Acceptance test:** kill a child's tmux session; within N minutes `run-state.md` reads `STALLED`
  and Mission Control shows it.

### P2-04 · Fix or retire the Meta creative dashboard's data path
**Disposition: APPLY NOW.**

- **Evidence:** `deliverables/fusion-meta-ads-review-2026-08-24/index.html` line 395 hardcodes
  `const SOURCE_ROOT = '/.tmp/overnight-2026-08-10/data/'` and fetches `meta_ads.json` and
  `meta_ad_insights.json` from it. `.tmp/` is gitignored; neither file exists in any commit or on
  disk. The one artifact that actually does creative-level comparison — the closest thing built to
  what Matt asked for — cannot be reopened by anyone.
- **Smallest safe fix:** commit the two JSON files next to the page and make `SOURCE_ROOT` relative.
- **Effort:** trivial. **Risk:** check the JSON for personal data before committing.
- **Acceptance test:** open the page from a clean clone over a local server; the creative table
  renders with data and the console shows no failed request.

---

## Group 3 — Simplification and deletion

### P3-01 · Do not relaunch the eight-child architecture as designed
**Disposition: NEEDS MATT'S DECISION**, recommendation: do not relaunch it in this shape.

- **Evidence:** T1 consumed 555,380 tokens to produce 716 lines of Markdown index describing
  evidence that already existed as real data files one branch over. Seven of eight children produced
  nothing. The frozen goal puts all three deliverables Matt actually named — dashboard, strategy A,
  strategy B — at chain positions 3 through 8, behind two enabler tasks he never asked for
  (`goal-v1.md:44-51`). Eight tasks and seven dependency edges to deliver three artifacts; one stall
  at stage 1 produced zero of three. Against Matt's verbatim *"give me a more consice prompt. i need
  the 80/20. dont overengineer."*
- **The smaller architecture that delivers the same three things:** three parallel children reading
  the already-committed report package — dashboard, strategy A, strategy B — with the evidence
  corpus as optional enrichment rather than a gate. No dependency graph, no handoff ledger, no
  release-message protocol, and no single point of failure.
- **Prerequisite:** Groups 1 and 2 first. Relaunching against the current form-start conversion
  signal and campaign-only data would produce three polished artifacts built on a proxy metric.
- **Acceptance test:** the three deliverables exist and each cites `report_data.json` fields that
  actually carry data.

### P3-02 · Collapse the control-file apparatus
**Disposition: REMOVE OR MERGE.**

- **Evidence:** 49 control files carry 3,500 lines of ceremony around 716 lines of actual evidence.
  Five separate documents assert whether T2 is running — `run-state.md:25`, `child-registry.md:9`,
  `dependency-handoff-ledger.md:8`, `children/t2-measurement-audit/run-state.md:3` and
  `~/ops/sessions/fusion-ppc-t2.json` — and they disagree, which is how the system came to claim
  `ACTIVE` while `tmux` was empty. `launch_child.py:41-53` is **byte-identical** to
  `~/ops/run-in-cloud.sh:260-272`; the 291-line launcher re-implements the platform launcher minus
  its Slack post. Seven idle worktrees hold **10.5 GB** for zero commits of work (the full
  `Fusion-ppc-children/` tree measures 14 GB).
- **Smallest safe fix:** one generated status table derived from `git branch --contains` and
  `tmux ls`, which cannot go stale; one goal file per child without the 502 duplicated preamble
  lines; `run-in-cloud.sh --no-slack` instead of a copied launcher.
- **Concrete estimate:** −34 of 49 control files, −2,457 lines, −16 stale `~/ops` artifacts,
  −10.5 GB of worktrees, roughly 30 manual bookkeeping steps removed. **Evidence files removed: 0
  of 12.** The accepted T1 corpus is unique evidence and stays untouched.
- **Acceptance test:** the status table is regenerated from git and tmux, disagrees with nothing,
  and reports `STALLED` for the current run rather than `ACTIVE`.

### P3-03 · Repoint or shelve the KPC keyword system
**Disposition: NEEDS MATT'S DECISION.**

- **Evidence (ORCH-CORR-3):** `advice_output-20260824T111713-2aa95a23.json` puts **61 of 61**
  criterion rows in `wait_for_evidence`, none approved and none to investigate, because both Search
  campaigns are paused with zero traffic. `config.yaml` says so itself and excludes PMax, which
  carries 100% of spend. The system also ships literal `TODO:` placeholders in
  `conversion_tracking` and a Mac-only `credential_source`
  (`/Users/agency/.config/riport-30d/clients/fusiontables-ads-client.json`), so it cannot refresh
  from the devbox Matt asked the work to run on.
- **Options:** repoint it at PMax search themes and asset groups, or shelve it until Search is
  re-enabled. Running it monthly against a paused structure produces 61 rows of "wait" and costs a
  review cycle each time.

---

## Group 4 — Decision quality for budget allocation

### P4-01 · Fetch the dimensions Matt actually asked for
**Disposition: APPLY NOW** (read-only collection change).

- **Evidence:** `report_data.json` has no ad-group, ad, creative, asset-group, placement, device,
  audience or demographic dimension anywhere; `ppc.search_terms` and `ppc.change_events` are both
  empty lists. The pipeline stops at campaign level: 5 Google rows, 1 Meta row.
- **Scope:** Google — PMax asset-group performance, search-theme and listing-group views, plus the
  device and geo segments PMax does expose. Meta — adset, ad and creative level with placement, age
  and gender breakdowns, which Meta reports fully.
- **Effort:** medium. **Risk:** low, all read-only.
- **Acceptance test:** a refreshed `report_data.json` contains non-empty creative-level rows for
  Meta and asset-group rows for Google, each with impressions, clicks, spend and the corrected
  primary conversion, and totals reconcile to the account totals already verified.

### P4-02 · Label every comparison with sample size and confidence
**Disposition: APPLY NOW.**

- **Evidence:** at 13 enquiries a month, splitting by creative x placement x age x device produces
  cells with zero or one conversion. Nothing in the current report or the KPC output states sample
  size or confidence next to a comparison.
- **Smallest safe fix:** every segment row shows its denominator and conversion count, and any row
  below a stated threshold renders as "insufficient data", never as a rate.
- **Acceptance test:** a segment with fewer than the threshold conversions cannot display a CPA or a
  conversion rate anywhere in the output.

### P4-03 · Give the comparison something to compare
**Disposition: NEEDS MATT'S DECISION.**

- **Evidence:** one enabled PMax campaign and one Meta campaign. PMax withholds most of the
  dimensions Matt named by design.
- **The decision:** either accept that Google-side comparison stays at asset-group grain and put the
  creative and targeting comparison on Meta where the data exists, or restructure the Google account
  so there are comparable units. This is a media-strategy decision with spend consequences and it is
  Matt's to make, not the reporting layer's.
- This is the actual content of the two strategies (T4A single landing page, T4B full suite under the
  1.5x ceiling) that were commissioned and never produced.

---

## Group 5 — Client experience

### P5-01 · Render the funnel chart — the Meta half is already constructible today
**Disposition: APPLY NOW for the render; the y-axis honesty rules are not optional.**

- **Evidence:** every metric in `report_data.json` already carries its preceding-30-day twin in the
  same `{current, previous, unit, na}` envelope, so the last-30-vs-preceding-30 contract Matt asked
  for is **already implemented in the data shape**. For Meta a full funnel exists with no new
  collection and no new access:

  | Step | Field | Last 30 | Preceding 30 |
  | --- | --- | ---: | ---: |
  | Impressions | `meta_ads.totals.impressions` | 63,461 | 18,286 |
  | Clicks (all) | `meta_ads.totals.clicks` | 825 | 322 |
  | Link clicks | `meta_ads.actions.link_click` | 727 | 264 |
  | Landing-page views | `meta_ads.actions.landing_page_view` | 626 | 221 |
  | Form starts | `…VHK_form_start` | 14 | 8 |
  | Submitted enquiries | `crm.totals.leads` | 13 | 7 (different window — see P1-06) |
  | Qualified / quote / sale / revenue | — | `UNAVAILABLE` | `UNAVAILABLE` |

  Google's parallel funnel has a hole in the middle: impressions 105,555 → clicks 4,410 → **no
  landing or form-start grain, GA4 is absent** → 49 mixed-pool conversions → unattributable → 13
  enquiries.

- **What this changes:** the dashboard is closer than "never produced" suggests. What is missing for
  Meta is the *render*, not the *measurement*.
- **Two rules that must ship with it.** First, the y-axis is events, clicks and sessions — **not
  people**. Matt's wording was "the number of people reached that funnel step", and no field in this
  data counts people. Label it honestly rather than silently substituting. Second, every unavailable
  step renders as unavailable, never plotted at zero — the visible gaps are the argument for closing
  them.
- **Acceptance test:** the chart renders both periods side by side; every plotted number reconciles
  to `report_data.json`; no step is plotted at zero when its source is absent; the axis label does
  not say "people"; and no cross-step rate is computed across the mismatched CRM window until P1-06
  lands.

### P5-02 · Keep the report's honesty conventions
**Disposition: KEEP AS SAFETY CONTROL.**

The delivered `report.md` states that the 49 conversions are a platform signal and not a verified
enquiry or revenue, that Meta's result column is a form start, that SEO and funnel figures are absent
because GSC and GA4 are unavailable, and that none of the agent instructions were executed. It
excludes 7 QA rows from the enquiry count. These conventions are the reason this review can trust the
numbers at all. Carry them into every new surface.

---

## Group 6 — Later, once Groups 1 and 2 hold

- **FUTURE IDEA:** GA4 property plus BigQuery export to make the landing-funnel steps measurable
  (`source_health.ga4` = `absent`, `bigquery` = `disabled`).
- **FUTURE IDEA:** Search Console property for the organic baseline (`search_console` = `absent`).
- **FUTURE IDEA:** offline conversion import once the lead register carries click IDs — this is what
  finally lets qualified leads and revenue, not form starts, train the bidding.
- **FUTURE IDEA:** the owner action-review surface (T6) and the voice review, once there are
  decisions worth reviewing. The recommendation table already exists inside `report.md` with copyable
  agent instructions; a review UI on top of six rows is not yet worth its build cost.

---

# Addendum — items added after lanes 1 and 2 landed

These arrived from the requirements/user-flow lane and the live browser lane after the six groups
above were written. Two of them change the ordering of everything else, so they are listed first
rather than appended to their thematic group.

## P0 — do these before anything that touches attribution

| ID | Disposition | Item | Why it outranks the rest |
| --- | --- | --- | --- |
| **P0-01** | **APPLY NOW** | Fix the cache key so a cached page cannot serve a previous visitor's UTM and `gclid` (F-21). Exclude the ad-parameter query from the HTML cache key, or render the click-ID fields client-side only. | Every other attribution repair sits downstream of this one. Adding the CRM source column (P1-02) while the cache is poisoned writes *wrong* click IDs into the CRM instead of *no* click IDs, which is worse: it converts a known gap into a confident error. And offline conversion import — the single repair that would ever unlock revenue measurement — would train Google's bidding on identifiers belonging to other people. Found and graded P0 by the July auditors on 2026-07-30, written down once, never entered any dashboard, ledger or status surface since. |
| **P0-02** | **APPLY NOW** | Make failure states survive interaction (F-22). Set a `loadFailed` flag in the fetch `catch`; every render path checks it before drawing; tiles blank to `—` instead of sitting at `Loading`. | One click on a sort header currently turns *"Meta insights failed to load"* into *"No creatives match these filters — 0 of 0 rows"*. The first is a statement about the software; the second is a statement about the client's advertising. A measurement system that reports a confident zero when it is actually broken is worse than one that shows nothing, and this one does it on the exact surface Matt asked to be built. |

**Corrected repair sequence.** The plan above implied P1-02 (CRM source column) could go first. It
cannot. The order is:

1. **P0-01** — fix the cache key.
2. **P1-01 / P1-03** — repoint the enquiry conversion at a URL that exists, promote it to primary,
   demote the form start (F-01, F-02).
3. **P1-02** — add `source`, `gclid_or_fbclid`, `landing_page` columns to the CRM sheet (F-04).
4. Outcome and value fields on the CRM rows.
5. Only then, offline conversion import.

Any other order degrades the account rather than improving it.

## Added to Group 1 (measurement correctness)

| ID | Disposition | Item |
| --- | --- | --- |
| P1-08 | **APPLY NOW** | Reduce the landing page to one CF7 instance, or give the three distinct `form_location` values (F-29). Three instances triple the poisoned form actions from P0-01 and make `vhk_form_start` noisier than it already is. |
| P1-09 | **APPLY NOW** | Adopt the KPC `evidence_state` vocabulary — `retrieved` / `verified_zero` / `not_applicable` / `unavailable` / `derived` — inside `report_data.json` (F-28, F-03). The pattern is already implemented in this repository across 1,524 `field_ledger` rows. This is reuse, not new design, and it is the one change that makes every zero in the system self-describing. |

## Added to Group 3 (presentation honesty)

| ID | Disposition | Item |
| --- | --- | --- |
| P3-04 | **APPLY NOW** | Make the closeout generator resolve every link it emits and fail when one 404s (F-23). A page headed "Complete and verified" currently certifies HTTP 200 for a file that exists on no branch. |
| P3-05 | **APPLY NOW** | Add cost and CPA tiles to the closeout tile row, and put the "enquiries cannot be attributed to a channel" note inside the tile group rather than further down the page (F-26). |
| P3-06 | **REMOVE OR MERGE** | Replace the council deck's 198 byte-identical placeholder cards with one line stating the council has not been run (F-27). Nine named experts rendering the same fixed `ABSTAIN` string is worse than no card at all. |
| P3-07 | **APPLY NOW** | Ship the creative thumbnails beside the Meta dashboard and pin the **Triage** and **My decision** columns so they are reachable at 1280 px (F-24, F-14). |

## Added to Group 5 (needs Matt's decision)

| ID | Disposition | Item |
| --- | --- | --- |
| P5-03 | **NEEDS MATT'S DECISION** | The frozen goal demands *verified people per funnel step* (T3-04) while forbidding event-for-person substitution (T3-03). Both are mandatory; no source in the stack counts people. **Recommendation: relabel every axis in the units the data actually has** — impressions, clicks, sessions, form starts, enquiries — and print the unit on the chart. The budget-allocation decision does not need people; it needs consistent, honestly-labelled denominators. Confirm and the contradiction dissolves; insist on people and the chart cannot be built from any accepted source. |
| P5-04 | **APPLY NOW** | Remove or repoint `https://b62a73b2.cf-review-9e5.pages.dev/assets-p12/`, a live URL under the agency review domain serving a different client's asset pack (F-30). Low technical severity, non-trivial commercially. |
