{
 "updated_at": "2026-09-20T08:13+00:00",
 "updated_by": "Stream AW7",
 "delivery": "2026-09-18",
 "cannes": "2026-09-27",
 "note": "Edit this file on the host; the page reads it per request (import/ is mounted read-only into the container). Status values: todo | doing | done | blocked | deferred.",
 "waves": [
  {
   "key": "w1",
   "title": "Wave 1",
   "dates": "Fri 4 \u2013 Sun 6",
   "goal": "decide, unblock, back up"
  },
  {
   "key": "w2",
   "title": "Wave 2",
   "dates": "Mon 7 \u2013 Fri 11",
   "goal": "the heavy build; checkpoint Fri 11"
  },
  {
   "key": "w3",
   "title": "Wave 3",
   "dates": "Mon 14 \u2013 Tue 15",
   "goal": "features on top"
  },
  {
   "key": "w4",
   "title": "Wave 4",
   "dates": "Wed 16 \u2013 Fri 18",
   "goal": "one session: integrate, test, launch checklist"
  },
  {
   "key": "buf",
   "title": "Buffer",
   "dates": "Sat 19 \u2013 Fri 26",
   "goal": "deferred items, late content, Mon 21 human review, Cannes asks"
  }
 ],
 "streams": [
  {
   "key": "A",
   "title": "A \u00b7 Collectors & data",
   "session": "/stream-a",
   "tasks": [
    {
     "id": "A0",
     "wave": "w1",
     "title": "Paris (Extime) collector built and fully collected (7,032 listings)",
     "status": "done",
     "paid": true,
     "due": "2026-09-04"
    },
    {
     "id": "A1",
     "wave": "w1",
     "title": "robots.py: by-name matcher, one policy, crawl-delay [robots.py, 42 tests, 7 collectors on check_allowed; Shopify+Dubai 403 recorded]",
     "status": "done",
     "paid": false,
     "due": "2026-09-05"
    },
    {
     "id": "A2",
     "wave": "w1",
     "title": "Migration #1: accounts skeleton, raw_records, source_kind, identity_mode, vertical, visible=False [0a1b2c3d4e5f; up/down/up on dfp-devdb; backfill accounts idempotent]",
     "status": "done",
     "paid": false,
     "due": "2026-09-05"
    },
    {
     "id": "A3",
     "wave": "w1",
     "title": "RawListing.raw/vertical populated by every collector [raw+vertical on all 7 collectors; ingest writes raw_records (stub run on devdb); backfill vertical idempotent]",
     "status": "done",
     "paid": false,
     "due": "2026-09-05"
    },
    {
     "id": "A4",
     "wave": "w1",
     "title": "read_one() on Avolta, Shopify, ARI, Extime [read_one on Avolta/Shopify/ARI/Extime, refusals on Dubai+Heinemann; 13 tests on a real ATH product page]",
     "status": "done",
     "paid": false,
     "due": "2026-09-05"
    },
    {
     "id": "A5",
     "wave": "w1",
     "title": "Audit defect list (orphan tiles, size veto, Dubai in_stock, MEX currency, FX, images provenance, stuck runs) [defect list + collector skip hook + brand_key; 304 tests; backfills run twice on devdb]",
     "status": "done",
     "paid": false,
     "due": "2026-09-05"
    },
    {
     "id": "A10",
     "wave": "w2",
     "title": "Drinks refresh of the sixteen (minus Dubai), overnight [5 Sep refresh: 10 Avolta stores + Dublin + 4 Shopify, all ok, 2,829 prices; Dubai refused; Paris fresh from 4 Sep]",
     "status": "done",
     "paid": true,
     "due": "2026-09-07"
    },
    {
     "id": "A6",
     "wave": "w2",
     "title": "Beauty widening: per-vertical walk, Dublin prefixes, Shopify Spanish shelves, page-1-only at LHR/ATH/MAD/BCN [3d5f3f1: two trees per Avolta store, beauty page-1 + targeted product pages, held-URL re-read at query-blocked stores, Dublin /beauty/ targeted, Shopify Spanish+beauty shelves, Extime size cross-check on the live shape; 46 tests; LHR shrink measured from tonight's run counters]",
     "status": "done",
     "paid": true,
     "due": "2026-09-08"
    },
    {
     "id": "A7",
     "wave": "w2",
     "title": "Targeted 40 beauty list by Tue 8 (method in plan \u00a77) [fcdb0a7: .logs/runs/beauty-candidates-2026-09-05.md + import/beauty-targets.json; 20 fragrance at 5 stores, 20 skincare at 3-5; Dublin and Avolta page-1 reads pending their refresh]",
     "status": "done",
     "paid": true,
     "due": "2026-09-08"
    },
    {
     "id": "A8",
     "wave": "w2",
     "title": "Migration #3: brands table, size_value/unit, per-vertical attribute, merged_into_id, merge/reverification tables [b46e3f8: f3a4b5c6d7e8 rehearsed up/down/up on dfp_q; models + ingest veto + resolve_product_id; 16 tests + stub run on the dev copy]",
     "status": "done",
     "paid": false,
     "due": "2026-09-09"
    },
    {
     "id": "A9",
     "wave": "w2",
     "title": "Brand fold + uncategorised backfill + 52 duplicate groups as recorded merges [758233e: rederive + backfill brands/sizes/categories/merges rehearsed twice on a clean copy (2,215 brands, 1,032 categorised, 40 groups/44 rows merged, second pass a no-op); resolve_product_id follows merged_into_id; 27 tests]",
     "status": "done",
     "paid": true,
     "due": "2026-09-10"
    },
    {
     "id": "A13",
     "wave": "w2",
     "title": "Switch the nineteen airports on for the public site (after the verify tripwire is green); rian deploys [11 Sep: locations --only the nineteen on production and staging (four non-launch sources hidden). The site is members-only, so nothing public changed. Tripwire: verify --n 20 running on production (two shops in: 39 pass, 1 price moved); if it trips, hide again. Rian's thirty-page check and the bot mailbox stay open on the running list.]",
     "status": "done",
     "paid": true,
     "due": "2026-09-11",
     "rian": true
    },
    {
     "id": "A11",
     "wave": "w3",
     "title": "Two categories at twenty each, across airports",
     "status": "todo",
     "paid": true,
     "due": "2026-09-15"
    },
    {
     "id": "A12",
     "wave": "w3",
     "title": "Full recollection after the widening",
     "status": "todo",
     "paid": true,
     "due": "2026-09-15"
    },
    {
     "id": "A14",
     "wave": "buf",
     "title": "Paco Rabanne to Rabanne as a confirmed brand alias, unless M4 already recorded it: then verify and close the issue",
     "status": "todo",
     "paid": false,
     "due": "2026-09-16"
    },
    {
     "id": "A15",
     "wave": "buf",
     "title": "The six Avolta stores showing our own breakage: diagnosed per store from run history, one announced read each, fixed as tests",
     "status": "todo",
     "paid": false,
     "due": "2026-09-16"
    },
    {
     "id": "A16",
     "wave": "buf",
     "title": "The four Heinemann-family shops that worked and then refused: recorded as refusals with the date, never retried; the partnership ask drafted for rian",
     "status": "todo",
     "paid": false,
     "due": "2026-09-16"
    },
    {
     "id": "A17",
     "wave": "buf",
     "title": "One display rule for sizes (700ml against 70cl), applied in one place; the shop's own wording kept in the record",
     "status": "todo",
     "paid": false,
     "due": "2026-09-17"
    },
    {
     "id": "A18",
     "wave": "buf",
     "title": "Shops and terminals within one airport: the data-shape proposal written and a decide item raised; no migration without rian",
     "status": "todo",
     "paid": false,
     "due": "2026-09-17"
    }
   ]
  },
  {
   "key": "A2",
   "title": "A2 \u00b7 Rendered fetch (browser)",
   "session": "/stream-a2",
   "tasks": [
    {
     "id": "R1",
     "wave": "w1",
     "title": "Playwright sidecar: isolated network, egress allowlist, non-root, floor delay [sandboxed launch verified under cap_drop ALL + no-new-privileges + seccomp; render of a public page and refusal of an IP literal through the hardened container; compose config valid]",
     "status": "done",
     "paid": true,
     "due": "2026-09-06"
    },
    {
     "id": "R2",
     "wave": "w1",
     "title": "fetch.render() with the fetch() honesty contract [tests/test_render.py (27) + live render of example.com through the sidecar via render(); check.sh green]",
     "status": "done",
     "paid": true,
     "due": "2026-09-06"
    },
    {
     "id": "R3",
     "wave": "w1",
     "title": "Shilla probe: one rendered product page; stop if challenged [product page 5581103 rendered HTTP 200, no challenge, server-rendered document; run log .logs/runs/shilla-probe-2026-09-05.log; decisions #11 #12]",
     "status": "done",
     "paid": true,
     "due": "2026-09-06"
    },
    {
     "id": "R4",
     "wave": "w2",
     "title": "Singapore collector on the targeted set [collector built and tested on the real JW Blue record; first run 5 Sep stored 17 prices then 403 on page 19; no retry until rian answers decide-singapore-answered-403-on-the-19th-page-of-its-first-run]",
     "status": "blocked",
     "paid": true,
     "due": "2026-09-09"
    },
    {
     "id": "R5",
     "wave": "w2",
     "title": "read_one() for Changi via render (cap 20/run) [Seoul first run 5 Sep: 18 prices from 20 renders, no refusals]",
     "status": "done",
     "paid": false,
     "due": "2026-09-10"
    }
   ]
  },
  {
   "key": "Q",
   "title": "Q \u00b7 Quality & verification",
   "session": "/stream-q",
   "tasks": [
    {
     "id": "Q1",
     "wave": "w2",
     "title": "app.cli audit (JSON until its table lands) [audit.py + cli audit: 15 metrics, thresholds pinned to 2026-09-05 measurements, 34 tests, first snapshot in .logs/verification/]",
     "status": "done",
     "paid": true,
     "due": "2026-09-07"
    },
    {
     "id": "Q2",
     "wave": "w2",
     "title": "Migration #2: verification_runs/checks, audit_snapshots, rejected_observations [d1e2f3a4b5c6 rehearsed up/down/up on dfp_q (today's dump); rejections + skip_counts + audit_snapshots proven by writes]",
     "status": "done",
     "paid": false,
     "due": "2026-09-07"
    },
    {
     "id": "Q3",
     "wave": "w2",
     "title": "app.cli verify: per host, abort at first BLOCKED, tripwire verdicts, REVIEW [verify.py + verify/verify-status/verify-clear; rehearsed on dfp_q with a stand-in reader (all ten verdicts, block, clear, spot-checked); 47 tests; no real retailer run tonight by rule]",
     "status": "done",
     "paid": true,
     "due": "2026-09-09"
    },
    {
     "id": "Q4",
     "wave": "w2",
     "title": "QUALITY.md in plain language: what verify does and does not prove [QUALITY.md written for rian: what audit/verify prove and cannot, verdict table, tripwire, BLOCKED rule, public labels, first figures]",
     "status": "done",
     "paid": false,
     "due": "2026-09-10"
    },
    {
     "id": "Q5",
     "wave": "buf",
     "title": "Mon 21 Sep human review (5 h), lists emitted by audit",
     "status": "todo",
     "paid": true,
     "due": "2026-09-21"
    }
   ]
  },
  {
   "key": "B",
   "title": "B \u00b7 Pages & SEO",
   "session": "/stream-b",
   "tasks": [
    {
     "id": "B1",
     "wave": "w2",
     "title": "Product page body rendered server-side (headings, price table, awards) [curl of a product page shows the H1 and every price; 16 renderer tests; Lighthouse local mobile: CLS 0.003 to 0, LCP 3.2s to 3.0s, perf 93 both]",
     "status": "done",
     "paid": true,
     "due": "2026-09-05"
    },
    {
     "id": "B2",
     "wave": "w2",
     "title": "\u00a76 corrections: absolute URLs + PUBLIC_BASE_URL, gtin by width, availability, Organization, drop SearchAction, real 404s, HEAD, cache headers, no-store [curl: HEAD 200 no body, If-None-Match 304, unknown id and route 404+noindex, no-store on HTML+JSON, JSON-LD absolute with nested offers; 39 tests]",
     "status": "done",
     "paid": true,
     "due": "2026-09-05"
    },
    {
     "id": "B3",
     "wave": "w2",
     "title": "Structure page half-day sweep before it goes to Mark [rendered /structure checked: no 558, no five airports, no free/almost nothing, SearchAction line aligned, live linked count, beauty counts from the live DB 2026-09-04; one marked placeholder left for rian (s10 #10)]",
     "status": "done",
     "paid": true,
     "due": "2026-09-05"
    },
    {
     "id": "B4",
     "wave": "w2",
     "title": "Airport pages [curl of /airports/lhr-london shows the H1, four counts, cards with prices, CollectionPage+ItemList+Airport JSON-LD, sitemap entry; served root diffed identical to headless Chrome's DOM; 301/404/HEAD/304 pinned; 83 tests]",
     "status": "done",
     "paid": true,
     "due": "2026-09-09"
    },
    {
     "id": "B5",
     "wave": "w2",
     "title": "Brand pages (on the brands table) [brand pages on brands.slug (A8/A9 landed mid-session): /brands/johnnie-walker served root diffed identical to the SPA DOM on dfp_a; alias and case 301, thin brand 404, 96 sitemap entries on the rehearsal copy; CollectionPage+Brand+ItemList; 20 tests; product-page links wait on brand_slug in the product API (request to A)]",
     "status": "done",
     "paid": true,
     "due": "2026-09-11"
    },
    {
     "id": "B6",
     "wave": "w3",
     "title": "Category and category-at-airport pages (per \u00a710 #10)",
     "status": "todo",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "B7",
     "wave": "w3",
     "title": "IndexNow, per-bot robots, RSS, llms.txt, Dataset page [robots.txt with Content-Signal and per-bot groups from one switch table; /feed.xml RSS; /llms.txt; /data with Dataset JSON-LD (server body diffed identical to the SPA DOM); python -m app.cli_pages indexnow (dry run exercised on the dev copy, no network); 47 tests]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    }
   ]
  },
  {
   "key": "C",
   "title": "C \u00b7 Design",
   "session": "/stream-c",
   "tasks": [
    {
     "id": "C1",
     "wave": "w1",
     "title": "Brand alignment pass one (logo, palette, serif)",
     "status": "done",
     "paid": true,
     "due": "2026-09-03"
    },
    {
     "id": "C2",
     "wave": "w2",
     "title": "Mega menu [c06a766. Verified in headless Chrome on two dev copies (brands present / brands empty): hover, toggle, Enter/Tab/Escape, outside click, navigation, phone drawer sections; 38 panel links all 200; nav row 47.9px in both states; Lighthouse CLS 0.000 product page, 0.009 home. Waits only on B's header_html() mirror (issue filed, blocking) for the same deploy.]",
     "status": "done",
     "paid": true,
     "due": "2026-09-09"
    },
    {
     "id": "C5",
     "wave": "w2",
     "title": "Brand pass two: banners designed into the pages, sponsor sizes decided (then Adam gets the sizes) [11 Sep evening: rian rejected the first placements (IAB boxes dropped between sections read as arbitrary). Slots stay in code, hidden; a design pass makes them part of the layout before sizes go to Adam. Accent picker on /settings kept for Adam to choose. Flags: small flag beside the code (cards, airport hero), flag behind the code on the home picker.]",
     "status": "doing",
     "paid": true,
     "due": "2026-09-09",
     "rian": true
    },
    {
     "id": "C3",
     "wave": "w2",
     "title": "Featured savings and exclusives prominence [6fa56d8 (+1034869 AA gold text). Headline saving card from featured[0], exclusives band with live total, product-page saving enlarged, excl badge tone ready (B flips one word). Verified in headless Chrome 1280/390 on dfp_c; check.sh green.]",
     "status": "done",
     "paid": true,
     "due": "2026-09-10"
    },
    {
     "id": "C4",
     "wave": "w2",
     "title": "Instagram/YouTube links; CSS for Q/R surfaces on request [62ffa7e. Header and footer links behind window.__DFP_SOCIAL__ (server-injected, https only); renders nothing today. B wires the env setting, shell injection and sameAs (issue filed, costly).]",
     "status": "done",
     "paid": false,
     "due": "2026-09-14"
    }
   ]
  },
  {
   "key": "D",
   "title": "D \u00b7 Content",
   "session": "/stream-d",
   "tasks": [
    {
     "id": "D1",
     "wave": "w2",
     "title": "Articles table (kind: article | airport_writeup | category_intro) + import command [commit 89f33a0; migration a4b5c6d7e8f9 rehearsed up/down/up on dfp-devdb; import of md, txt, docx and /todo uploads rehearsed on a scratch DB; 45 tests]",
     "status": "done",
     "paid": true,
     "due": "2026-09-08"
    },
    {
     "id": "D3",
     "wave": "w3",
     "title": "Email capture (migration #5) + subscriber export [commits 000765e + 198d952: POST /api/subscribers throttled with honeypot and consent record, 11 tests incl. no-address-in-logs; owner-only CLI export rehearsed on dfp_d; SubscribeForm component ready, placement is C's (running list)]",
     "status": "done",
     "paid": true,
     "due": "2026-09-14"
    },
    {
     "id": "D2",
     "wave": "w3",
     "title": "Adam's articles published; airport write-ups; category intros [pages and blocks built and served since 0.33.0 (article pages, airport write-up block); waits only on Adam's hand-ins on /todo, none received]",
     "status": "blocked",
     "paid": true,
     "due": "2026-09-15"
    },
    {
     "id": "D4",
     "wave": "w3",
     "title": "Sponsor slots at IAB sizes [commit 3724f38: SponsorSlot for the nine IAB sizes reserves its box, static image only, nothing when no creative; registry in lib/sponsors.ts; positions and placement follow C5 (issue to C on the running list)]",
     "status": "done",
     "paid": true,
     "due": "2026-09-15"
    }
   ]
  },
  {
   "key": "E",
   "title": "E \u00b7 Infrastructure",
   "session": "/stream-e",
   "tasks": [
    {
     "id": "E2",
     "wave": "w1",
     "title": "Nightly pg_dump cron (installed 4 Sep 03:15, 14-day retention); rehearsed restore still to do",
     "status": "done",
     "paid": true,
     "due": "2026-09-05"
    },
    {
     "id": "E1",
     "wave": "w2",
     "title": "Egress test: rent a droplet for an hour, fetch robots + one product page from all 21 sources; rian approves the spend [Run 10 Sep from the real droplet (137.184.166.194) against a same-day mosiah control: 23 of 23 hosts identical (22 pages, Dubai refused on both). Text-fetch only; a bounded rendered run is next. .logs/runs/egress-2026-09-10.md]",
     "status": "done",
     "paid": true,
     "due": "2026-09-07",
     "rian": true
    },
    {
     "id": "E3",
     "wave": "w2",
     "title": "Sizing and real monthly cost to rian; rian decides the hosting figure to tell Adam [Two figures given to rian in chat 10 Sep from the brief's inputs: DFP alone without collectors 2 GB at $12; DFP with the browser sidecar, and as the shared live box, 4 GB at $24 (start here; resize CPU and RAM only). The $50 hold stands (rian, 10 Sep).]",
     "status": "done",
     "paid": true,
     "due": "2026-09-08",
     "rian": true
    },
    {
     "id": "E4",
     "wave": "w2",
     "title": "Provision the DigitalOcean droplet (rian's account and payment), Postgres, firewall [bwlive (137.184.166.194, Tailscale 100.106.255.15) bootstrapped 10 Sep by deploy/bootstrap-droplet.sh: deploy user, sshd keys-only + AllowUsers from the tailnet and mosiah, root off, unattended upgrades, Docker 29, 2 GB swap. Open: rian confirms the DO cloud firewall is attached; the sidecar fence (deploy/sidecar-firewall.sh) awaits his approval.]",
     "status": "done",
     "paid": true,
     "due": "2026-09-08",
     "rian": true
    },
    {
     "id": "E4b",
     "wave": "w2",
     "title": "Production deploy path from this repo; first deploy to the droplet; PUBLIC_BASE_URL set [deploy/production.sh; first deploy 10 Sep 23:51 UTC: seeded from the staging dump, migration head b5c6d7e8f9a0, /api/health 0.35.0, 13,307 products / 37,062 observations / 3 accounts; owner-check ok; PUBLIC_BASE_URL set; sidecar fence proven from inside the browser container.]",
     "status": "done",
     "paid": true,
     "due": "2026-09-09"
    },
    {
     "id": "E4c",
     "wave": "w2",
     "title": "dutyfreeprofessor.com pointed at production with certificates; Cloudflare zone checklist (bot fight mode, AI-bot blocking, managed robots off; cache bypass for /api and /owner) [dutyfreeprofessor.com on the droplet 11 Sep 00:2x UTC via the proxied apex A record (WP Engine records deleted); Cloudflare Origin CA certificate on Caddy (Let's Encrypt's remote validators could not reach the zone, 15 attempts); rian set Full (strict), bots/AI/managed-robots off, bypass rule; verified from three outside vantage points.]",
     "status": "done",
     "paid": true,
     "due": "2026-09-10",
     "rian": true
    },
    {
     "id": "E5",
     "wave": "w2",
     "title": "Host cron: audit nightly, verify after collect and weekly, nightly pg_dump on production [bwlive crontab: pg_dump 03:15 UTC (14 days) pulled to mosiah backups/production/ at 04:15; audit 04:00 UTC nightly; verify --n 200 --mode weekly Sundays 05:00 UTC. verify --n 20 after a collect stays a hand step (RUNBOOK).]",
     "status": "done",
     "paid": false,
     "due": "2026-09-11"
    },
    {
     "id": "E6",
     "wave": "w3",
     "title": "Monitoring and alerts to rian; RUNBOOK deploy / rollback / restore; one rehearsed restore [RUNBOOK 'Production' section; DO alert policies set by rian (memory/CPU/disk 80%); restore rehearsed 11 Sep on the droplet into a scratch database: 11 seconds for the 9.6 MB dump, counts match. External uptime check not yet wired (issue filed).]",
     "status": "done",
     "paid": true,
     "due": "2026-09-15"
    },
    {
     "id": "E7",
     "wave": "w4",
     "title": "Go-live checklist on production: every internal route closed, no-store headers, sitemap and robots correct, egress from the new IP re-checked [First pass 11 Sep through the domain: all 29 routes give the sign-in door except /login /welcome /forgot; every API read 401/302 with no-store; robots Disallow all and sitemap 404 (correct while members-only); egress re-checked from the production address (23/23 + rendered). The public flip is its own go-live step: do-flip-production-to-public on the running list.]",
     "status": "done",
     "paid": true,
     "due": "2026-09-16",
     "rian": true
    }
   ]
  },
  {
   "key": "F",
   "title": "F \u00b7 Awards",
   "session": "/stream-f",
   "tasks": [
    {
     "id": "F1",
     "wave": "w3",
     "title": "Selection strategy document for Adam [published as /discuss decision card 5 (item 40) with the full text in its thread; kept at notes/awards-strategy-for-adam-2026-09-05.md; ask recorded as do-adam-awards-strategy, gates F2]",
     "status": "done",
     "paid": true,
     "due": "2026-09-14"
    },
    {
     "id": "F2",
     "wave": "w3",
     "title": "Picker (recency, level, airport affinity); --rebuild upserts by natural key; pin via overrides [picker + rebuild by natural key + 78 tests landed (e2d50a3), rehearsed on a throwaway DB copy; wiring into catalog_queries is issue-wire-award-picker (B); the pin waits for the overrides table (decide-overrides-before-r): it plugs in at catalog_queries.get_product/_top_awards via award_picker.pin_from_override, and the stale-pin reverification in awards_import.import_awards after reconcile_awards]",
     "status": "blocked",
     "paid": true,
     "due": "2026-09-15"
    }
   ]
  },
  {
   "key": "R",
   "title": "R \u00b7 Accounts and owner area",
   "session": "/stream-r",
   "tasks": [
    {
     "id": "O1",
     "wave": "w2",
     "title": "Default-deny auth middleware + route-inventory test (closes nine open write routes) [delivered as N1 (commit d5f89b0): default-deny middleware, mutations.py as the allowlist, owner sign-in stopgap; R's accounts replace the credential]",
     "status": "done",
     "paid": false,
     "due": "2026-09-10"
    },
    {
     "id": "L1",
     "wave": "w2",
     "title": "Vendor the BW kit and React pack byte-identical with a manifest and vendor-check in check.sh; argon2 dependency; the X-Auth-User grep gate [27 files vendored, manifest + vendor-check in check.sh, tsc -b green under the reference; hand edit proven to fail]",
     "status": "done",
     "paid": false,
     "due": "2026-09-11"
    },
    {
     "id": "O2",
     "wave": "w2",
     "title": "Migration #4 and the models: accounts columns, account_credentials, levels, members and grants, sessions, account_tokens, audit_log, overrides, the seven who-columns; backfills accounts, levels, authors, overrides; cli_accounts.py; rehearsed on dfp-devdb up and down twice [b5c6d7e8f9a0 rehearsed up/backfills x2/down/up on dfp_r; 7 tests]",
     "status": "done",
     "paid": false,
     "due": "2026-09-11"
    },
    {
     "id": "L3",
     "wave": "w2",
     "title": "Identity: argon2id passwords, DB sessions and the loader middleware, hashed single-use tokens carried in the link's fragment, the local directory adapter, the mail seam, the audit sink (remove maps to disable), /api/auth/*, the silent per-account lock and the address throttle [sessions, directory, mail seam, audit sink, /api/auth/*, CLI; 41 tests incl. identical refusal sequence and lockout on a fake clock]",
     "status": "done",
     "paid": false,
     "due": "2026-09-11"
    },
    {
     "id": "L5",
     "wave": "w2",
     "title": "Kit wiring (before L4): the mapped store, six permissions registered, seed levels, View As policy with owner-only act mode, build_router at /api/bw, DFP's own /api/bw/me with the capabilities block, /api/whoami, /api/ops/impersonations [mapped store, sentinel owner, can(), View As policy, /api/bw mounted, DFP /api/bw/me; 13 tests]",
     "status": "done",
     "paid": false,
     "due": "2026-09-11"
    },
    {
     "id": "L4",
     "wave": "w2",
     "title": "Access policy (after L5): services/access.py over reads, pages and mounts; safe_next; the must-change gate; act_as.write from the middleware; SITE_ACCESS members-only mode; docs off in production; the inventory test rewritten; the stopgap's server half retired [access.py, decide() case table, inventory over reads/pages/mounts/App.tsx, members-mode answers, traversal probes, act_as.write; stopgap retired]",
     "status": "done",
     "paid": false,
     "due": "2026-09-11"
    },
    {
     "id": "L6",
     "wave": "w2",
     "title": "Authorship from the session on every write; payload author fields removed; comment edit rules; item:* thread scoping [author from the session in 4 routers; edit rules; item:* scoping; 6 tests]",
     "status": "done",
     "paid": false,
     "due": "2026-09-11"
    },
    {
     "id": "L7",
     "wave": "w2",
     "title": "The SPA: login (safeNext), forgot, set-password (token from the hash), account and admin pages; the header account menu; capability guards; name boxes, OwnerGate and api/owner.ts replaced by the capabilities [six pages, account menu, capability guards, name boxes and OwnerGate gone; tsc and build green]",
     "status": "done",
     "paid": false,
     "due": "2026-09-11"
    },
    {
     "id": "L8",
     "wave": "w2",
     "title": "Tests: the kit's conformance pack vendored under tests/kit with a DFP conftest; DFP's own suites for policy, sessions, passwords, tokens, lockout, members mode, authorship [45 conformance tests byte-identical + DFP suites; 1016 tests in 7 s]",
     "status": "done",
     "paid": false,
     "due": "2026-09-11"
    },
    {
     "id": "L9",
     "wave": "w2",
     "title": "Docs: main/docs/ACCOUNTS.md, ARCHITECTURE, CLIENT-SURFACES, RUNBOOK (Accounts, rollout, transfer of ownership), DATA-MODEL, agents.md pointer, .app.env.example, CHANGELOG, KICKOFFS, the kit upstream proposals [ACCOUNTS.md, RUNBOOK Accounts, CHANGELOG one deploy, upstream proposals; docs-check strict pending final]",
     "status": "done",
     "paid": false,
     "due": "2026-09-11"
    },
    {
     "id": "L10",
     "wave": "w2",
     "title": "End-to-end rehearsal on a local server against dfp_r (the curl list and the browser round trip); the handoff with rian's ordered steps; green and ready [27 curls + 23-step scripted round trip green on dfp_r; the browser step is rian's on the public host]",
     "status": "done",
     "paid": false,
     "due": "2026-09-11"
    },
    {
     "id": "L11",
     "wave": "w2",
     "title": "If time remains: the Resend mail provider behind MAIL_PROVIDER=resend, inert until its lines exist [ResendMailer, stdlib, inert until MAIL_PROVIDER/RESEND_API_KEY/MAIL_FROM; 4 tests]",
     "status": "done",
     "paid": false,
     "due": "2026-09-12"
    },
    {
     "id": "T1",
     "wave": "buf",
     "title": "R2: migration #6 (threads, comment columns, notifications, email_sends), backfill threads, services/discussion.py ported from the Interaction Standard's reference [migration c7d8e9f0a1b2 rehearsed up/down/up on dfp_r; backfill threads twice (second zero); services/discussion.py ported; 19 tests in test_discussion_threads.py]",
     "status": "done",
     "paid": false,
     "due": "2026-09-23"
    },
    {
     "id": "T2",
     "wave": "buf",
     "title": "R2: the caddie-ui pack stamped byte-identical; Composer, ThreadCard, Body, Lightbox inline on /discuss; author from the session; own edit, curate any; soft delete [caddie-ui 1.1.0 stamped byte-identical (MANIFEST-caddie-ui.sha256, vendor-check); SubjectThread adapter on /discuss; edit/delete/resolve routes; Vite build green]",
     "status": "done",
     "paid": false,
     "due": "2026-09-23"
    },
    {
     "id": "T3",
     "wave": "buf",
     "title": "R2: /structure, /quote, /todo and /plan on the same components with one subjects read per page [SubjectThread on /structure, /quote, /todo, /plan; FeatureThread and its hooks retired; later keys frozen in quote.ts and pinned]",
     "status": "done",
     "paid": false,
     "due": "2026-09-24"
    },
    {
     "id": "T4",
     "wave": "buf",
     "title": "R2: notifications (mention, reply, decision), the inbox routes, the Bell in the header [GET/POST /api/notifications; Bell adapter in the header; mention/reply/resolved/decision events; 10 tests in test_discussion_notifications.py incl. T10's plan-thread guarantee]",
     "status": "done",
     "paid": false,
     "due": "2026-09-24"
    },
    {
     "id": "T5",
     "wave": "buf",
     "title": "R2: attachments in comments through the uploads seam under /api/attachments [routers/attachments.py under /api/attachments/{type}/{id}; sniff-and-strip seam; composer attach on every thread; 5 hostile-surface tests]",
     "status": "done",
     "paid": false,
     "due": "2026-09-25"
    },
    {
     "id": "T6",
     "wave": "buf",
     "title": "R2: mail: the Resend provider, an app-only env file (security review), email_sends caps, invites and resets by email, the notify digest command [caps in email_sends, invites/resets/notices counted, notify digest (cron line in RUNBOOK Mail), the mail preference; inert until W3's app-only env file carries the MAIL_* lines; the compose review is on the running list for W3]",
     "status": "done",
     "paid": false,
     "due": "2026-09-25"
    },
    {
     "id": "V1",
     "wave": "buf",
     "title": "R3: the /api/ops/* reads (sources, runs, rejections, verify, audit, merges, freshness, impersonations) gated by sources.manage",
     "status": "todo",
     "paid": false,
     "due": "2026-09-25"
    },
    {
     "id": "V2",
     "wave": "buf",
     "title": "R3: the collection page; the kill switch toggle with an actor in the audit log; verify-clear from the session; the sources enable/disable CLI",
     "status": "todo",
     "paid": false,
     "due": "2026-09-26"
    },
    {
     "id": "V3",
     "wave": "buf",
     "title": "R3: prune commands (sessions, tokens, audit-log) and their cron lines on the machine E5 picks",
     "status": "todo",
     "paid": false,
     "due": "2026-09-26"
    },
    {
     "id": "V4",
     "wave": "buf",
     "title": "R3, gated: the scheduler as a watched trial behind FEATURE_SCHEDULER, only after decide-where-collectors-run and the hand-run decide are answered",
     "status": "todo",
     "paid": false,
     "due": "2026-09-26"
    },
    {
     "id": "O3",
     "wave": "buf",
     "title": "Five owner surfaces (issues, summaries, posture, inbox, drafts) [superseded 10 Sep by the accounts plan: the owner surfaces are R3's ops page (V1, V2) and R2's inbox]",
     "status": "deferred",
     "paid": false,
     "due": "2026-09-25"
    },
    {
     "id": "T7",
     "wave": "buf",
     "title": "R2: a staging refresh preserves discussion_comments and every table Adam writes; the procedure in the runbook, rehearsed on dfp-devdb [app.cli staging-refresh export/apply + main/scripts/staging-refresh.py; rehearsed twice on dfp-devdb with Adam's 11 Sep comments; tests/test_staging_refresh.py]",
     "status": "done",
     "paid": false,
     "due": "2026-09-14"
    },
    {
     "id": "T8",
     "wave": "buf",
     "title": "R2: the pages collapsed: the proposal written into the brief first, every subject key kept, every old address redirecting [three pages: /discuss in four tabs (?tab=), /issues its own page, /plan; 301s on /todo /structure /quote with the fragment inherited; tests in test_site_routes.py TestMovedClientPages]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "T9",
     "wave": "buf",
     "title": "R2: issues that resolve and archive from the page, the archived set reachable; the write path R3's clear will share [archived state + POST /api/items/{id}/archive; show=recent|archived|all filter on /issues; the route shape in ACCOUNTS.md Recorded writes; test_items_archive.py]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "T10",
     "wave": "buf",
     "title": "R2: a comment mentioning Claude on a rian-only page is a subject a session reads on its next run; a test pins that a plan thread never reaches a client [@claude in the mention directory on plan.view subjects only (T1); app.cli discussion inbox --for claude [--since]; read order in checkpoint.md and agents.md; tests pin no client row and the drop]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "T11",
     "wave": "w3",
     "title": "The one side panel: every thread the account may see, docked right, card to subject, Reply inline (pack SidePanel) [DiscussionPanel.tsx over the pack SidePanel; tsc, vite build and check.sh green (1323 tests); commit R: T11]",
     "status": "done",
     "paid": false,
     "due": "2026-09-14"
    },
    {
     "id": "T12",
     "wave": "w3",
     "title": "Per thread for the viewer: unread, needs-you, resolved; read state per account; tabs Needs you / Unread / Open / All, Show resolved [migration e9f0a1b2c3d4 rehearsed up/down/up on dfp-devdb; tests/test_discussion_panel.py (6); check.sh green 1329 tests; commit R: T12]",
     "status": "done",
     "paid": false,
     "due": "2026-09-14"
    },
    {
     "id": "T13",
     "wave": "w3",
     "title": "Resolve, reopen and a needs-follow-up mark with a note, from the list, recorded [followup + followup/clear routes, the Follow-ups tab, controls on both cards; 3 tests; check.sh green 1340; commit R: T13]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "T14",
     "wave": "w3",
     "title": "Legacy mentions ring: the backfill emits mention notifications for old comments, idempotent; the digest lists Needs-you [backfill legacy_mentions (+ at the end of backfill threads), rehearsed both ways on a staging copy; the digest mails Needs-you only; 3 tests; check.sh green 1347; commit R: T14]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "T15",
     "wave": "w3",
     "title": "Topics: a thread that belongs to no section, started from the panel (subject type topic) [POST /api/discussion/topics + the panel footer starter + Topics on the review tab; 3 tests; check.sh green 1332; commit R: T15]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "T16",
     "wave": "w3",
     "title": "@ offers names: the people route on our side; the picker requested from the caddie-ui pack [GET /api/discussion/people + the hint under every composer; the picker request stays with caddie on the running list; 2 tests; check.sh green 1334; commit R: T16]",
     "status": "done",
     "paid": false,
     "due": "2026-09-16"
    },
    {
     "id": "T17",
     "wave": "w3",
     "title": "Resolve and reopen with a closing comment, recorded [resolve {resolved, comment?}; ClosingWord on page and panel; 3 tests; check.sh green 1337; commit R: T17]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "T18",
     "wave": "w3",
     "title": "The re-threading: a recorded move-comment write, and the 13 Sep map applied once (topics for Adam's drafts, cruise ports, dutyfreeawards.com, indexing) [POST comments/{id}/move + discussion rethread --check|--apply; rehearsed on a staging copy (21 entries, second apply zero); 5 tests; check.sh green 1344; commit R: T18]",
     "status": "done",
     "paid": false,
     "due": "2026-09-16"
    }
   ]
  },
  {
   "key": "DOCS",
   "title": "Docs",
   "session": "/stream-docs",
   "tasks": [
    {
     "id": "X1",
     "wave": "w1",
     "title": "Hygiene: old-name headers, brief rewrite with NOW block, dead files, poc-dfp delivered [brief 40 lines with NOW block; 5 headers retitled; nginx.conf + deploy-failed.log gone; guardrails moved to main/docs/COLLECTORS.md; proposal bannered]",
     "status": "done",
     "paid": false,
     "due": "2026-09-05"
    },
    {
     "id": "X2",
     "wave": "w1",
     "title": "agents.md to \u2264140 lines; main/docs shells (COLLECTORS, RUNBOOK, QUALITY, ARCHITECTURE, DATA-MODEL) [agents.md 129 lines; seven main/docs shells each opening Sources of truth; check.sh green]",
     "status": "done",
     "paid": false,
     "due": "2026-09-05"
    },
    {
     "id": "X3",
     "wave": "w1",
     "title": "docmap.py + docs-check.sh FAIL gates; project /checkpoint command [docmap.py 3 blocks; docs-check.sh FAIL+WARN tiers wired into check.sh; project /checkpoint loads; gate found version.py lagging CHANGELOG]",
     "status": "done",
     "paid": false,
     "due": "2026-09-05"
    },
    {
     "id": "X4",
     "wave": "buf",
     "title": "WARN gates, weekly doc sweep, CLIENT-SURFACES/SEO docs",
     "status": "deferred",
     "paid": false,
     "due": "2026-09-23"
    }
   ]
  },
  {
   "key": "OWN",
   "title": "Rian",
   "session": null,
   "tasks": [
    {
     "id": "P1",
     "wave": "w1",
     "title": "Decisions \u00a710 #1 and #2 (scope vs capacity; the Fri 11 milestone) [decided 5 Sep: everything stays, unpaid ranked last, pulled forward Fri 11 if green; Fri 11 = option (a)]",
     "status": "done",
     "paid": false,
     "due": "2026-09-05",
     "rian": true
    },
    {
     "id": "P2",
     "wave": "w1",
     "title": "Send Mark the /structure page after B3; comments by Thu 10 [sent 5 Sep with the /todo page; comments by Thu 10]",
     "status": "done",
     "paid": false,
     "due": "2026-09-06",
     "rian": true
    },
    {
     "id": "P3",
     "wave": "w2",
     "title": "Reply pass to Adam's sixteen threads (plan \u00a710 #15), Mon 7 [reply pass done 5 Sep; some threads needed no answer]",
     "status": "done",
     "paid": false,
     "due": "2026-09-07",
     "rian": true
    },
    {
     "id": "I1",
     "wave": "w4",
     "title": "Wave 4 integration: full test run, docs-check strict, cross-device, accessibility, performance [.logs/verification/quality-pass-2026-09-19.md: the internal set is closed (243 routes walked, 154 fetched with no cookie, 0 leaks), no layout break at 1440 or 390 on nine pages, four accessibility defects found and fixed with tests, the bundle at 137,953 bytes gzipped entry against a 250 kB bar, and the cost that remains is the two server-rendered pages' reads, filed as four items with their parts timed. Nothing blocks a launch; twelve issues carry the rest.]",
     "status": "done",
     "paid": true,
     "due": "2026-09-17"
    },
    {
     "id": "P4",
     "wave": "w2",
     "title": "Deploy checkpoints midday/evening; dump before any migration",
     "status": "todo",
     "paid": false,
     "due": "2026-09-18",
     "rian": true
    },
    {
     "id": "P6",
     "wave": "w4",
     "title": "Delivery walkthrough with Adam",
     "status": "todo",
     "paid": false,
     "due": "2026-09-18",
     "rian": true
    },
    {
     "id": "P5",
     "wave": "buf",
     "title": "Cannes asks: Dubai, Avolta pagination/sitemaps, Shilla",
     "status": "todo",
     "paid": false,
     "due": "2026-09-26",
     "rian": true
    }
   ]
  },
  {
   "key": "S",
   "title": "S \u00b7 Structure review",
   "session": "/stream-s",
   "tasks": [
    {
     "id": "S1",
     "wave": "w2",
     "title": "Airport URL leads with the airport's common name (JFK code-first); every earlier shape 301s [e119380; curl-verified on dfp_s: new path 200, six old shapes 301 keeping the query, jfk-new-york 200, hidden airport 404]",
     "status": "done",
     "paid": false,
     "due": "2026-09-11"
    },
    {
     "id": "S2",
     "wave": "w2",
     "title": "Review outcomes in code: the Perfume category; a brand page needs three products at two airports; the price of record leads the page and is the only price in the markup [d4567e6 Perfume (+backfill perfume, rehearsed 2,219 rows on dfp_s), fe5ffa9 brand floor (44 pages on dfp_s at four airports), next commit price of record; 859 tests, check.sh green]",
     "status": "done",
     "paid": false,
     "due": "2026-09-11"
    },
    {
     "id": "S3",
     "wave": "w2",
     "title": "/structure rebuilt around the review outcome; /todo airport ask reworded; built rows flipped to live [commit on /structure and /todo; check.sh green; seed applied live, row 9 kept id, status and comment 148]",
     "status": "done",
     "paid": false,
     "due": "2026-09-11"
    },
    {
     "id": "S4",
     "wave": "w2",
     "title": "Drafts for rian: the nineteen airports, answers to the open questions, the frame for Adam's two documents [notes/review-drafts-2026-09-10.md: the nineteen with names from the database, answers to the four open questions with the build assumptions, the frame for Adam's two documents (both answer with a sign-in page; not read), the todo:9 reply]",
     "status": "done",
     "paid": false,
     "due": "2026-09-11"
    }
   ]
  },
  {
   "key": "N",
   "title": "N \u00b7 Night of 10 Sep",
   "session": "/stream-n",
   "tasks": [
    {
     "id": "N1",
     "wave": "w2",
     "title": "O1: default-deny auth middleware, the mutating-route inventory as its allowlist, an owner login for the routes behind it [commit; 883 tests incl. 26 policy tests; curl-verified on dfp_n: owner routes 403 without cookie, admitted with it, Origin mismatch refused, public routes unchanged]",
     "status": "done",
     "paid": false,
     "due": "2026-09-10"
    },
    {
     "id": "N2",
     "wave": "w2",
     "title": "Every document current: main/docs against their sources, tier-1 files, stream briefs, plan statuses; the issue log complete [commit: seven docs checked against sources and corrected, agents.md pointers, stream briefs stamped, plan statuses corrected, handoff trimmed, fourteen items filed and one resolved; docs-check --strict 0 fail]",
     "status": "done",
     "paid": false,
     "due": "2026-09-10"
    },
    {
     "id": "N3",
     "wave": "w3",
     "title": "Awards: a shorter competition entry no longer lands on the longer retail variant; medal artwork gaps [d50a352 matcher veto + apostrophe fix (rehearsed on dfp_n, every changed row read; review file); artwork commit with test_medal_artwork.py]",
     "status": "done",
     "paid": false,
     "due": "2026-09-10"
    },
    {
     "id": "N4",
     "wave": "w2",
     "title": "Home page: the hero's airport picker no longer shifts the card when its list arrives [commit; Lighthouse mobile home CLS 0.0166 to 0.0007, perf 97, two runs each side]",
     "status": "done",
     "paid": false,
     "due": "2026-09-10"
    },
    {
     "id": "N5",
     "wave": "w4",
     "title": "Review pass: every route against a restored dump, house style in shopper-facing text, doc gates strict, obvious fixes landed, the rest filed [159 routes, sitemap entries and API reads all 200 on dfp_n, no tracebacks, no console errors; fifteen em dashes reworded (c3e5f09) with test_house_style.py; Lighthouse mobile home 97 / product 93 / airport 98 / brand 97, a11y 100, CLS 0; favicon and the rest filed]",
     "status": "done",
     "paid": false,
     "due": "2026-09-10"
    }
   ]
  },
  {
   "key": "M",
   "title": "M \u00b7 Matching and merging",
   "session": "/stream-m",
   "tasks": [
    {
     "id": "M1",
     "wave": "w3",
     "title": "Listings view on /collectors: the tile as collected beside what we made of it [GET /api/collectors/listings + the Listings tab; 23 tests on real fragments; on the 11 Sep copy 23,771 listings, 11,098 with a tile kept, 35 differ in brand, 39 in size]",
     "status": "done",
     "paid": false,
     "due": "2026-09-13"
    },
    {
     "id": "M2",
     "wave": "w3",
     "title": "The line above the product and the variation vocabulary (migration + backfills, rehearsed) [migration c6d7e8f9a0b1 rehearsed up/down/up; backfill lines and variations twice on dfp_m: 13,683 lines, 1,806 with 2+ products, 41 wordings, 1,986 beauty products stamped; the fourteen 1 Million rows one line per house, three variations, set apart (test_line_backfills.py)]",
     "status": "done",
     "paid": false,
     "due": "2026-09-13"
    },
    {
     "id": "M3",
     "wave": "w3",
     "title": "match_key v3: brand prefix and concentration stripped; the 100 ml 1 Million EDT becomes one product at five airports [v3 key rehearsed on dfp_m: 500 groups merged (555 rows), 321 left for a person, comparable 3,675 to 3,731; 1 Million EDT 100 ml one product at 4 airports with the alias simulated, CDG's bare row a suggestion (test_keying.py)]",
     "status": "done",
     "paid": false,
     "due": "2026-09-14"
    },
    {
     "id": "M4",
     "wave": "w3",
     "title": "Aliases at every level with a preferred name; Paco Rabanne \u2192 Rabanne as rian's first confirmed alias [apply_brand_alias / apply_line_alias / apply_variation_alias with decided_by and decided_at, re-key and fold; readers resolve in the key, the listings table and the brand page; test_aliases.py on the fourteen rows; Paco Rabanne to Rabanne waits for rian's confirm in M6, never set by a rule]",
     "status": "done",
     "paid": false,
     "due": "2026-09-14"
    },
    {
     "id": "M5",
     "wave": "w3",
     "title": "Suggestions, rules first: brand, line and product candidates with a readable reason and a score [app.cli suggest: 27 brand, 2,039 line, 1,054 product pairs on dfp_m, Paco Rabanne to Rabanne first at 0.95; each pair carries a sentence (detail.why); test_suggest.py pins every rule; idempotent, second run changes nothing]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "M6",
     "wave": "w3",
     "title": "The merge session on /collectors: keyboard confirm / reject / skip, preferred name, decided_by recorded [Merge view with y/n/s and 1/2 keys, preferred name, decided_by from the session, audit log; POST confirm/reject/suggest under sources.manage; test_merge_session.py walks the alias confirm as rian; tsc green; not yet opened in a browser (branch not deployed)]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "M7",
     "wave": "w3",
     "title": "Inheritance rules for barcode and image, written and tested; never invented [gtin_source=merge on a gained barcode; two barcodes need a note and the record keeps both; image with provenance; stated in DATA-MODEL.md, pinned in test_inheritance.py]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    }
   ]
  },
  {
   "key": "G",
   "title": "G \u00b7 The complete airport page",
   "session": "/stream-g",
   "tasks": [
    {
     "id": "G1",
     "wave": "w3",
     "title": "Opening hours collected where the airport operator's robots allow: a robots verdict per operator domain, one collector per operator platform, the airport_hours table (migration, rehearsed) [robots read per operator domain (18 domains, verdicts in .logs/runs/hours-robots-2026-09-11.md); collectors for Heathrow, Dublin and Toronto Pearson read the live pages into dfp_g rows 1-3; migration c7d8e9f0a1b2 rehearsed up/down/up; 32 hours tests; 16 airports to hand population]",
     "status": "done",
     "paid": false,
     "due": "2026-09-14"
    },
    {
     "id": "G2",
     "wave": "w3",
     "title": "Hours by hand: app.cli hours set, the same shape as a collected set, with the date and who entered it; a human value is never overwritten by a collector [hours set writes a dated hand row named to the account (proved on dfp_g: ATH row 4); the reader shows hand over collected and stores a later collect unshown; backfill hours_seed idempotent (seeds zero today, no guide carries an hours constant); guide_for(iata, db) reads the table; 9 store tests on SQLite]",
     "status": "done",
     "paid": false,
     "due": "2026-09-14"
    },
    {
     "id": "G3",
     "wave": "w3",
     "title": "Provenance per airport: collected or hand-entered and the date, nothing where neither exists; the read and the component, the shape written down for the review area [GET /api/airports/{iata}/hours (public-when-open, in open_routes.json) and guide.hours_provenance; the facts panel prints 'collected 11 Sep 2026' beside the hours on the server and in AirportHours.tsx, the placeholder view the full line; proved on dfp_g for LHR (collected) and ATH (hand); COLLECTORS.md Opening hours section; issues filed for M and R3; 1123 tests, check.sh green]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "G4",
     "wave": "w3",
     "title": "Featured at this airport, by family [services/airport_featured.py runs featured.pick_featured per family over the airport's own rows; AirportDetail.featured, one row per family in seo.airport_body and AirportFeatured.tsx, absent where nothing compares; proved on dfp_g (LHR, DUB, ZRH rows by family); 6 picker tests on seeded rows plus the body pins; check.sh green]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "G5",
     "wave": "w3",
     "title": "Shopping-feature filters on the airport page (award winners, travel exclusives, comparable only), pointing back at the clean address [the three chips (Comparable, Award winners, Travel exclusives) on the airport page's full list as ?multi_only|awarded_only|exclusives_only=true, the names /api/products takes, kept across category chips, sort and pages, dropped on the other shelves; canonical stays the clean page (pinned in test_seo_airport and test_site_routes); urls.py and urls.ts move together (the mirror test reads both); GET /api/airports/{iata} takes them; check.sh green]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "G6",
     "wave": "w3",
     "title": "A comparison preset: the comparison tool opened on this airport [Compare from here on the airport hero (server body and SPA) opens /savings?from=<IATA>; SavingsPage holds the departure as view state read once on mount, leads the comparison with it beside the saved airports and never writes the saved set; urls.py savings_from_path mirrored by urls.ts savingsFromPath/savingsDeparture (pinned); check.sh green]",
     "status": "done",
     "paid": false,
     "due": "2026-09-16"
    },
    {
     "id": "G7",
     "wave": "w3",
     "title": "Category and category-at-airport pages at the structure proposal's thresholds; beauty switches on as its coverage qualifies [category-at-airport pages at /airports/<airport>/<category-slug>, built only where the pair holds 15+ published products (coverage.CATEGORY_AT_AIRPORT_MIN_PRODUCTS, read at request time; a decide records the number): server body and head, the SPA page, the API, the sitemap, the airport page's rail of links; 96 pages across 17 airports on the 11 Sep copy, LHR whisky proved; the site-wide category page is not built (structure.ts names /alcohol/<slug>, not /categories/; a decide names it); check.sh green]",
     "status": "done",
     "paid": false,
     "due": "2026-09-16"
    }
   ]
  },
  {
   "key": "W",
   "title": "W \u00b7 Go-live readiness",
   "session": "/stream-w",
   "tasks": [
    {
     "id": "W1",
     "wave": "w4",
     "title": "Privacy, terms and contact pages served by rule from public/, linked from the footer; the wording marked draft until Adam approves it",
     "status": "todo",
     "paid": false,
     "due": "2026-09-16"
    },
    {
     "id": "W2",
     "wave": "w4",
     "title": "The lazy chunks gated behind the session while members-only; the main bundle and the sign-in chunk stay public; a test pins both",
     "status": "todo",
     "paid": false,
     "due": "2026-09-16"
    },
    {
     "id": "W3",
     "wave": "w4",
     "title": "An app-only env file in compose so the database container reads no app secret: security review, the change staged and audited, the droplet twin as a checklist for rian",
     "status": "todo",
     "paid": false,
     "due": "2026-09-17"
    },
    {
     "id": "W4",
     "wave": "w4",
     "title": "The unauthenticated sweep as a script: every App.tsx route and its /api/* reads curled without a session, the internal set proved closed; runs again on the day [main/scripts/route-sweep.py written and tested; RUNBOOK step 18 names it]",
     "status": "done",
     "paid": false,
     "due": "2026-09-17"
    },
    {
     "id": "W5",
     "wave": "w4",
     "title": "The production edge checklist with the exact commands: trusted proxies on the droplet, cache bypass for the auth paths, Bot Fight Mode off; rian runs it",
     "status": "todo",
     "paid": false,
     "due": "2026-09-17"
    },
    {
     "id": "W6",
     "wave": "w4",
     "title": "The Extime-is-Paris correction in copy with no address change; the IndexNow mint written as rian's one command block, never run here",
     "status": "todo",
     "paid": false,
     "due": "2026-09-18"
    }
   ]
  },
  {
   "key": "L",
   "title": "L \u00b7 Listings, quantity and the merge desk",
   "session": "/stream-l",
   "tasks": [
    {
     "id": "LT1",
     "wave": "w3",
     "title": "The quantity parser, the v4 key and variation kinds; pure code [services/quantity.py; v4 slot value+unit in every dimension; tests/test_quantity.py pins 7293 (75g), 14386 (3.5ml+4g|set), 0.375L, the ounce rule, the rounding; kinds color and flavor in test_lines; 1414 tests, check.sh green; commit L: LT1. Deviations noted: grams snap within 6 percent (1 oz cream is 30 g); the Makeup line key keeps the shade tail until rian's open makeup-shades decision.]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "LT2",
     "wave": "w3",
     "title": "One migration b3c4d5e6f7a8, one head, rehearsed up/down/up [alembic/versions/b3c4d5e6f7a8_quantity_listed_decided.py revising a2b3c4d5e6f7; up/down/up clean on dfp_l (restored from the 14 Sep nightly); alembic heads = b3c4d5e6f7a8 only; head pin moved in the same commit; 1414 tests, check.sh green; commit L: LT2.]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "LT3",
     "wave": "w3",
     "title": "Decided beats rule: override reader in keying, tombstone follow, pin and ignore [services/overrides.py; Maps.decided read first in product_key; tombstone follow in ingest (human merges only, current rules, never unknown); pin and ignore routes; tests/test_decided.py (18 tests: overrides survive rekey+backfill lines, the 1 Million 10cl case, stale/rule/unknown tombstones refused, newest merge wins, pin holds, ignore hides, routes 401/403/200, typed name at confirm); 1432 tests, check.sh green; commit L: LT3. Ignore reaches catalog_queries and /collectors only tonight; do filed for trip/catalog/indexnow/coverage/audit.]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "LT4",
     "wave": "w3",
     "title": "Quantity and kinds through collectors, ingest and backfill quantities; rederive rehearsed [dfp_l, after-deploy order: quantities 16767 written / 169 size_ml filled / 1 cleared (7293 -> 75g; 14386 -> 3.5ml+4g|set), 2nd run 0; variations 325 wordings, 2327 stamped, 38 kinds; lines 58 created, 66 pointed; rederive 17373 keys to v4, 612 tombstones rekeyed; merges --check read (6 mergeable, 319 candidate groups: 251 gtin, 67 gtin+unknown, 1 quantity_unknown), merges 6 groups / 15 pairs, 2nd run 0. Stated by vertical before->after: beauty 5933->6560, confectionery 16->43, liquor 7398->7498; Makeup 267->531. Live units: ml 13407, g 644, pcs 36, none 2650. vs l_v3_keys: ml joins 0, ml splits 1 (Louis Roederer 'Collection', a set word); 38 regrouped: 24 sets, 6 gram/piece, 5 unknown, 3 confectionery (bare names split from sized siblings, e.g. Lindor Tube Milk vs 400g). 1471 tests, check.sh green; commit L: LT4.]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "LT5",
     "wave": "w3",
     "title": "Listed columns at ingest and backfill listed from the newest fragment [services/collected.py (listed_fields + write_listed, one reader for ingest and backfill); dfp_l: backfill listed --check then twice: 11098 filled, 12673 no fragment (NULL, not empty): CDG 200/7036, DXB 1489, HEINEMANN-GLOBAL 1035, KEF 963, SYD 444, SCA 1806 zero; BOG 2484/2491, PTY 2440/2444, YUL 1035/1036, DUB 727/746, LHR 536/541, ZRH 477/496, BCN/ATH/ICN/SIN complete; second run 0; last_seen_at filled on all 23771. tests/test_listed.py 15 tests; 1486 tests, check.sh green; commit L: LT5.]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "LT6",
     "wave": "w3",
     "title": "The giant Listings table with the column picker, SQL sorts and CSV [services/listings_table.py + components/collectors/ListingsTable.tsx; dfp_l: first page all 68 columns 0.46 s (EXPLAIN ANALYZE 177 ms), sort=listed_brand per_page=500 over 23,771 rows 0.95 s, CSV airport=BOG 2,491 rows, differs brand 11 / name 9605 / quantity 146; measured through the service against dfp_l, not a local uvicorn (no static symlink made); tsc clean; tests/test_listings_table.py 10; 1497 tests, check.sh green; commit L: LT6. Not verified in a browser: no signed-in browser this session.]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "LT7",
     "wave": "w3",
     "title": "Merge desk: tables, batch approve, proposals rules never withdraw, the proposal files [services/merge_desk.py, MergeDesk.tsx (Desk view), batch route, propose route + CLI, proposals: brands 56 pairs, lines 226 pairs (rule pairs at 0.8 endorsed); dfp_l: desk/brands 2,365 rows + 27 pairs 0.15 s, desk/lines 13,772 + 2,036 0.11 s; propose --check: brands 56 insert, lines 226 endorsed, nothing written; tests/test_merge_desk.py 9 (batch of 3+2 regenerates once, rian recorded, NOTE_REQUIRED reported and the rest apply, PAIR_DECIDED refused, proposed survives generate, 401/403); tsc clean; check.sh green; commit L: LT7. Not verified in a browser.]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    },
    {
     "id": "LT8",
     "wave": "w3",
     "title": "The recrawl, told honestly: Extime window drafted, the running-list items, the Dubai story [No collector ran. Window file .logs/runs/window-2026-09-15-extime-fragments.md (not started, bwlive, commands in order); the two do items and the decide on the running list; COLLECTORS.md posture rows for the six shops; _story says 'refused since <date>' from the newest BLOCKED check (test pinned); RUNBOOK after-deploy block under b3c4d5e6f7a8 and the recrawl paragraph; 1506 tests, check.sh green; commit L: LT8.]",
     "status": "done",
     "paid": false,
     "due": "2026-09-15"
    }
   ]
  },
  {
   "key": "K0",
   "title": "K0 \u00b7 Ground the catalogue refactor",
   "session": "/stream-k0",
   "tasks": [
    {
     "id": "K0.1",
     "wave": "w4",
     "title": "Rebase the refactor branch onto master, check green [rebased onto master 10920b2 (39 commits, one union conflict in CHANGELOG Unreleased); check.sh green, 1535 tests, doc gates 0 fail; pre-rebase 81f9c31]",
     "status": "done",
     "paid": false,
     "due": "2026-09-16"
    },
    {
     "id": "K0.2",
     "wave": "w4",
     "title": "REVIEW-PROCESS.md v1: the certain boundary and every grouping default [main/docs/REVIEW-PROCESS.md v1, 150 lines: certain boundary as a numbered list of 7, eleven grouping defaults each citing its walk-through item, proposal contents, spot-check rule, value order, version line; docs-check 0 fail]",
     "status": "done",
     "paid": false,
     "due": "2026-09-16"
    },
    {
     "id": "K0.3",
     "wave": "w4",
     "title": "The doc gates: agents.md line, docmap, docs-check [agents.md Read first item 6 points at REVIEW-PROCESS.md; 140 lines exactly; docmap --write clean; docs-check 0 fail]",
     "status": "done",
     "paid": false,
     "due": "2026-09-16"
    }
   ]
  },
  {
   "key": "K1",
   "title": "K1 \u00b7 Names: one term per concept everywhere",
   "session": "/stream-k1",
   "tasks": [
    {
     "id": "K1.1",
     "wave": "w4",
     "title": "The rename-only migration, rehearsed up, down, up [c1d2e3f4a5b6 rehearsed up, down, up on dfp_k1 (nightly 2026-09-16): 5 tables, 13 columns, 27 constraints, 22 indexes, 5 sequences renamed; legacy pair dropped and restored 1072/1072 on downgrade; no old name left in the catalog; alembic heads one head]",
     "status": "done",
     "paid": false,
     "due": "2026-09-17"
    },
    {
     "id": "K1.2",
     "wave": "w4",
     "title": "Models and services renamed per the map [nine rows of the map in nine commits; 1,537 tests; proof on dfp_k1_base: old code rederive (1044/935/589), then c1d2e3f4a5b6 + suggestion_decisions (0) + attribute_keys (2803) + K1 rederive = 0/0/0/0]",
     "status": "done",
     "paid": false,
     "due": "2026-09-17"
    },
    {
     "id": "K1.3",
     "wave": "w4",
     "title": "Routers, the typed client and the SPA renamed; the tab and button words [schema.ts regenerated from the renamed backend; 33 SPA files follow the field map (subagent, diff read); tabs Suggestions and Product variants, buttons Confirm same / Keep separate; npx tsc -b clean]",
     "status": "done",
     "paid": false,
     "due": "2026-09-17"
    },
    {
     "id": "K1.4",
     "wave": "w4",
     "title": "Tests, scripts and docs renamed; the retired-identifier survivor test [tests, scripts, docs and generated maps on the new names; VOCABULARY's code column matches every word; test_one_term_per_concept greps app and web/src for every retired identifier (named exceptions with reasons); docs-check 0 fail]",
     "status": "done",
     "paid": false,
     "due": "2026-09-17"
    },
    {
     "id": "K1.5",
     "wave": "w4",
     "title": "The after-deploy line, the CHANGELOG line, the hand-back [RUNBOOK: the K1 block (migration c1d2e3f4a5b6, backfill suggestion_decisions, backfill attribute_keys) ahead of the v5 block; CHANGELOG line under Unreleased]",
     "status": "done",
     "paid": false,
     "due": "2026-09-17"
    }
   ]
  },
  {
   "key": "K5",
   "title": "K5 \u00b7 The product line page",
   "session": "/stream-k5",
   "tasks": [
    {
     "id": "K5.1",
     "wave": "w4",
     "title": "The product line read: variants, attributes, prices per shop, the comparison, the representative [tests/test_product_line_page.py 10 green (every W20 case, the tie, merged and foreign ids, alias, redirect, hidden); dfp_k5 reads 13-180 ms]",
     "status": "done",
     "paid": false,
     "due": "2026-09-18"
    },
    {
     "id": "K5.2",
     "wave": "w4",
     "title": "The route, the query parameters, the canonical, old addresses 301 [test_site_routes 60 green + test_seo_line 4; dfp_k5 curl: old JW Blue Label 1L address 301 to the line ?variant=60&airports=LHR, which is noindex, follow with the bare canonical; merged id 3253 301 to famous-grouse-finest?variant=10; unknown 404]",
     "status": "done",
     "paid": false,
     "due": "2026-09-18"
    },
    {
     "id": "K5.3",
     "wave": "w4",
     "title": "ProductLinePage: selectors from picked attributes, the price cases, shown attributes [ProductLinePage.tsx; tsc clean; dfp_k5 URLs prove every W20 case (JW Blue Label ask_variant / ?variant=60 ask_airport / &airports=LHR,SAL chosen_airports with SAL missing; ballantines-finest?variant=43 single_place DXB / &airports=LHR,CDG none_at_chosen); headless Chrome screenshot checked]",
     "status": "done",
     "paid": false,
     "due": "2026-09-18"
    },
    {
     "id": "K5.4",
     "wave": "w4",
     "title": "Cards and search deep-link with variant and airports; the card representative [cards and suggestions carry path; dfp_k5 Bumbu XO card LHR,CDG opens chosen_airports LHR 33.80 CDG 48.33; blue label suggests one per line; 1,665 tests]",
     "status": "done",
     "paid": false,
     "due": "2026-09-18"
    },
    {
     "id": "K5.5",
     "wave": "w4",
     "title": "Server-rendered body, ProductGroup JSON-LD, noindex follow [test_seo_line 8 green; dfp_k5 crawler view of JW Blue Label: noindex, follow, ProductGroup 3 variants 19 dated offers, no aggregate, asks for a quantity; each served case checked by URL]",
     "status": "done",
     "paid": false,
     "due": "2026-09-18"
    },
    {
     "id": "K5.6",
     "wave": "w4",
     "title": "Tests: routes, SEO, every W20 case, the deep link, the tied line [test_product_line_page 12, test_seo_line 8, test_site_routes 60, test_search_suggest 5, route inventory; check.sh checks passed on the tree, 1,669 tests]",
     "status": "done",
     "paid": false,
     "due": "2026-09-18"
    }
   ]
  },
  {
   "key": "K6",
   "title": "K6 \u00b7 Publish: reachable, quality, indexed",
   "session": "/stream-k6",
   "tasks": [
    {
     "id": "K6.1",
     "wave": "w4",
     "title": "publish.py: is_indexed, is_hidden, redirect_for, the candidate rules [tests/test_publish.py 8 green: the facts (hidden wins, an alias row is never indexed), redirects flattened with no self-loop, the slug at confirm, the three candidate rules, suggest idempotent; check.sh 1,685 tests]",
     "status": "done",
     "paid": false,
     "due": "2026-09-19"
    },
    {
     "id": "K6.2",
     "wave": "w4",
     "title": "Reachable on discovery; hidden answers 302, never 404 [dfp_k6 (template of dfp_k3_final) through the real routes: thin brand 100-pipers 200 (was 404), line 200; after hidden decisions brand 302 /products?brand=, line 302 /brands/johnnie-walker, LHR 302 /airports, API 404; test_site_routes TestHiddenPages, test_brand_slug rewritten; check.sh passed 1,687]",
     "status": "done",
     "paid": false,
     "due": "2026-09-19"
    },
    {
     "id": "K6.3",
     "wave": "w4",
     "title": "The final slug at confirm and flat redirects [dfp_k6 through the routes: line alias 1 renames glenfiddich-vat1 to glenfiddich-rehearsal-first-name, alias 2 to -final-name; old and middle slugs each ONE 301 to the final, 2 redirect rows both pointing at it; an indexed line kept its slug; brand alias of an alias now one hop (glen-moray to glenfiddich). Live only through merges.apply_*_alias: the desk, sheet and replay paths bypass the hook (P1 issue on K3)]",
     "status": "done",
     "paid": false,
     "due": "2026-09-19"
    },
    {
     "id": "K6.4",
     "wave": "w4",
     "title": "noindex follow; sitemap and IndexNow limited to approved pages [fresh dfp_k6: sitemap 9 URLs and 0 generated pages until approvals; brand, airport and line heads noindex, follow, then indexable once approved; after approving johnnie-walker, CDG and one line the sitemap lists exactly those; IndexNow 30d 0 URLs before, the approved airport and line after; a hidden brand in neither. 1,697 tests, check.sh passed]",
     "status": "done",
     "paid": false,
     "due": "2026-09-19"
    },
    {
     "id": "K6.5",
     "wave": "w4",
     "title": "The index CLI and the candidates list on /review [dfp_k6: index suggest 2,078 + 1 removal, second run 0; list shows the three facts; approve 2 changed / 3 refused (hidden, unknown) with the undo line; desk read 0.27 s for 100 of 2,078; glenfiddich panel 1 brand + 16 lines approved as one bulk desk batch in 0.39 s, undo-batch reversed 17 rows, decisions verify 0 drift; both routes owner-only (test_review_sheet counts 9); check.sh passed]",
     "status": "done",
     "paid": false,
     "due": "2026-09-19"
    },
    {
     "id": "K6.6",
     "wave": "w4",
     "title": "Tests [tests/test_publish.py 14 and tests/test_publish_confirm.py 4 (SQLite), plus TestHiddenPages and TestSitemap in test_site_routes, TestIndexNow in test_crawl_surface, test_brand_slug rewritten, the article sitemap test unchanged in claim: hidden 302 and absent everywhere, a new line reachable and noindex, two aliases flat, sitemap and IndexNow approved pages only, a ping only on approval, the candidate rules. 1,702 passed; check.sh checks passed]",
     "status": "done",
     "paid": false,
     "due": "2026-09-19"
    }
   ]
  },
  {
   "key": "K7",
   "title": "K7 \u00b7 The launch path",
   "session": "/stream-k7",
   "tasks": [
    {
     "id": "K7.1",
     "wave": "w4",
     "title": "deploy/production.sh --replace-db, errors fatal, sessions revoked, counts compared [dfp_prod_like (production nightly, b5c6d7e8f9a0) replaced from a launch dump in 13.3 s: head d2e3f4a5b6c7, 54 tables/130,290 rows equal, 13 sessions revoked, sources identical; five argument refusals and a dry run checked]",
     "status": "done",
     "paid": false,
     "due": "2026-09-19"
    },
    {
     "id": "K7.2",
     "wave": "w4",
     "title": "The consolidated migration chain from 0.35.0 in RUNBOOK [RUNBOOK Production: the chain from b5c6d7e8f9a0 run end to end on a copy of production's nightly, every line exit 0 in 292 s, rederive then 0, merges 191 groups, verify 0 drift; propose --file absent with the reason]",
     "status": "done",
     "paid": false,
     "due": "2026-09-19"
    },
    {
     "id": "K7.3",
     "wave": "w4",
     "title": "The launch checklist [RUNBOOK.md 'Launch checklist (staging to production)': 23 lines each with its expected output, the rollback line; every printed string checked against the scripts, the sessions CLI and /api/health]",
     "status": "done",
     "paid": false,
     "due": "2026-09-19"
    },
    {
     "id": "K7.4",
     "wave": "w4",
     "title": "Rehearsal on dfp-devdb against a production-like copy [.logs/runs/rehearsal-2026-09-17-k7-replace.md: dfp_prod_like replaced 54 tables/130,290 rows equal, 13 revoked, 13.3 s; mismatch (feature_priorities dropped) REFUSED at the counts with rollback printed, rollback restored b5c6d7e8f9a0 in 6.8 s; dry run changed nothing; chain from 0.35.0 exit 0 in 292 s]",
     "status": "done",
     "paid": false,
     "due": "2026-09-19"
    },
    {
     "id": "K7.5",
     "wave": "w4",
     "title": "Tests of the script's refusals [tests/test_production_script.py 9 green (argument handling, --seed-db gone, --local never on staging, missing dump/sidecars, no --app-exec, unreadable dump); HEAD export 80c7cec check.sh checks passed, 1,686 tests]",
     "status": "done",
     "paid": false,
     "due": "2026-09-19"
    }
   ]
  },
  {
   "key": "K3",
   "title": "K3 \u00b7 Certain-only identity",
   "session": "/stream-k3",
   "tasks": [
    {
     "id": "K3.1",
     "wave": "w4",
     "title": "Options as fields from the collectors; the RingConn fixture [RawListing.options; Shopify and Extime fill it; backfill options on the staging copy: 3,139 variants named by their title, 902 given option fields, second run 0; tests/test_options.py (the RingConn fixture: two attributes, n/a slot, own line; a fresh fragment never reaches the tail reader)]",
     "status": "done",
     "paid": false,
     "due": "2026-09-18"
    },
    {
     "id": "K3.2",
     "wave": "w4",
     "title": "The certain boundary rehearsed three ways on the staging copy; the numbers; rules v6 [three boundaries rederived on dfp_k3_a/b/c: listings keying apart 699/514/508 of 10,008, comparisons losing a shop 114/85/83, automatic merge groups 5 (0 wrong) / 22 (3 wrong) / 22 (3 wrong); boundary (a); IDENTITY_RULES_VERSION 6; REVIEW-PROCESS.md section 1 v2 held to the code by tests/test_certain_boundary.py]",
     "status": "done",
     "paid": false,
     "due": "2026-09-18"
    },
    {
     "id": "K3.3",
     "wave": "w4",
     "title": "Every open word list becomes a proposal generator [proposal_rules.py: 14 passes rule:<list>:6; on the copy 936 proposals, second run inserts none, rederive after it prints 0; tests/test_proposal_rules.py and the converted list-deletion tests]",
     "status": "done",
     "paid": false,
     "due": "2026-09-18"
    },
    {
     "id": "K3.4",
     "wave": "w4",
     "title": "Stated-attribute and Keep separate vetoes on the automatic merge; a person may still Confirm same [merges.identity_differs through the accessor (ABV 40 v 43 vetoes, 40 v 40.0 and stated v silent do not); Keep separate veto; the person's Confirm same across 40/43 holds at the next sighting of either key: tests/test_vetoes.py]",
     "status": "done",
     "paid": false,
     "due": "2026-09-18"
    },
    {
     "id": "K3.5",
     "wave": "w4",
     "title": "New listings after an approved sheet: land by certain key or become a proposed addition [ingest: barcode, else placed, else certain key, else own variant on own line plus an arrival:6 proposal to the nearest approved line; tests/test_arrivals.py]",
     "status": "done",
     "paid": false,
     "due": "2026-09-18"
    },
    {
     "id": "K3.6",
     "wave": "w4",
     "title": "The slug hook at confirm (K6's function, stubbed until it lands) [both alias appliers call publish.slug_at_confirm(row, chosen name); tests/test_slug_hook.py]",
     "status": "done",
     "paid": false,
     "due": "2026-09-18"
    },
    {
     "id": "K3.7",
     "wave": "w4",
     "title": "Tests and the rederive proof [fresh copy dfp_k3_final in the after-deploy order: rederive second run 0/0/0; merges --check 5 groups, 0 wrong by a person's reading; rule_proposals 936, second run inserts none; decisions verify 0 drift; check.sh passed with 1,642 tests]",
     "status": "done",
     "paid": false,
     "due": "2026-09-18"
    }
   ]
  },
  {
   "key": "K2",
   "title": "K2 \u00b7 Schema: the ledger, proposals, attributes, places",
   "session": "/stream-k2",
   "tasks": [
    {
     "id": "K2.1",
     "wave": "w4",
     "title": "The migration: ledger, proposals, redirects, places, uids, hidden and indexed; overrides dropped; up, down, up rehearsed [d2e3f4a5b6c7 rehearsed up, down, up on dfp_k2 (nightly 16 Sep + K1): 7 new tables, uid on 4 tables (2,365 brand uids), hidden/indexed, suggestion and merge columns, key widened to 260; triggers proven by psql (UPDATE and DELETE on decisions refused, batch accepts only its close, batch DELETE refused); downgrade recreates overrides exactly]",
     "status": "done",
     "paid": false,
     "due": "2026-09-17"
    },
    {
     "id": "K2.2",
     "wave": "w4",
     "title": "Models, natural keys, the effective read; Maps.decided and Maps.separated [models/decisions.py and models/places.py; uid/hidden/indexed/closed_reason/decision_id/batch_id columns; natural_keys build/parse/resolve (uid, own barcode, listings through a chain, VARIANT_SPLIT, LINE_MISMATCH, redirects last, mint for wordings and pairs); effective() newest by (decided_at, id) with release; Maps.decided with legacy names, Maps.separated survivor-chased; 10 new tests, 1,548 green]",
     "status": "done",
     "paid": false,
     "due": "2026-09-17"
    },
    {
     "id": "K2.3",
     "wave": "w4",
     "title": "The attribute registry and the one accessor; GUARDED_COLUMNS from it [services/attributes.py: KINDS with storage (quantity typed, abv/country/is_exclusive columns, marked kinds under K1's shape, the rest JSON), identity and display, option:<name> on sight, of/get/set/identity_part/quantity_slot/kinds_for; GUARDED_COLUMNS derived; ingest.enrich_from_raw reads it (a decided empty abv survives a 43); GIN not added: containment scan 4.7 ms over 17k rows on the copy; 1,555 green]",
     "status": "done",
     "paid": false,
     "due": "2026-09-17"
    },
    {
     "id": "K2.4",
     "wave": "w4",
     "title": "Places and shop_places; backfill from IATA; the comparison unit defined once [services/places.py (kind registry, unit(), unit_count(), unit_join(), primary_of, count_units); 9 counts in catalog_queries routed through the one unit, 11 shop joins carry the primary place; backfill places on dfp_k2: 21 places, 21 shop rows, second run 0; airport/brand/facts counts equal before and after; grep test guards the unit; 1,558 green]",
     "status": "done",
     "paid": false,
     "due": "2026-09-17"
    },
    {
     "id": "K2.5",
     "wave": "w4",
     "title": "The writer, the appliers and the overrides shim; every existing writer a batch [decisions/writer.py (batch, record's seven steps, human-first, rule_value, uuid5 consequences, advisory locks), appliers.py (every applier split; the alias cascades and the merge carry as consequence rows), undo.unmerge from the merge record; overrides.py is the shim with the field map, clear a release, move the carry, checked refused; merge session, desk, routes, CLI, listings table through it; Override model retired; 1,558 green]",
     "status": "done",
     "paid": false,
     "due": "2026-09-17"
    },
    {
     "id": "K2.6",
     "wave": "w4",
     "title": "The machine paths honour the ledger: kept_separate and decided_member vetoes, fold stamping, prune [merges._conflicts gains kept_separate and decided_member (maps loaded fresh per fold); suggest.generate closes with closed_reason only in a savepoint per row and reopens a pair a rule offers again; Suggestion.is_open() is the one predicate for the desk, queue, progress, audit and prune; prune keeps any line a decision or redirect names; 3 tests; 1,561 green]",
     "status": "done",
     "paid": false,
     "due": "2026-09-17"
    },
    {
     "id": "K2.7",
     "wave": "w4",
     "title": "Undo and batch undo; the decisions CLI (list, undo-batch, verify) [decisions/undo.py: undo (in force directly or through a restore; consequences first; restores the predecessor, releases to the rules, reinstates when the target is an undo; ENTITY_MERGED, DECISION_SUPERSEDED), undo_batch (kind undo, tail folds first from their records, descending id, superseded rows named and skipped), verify (drift report); app.cli decisions list|undo-batch|verify; 3 tests; 1,564 green]",
     "status": "done",
     "paid": false,
     "due": "2026-09-17"
    },
    {
     "id": "K2.8",
     "wave": "w4",
     "title": "Export and replay by natural key; staging-refresh carries the ledger [decisions/replay.py export (JSON lines by natural key, no local id, batches and passes by uid and name, --batch refuses BATCH_UNDONE) and replay (same batch uids, per-row savepoints, uid match, older rows inserted not materialised, DECISION_HELD parked, remainder file retried first, --check rolled back); app.cli decisions export|replay; staging-refresh guard counts decisions and proposals with ON_ERROR_STOP and fails hard, the ledger exported before the drop and replayed after upgrade head; 4 tests; 1,568 green]",
     "status": "done",
     "paid": false,
     "due": "2026-09-17"
    },
    {
     "id": "K2.9",
     "wave": "w4",
     "title": "The 22 tests, the two-copy replay acceptance, docs, hand-back [22 spec tests covered across test_effective/natural_keys/attributes/places/machine_paths/undo/replay/writer/refresh_guard (K4's four sheet tests are K4's); two-copy rehearsal on dfp_k2 and dfp_k2b (A's uids copied, sequences +1000, an extra derived line and a -2 slug): 51/51 applied, 0 parked, second replay 0, undo-batch on B 38 rows, verify 0 drift both, rederive 0 both, columns equal by slug (141/142 variants, 25/26 lines: a rules fold and a post-fork line); docs, RUNBOOK block, VOCABULARY, CHANGELOG; check.sh green 1,573]",
     "status": "done",
     "paid": false,
     "due": "2026-09-17"
    }
   ]
  },
  {
   "key": "K4",
   "title": "K4 \u00b7 Review: the file, the sheet, approval, undo",
   "session": "/stream-k4",
   "tasks": [
    {
     "id": "K4.1",
     "wave": "w4",
     "title": "The proposals load and withdraw; the file schema [tests/test_proposals_load.py 10 green (check leaves every count unchanged, sha no-op, stale unless partial, spot-check deterministic and kept, schema names row and field, replay approves on load, withdraw lists undo); --check on dfp_k4 left proposals 0/passes 0]",
     "status": "done",
     "paid": false,
     "due": "2026-09-18"
    },
    {
     "id": "K4.2",
     "wave": "w4",
     "title": "The sheet reads and the batch and ledger reads [tests/test_review_sheet.py 7 green (GET writes nothing: counts, stamps, session clean; groups and spot-checks; effective now; drift; staleness by evidence not rederive; history and ledger; every /api/review route owner); tsc clean on the regenerated client]",
     "status": "done",
     "paid": false,
     "due": "2026-09-18"
    },
    {
     "id": "K4.3",
     "wave": "w4",
     "title": "The approval, the reject as Keep separate, the undo routes [tests/test_review_approval.py 13 green: all excludes spot-checks and held (counts loud), adopt by slug and by rule key before mint, SLUG_TAKEN cascades, merged-since-load lands on survivor, merge after memberships, pair reject = Keep separate with pass kept, correction is individual person act, COLLECTION_RUNNING gates approve and undo, undo then re-approve, absorbs, routes 200/404/422]",
     "status": "done",
     "paid": false,
     "due": "2026-09-18"
    },
    {
     "id": "K4.4",
     "wave": "w4",
     "title": "The /review page with the K6 panel slot; no cell editor [95d9d2c: /review page (sheet list, spot-checks first, per row/line/all approve, reject with a sentence, loud banner, history with undo, ledger link, j/k/a/r, empty ReviewPanels slot); tsc clean, vite build emits the chunk, route inventory green; rendered against the CHANEL rehearsal in K4.5]",
     "status": "done",
     "paid": false,
     "due": "2026-09-18"
    },
    {
     "id": "K4.5",
     "wave": "w4",
     "title": "The CHANEL rehearsal end to end, replayed onto a second copy; REVIEW-PROCESS.md v2 [f6eeab9: dfp_k4 load 48 (46 resolved, 2 creatable, 0 parked, 8 spot); line 3 approved; all 34 approved/0 held/3 refused (waiting header); spot 10; reject = Keep separate; verify 69 checked/18 drift (shades, K3 issue); undo-batch 5, re-approve 3; export 74; replay dfp_k4b 74 applied/0 parked, second 0; load on B 48 matched; A=B on 929 facts except the 7 decided: keys (K2 issue); page screenshot checked]",
     "status": "done",
     "paid": false,
     "due": "2026-09-18"
    },
    {
     "id": "K4.6",
     "wave": "w4",
     "title": "Tests: load idempotent, staleness by evidence, approval order, the gates, GET writes nothing [31 review tests across test_proposals_load (11), test_review_sheet (8), test_review_approval (13): spec 14, 15, 16, 17, 22, GET writes nothing, all excludes spot-checks and held, loud counts, schema names row, owner-only and anonymous/member refused; HEAD 4d2c5b0 clean export: 1605 passed, check.sh checks passed]",
     "status": "done",
     "paid": false,
     "due": "2026-09-18"
    }
   ]
  },
  {
   "key": "K9",
   "title": "Brand split and the trailer list",
   "session": "/stream-k9",
   "tasks": [
    {
     "id": "K9.1",
     "wave": "buf",
     "title": "Measure the folds on a copy and pin the fixture [23 of 163 multi-spelling brand rows are trailer-caused folds (450 variants, 52 spellings; liquor 19, beauty 4); fixture + 49 tests]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K9.2",
     "wave": "buf",
     "title": "The brand split: one decision, variants moved, keys re-derived [split_brand: one (brand,split) row; rehearsed on dfp_k9, 4 variants moved, 4 lines created and 4 pruned, verify 0 drift; 14 tests]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K9.3",
     "wave": "buf",
     "title": "The undo: a split returns the database to its prior state [split+undo on dfp_k9: brands 2365-2366-2365, lines 13879 throughout, three state fingerprints identical, verify 0 drift]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K9.4",
     "wave": "buf",
     "title": "A rejected fold proposal offers the split [rejected rule:brand_trailers row carries a split offer; one route + sheet panel; rehearsed on dfp_k9, 4 variants moved, verify 0 drift]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K9.5",
     "wave": "buf",
     "title": "The trailer list scoped per vertical; nothing for an unlisted one [trailers per vertical; on dfp_k9b 0 of 2365 slugs change, 0 of 2681 spellings re-home, 0 of 16756 keys move; REVIEW-PROCESS v4, no rederive]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K9.6",
     "wave": "buf",
     "title": "The word lists and their words shown in the review area [/review Word lists panel: 17 lists, words, vertical, acts-or-proposes, folded counts (liquor 19 beauty 4) and links; read-only, pinned]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K9.7",
     "wave": "buf",
     "title": "Rehearse on a copy, replay onto a second, hand back [split/check/undo on dfp_k9c fingerprint-identical, exported and replayed onto dfp_k9d 1 applied 0 parked, verify 0 drift; RUNBOOK + rehearsal log]",
     "status": "done",
     "paid": false
    }
   ]
  },
  {
   "key": "K10",
   "title": "The review area and the price block",
   "session": "/stream-k10",
   "tasks": [
    {
     "id": "K10.1",
     "wave": "buf",
     "title": "The shell: three tabs, the address carries the tab [three tabs, ?tab= in the address, sheet under folding; K9.6 mounts WordLists at DecisionPanels (named in both handoffs); check green]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K10.2",
     "wave": "buf",
     "title": "How decisions are made: the boundary, the lists, the thresholds, the defaults [the tab reads REVIEW-PROCESS.md (7 sections, 22488 chars) + 13 passes 938 waiting, read from dfp_k10; markdown tables added; 12 tests]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K10.3",
     "wave": "buf",
     "title": "Folding and splitting at every level, with counts [four levels with counts first; on dfp_k10 3913 waiting (lines 2572 vs brands 165); pairs never double-counted with their rule reasons; 14 tests]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K10.4",
     "wave": "buf",
     "title": "Publish candidates as their own tab [publish tab pages and filters; on dfp_k10 2081 waiting (364/1701/16), 'glenfiddich' narrows to 17, the 1600th line is one press away; 3 tests]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K10.5",
     "wave": "buf",
     "title": "The price block: choose in the block, browse prices by airport [four states: block offers every variant, a bottle with no airports prints every shop's price + date, airports keep the comparison with the rest behind a disclosure; 7 tests]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K10.6",
     "wave": "buf",
     "title": "Tests for the tabs and the four price states, and the rehearsal [three tabs read together over one staging-shaped db (5 tests); rehearsal on dfp_k10 written up; 1747 pass, check green]",
     "status": "done",
     "paid": false
    }
   ]
  },
  {
   "key": "G2",
   "title": "G2 \u00b7 The airport page, finished",
   "session": "/stream-g2",
   "tasks": [
    {
     "id": "G8",
     "wave": "buf",
     "title": "Sources reconciled (Adam's 13 Sep doc is the text of record; rian confirms no later edit) and the guide document defined: a kind-neutral schema on the place, areas with a per-kind label, one home in DATA-MODEL.md [the document model with Adam's seven kinds, unknown kind and unknown field both refused, hours impossible to carry; area_label on every place kind; DATA-MODEL Place guides; 8 tests; check.sh green]",
     "status": "done",
     "paid": false,
     "due": "2026-09-19"
    },
    {
     "id": "G9",
     "wave": "buf",
     "title": "The guide lives on the place: places guide import/export CLI, import/places/<slug>.json, Heathrow round-trips byte-identical and the code constant is deleted [places guide import/export; Heathrow's document from the 16 Sep profile with the 10 Sep guide's specifics merged and 6 conflicts in verify; import idempotent (written/unchanged/check), export equals the file byte for byte; the constant deleted; before and after bodies diffed on a restored copy; 1974 tests, check.sh green]",
     "status": "done",
     "paid": false,
     "due": "2026-09-19"
    },
    {
     "id": "G10",
     "wave": "buf",
     "title": "The page, pass one in the browser: Mission re-read, featured cut to one row, hours to one line, Know-before-you-shop accordions, jump links, write-up part retired, Airport JSON-LD; server body and SPA together; heights measured; checkpoint 1 [browser pass one on staging: page 4,938px desktop with the view off; best value cut to one card per family and set to scroll sideways on a phone (it was 2,749px stacked, 6.7 screens to the products); hours one line with By terminal; contents nav; terminal straplines shortened to labels; Airport markup carries the named shops]",
     "status": "done",
     "paid": false,
     "due": "2026-09-19"
    },
    {
     "id": "G11",
     "wave": "buf",
     "title": "Placeholders as the part: in-place skeletons sized like the real map, charts, hours, area, allowances and tips, corner tag and hover note, the list panel deleted; view off looks finished on LHR and ICN [in-place placeholders sized like the real part (map, chart, cards, rows, chips, a line of hours), the instruction on hover, the gaps panel deleted, the owner strip at the end carrying the count, the hours provenance and the guide's own verify notes; browser verification after the next deploy]",
     "status": "done",
     "paid": false,
     "due": "2026-09-20"
    },
    {
     "id": "G12",
     "wave": "buf",
     "title": "Passes two and three on Heathrow from saved screenshot sets (desktop, 390px, view on and off), the third against the Mission line by line; checkpoint 2 and the template approval decide [three browser passes on staging: placeholders verified on Heathrow and on ICN (no guide) at both settings, desktop 4,938px with the view off and the phone at 4.6 screens to the products from 6.7; the facts panel went to two columns on a phone; the template approval decide is on the running list]",
     "status": "done",
     "paid": false,
     "due": "2026-09-20"
    },
    {
     "id": "G13",
     "wave": "buf",
     "title": "The eighteen converted from Adam's 13 Sep text: the markdown split, then a voice pass per airport, shops mapped to retailers we read, Adam's caveats in verify, house style green",
     "status": "todo",
     "paid": false,
     "due": "2026-09-21"
    },
    {
     "id": "G14",
     "wave": "buf",
     "title": "The eighteen imported on a staging copy, every page rendered and measured, thin airports named, the after-deploy import list, Adam's verification list as a do",
     "status": "todo",
     "paid": false,
     "due": "2026-09-21"
    },
    {
     "id": "G15",
     "wave": "buf",
     "title": "Hours by hand from Adam's text only where he states a time, provenance in detail, a run note; nothing entered where he states none",
     "status": "todo",
     "paid": false,
     "due": "2026-09-21"
    },
    {
     "id": "G16",
     "wave": "buf",
     "title": "The airports index reads has_guide from the place, SEO.md and RUNBOOK.md updated, CHANGELOG Unreleased, v8-feedback; checkpoint 3 with the full after-deploy list",
     "status": "todo",
     "paid": false,
     "due": "2026-09-21"
    }
   ]
  },
  {
   "key": "K12",
   "title": "The review we go live with",
   "session": "",
   "tasks": [
    {
     "id": "K12.1",
     "wave": "buf",
     "title": "Before the sweep: empty lines pruned on undo, the parser fingerprint, the sweep runbook [green on claude/review-golive; rehearsal .logs/runs/rehearsal-2026-09-18-k12.md]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K12.2",
     "wave": "buf",
     "title": "collected_name as an evidence source for fragment-less listings [green on claude/review-golive; rehearsal .logs/runs/rehearsal-2026-09-18-k12.md]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K12.3",
     "wave": "buf",
     "title": "The precedent register, and the file that names its precedent [green on claude/review-golive; rehearsal .logs/runs/rehearsal-2026-09-18-k12.md]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K12.4",
     "wave": "buf",
     "title": "Attention becomes a control [green on claude/review-golive; rehearsal .logs/runs/rehearsal-2026-09-18-k12.md]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K12.5",
     "wave": "buf",
     "title": "The pass packet, the cross-divide generator, the settled stamp, pass status [green on claude/review-golive; rehearsal .logs/runs/rehearsal-2026-09-18-k12.md]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K12.6",
     "wave": "buf",
     "title": "The review itself: survey, precedent groups, the two-decision fold card, the merge card [green on claude/review-golive; rehearsal .logs/runs/rehearsal-2026-09-18-k12.md]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K12.7",
     "wave": "buf",
     "title": "The documents and the skill: REVIEW-PASS.md, REVIEW-PROCESS v6, the thin skill [green on claude/review-golive; rehearsal .logs/runs/rehearsal-2026-09-18-k12.md]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K12.8",
     "wave": "buf",
     "title": "Rehearsal end to end on a copy, hand-back [green on claude/review-golive; rehearsal .logs/runs/rehearsal-2026-09-18-k12.md]",
     "status": "done",
     "paid": false
    }
   ]
  },
  {
   "key": "AW1",
   "title": "Three new collectors",
   "session": "",
   "tasks": [
    {
     "id": "AW1.1",
     "wave": "buf",
     "title": "The scout: enumerate, read robots, one listing read each, rank, choose three [scout ran 19 Sep 17:58-19:05 UTC, window closed with 13-host table, no refusal; chosen avolta-las, avolta-lgw, avolta-hel; fixtures parse to the scout's counts]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW1.2",
     "wave": "buf",
     "title": "First chosen source: configuration, registry, tests on the real tiles [avolta-las configured; 10 tests on the scout's real Las Vegas grid and robots (15 tiles, 4 multi-size, 5 barcodes, USD declared, paged at Crawl-delay 60); check.sh green]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW1.3",
     "wave": "buf",
     "title": "Second chosen source: configuration, registry, tests on the real tiles [avolta-lgw configured; 10 tests on the scouted robots and grid: ?p=2 refused so page-one-only, 9 tiles, 1 multi-size, 2 barcodes (22%), GBP, Crawl-delay 30; slug gatwick-lgw-london in both tables; check.sh green]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW1.4",
     "wave": "buf",
     "title": "Third chosen source: configuration, registry, tests on the real tiles [avolta-hel configured; 11 tests on the scouted robots and grid: no Disallow so paged, 10 tiles, 3 multi-size, 2 barcodes (20%), EUR, Crawl-delay 60, hidden equal-figure member blocks yield no was-price; slug helsinki-vantaa-hel in both tables; Finland flag fi.svg plus both countries.ts lines; check.sh green]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW1.5",
     "wave": "buf",
     "title": "Window W1 for the orchestrator, and what to read afterwards [window-2026-09-19d-new-collectors.md with the gate, launch and after-run commands; admit-queue, guard and finish adapted in new-collectors-2026-09-19d/ (ceiling 3000 MiB for the 4 GB cap, exact slug patterns, GiB parsing fixed, bash -n clean); finish.sh's four queries proven read-only on the real database; the airport page check rehearsed on a restored copy with Heathrow (hidden renders nothing, shown renders the H1), copy dropped; handoff prepended; check.sh green]",
     "status": "done",
     "paid": false
    }
   ]
  },
  {
   "key": "AW3",
   "title": "AW3 \u00b7 Pictures at three levels",
   "session": "",
   "tasks": [
    {
     "id": "AW3.1",
     "wave": "buf",
     "title": "Survey and manifest [import/images/adam-2026-09-19.json from the 19 Sep nightly on dfp_aw3: 611 entries; chosen brand 26, line 36, variant 21; 33 tests in test_image_manifest.py; check.sh green]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW3.2",
     "wave": "buf",
     "title": "Storage, provenance and the cascade [migration aw3b1c2d3e4f rehearsed up, down, up on dfp_aw3 (fresh 19 Sep nightly); backfill image_sources moved 1392 sources and stamped 1392 levels, second run 0; imagery.py cascade, tier, set_image, credit, store; 29 tests + 3 inheritance; check.sh green]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW3.3",
     "wave": "buf",
     "title": "Stage, import, and every reader on the cascade [images stage ran from the worktree into the mounted uploads (57 downloads, 6 files, 82 derivatives stored, 1 EPS refused on content type); manifest committed with url fields; images import --check, import and a re-run rehearsed on a copy of dfp_aw3 (25 brand, 36 line, 21 variant applied, 1 not_staged, image_admin_kept 0, re-run all unchanged); every reader on pictures_of/resolve with image_level and image_credit, brand mark classed thumb--brand and out of JSON-LD/og:image, brand logo on the Brand node; 24 new tests in test_cli_images and test_catalog_pictures plus the seo level cases; check.sh green]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW3.4",
     "wave": "buf",
     "title": "Wider legitimate coverage [8a742b6: OFF name search reads products again, pinned on a five-row payload (the 18 refused for the 12); images lines promotes a line's representative OFF photo (546 on the 19 Sep copy, no network); images logos built with the label, P31 and licence refusals, then refused at review (c2382f2): robots.txt read per host before its first request, both Wikimedia hosts disallow /w/, decide item open, not run]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW3.5",
     "wave": "buf",
     "title": "The /images page, coverage, docs [9ce78ef: images coverage prints per level (brand/line/variant: total, admin, public, without, from_line, from_brand, none); /images stat cards per level, pill by source prefix (Adam's/public/none, line or brand mark), Brand image now column, Wanted first lists via /api/images/wanted; test_image_ask.py pins rows, coverage, wanted and access (40 tests green with route inventory). Rehearsed on a copy of the 19 Sep dump: coverage before brand 0/2418, line 0/14428, variant 1282 public; after import+lines brand admin 25, line admin 36 public 546, variant admin 21 public 1273. RUNBOOK Coverage + the after-deploy order, COLLECTORS posture bullet, CHANGELOG line, handoff entry. check.sh: checks passed.]",
     "status": "done",
     "paid": false
    }
   ]
  },
  {
   "key": "AW2",
   "title": "Featured selection v3",
   "session": "",
   "tasks": [
    {
     "id": "AW2.1",
     "wave": "buf",
     "title": "Adopt the panel's design into the brief, and measure the page as it is [app.cli featured evidence (D7) on a restored copy: home 7 of 9 targets (6 winners of 8, one champion in the first four), Perfume 5, Whisky 3, Makeup 7; before numbers in the brief and AW2-evidence-before.json; 49 tests]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW2.2",
     "wave": "buf",
     "title": "featured.py v3, every featured sort through it, the admin pin [order() behind every featured sort; migration aw2c1d2e3f4a up/down/up on dfp_aw2b; featured pin/unpin/list rehearsed on the copy with undo; check green, 2305 tests]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW2.3",
     "wave": "buf",
     "title": "The methodology page [docs/FEATURED.md v1 (8 sections, 23 constants tied both ways to featured.DOC_CONSTANTS, house style tested), read per request by process_doc.read_doc; GET /api/featured/method (404 DOC_MISSING when absent or unversioned); /how-we-choose served ahead of the catch-all with the h1 and 'Version 1, 20 September 2026' (noindex, follow until approved), SPA page seeded; 'How we choose' anchor on all eight rails and their seo.py mirrors, asserted exactly when the rail is featured; 2330 tests, tsc and vite build green, docs-check 0 fail]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW2.4",
     "wave": "buf",
     "title": "Evidence after, tuning by numbers, hand-back [featured evidence on a fresh restore of the pre-D1 dump migrated to aw2c1d2e3f4a (up, down, up): first 8 targets met home 7 to 9 of 9, Perfume 5 to 9, Whisky 3 to 9, Makeup 7 to 8; every rail's median above the pool's p75, both champions in the first four everywhere, gates 0; AWARD_BONUS 0.25 to 0.10 by the sweep (home four medals of eight, was six), VERSION 2; home 395 ms (624 before), /products first page 542 ms (1,125 before) with the comparisons ranked first; check.sh green; tables in the brief, handoff written]",
     "status": "done",
     "paid": false
    }
   ]
  },
  {
   "key": "AW4",
   "title": "AW4 \u00b7 The live collectors page",
   "session": "",
   "tasks": [
    {
     "id": "AW4.1",
     "wave": "buf",
     "title": "Adopt the recorded design [D1 to D11 read against the tree: alembic heads = aw2c1d2e3f4a; run_collector, is_stuck, held_listing_refs, check_allowed, fetch/render, live/_story, PERMISSION and cmd_collect are as the brief assumes; only changi and shilla import render; ApiError drops summary; the three pinning suites pass (25)]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW4.2",
     "wave": "buf",
     "title": "The control plane: migration, the hook, pace, the routes [daaf8cf + 8d follow-up: migration aw4a1b2c3d4e up, down, up on dfp_aw4 (26 sources run/discover, 151 runs); hook, lock, SIGTERM, six routes; 55 tests; Postgres rehearsal: run stopped by rian with the batch committed, a second start CollectorLocked with no row; check.sh 2388 passed]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW4.3",
     "wave": "buf",
     "title": "Progress, counters, modes, memory, hours: the live read [LiveOut typed: nine state words, counters after resolution, percent and ETA, memory, the hours group; live() 9 statements warm on the 19 Sep dump (dfp_aw4c at aw4a1b2c3d4e, 20 ms, 26 collectors, the five sweep orphans read stalled); 2399 tests, check green]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW4.4",
     "wave": "buf",
     "title": "The page [LiveBoard and CollectorRow (D9) replace LiveTab: useLivePoll (3 s visible, 30 s hidden, one fetch in flight, refetch on visibilitychange and after every action, last good data kept with 'not refreshed since', a 401 stops polling with the sign-in link), useCountUp (rAF, snaps down, reduced-motion at once). Screenshots at 1440 and 390 (390 stacks one card per collector, scrollWidth 390) against the SPA dev server and a local API on dfp_aw44 (restored from dfp-2026-09-19-pre-D2.dump, upgraded to aw4a1b2c3d4e, one collector seeded per state): the running row reads '60% of about 1,240 \u00b7 about 40 min to go', the recheck '24 of 30 checked', paused shows no ETA, memory '1.6 GiB of 4.0 GiB' with the shed and start marks, the freeze banner with its stamp. Driven over CDP: Pause -> pausing, Resume -> running, pace 2 -> 'pace set to 2 s', Stop on a stalled row -> Stop now appears, Stop now with no pid -> PID_UNKNOWN shown, Mark as ended -> idle, six audit rows. check.sh: checks passed, 2399 tests.]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW4.5",
     "wave": "buf",
     "title": "The W2 verification plan, and the documents [W2 plan .logs/runs/window-2026-09-19-w2-live-page.md: 12 steps on shopify-yul (its robots.txt read once: Allow /, no Crawl-delay, so the 1 s floor holds and 3 s then 6 s measure the pace), each with the page text, its psql check and a fail line, plus the gate, the close and a shell-only lane; the page half is rian's because sources.manage is held by no seeded level; all 19 psql lines parse on dfp_aw4c at aw4a1b2c3d4e. RUNBOOK Collecting rewritten (page actions, modes, the pace rule, the shell flags, the state words, the deploy freeze) and the Deploy step's freeze lines; COLLECTORS.md a paragraph; ARCHITECTURE.md a sentence; CHANGELOG line; docmap written; docs-check 0 fail; check.sh checks passed]",
     "status": "done",
     "paid": false
    }
   ]
  },
  {
   "key": "AW5",
   "title": "AW5 \u00b7 The launch surface",
   "session": "/stream-aw5",
   "tasks": [
    {
     "id": "AW5.1",
     "wave": "buf",
     "title": "Page status: the four words, the read, the badge [20 tests in test_page_status.py plus 4 in test_access.py: the four words per kind, the resolver pinned against the head builders' noindex/follow and against sitemap_entries for a brand, line, place, article, category and three static addresses, indexed and not; anonymous 401 in both site modes, a member-level account 403, admin and owner 200; no app/ source carries the class. On a restore of dfp-2026-09-19-pre-D3 (dfp_aw5): 29 addresses in 21 ms, 0.7 ms each, session never dirty, and all 9 sitemap URLs agreed.]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW5.2",
     "wave": "buf",
     "title": "Ten sample articles, the sample and tags columns, the import commands [Migration aw5d1e2f3a4b up/down/up on a fresh restore of pre-D3; ten samples imported (sample+tags), published to 11 rows, 'unpublish --samples' took exactly the ten to draft and left the real piece published; found and fixed staging's articles_id_seq sitting under its own row (the first import would have died on articles_pkey); 2466 tests, check green]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW5.3",
     "wave": "buf",
     "title": "The article centre: /articles server-rendered, the grid, tags, related lines [On the copy: /articles renders 11 cards server-side (1 lead, 10 sample badges, 14 tag chips, an ItemList of 11) at noindex, follow; ?tag=whisky narrows to 1; the sitemap holds the pieces and not the centre; check.sh green, 2486 tests]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW5.4",
     "wave": "buf",
     "title": "Email capture verified end to end on the copy, the export procedure [Rehearsed on a fresh copy of the 19 Sep pre-D3 dump at head aw5d1e2f3a4b: the form's post writes one row and a second post of the same address answers identically with one row; no Origin 403, honeypot 200 with nothing stored, bad address and unticked consent 422, the sixth attempt from one client key 429, no address in any log line; anonymous post 401 while SITE_ACCESS=members and 200 with public; stats 1/1/0, export --out mode 600 under umask 022 with the ten EXPORT_COLUMNS, unsubscribe stamps the row and the default export drops it (0 rows, 1 with --include-unsubscribed), a re-subscribe reuses the same row. Export procedure written into RUNBOOK Content; six new tests in test_subscribers.py; 2492 tests, check.sh green.]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW5.5",
     "wave": "buf",
     "title": "Rehearsal and hand-back [Rehearsed on dfp_aw55, a fresh restore of backups/dfp-2026-09-19-pre-D3.dump: migration aw5d1e2f3a4b up, down, up; backfill level_permissions granted admin (+pages.status) then granted none on a second run; the ten imported and published to 11 published pieces (10 samples); /articles 200 with 11 cards, 10 Sample badges and an ItemList of 11 at noindex, follow, ?tag=whisky 1 and ?tag=buying 4; sitemap 18 addresses without the centre, and putting /articles into INDEXED_PAGES moved the badge, head and sitemap together; the status read is anonymous 401 in both site modes, member 403, admin 200 and the owner 200 from a member level; no served body carries page-status; the launch step took 11 cards to 1 and the sitemap to 8. check.sh green, 2492 tests.]",
     "status": "done",
     "paid": false
    }
   ]
  },
  {
   "key": "AW6",
   "title": "AW6 \u00b7 The launch plan",
   "session": "/stream-aw6",
   "tasks": [
    {
     "id": "AW6.1",
     "wave": "buf",
     "title": "The plan document: mosiah to live with times, and the two environments after it [The launch plan exists at .logs/planning/launch-plan-2026-09-19.md: seven gates and 22 clocked lines from the freeze to the announcement, each citing its RUNBOOK line; measured times from the 17 Sep rehearsal (dump 5.0 s, dry run 0.4 s, replace 13.3 s, rollback 6.8 s) and check.sh 1 m 38 s re-measured today, allowances marked as allowances; part two covers pushes, the collector arithmetic against the droplet's 4 GB, discussions, a 31-row pages-by-environment table and the W gaps. check.sh printed checks passed (2,496 tests, docs-check 0 fail).]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW6.2",
     "wave": "buf",
     "title": "The environment line: SITE_ROLE hides the development surfaces on live [SITE_ROLE in config.py (is_live, only the literal live); access.py names all 125 permission-gated routes and SPA paths in DEVELOPMENT_ROUTES (35), DEVELOPMENT_SPA (8) or LIVE_FEATURES (82), and decide(live=True) answers 404 before any sign-in redirect; /api/health carries role; the SPA drops the links and routes on developmentSurfaces. 2,503 tests, check.sh green.]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW6.3",
     "wave": "buf",
     "title": "The proposal double-check: every accepted quote line against what is built [All 17 keys of the quote block checked against the tree and staging (launch plan, last section: a verdict and a proof per line). Four status words were wrong; AWAY section 6 corrected in place. Three items raised: A11/C5 launch scope, Singapore's 27 listings, the stale awards-pin comment. check.sh: 2,503 tests, checks passed.]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW6.4",
     "wave": "buf",
     "title": "The launch marker: --replace-db refuses after the launch unless forced [deploy/production.sh writes backups/production/LAUNCHED at the end of step 9 and copies it to the droplet's backups/; --replace-db then refuses before step 1 and before any ssh, printing the marker's own line and naming --force-replace-db. --dry-run reports it the same way and writes none; a --local rehearsal is never blocked. Six new tests in tests/test_production_script.py (15 in the file, 2,509 in the suite), check.sh green; RUNBOOK Production and the plan's part two say it.]",
     "status": "done",
     "paid": false
    }
   ]
  },
  {
   "key": "AW7",
   "title": "The quality pass",
   "session": "",
   "tasks": [
    {
     "id": "AW7.1",
     "wave": "buf",
     "title": "The full run and the unauthenticated sweep [route-sweep.py fetched all 243 routes on staging with no cookie: 88 401s, 50 302s to /login, 0 leaks, exit 0; check.sh green at 2,518 tests; docs-check --strict 11 fail in the worktree and 10 in the main checkout, all timestamp or false, two filed as issues]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW7.2",
     "wave": "buf",
     "title": "Cross-device: nine pages at 1440 and 390 [nine pages at 1440 and 390 on a restored copy served locally: 0 unreachable breaks and 0 hard-cut text, the 13 wide elements all inside a deliberate sideways scroller; the page-status badge absent from all 8 server-rendered bodies signed out and signed in; page-probe.py with 11 tests; two info issues filed]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW7.3",
     "wave": "buf",
     "title": "Accessibility of the new components [the nine pages read from Chrome's accessibility tree at 1440 and 390: the unnamed navigation on every page at 1440 is gone (0 of 9), the article index no longer skips h1 to h3, one banner, one main and one contentinfo everywhere, 0 of 40 focus steps without a ring and 0 unnamed controls in 423 tree nodes on the live page; 14 contrast pairs added, all passing under all 9 schemes; the Start menu closes on Escape; 6 new tests, check.sh green at 2,535; four issues filed for what was larger than one rule]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW7.4",
     "wave": "buf",
     "title": "Performance: the three heaviest pages and the bundle [Three vantages timed, five runs each: staging anonymous (90 to 97 ms, no handler), the handlers in the app container (category_detail 1,236 ms median, list_product_variants 952, airport_detail 902), and the restored copy served here before and after ANALYZE, which moved nothing. Five of nine targets over AW7's 500 ms p50 bar, none over 1 s. Bundle well inside: entry 137,953 bytes gzipped of a 250 kB bar, largest lazy chunk 20,306 of 200 kB. EXPLAIN shows no missing index, so no migration. Four issues filed with their parts timed; report section AW7.4.]",
     "status": "done",
     "paid": false
    },
    {
     "id": "AW7.5",
     "wave": "buf",
     "title": "The report, the handoff, I1 on /plan [Report closed with the verdict and an index of all 27 findings (4 fixed, 12 filed, 2 carried, 9 closed); docs/QUALITY.md gains 'The pass before a launch' with no counts or dates; handoff prepended at 25 lines; I1 done; the local copy dropped and the server stopped.]",
     "status": "done",
     "paid": false
    }
   ]
  }
 ],
 "quote": [
  {
   "key": "core-environment",
   "name": "Live production environment",
   "tasks": [
    "E1",
    "E3",
    "E4",
    "E4b",
    "E4c",
    "E5",
    "E6",
    "E7"
   ]
  },
  {
   "key": "core-airports",
   "name": "Sixteen airports live (nineteen at launch)",
   "tasks": [
    "A10",
    "A6",
    "A13"
   ]
  },
  {
   "key": "core-quality",
   "name": "Quality, performance and soft launch",
   "tasks": [
    "Q1",
    "Q2",
    "Q3",
    "Q4",
    "I1",
    "P6"
   ]
  },
  {
   "key": "core-crawlable",
   "name": "Emitting HTML that AI can read",
   "tasks": [
    "B1",
    "B2"
   ]
  },
  {
   "key": "core-brand",
   "name": "Brand alignment",
   "tasks": [
    "C1",
    "C5"
   ]
  },
  {
   "key": "structure-proposal",
   "name": "Site structure proposal",
   "tasks": [
    "B3",
    "P2"
   ]
  },
  {
   "key": "airport-pages",
   "name": "Airport pages",
   "tasks": [
    "B4"
   ]
  },
  {
   "key": "brand-pages",
   "name": "Brand pages and browsable brands",
   "tasks": [
    "A8",
    "A9",
    "B5"
   ]
  },
  {
   "key": "mega-menu",
   "name": "Mega menu",
   "tasks": [
    "C2"
   ]
  },
  {
   "key": "articles",
   "name": "Adam's articles published",
   "tasks": [
    "D1",
    "D2"
   ]
  },
  {
   "key": "prominence",
   "name": "Featured savings and exclusives given prominence",
   "tasks": [
    "C3"
   ]
  },
  {
   "key": "categories",
   "name": "Two further product types, twenty each",
   "tasks": [
    "A7",
    "A11"
   ]
  },
  {
   "key": "awards",
   "name": "Awards selection strategy",
   "tasks": [
    "F1",
    "F2"
   ]
  },
  {
   "key": "email-capture",
   "name": "Email capture",
   "tasks": [
    "D3"
   ]
  },
  {
   "key": "sponsor-slots",
   "name": "Sponsor banner positions",
   "tasks": [
    "C5",
    "D4"
   ]
  },
  {
   "key": "new-collectors",
   "name": "Three new data collectors",
   "tasks": [
    "A0",
    "R1",
    "R2",
    "R3",
    "R4"
   ]
  },
  {
   "key": "merge-session",
   "name": "One page per real product: the merge session (rian's decision, 12 Sep; unpaid)",
   "tasks": [
    "M1",
    "M2",
    "M3",
    "M4",
    "M5",
    "M6",
    "M7"
   ]
  }
 ]
}
