Period: Tue 19 May 2026 · Portfolios touched: dare · dare-pipeline · Tripwires: 9 → 10 · Cycle closed: build → catch → fix → green, single session
~/bin/dare_email_health_audit.py queries DMARC + SPF + DKIM + MX for dare.co.uk; 6 checks per domain; verdict logic aggregates worst-of; HTML+MD detail report; integrated into dare_daily_hygiene.py as the 10th tripwire row; mirrored to dare-pipeline/scripts/ for GH Actions nightly run.spf.messagingengine.com Fastmail leftover. Programmatically removed via CF API. Re-run = ✅ GREEN. The four-link loop (build → observe → fix → systemize) executed inside a single session.feedback_report_granularity_inverse_to_health), the 11th tripwire addition triggers the universal-site-health-report refactor. Today is the last clean per-tripwire addition; next one consolidates.| File | Role | Lines |
|---|---|---|
~/bin/dare_email_health_audit.py | Standalone audit — queries DNS, computes verdict, emits HTML+MD report. Same shape as dare_jsonld_presence.py (the lifted template). | ~280 |
~/bin/dare_daily_hygiene.py | Added _adapter_email_health + a TRIPWIRES entry with rationale. Imports the audit, returns the standard adapter dict. | +30 edited |
~/bin/dare_dev_reports_publish.py | Added ("dare_email_health_*.html", "Email Health (DMARC/SPF/DKIM)") to REPORT_PATTERNS so catalog rows surface the new report type. | +5 edited |
~/Code/dare-pipeline/scripts/* | Mirrored all three for GH Actions cron — commit 98c186d on main. | same content |
| # | Check | Logic |
|---|---|---|
| 1 | DMARC presence | TXT exists at _dmarc.<domain> and starts with v=DMARC1 |
| 2 | DMARC policy (p=) | reject = green · quarantine = yellow · none or missing = red |
| 3 | DMARC enforcement (pct=) | 100 (or absent = 100 default) = green · 50–99 = yellow · <50 = red |
| 4 | SPF presence | TXT at apex contains v=spf1 |
| 5 | SPF cleanliness | Known-stale-provider includes flagged. Day-1 list: spf.messagingengine.com, sendgrid.net, _spf.resend.com (the three providers retired yesterday). |
| 6 | DKIM aligned with active provider | MX records pattern-matched to a provider (Google/Fastmail/Microsoft/Zoho); expected selector must resolve. |
dare_portfolio_domain_health.py as a separately parked build per project_cross_portfolio_domain_audit.md. Today's build is the dare.co.uk-specific tripwire.The cleanest demonstration of the four-link loop pinned yesterday (user_memories_as_natural_checksums → "how new checksums get minted"):
| Link | This session |
|---|---|
| Build the checksum | Email-health tripwire deployed at ~07:48 local |
| Observe a failure | First run = ⚠ YELLOW · 5 green / 1 yellow · "1 stale provider in SPF — spf.messagingengine.com (Fastmail — Dan migrated back to Google 2026-05-18)" |
| Fix the one instance | Programmatically PATCH the dare.co.uk SPF TXT record via CF API · captured restore-path snapshot first · removed include:spf.messagingengine.com · verified at both CF authoritative nameservers |
| Systemize the class | The tripwire ITSELF is the systemization — any future placeholder-provider drift in the SPF (or DMARC policy weakening, DKIM selector breakage, etc.) gets caught on the next nightly run instead of via Dan-eyeballing-a-paste |
Re-run after the SPF fix = ✅ GREEN · 6 green / 0 yellow / 0 red. Daily-hygiene rollup refreshed; catalog republished. Tomorrow's automated cron run will reflect green from the start.
Started the day with reports.dare.co.uk missing today's Tuesday 19 May section entirely. Root cause: two clocks with no handoff between them.
| Cron | Where | Runs at | What it does |
|---|---|---|---|
| 6am — remote | GH Actions in xlab-nyc/dare-pipeline | 06:00 UTC daily | Refresh dash.dare.co.uk Cloudflare analytics + run publisher to push the reports catalog |
| 7:30am — local | Dan's Mac launchd | ~07:30 after wake | Daily-hygiene tripwire generators (need filesystem access to ~/Code/dare-co-uk) |
The 6am publisher runs BEFORE the 7:30am generators create today's reports — so it publishes yesterday's content and never re-runs after the new files land. Manual republish-twice fixed today (one to surface the 9 generator outputs, one for tidiness after the email-health was added as #10).
Structural fix queued: chain the publisher to run as a post-step after dare_daily_hygiene.py completes locally, OR add a separate local launchd job for the publisher scheduled at 8:00am (after the 7:30am generators). ~5 lines of bash. Small, deliberate fix for next session.
10 reports surfaced under today's section at reports.dare.co.uk:
5 green / 4 yellow / 0 red. Healthy site state. The Email Health row going green on day 1 is the textbook "tripwire's intended outcome" — surface, fix, lock in.
| Memory | One-line |
|---|---|
feedback_report_granularity_inverse_to_health | 9 daily reports = manageable; 10+ triggers consolidation into one universal site-health report. End-state target for a perfectly-running site = 2 files (1 universal + 1 session report), not 17. Granularity responds to problem volume. |
The cross-portfolio memory pinned yesterday (project_cross_portfolio_domain_audit) was extended with the dormant-with-intent reframing — audan.co's role as Audrey's breakout-product target proves that "dormant" doesn't mean "vanity."
Dan UI-clicked through the dashboard enrollment for all 14 active CF zones. From 1 enrolled at session-start (dare.co.uk) to 14 of 14 by mid-afternoon. CF DMARC Management is free for unlimited domains (vs DmarcDkim's 3-domain cap) — same source data, unified cross-portfolio pane.
The audreyinc.com case was diagnostic: its DMARC was policy-only (v=DMARC1; p=reject; sp=reject; adkim=s; aspf=s;) with no rua= field at all — meaning DMARC reports were being generated globally and discarded for who-knows-how-long. CF enrollment added the rua channel. Critical fix for the most commercially-load-bearing domain in the portfolio.
The day's email-health tripwire (built this morning) gained a 7th row this afternoon: CF DMARC Management portfolio coverage. DNS-only check across all 14 portfolio zones — flags any zone that's slipped out of CF enrollment. Day-1 verdict: 14/14 = ✅ green. Future regressions (a zone-level DMARC edit accidentally strips the CF rua) trigger YELLOW the next morning.
The 10-tripwire ceiling holds — this was a NEW CHECK within an existing tripwire row, not a new tripwire. The granularity-inverse-to-health rule still respected; 11th tripwire remains the consolidation trigger.
The QR pattern sketched 2026-05-18 (audrey 100s-of-items scale story) shipped its v1 foundation. Three foundational decisions greenlit; v1 path executed:
codes.json — canonical registry. 4 active codes today: tk01 (Ardisam), bg01 (Billy Goat), hr01 (Honda HRX-217), jd01 (John Deere Z665)id.gf.cx/<code> per-item public stubs — calming palette, make/model/serial + found@gf.cx + Sugan PO Box, NO address leak verifiedhttps://id.gf.cx/<code> directly — error correction H (30% damage tolerance), high-res + print-ready variants~/bin/gfcx_id_stubs_generate.py, ~/bin/gfcx_qr_generate.py) — both idempotent, regenerable, read canonical codes.jsonv1 path = QRs route to public stubs directly. Owner navigates pa.gf.cx via bookmark (already works). v2 (parked) = Pages Function smart router + CF Access gate for seamless owner/stranger routing from a single QR. Build when seamless UX becomes load-bearing.
The 4-step "add a new item" pattern is now battle-tested: edit codes.json → stub generator → QR generator → wrangler deploy. Future codes follow the same recipe.
Dan nit-pick from the dashboard: the existing "View all →" link on top-countries / status-codes cards should advance to the NEXT batch (e.g., countries #5-8 → #9-12 → wraps back to #1-4) rather than expanding everything. Preserves the fixed vertical budget while still surfacing the long tail. Sharpened with a screenshot:
dash.dare.co.uk · top-countries + status-codes cards. The accent-red "View all →" link already exists; the build wires up rotation behaviour through it.
Sketch sharpened from the screenshot: the rotation pattern splits per section shape. Top-countries gets rotation through batches of countries (US/FR/SG/CA → DE/UK/IN/AU → etc.). Status codes only have 4 categories (2xx/3xx/4xx/5xx) — no batch-2 of categories — so rotation cycles each card through its internal breakdown (200/204/206/etc. within 2xx). Both share the same "View all →" affordance, different cycle logic. ~30 LOC JS + ~15 LOC CSS + ~10 LOC per renderer (Shape A only). Parked per parked_sketch_dash_view_all_rotating_batches.md.
project_cross_portfolio_domain_audit.md. Greenlit yesterday for build "after the email-health tripwire ships" — that's now done.<title> tags. Layer 2 (the publisher's extraction) works; Layer 1 per-renderer remains.| Repo | SHA | Subject |
|---|---|---|
| xlab-nyc/dare-pipeline | 98c186d | daily-hygiene: add email_health as 10th tripwire (DMARC/SPF/DKIM/MX) |
Plus one DNS API operation against dare.co.uk's Cloudflare zone (SPF TXT PATCH — remove spf.messagingengine.com) and ~5 catalog republishes across the session.
The morning closed by naming three forward-builds for "tomorrow." None of them waited. The afternoon ran through the queue end-to-end plus a fourth and fifth lift that emerged from the audit findings. What follows is the post-9am arc, surface by surface.
Dan-flagged nit-pick at mid-morning: green cards should fall to the bottom; red/amber climb to the top. Implemented as a verdict-sort with the cards reordered red-alert → amber → green → pending. Then a forward-looking refinement: when 7d / 30d / 90d windows accrue data, the same card must stay in the same position across all four windows — otherwise toggling reshuffles the dashboard. Position now locks to the 24h verdict; per-window verdict reflects only in colour + number. Card identity stays stable as the dashboard ages.
Two visual cleanups landed alongside: unit text now word-boundary truncates instead of mid-word ("pages have SEO-title issues…" not "iss"; "pages (35.4%) no body image" not "(35"), and a 180px min-height on .health-card prevents the single-card-in-a-final-row from reading as a smaller legacy element. The Email health card also picked up its missing label_to_prefix entry — it had been rendering as a non-clickable <div> all morning until Dan's eye caught the legacy markup tonight.
Greenlit at 9am, shipped via background subagent in ~8min wall-clock. Walked each of the 14 zones across DMARC posture, SPF cleanliness, DKIM provider alignment, JSON-LD coverage, sitemap, og:image, robots, llms.txt, Pages mapping, and security_level. Output: a 2,570-word markdown report at devreports.dare.co.uk/dare_cross_portfolio_audit_2026-05-19.
The matrix shape revealed a portfolio that's bimodal: 5 active brand surfaces vs 9 asleep ones (5 parked-redirects to gf.cx, 4 effectively dark). The asleep ones were dragging down the visual story without paying any commercial freight. Concentration of cleanup wins followed cleanly from the matrix.
The trigger fired today: a real guest readied to book a scarf-tying event. Inspection of bookings.audreyinc.com revealed a critical gap — the form captured locally but never submitted anywhere. The Phase A page was stashing the booking in window.__lastBooking, a JS variable that dies when the tab closes. Customer saw a confirmation page; audrey would have received nothing.
Closed the gap by cloning the dare-contact Worker pattern into a fresh ~/Code/audrey-bookings repo (now at xlab-nyc/audrey-bookings). Pages Function at /api/book validates the POST, fires two Resend emails — audrey gets the booking details with reply_to = customer, customer gets an audrey-voiced confirmation. from: is noreply@dare.co.uk (verified Resend domain on the dare account); swap to noreply@audreyinc.com when that domain gets verified.
fetch("/api/book")s a real backend.Beta apex had zero JSON-LD blocks before this. The agent-edge worker was serving /llms.txt + /agent-config.json correctly, but inline structured data — what Google and most agentic crawlers actually read — was missing. Closed with a single @graph block carrying five nodes: LocalBusiness/PetStore + three Service nodes (long-stay, short-stay, pick-up & drop-off) + a WebSite publisher reference, all @id-anchored to the canonical https://dogwood.house/#business.
Content derives from the canonical agent-config.json (single source of truth in ~/Code/agent-edge/src/domains/dogwood-house.js). og:image captured at 1200×630 via dare_og_capture.py and uploaded to R2 at images.dare.co.uk/og/dogwood-house.png. og:title, og:description, twitter:card meta added alongside.
@graph now live.The cross-portfolio audit had surfaced four cleanups; Dan greenlit all four; a new token minted with Zone:DNS:Edit + Zone:Zone Settings:Edit across all account zones unblocked the writes (stored at op://Code Shared/Cloudflare portfolio DNS edit/password). Execution sequence:
"v=spf1 include:spf.messagingengine.com include:_spf.google.com ~all" → "v=spf1 include:_spf.google.com ~all" (Fastmail leftover from the migration audrey did long before today's flywheel work). Audrey email-health verdict flipped from yellow to green (7/7 checks) on the next re-run. Same recipe as the dare.co.uk SPF cleanup from yesterday — the recipe scales.23.21.x range). Both domains now resolve to nothing at the apex (curl returns 000), which is far safer than pointing at a stranger's potential future EC2 tenant.199.60.103.x). The 403 the audit saw was Squarespace's edge; the 403 we get now is Cloudflare's default-when-no-record — meaning the Squarespace remnant is properly severed. MX records (Google Workspace) left intact — possibly still receiving mail.www proxies through CF anycast (172.67.x). No action needed.Dan dropped a GSC Coverage CSV bundle from search.google.com (4 files: Chart, Critical issues, Non-critical issues, Metadata). Until GSC API OAuth lands — parked for tomorrow at ~30 min — manual exports are the only source for per-reason indexing breakdowns. Captured the data with a convention: ~/Downloads/gsc_exports/<site>/<YYYY-MM-DD>/, sibling drop directory the future ingestion script walks.
Built ~/bin/dare_gsc_ingest.py as the walker. Reads the four CSVs per date, normalises into JSONL history at ~/Downloads/gsc_history/<site>.jsonl, computes delta vs prior snapshot, renders Markdown report at ~/Downloads/dare_gsc_<today>.md. Replace-on-same-date semantics keep history clean. First snapshot captured: 1,221 not-indexed across 10 reasons (448 crawled-not-indexed, 409 noindex, 276 discovered-not-indexed leading). Delta view unlocks on the next export.
feedback_link_to_authoritative_analysis.md — Dan-confirmed rule that every clickable element on a dashboard/health surface must lead to a curated report with interpretation + recommendation. "If we don't own an authoritative page on the topic, don't make the surface clickable yet." The test: "If I click this, do I land on a page that thinks for me, or just shows more raw numbers?" If thinking: link. If raw: rebuild the destination, OR strip the link.feedback_report_structure_so_what.md — the visual treatment of the inline "So what:" callout box (pink-tinted background, maroon left-border accent) is now codified, along with the audrey-ROI-filter framing and the compounding-return / cheapest-insurance close. Pattern lifts cleanly across dare / dogwood / audrey / client work.reference_gsc_manual_csv_exports.md — convention for where GSC exports live, what each of the four CSVs contains, and the future-pull plan once OAuth wires up. Solves the recurring "Dan tidied the file off Desktop before I could process it" problem with a save-immediately discipline.| Surface | State this morning | State now |
|---|---|---|
| bookings.audreyinc.com | Form captured locally only; audrey received nothing | POSTs to /api/book → audrey + customer both emailed via Resend |
| beta.dogwood.house | 0 JSON-LD blocks, no og:image | 5-node @graph (LocalBusiness/PetStore + Services + WebSite), og:image at 1200×630 |
| dash.dare.co.uk | Cards unsorted; email-health rendered as non-clickable <div>; mid-word truncation visible on desktop | Sorted red→amber→green→pending, position-locked to 24h, all 9 cards clickable to authoritative reports, word-bounded units, height parity |
| audreyinc.com (email auth) | SPF carrying stale Fastmail include; email-health amber | SPF clean (Google-only); email-health green |
audan.co / brooklynbrit.com / dogwoodhouse.org | Resolving to dead AWS / Squarespace IPs (phishing risk) | Records nuked; safe NXDOMAIN-like state |
| Repo | SHA | Subject |
|---|---|---|
| xlab-nyc/dare-pipeline | 98c186d | daily-hygiene: add email_health as 10th tripwire (DMARC/SPF/DKIM/MX) (morning) |
| xlab-co/toolkit | 1b0825c | og:image rollout: dash + health.* + placeholder-leak tripwire |
| xlab-co/toolkit | c0cad4d | Daily hygiene #10: Email Health tripwire (DMARC/SPF/DKIM) |
| xlab-co/toolkit | 4bcf55f | gfcx QR system tooling: stub + QR + routes generators |
| xlab-nyc/home-projects | 6d5650e | qr v2 smart router: pa.gf.cx/r/[code] Pages Function + regenerate QRs |
| xlab-nyc/dare-co-uk | d1444115 | /about: shift "dashboard" link from dashboard.dare.co.uk to dash.dare.co.uk |
| xlab-nyc/dare-co-uk | 823e6894 | CLAUDE.md: tighten /albums/cedars-of-lebanon/ note + add CF Image Transformations follow-up |
| xlab-co/toolkit | 7b45ec7 | dash health-grid: sort red/amber→green→pending, lock positions to 24h verdict |
| xlab-co/toolkit | 56fbc4a | dash health-card unit text: word-boundary truncation (kill mid-word cuts) |
| xlab-co/toolkit | 27e8ce5 | dash health-card: min-height 180px for single-item-row stretch parity |
| xlab-co/toolkit | d95c2eb | dev-reports catalog: Live surfaces row uses dash.dare.co.uk |
| xlab-nyc/dare-pipeline | e6f375e | dev-reports catalog: Live surfaces row uses dash.dare.co.uk (sibling mirror) |
| xlab-co/toolkit | 90cf912 | dare_gsc_ingest.py — ingest manual GSC Coverage CSV exports → JSONL + delta report |
| xlab-nyc/audrey-bookings | fd45762 | audrey-bookings — Phase B lite: wire submit → /api/book → Resend (NEW repo) |
| xlab-nyc/dogwood-house | 253250c | beta.dogwood.house: ship LocalBusiness/PetStore JSON-LD + og:image (branch phase-1/secrets-foundation) |
| xlab-nyc/dare-pipeline | 354108d | dare_cf_analytics.py: sync from ~/bin canonical (sort + lock-to-24h + min-height + email-health click-through) |
Plus 4 DNS API operations (1 SPF PATCH on audreyinc.com + 5 A-record deletes across audan.co, brooklynbrit.com, dogwoodhouse.org), 1 new repo created on GitHub (xlab-nyc/audrey-bookings), 1 new CF API token minted + 1Password-stored (Cloudflare portfolio DNS edit), 3 og:image captures into R2, and ~8 dev-reports catalog republishes across the session.
The morning's framing held: yesterday's substrate compounded into today's load-bearing tripwire. The afternoon then went further than expected. The forward-builds queued in the morning closing — cross-portfolio audit, source-renderer titles, cron-ordering — all became "in-flight" or "shipped" by EOD instead of "tomorrow." What enabled this was a deliberate token mint and a willingness to actually execute the audit findings rather than queue them.
Two specific patterns deserve carrying forward. First, the "lock-to-24h" decision on the dash health-grid — building for the absent future-data so today's deploy doesn't ship a latent bug. Second, the bookings.audreyinc.com gap — discovered only because the trigger fired (a real guest). The lesson: Phase-A scaffolding that "looks done" needs a real-user-imminent forcing function to expose what isn't connected. Triggers reveal architecture.
Tomorrow opens with a much shorter list: GSC API OAuth (~30 min, unblocks programmatic per-reason indexing), then the production dogwood.house JSON-LD mirror (separate from beta — same shape but the apex index.html still has 0 blocks), then the tagline revert on dare.co.uk when Dan decides what to revert to. The audrey commerce flywheel is unblocked at the architecture layer; the next trigger is the real guest's actual booking.