# Review rules W01 to W20

Review mode rates an existing workflow against these rules. Design mode uses the same rules to grade a new design before the pilot. Severity means the same thing in every rule:

- FAIL: the workflow contacts someone it must not, overwrites or clears data it does not own, creates duplicates, or spends without an accepted result. A FAIL stays a FAIL unless `decisions.md` records an exception; cite the entry when you downgrade.
- WARN: a plausible defect that needs an owner decision or a runtime test.
- PASS: checked and correct, stated with the evidence (file, export, screenshot or script line).

Every finding starts as a lead. "Confirm" turns it into a finding with evidence. A rule you could not check goes in the report's "Not checked" list with the reason; it is never silently PASS.

## W01 decision-owner-stop

- Check: one decision sentence, business owner, operator, unit (account or contact), action, outcome window and a stop control.
- FAIL: no accountable owner, or no way to pause new enrollments and CRM writes.
- WARN: the decision is not written down; the stop control exists but was never tested.
- Evidence: the spec header or interview notes; a dated stop-control test record.
- Confirm: ask who can pause the workflow today and how; ask for the last time it was paused and what still ran.

## W02 signal-contract

- Check: each signal has a class, event key, identity basis, three clocks, expiry and action ceiling ([signals-and-scoring.md](signals-and-scoring.md)).
- FAIL: an inferred company-level signal (IP-to-company web intent, third-party intent topics) drives person-level outreach or a personal claim ("you visited our pricing page"); an unknown source date is treated as current in a message.
- WARN: missing expiry or clocks; collection time used as event time.
- Evidence: signal export columns; message templates; invariants I8 and I12.
- Script: `route_dry_run.py` I8, I12.

## W03 replay-and-episodes

- Check: delivery dedupe by event key; business dedupe by account episode (account plus play within expiry).
- FAIL: a repeated delivery creates a second task or enrollment.
- WARN: no episode grouping, so the same news from two providers creates two tasks; rows without an event key.
- Evidence: a replay test; task and enrollment counts grouped by event key and by episode.
- Script: `route_dry_run.py` I1: a repeat delivery that acts is a FAIL; an episode with a second action from another delivery is a WARN.

## W04 exclusion-before-spend

- Check: suppression, customer, open opportunity, active conversation, internal, competitor and region exclusions run before every paid step.
- FAIL: paid attempts on excluded records.
- WARN: exclusion snapshot older than 24 hours at run time.
- Evidence: the run ledger or the Clay usage Run view joined to exclusion reasons.
- Script: `enrichment_cost.py` W04.

## W05 field-consumers

- Check: every purchased field has a consumer: a gate, score, route, message claim or owned CRM property.
- FAIL: personal emails or mobile numbers bought without a recorded privacy decision.
- WARN: fields bought with no consumer (phone with no phone workflow, biographies nobody reads).
- Evidence: the column list compared with the consumers named in the spec.

## W06 waterfall-stop-and-states

- Check: the waterfall stops at the first accepted candidate; not_attempted, pending, no_result, candidate_held, candidate_rejected, accepted and error stay apart; errors are retried.
- FAIL: attempts after acceptance; more than one accepted candidate per record and data point; timeouts recorded as no result.
- WARN: an error falls through to the next paid provider without a retry; pending treated as failed.
- Evidence: the ledger; the Apollo waterfall data request log; the Clay column view.
- Script: `enrichment_cost.py` W06.

## W07 validation-policy

- Check: automated sends go only to accepted validation statuses with identity matched.
- FAIL: catch-all, accept-all or unknown addresses enrolled automatically without a recorded decision; a Clay Work Email waterfall on the Balanced or Aggressive strategy feeding cold email without a decision.
- WARN: the provider's raw status is not kept beside the normalized decision; catch-all addresses accepted under a decision but sent in the same campaign as valid ones.
- Evidence: `validation_status` of accepted ledger rows; a screenshot of the waterfall settings.
- Script: `enrichment_cost.py` W07.

## W08 marginal-cost

- Check: each stage's marginal cost per accepted record against the owner's ceiling.
- FAIL: a stage above the agreed ceiling.
- WARN: no ceiling and a stage above 3 times the blended cost of earlier stages; a stage that spends and accepts nothing; fewer than 200 records entering a judged stage, per segment, behind a provider-order decision.
- Evidence: `enrichment_cost.py` output.
- Script: `enrichment_cost.py` W08.

## W09 caps-and-reconciliation

