{
 "updated_at": "2026-09-19T01:44+00:00",
 "updated_by": "Stream K12",
 "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",
     "status": "todo",
     "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",
     "status": "todo",
     "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": "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 [undo test on a minted line; fingerprint test x9; check green]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K12.2",
     "wave": "buf",
     "title": "collected_name as an evidence source for fragment-less listings [load test both sides; check green]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K12.3",
     "wave": "buf",
     "title": "The precedent register, and the file that names its precedent [8 tests; migration rehearsed up/down/up on dfp_k12; check green]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K12.4",
     "wave": "buf",
     "title": "Attention becomes a control: floors, NOTE_REQUIRED, bulk by precedent group, the round cap, unseen words [3 control tests; check green]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K12.5",
     "wave": "buf",
     "title": "The pass packet, the cross-divide generator, the settled stamp, pass status [4 packet tests; check green]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K12.6",
     "wave": "buf",
     "title": "The review itself: survey, precedent groups, the two-decision fold card, the merge card [3 card tests; tsc clean; check green]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K12.7",
     "wave": "buf",
     "title": "The documents and the skill: REVIEW-PASS.md, REVIEW-PROCESS v6, the principles, the thin skill [spec + v6 + 5 spec tests; check green]",
     "status": "done",
     "paid": false
    },
    {
     "id": "K12.8",
     "wave": "buf",
     "title": "Rehearsal end to end on a copy, hand-back [rehearsal on dfp_k12: every refusal, 3 merges, 44 decisions undone to baseline, 0 drift; check green]",
     "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"
   ]
  }
 ]
}
