Appearance
Verification and reconciliation plan
Date: 2026-09-06. Tests below are required future evidence unless explicitly listed as run. Use the financial contract for amount/time bases. A green UI/build alone does not prove reconciliation.
Deterministic reference fixture
Use synthetic IDs, USD, and an explicitly synthetic zero-fee policy; no production policy is implied. Post all facts before the common asOf. Derive these expected values independently of implementation helpers.
| Campaign / submission | Creator | Committed | Funded views | CPM / RPM | CPM spend | Gross earning |
|---|---|---|---|---|---|---|
| A / A1 | Creator One | A total 100 | 10,000 | 2 / 1.5 | 20 | 15 |
| A / A2 | Creator Two | Included above | 5,000 | 2 / 1.5 | 10 | 7.5 |
| B / B1 | Creator One | B total 40 | 4,000 | 3 / 2 | 12 | 8 |
Campaign A: spend 30, gross expense 22.5, remaining 70. B: spend 12, gross expense 8, remaining 28. Total expense 30.5; total committed 140 = spend 42 + remaining 98.
One confirmed cross-campaign operation settles A1 gross 9 + B1 gross 3 = 12; sponsor A must report 9 and B 3, never 12 for each. Of the remaining A1 6, hold 2 and make 4 available. Of remaining B1 5, reserve 2 in an unknown operation and leave 3 unfinalized. A2 7.5 is available. Thus paid 12 + reserved 2 + held 2 + available 11.5 + unfinalized 3 = 30.5. Pending subtotal is 16.5; outstanding 18.5. Creator One total is 23; Creator Two 7.5. Multiple hold reasons on the same 2 must still produce held 2.
Add separate parameterized variants for fees/withholding/referrals, negative adjustments and recovery; do not assume the simple zero-fee identity covers those. Test a sub-cent payable balance (e.g. 0.015001) against the payout owner's agreed policy: proposed floor-to-cent reserves 0.01 and leaves 0.005001 payable, without a new earning entry.
Required fixtures and invariants
| Fixture | Expected evidence | Owners |
|---|---|---|
| Distinct CPM/RPM, short/long rates, lower/higher individual override, overlapping group winners/ties | Exact recorded rates/provenance, no type precedence, no revaluation of earlier entries; margin can be negative without hidden rule. | A01/A03; #26/#27/#53 |
| Same observation delivered twice; concurrent jobs/approval/payout refresh near last funded view | One economic source and one paired spend effect; total committed not exceeded; losing transaction cannot leave audit/money half-written. | #37/#53/#54/#42; A17 verifies producer evidence |
| Many one-view polls versus one larger observation in same policy segment | Same cumulative micro amount with segment-level rounding; no per-poll/row cent drift. Exact view-cap/minimum and zero-CPM boundaries specified by upstream. | #53/#54, A01/A03 |
| Rate/cap/membership edits after accrual; reapproval and campaign pause/end | Earlier money unchanged; no reads use current state to resurrect/cancel facts. No positive accrual outside documented eligible intervals. | #35/#42/#53/#54; A03/A09 |
| Rejected/flagged/unavailable source later accepted; unavailable shares versus zero | Technical performance retained separately from earning eligibility; unsupported/null is not zero. Flagged is not automatically fraud. | A02/A07/A08, #37/#55 |
| Sparse snapshots, first observation, UTC midnight, leap day, delayed ingestion, out-of-order/repeated timestamps | Last pre-window baseline included; deterministic ordering; signed corrections; exactly requested UTC buckets; no upload/live inference. Legacy recording-time/budget-clamped provenance disclosed. | A02/A10, #37 |
| Current accepted cohort versus historical status modes | Stock/performance cohort labelled; historical status at observation or fixed asOf comes from committed events, with delta baseline computed before filtering. Later review cannot rewrite a fixed historical result or erase paid history; pre-history status remains unknown. | A02/A04/A07/A10; #41/#42 |
| Group leave/rejoin, overlapping groups, archive, unmapped/null identity | Each member sum = group de-duplicated facts; group union + unattributed = campaign, not sum of overlapping groups. Historical financial attribution survives departure. | A09/A16 |
| Reservation → processing/unknown → success; proven failure/release; duplicate/out-of-order confirmation | Paid only once after success, unknown stays reserved, release exactly once; failed/released amount is history, not paid or new earnings. | #56–#59, A03/A05/A08 |
| Post-payment correction/recovery | Original success remains; attributable assessment/offset/recovery/write-off separate; no negative finalized payout or replayed transfer. | #55/#58, A03/A05 |
| Legacy one-shot, mixed item/non-item payments, missing completedAt, fee/referral amounts, unmapped IDs | Known actual amounts preserved; unprovable campaign/date/gross-net allocation marked partial/unavailable; no proportional guess or blanket backfill. | A03/A05/A10 |
| More than a page/top-N, platform/status/date filters, zero results | Summary over full filtered scope; deterministic unique tiebreaker, bounded rows/queries, invalid limits rejected; pages/top-N are never campaign totals. | A02/A04–A09/A12–A16 |
| Concurrent reads during accrual/payment/hold/revocation | Consistent snapshot/watermark within response; no mixed paid/reserved double count; revoked report discarded after assembly. | A03/A10/A17 |
| Unauthenticated, wrong creator, insufficient staff, provider/RBAC unavailable, stale cache | Existing 401/403/503 boundaries retained; no cache bypass or sensitive new fields on public campaign routes. | Every API unit / separate RBAC owner |
Cross-view assertions
At a fixed database snapshot compare raw exact DTO amounts before formatting. Creator total = sum own campaign totals = sum own submission positions, including retained historical identities. Staff campaign totals = sum creators plus unattributed facts. Payout operation amounts reconcile through allocations, not joins to every item/attempt. Group union and current-accepted sponsor scope are explicitly different comparisons where appropriate.
Sponsor and staff comparison uses the same campaign, accepted-current cohort, platform, observation basis and 90-day window for performance, and completed gross allocations across all relevant historic submissions for payouts. Gross sponsor payouts need not equal net creator credits. Present current effective views, observed changes and money-posting series separately rather than forcing them to match.
An endpoint unavailable, partial source, or delayed watermark must be visible in UI. Exercise empty, loading, error, 403, 503, partial, negative chart, single-point chart and retry states. Synthetic sparkline decoration cannot be presented as actual change.
Migration and cutover gates
Analytics PRs require no new schema by default. The following is the minimum contract before upstream observation/funding/earning/hold/payout migrations are scheduled. Exact table names, constraints and scripts are reviewed with those owners; do not ship placeholder migrations from this document.
| Gate | Rollout / backfill / mixed-version / rollback requirements |
|---|---|
| M-01 Observation expansion (#37) | Add nullable source/time/metric/correction fields or an additive observation table and stable unique source key. Old workers omitting fields stay valid. Store old snapshots as legacy_recorded without fabricated scraped_at; only map raw job evidence when uniqueness can be proved. Backfill restartably with checkpoints and duplicate/out-of-order reports. New/old reconciler writers cannot both create observations for one source; preserve transactional applied guard. Reader rollback retains all new observations and disables only new reads/writes as explicitly agreed. |
| M-02 Funding/earning foundations (#31/#53/#54) | Add exact append-only facts with source uniqueness, immutable attribution and deletion restrictions; no rewrite of landed rate migrations. Define canonical writer/cutover epoch and negative correction policy first. Never convert Campaign.budget into verified receipt. Backfill only evidenced history with stable keys and documented opening balances approved by the data/finance owner; otherwise retain explicit gaps. Shadow reads compare to journal arithmetic, not force equality to old estimates. Rollback disables new writer activation safely, retaining economic facts; never drop ledgers or re-enable a competing writer. |
| M-03 Hold/allocation/provider facts (#55/#56/#59) | Add amount-level states/events/operation uniqueness and provider references without overloading old PayoutItem meaning. Settle/reconcile or explicitly isolate all open legacy operations before switching writers; never derive paid from approval counters. Old API clients retain legacy reads while new fields are gated. Backfill completed allocations only with proof and retain source/coverage. On rollback stop new dispatch/reservation and continue required reconciliation of in-flight operations; unknown money cannot be “rolled back” by retrying legacy sends. |
| M-04 Query indexes (only if measured) | Explain target plan/cardinality; baseline realistic volume and p95 target recorded before change. If concurrent index creation is needed, use a separately reviewed non-transactional deployment step compatible with the repository migration runner, track failed-index recovery. Do not add materialized balances as another authority. Drop only optional indexes on rollback, never facts. |
| M-05 Deployment verification | Inspect actual migration status and compare schema on an explicitly selected environment; current code/merged PR is not proof of applied migration. Preserve migration checksums, rebase ordering after concurrent prerequisite merges. Apply migrations before compatible backend, regenerate Prisma and restart each process, then frontend. No reset, db push, destructive cleanup, or production data writes for analytics validation. |
Verify each migration on a fresh disposable database and an upgrade fixture containing legacy/mixed rows. Run backfill twice, interrupt/resume, compare counts/exact sums/source hashes, verify unique/check/FK/immutability constraints and the last-view concurrency test. Capture unknown/unmapped counts and state-by-state reconciliations. Source facts must remain after campaign/submission archive or attempted hard deletion. Rollback rehearsal must not resend a payout or delete financial history.
Older payout proposals call existing data disposable and suggest a clean cutover. That is not verified for the current target. D-05 must resolve it before any data treatment beyond additive preservation.
Focused validation commands and safety
Backend pure test pattern: node --require ts-node/register --test <explicit-pure-files>. Inspect imports first. npm test has an offline isolation runner, but some historical suites use source-string assertions; those do not prove database races. Add meaningful DB behavioral tests for financial/schema changes rather than more source-text matches.
Integration tests create/delete fixtures: require an explicitly supplied disposable loopback database whose name ends in _test, no application startup workers or payment adapters. Check the selected suite's guard; do not assume test:integration enforces this by itself. Migration tests require dedicated target/shadow databases. Never run an application against configured .env just to inspect analytics. Read-only production inspection, if separately undertaken within user scope, uses a read-only transaction and records aggregate/redacted evidence only.
Frontend: focused native Node API/formatter tests where applicable, changed-file lint, production build/typecheck and manual browser fixture validation. Reuse tests/public-report-api.test.mjs; a full component framework is not a prerequisite. Scraper changes belong to #36/#37 and use its Vitest/job-contract tests plus isolated worker smoke.
Validation actually run in this foundation session
- Backend rate domain/history/migration suites: 11 passed, using
node --require ts-node/register --test src/utils/rates/domain.test.ts src/utils/rates/history.test.ts src/utils/rates/migration.test.ts. In-memory/source-contract tests; no database constraints or deployment were exercised. - Frontend
node --test tests/public-report-api.test.mjs: 9 passed. Request/response mocks; no real bearer grant or browser used. - Documentation links, snapshot counts, workstream coverage and preservation checks: see final handoff validation record.
- No full backend/frontend build, DB integration/migration, production DB inspection, provider call, browser smoke or worker deployment was performed. Prior merged PR reports are historical evidence only; #76's reported group integration failures remain an upstream gate.
Manual acceptance before completion
Walk a complete synthetic campaign with two creators and two campaigns: observe → review → accrue → hold/release → reserve → uncertain → confirm; verify each actor's approved view and sponsor on/off disclosure. Repeat with archived group, late correction, legacy gap, page/filter changes, concurrent browser refresh and token revocation. Confirm creator-facing money labels, UTC dates, partial coverage and no hidden overrun clamp. Record browser/device, snapshot/fixture ID, exact comparisons and relevant PRs. Release checks additionally require upstream funding/payout sandbox evidence, worker supervision, target migrations and separate RBAC owner-access/browser verification.