- Check: spend controls enforced in the tools (auto-run off, run conditions, filtered views of approved rows, credit limits, Enterprise workbook limits), not by a chat instruction; billed usage against modeled usage.
- FAIL: a paid Clay column with table auto-run on, no run condition and a scheduled source updating existing rows; an MCP identity with no credit limit used for agent work.
- WARN: billed against modeled variance above 10% for a provider; a per-record or batch spend cap in the spec that no tool enforces and nobody checks after the run.
- Evidence: Clay table and column settings screenshots; the Clay MCP users page; usage CSV exports.
- Script: `enrichment_cost.py` W09 with `--billed`.

## W10 scoring-structure

- Check: gates outside the points; fit and engagement separate; unknown flagged; no email opens.
- FAIL: suppression, customer or open opportunity expressed as negative points; direct requests gated by a score threshold.
- WARN: email opens or unidentified page views score; one composite score drives the action; unknown counted as zero fit.
- Evidence: the score definition; HubSpot score properties and their criteria; invariant I10.
- Script: `route_dry_run.py` I10.

## W11 capacity

- Check: proactive band size against weekly review capacity.
- FAIL: overflow beyond capacity sent to an automated sequence.
- WARN: the band is larger than capacity.
- Evidence: band counts per week; invariant I9.
- Script: `route_dry_run.py` I9 with `--capacity`.

## W12 routing-precedence

- Check: owners, customers, open opportunities and suppressed contacts are protected by rule order.
- FAIL: an existing owner overwritten; an open-opportunity or customer contact routed to a prospecting sequence or pool; HubSpot rotation with "Overwrite if [object] has an existing owner" on for records that can already have an owner.
- WARN: the owner field set from the creating or integration user; Salesforce leads assigned without first matching them to an account and an existing contact.
- Evidence: invariants I2 to I5; workflow screenshots; owner property history filtered by source.
- Script: `route_dry_run.py` I2, I3, I4, I5.

## W13 fallback-and-clocks

- Check: every unmatched item reaches a monitored fallback; `received_at` survives reassignment.
- FAIL: a no-owner outcome with no fallback route; reassignment resets the response clock.
- WARN: the fallback has no escalation deadline or no named duty owner.
- Evidence: invariant I6; CRM timestamps on reassigned records.
- Script: `route_dry_run.py` I6.

## W14 gate-at-enrollment

- Check: eligibility re-checked at enrollment time, not at list build; each sending mailbox's daily load forecast before a batch is approved.
- FAIL: the gate runs only at list build; a sequence route without a valid email, an approved message, fresh evidence or a matched identity.
- WARN: approvals with no scope or expiry; a batch approved without a per-mailbox forecast of new and follow-up sends against each mailbox's daily limit.
- Evidence: invariants I7 and I11; enrollment logs with timestamps; the sequencer schedule export per mailbox.
- Script: `route_dry_run.py` I7, I11.

## W15 suppression-propagation

- Check: opt-outs reach active campaigns in every client scope and survive re-import.
- FAIL: an import-only list is the only suppression (an Instantly connected Google Sheets blocklist); Smartlead unassigned blocklist entries relied on for client campaigns; the queued-contact test fails.
- WARN: suppression kept only by deleting leads.
- Evidence: blocklist configuration per scope; the test record's timeline.

## W16 stop-and-reply

- Check: stop rules and human ownership of replies.
- FAIL: stop on reply off for a prospecting sequence; replies land in an inbox nobody owns.
- WARN: company-level stop undecided; out-of-office treated as a stop or as interest.
- Evidence: campaign settings; a reply test from controlled inboxes.

## W17 sender-compliance

- Check: per sending path, aligned SPF or DKIM, DMARC pass, one-click unsubscribe on marketing mail, complaint and bounce levels ([routing-and-outbound.md](routing-and-outbound.md)).
- FAIL: DMARC fails on a received message; promotional mail without one-click unsubscribe from a bulk sender; Postmaster Tools spam rate at 0.3% or higher.
- WARN: spam rate at or above 0.1% on any day; opt-outs plus negative replies above 1% of delivered messages over 7 days; a batch with hard bounces above 2% of attempted sends; no Postmaster data for a path that sends mainly to business mailboxes and no opt-out and negative-reply rate monitored (list the spam rate under Not checked).
- Evidence: received message headers; Postmaster Tools, or "no data" with the substitute signal used; per-domain daily counts of delivered, hard bounces, opt-outs and replies by the sequencer's category label, never reply text; placement tests to controlled mailboxes.

## W18 field-ownership

- Check: writes limited to owned properties, blank and clear semantics explicit, conflicts routed to candidate properties ([crm-writeback.md](crm-writeback.md)).
- FAIL: blocked cells in a proposed or applied change (never-write property, property not in the contract, writer not allowed, clear not allowed); property history shows the integration changing a never-write property.
- WARN: clears that need approval; conflicts with no review owner; mapped columns the writer may not write.
- Evidence: `check_writeback.py` change list; HubSpot property history filtered by source; Salesforce field history.
- Script: `check_writeback.py` W18.

## W19 create-update-identity

- Check: update by ID, create behind a gate, creates serialized, timeouts reconciled.
- FAIL: a failed lookup leads to create; duplicates created by the workflow; Clay's Salesforce Create record with Duplicate rule override on; an update target that is not found treated as a create; IDs damaged by a spreadsheet.
- WARN: lookup-then-create without serialization or a unique key; create rows checked against an export that holds no lookup on their email or domain.
- Evidence: `check_writeback.py`; a duplicate scan by domain or email and creator; Clay action settings.
- Script: `check_writeback.py` W19.

## W20 monitoring-feedback

- Check: metrics with source, threshold, owner and first action; a weekly review that records rejection reasons ([monitoring.md](monitoring.md)).
- FAIL: no monitoring of unknown outcomes or of the fallback queue.
- WARN: metrics without owners; no rejection reason capture.
- Evidence: the runbook; review notes.

## Mapping

- Step 1 decision: W01. Step 2 signals: W02, W03. Step 3 identity and exclusions: W04. Step 4 enrichment: W05 to W09. Step 5 scoring: W10, W11. Step 6 routing: W12, W13. Step 7 outbound: W14 to W17. Step 8 writeback: W18, W19. Step 10 monitoring: W20.
- Scripts: `route_dry_run.py` covers W02, W03, W10 to W14 through invariants; `enrichment_cost.py` covers W04 and W06 to W09; `check_writeback.py` covers W18 and W19. W01, W05, W15, W16, W17 and W20 need configuration, tests and headers, never a script alone.

## Route invariants

`route_dry_run.py` checks these on every run. A policy cannot switch them off.

- I1 replay-and-episodes (W03): rows sharing a `source_event_key`: only the first may act; later rows route to replay. One `episode_id` that gets more than one task, nurture, research or sequence route from separate deliveries is a WARN: attach the later evidence to the open item.
- I2 suppressed (W12): never sequence or nurture; a task of any kind (owner, pool, opportunity owner, account team or fallback) only for a direct request, because a human answer to a request is not marketing.
- I3 open-opportunity (W12): never sequence, pool task, nurture or research; any owner assigned, the fallback included, equals the opportunity owner, or the covering rep while that owner is away. The fallback is right only when neither can act (owner blank, or away with no cover). `cs_task` is exempt: the customer rule runs first, so an expansion opportunity on a customer stays with the account team.
- I4 existing-owner (W12): when an account owner exists and the route assigns someone (owner task, pool task, sequence, or the fallback on a matched identity), it is that owner or the covering rep. The fallback is right only when the owner is away with no cover. A sequence that assigns nobody on an owned account fails too: a sequence is for unowned new business, or it names that owner.
- I5 customer (W12): never sequence, pool task, nurture or research, and never the fallback while the account owner or covering rep can act; the expected route is an account team task.
- I6 coverage (W13): every row gets exactly one route, and every task route has an owner, including the fallback.
- I7 sequence-eligibility (W14): valid email (from `--accept-validation`), approved message, evidence age within expiry, no active sequence, no human reply inside `--reply-window` (default 30 days), matched identity.
- I8 inferred-signal (W02): an inferred signal routes to a sequence only when corroborated.
- I9 capacity (W11): owned items per pool or owner above `--capacity` is a WARN.
- I10 direct-request (W10): reaches a human route (owner, pool, opportunity owner, account team, service, fallback) or replay.
- I11 identity (W14): ambiguous, personal-domain or unmatched identity never routes to a sequence; hold, fallback, service, or a human route for direct requests.
- I12 evidence-age (W02), for rows that are not direct requests: evidence past `expiry_days` never becomes a task or nurture. Evidence with no age or no expiry goes to research, hold or no action, or reaches its customer or opportunity owner (R04 and R05 run first) as a note to verify the date; never a new owner, pool or fallback task or nurture. I7 covers sequences.

## Runtime tests on live systems

Some confirmations need a real system: a suppression test, a reply test, a stop-control test, a writeback read-back.

- This skill does not run these tests; it lists each as pending with its AC ID and owner. When the owner later asks an agent to run one, the rules below apply.
- Tests that create records, enroll contacts or send messages need the user's explicit approval for that test, in chat, before it runs.
- Use controlled inboxes the team owns, test records named as tests, and a sandbox or test portal where the vendor offers one.
- List every destination record a test creates (CRM records, sequencer leads, tasks, notes) and who removes or excludes them afterwards.
- A test that sends to a real prospect is not a test. Stop and propose a controlled one.
- Record each test with time, account or workspace, the identity used, the steps, the observed result and the evidence link.
