⚠️ Apr 27 10:25 CT correction: Earlier handoff referenced "post 999" for the native-save smoke gate. Producer-as-verifier check (Apr 27 09:55 CT) found post 999 is registered as the auditor's fake-fixture decoy in auditor_tools.py:46 KNOWN_POST_IDS. Smoke fixture corrected to canonical post 258 (M1 baseline 146 elements). Smoke ran 10/10 PASS in 5.82s on the corrected fixture. See MH bsp-apr27-smoke-gate-built-and-fixtures-corrected.
✅ Apr 27 EOD update: Mon recovery shipped clean. 22/22 staging pages provisioned + content-cloned. All native-save callers consolidated on dispatcher_safety.native_save_with_external_verify (defense-in-depth · §7 draft-meta failure mode caught experimentally + codified). Location mining 14/14 · Service mining 7/7 (service_page_mining.py Apr 27 ship). Bricks Codebase Doc + Figma Codebase Doc both TIER 0. Tue AM 09:00 CT dispatch: GREEN. See MH bsp-apr27-end-of-day-final-state for full receipts.
This document is NOT a checklist. It is the live execution record of cutover_rehearsal.py --preflight with actual data, actual PASS/FAIL per gate, actual receipts. Re-running the script overwrites this report. Robert reviews real numbers, not theoretical instructions. Linked from the Battle Plan and Audrey's Library, indexed by Zeus RAG.
📐 Source: BSP_Apr18_Claude_Design_For_Audrey.html
| Slug | /sewer-camera-inspection/ |
| Vibe | Diagnostic · explainer-style · trust-builder |
| Why now | FREE Camera nav CTA needs the SEO variant at /services/sewer/... |
| Lift | 2-3 hrs · ship May 8-15 |
| Brief | Robert sends Mon May 5 AM · Stephanie format · Slack DM |
Per Menu Page Strategy §9: project-by-project · one template + one hero per week max.
Every playbook this Bible plugs into. Open in tabs side-by-side for execution. Re-fetch any time uncertain.
| Playbook | Why this week | Owner |
|---|---|---|
| 🧱 BSP_Bricks_Codebase_Documentation | The framework Claude must plug into AT ALL TIMES — populate scripts, native-save endpoint, walker v3, element-tree shape | Robert |
| 📘 BSP_Menu_Page_Strategy_Playbook | Slug taxonomy §6 (/services/{cat}/{slug}/) · §7 Phase 2 cadence · §9 Audrey workflow rules · §2 Audrey-on-top rule | Robert |
| 🎨 Apr18_Claude_Design_For_Audrey | Audrey design specs locked Apr 18 · Inter, doodle, wave hero, color tokens | Audrey |
| 🎨 audrey_creative_library | Audrey's living library · Week 2 hero target | Audrey |
| ⛏️ Service_Page_Copy_Mining_System | Apr 21 mining canon — Component 9 routes service script here | Robert |
| 📊 BSP_Bricks_Staging_Audit_Apr27 | Real-data audit of 15 staging pages · gap surfaces | Robert |
| 📚 BSP_Content_Database_Inventory | Mining systems + 8 data sources + per-page content map | Robert |
| ⚔️ BSP_Website_Platform_Battle_Plan | Overall battle plan · v4 callout cross-link | Robert |
| 📡 BSP_Ads_Attribution_Breakthrough | GCLID + Monday Pipeline · post-cutover RSA URL bulk update reference | Robert |
| 📜 BSP_Master_Session_History | Paper trail · query before any decision · log every action via nexus_html_logger.py | Robert |
| Day | 🤖 Robert (terminal + Bricks UI) | 🎨 Audrey | Gate / Output |
|---|---|---|---|
| Sun PM Apr 27 | Write mining_brief_schema.json · final Bible re-read · stop-loss budget set $80 council | — | Schema file present + smoke-validated |
| Mon AM Apr 27 | 08:00 dual council dispatch (A: service · B: location) via council v2 · 11:00 native-save smoke 10/10 · 13:00 both v1 scripts ready | — | 2 scripts at v1 + smoke 10/10 |
| Mon PM | CITY_ZIPS extended for 6 TIER B · 17:00 sewer-repair trial run + Playwright independent verify · GBP location_id fix · pause /lp/drain-cleaning-special/ ads · Rank Math rules staged DISABLED | — | 1 service page populated end-to-end + verified |
| Tue AM Apr 29 | Full parallel populate: 7 service pages + 15 location pages | (optional) review Template A + B mockups · Slack 👍 | 22/22 pages populated DRAFT |
| Tue PM | Build Kalen review dashboard /preview/may1/ · ~5 hrs · Slack DM thread per page · auto-screenshot desktop+mobile | — | Dashboard live · Kalen onboarded |
| Wed AM Apr 30 | Build 6 informational pages: About · Contact · FAQ · Financing · Reviews · Coupons-deferred-redirect-only | — | 5 informational pages DRAFT (Coupons stays redirect-only) |
| Wed PM | Kalen batch 2 review pass · iterate redlines · publish DRAFT → READY for in-place 30 pages | — | ≥ 25/30 READY |
| Thu AM May 1 (early) | Final QA · Playwright sweeps · curl matrix preflight · build nested nav | (optional) final visual sweep on dashboard | 30/30 APPROVED gate · cutover_rehearsal.py PASS |
| Thu PM | Kalen final approval · runbook re-read · backup snapshot taken | — | Kalen sign-off received |
| Fri May 2 08:00 CT | CUTOVER: backup → slug rename in Bricks UI (9 pages) → Rank Math rules ENABLE → theme switch Oxygen→Bricks → curl matrix verify → Big Sale tracking verify · monitor 90 min | — | Phase 1 LIVE · 30 pages · Big Sale tracking intact |
Daily standup at 09:00 CT: 5-min Slack post — yesterday's gate hit/missed · today's 1-line focus · blockers (PROBLEM/IMPACT/SOLUTION/DATA/NEED format).
wp plugin list --status=active | grep redirect. Per v3 §2: Rank Math Redirections category bulk-action (B2 pivot) is the rollback gate. Plugin must be confirmed active.Gate 6 PIVOTED: earlier run flagged Redirection plugin REST 404 on staging — surfaced a real architectural conflict. Robert chose Path B2: use Rank Math Redirections module (already on prod) instead of installing a 2nd redirect manager. Result: Gate 6 now PASS, zero plugin install, zero conflict, single source of truth.
Why this is bulletproof default:
callbrightside.com verified via rank-math-schema JSON-LD signature.wp_options), not theme files. Cutover theme switch preserves them.Rollback flow under B2:
1. Theme switch Bricks → Oxygen (1-click, WP admin → Appearance → Themes)
2. Rank Math → Redirections → Categories → "May 1 Cutover" → bulk action: Disable all
3. (Optional) Unpublish 9 new Bricks pages
→ Old URLs serve Oxygen content as before. 30-min rollback budget intact.
| Old URL (Oxygen) | New URL (Bricks) | Category | RSAs | Notes |
|---|---|---|---|---|
/sewer-repair/ | /services/sewer/sewer-repair/ | Sewer | 49 | THE converter (17.2% conv rate) |
/trenchless-sewer-repair/ | /services/sewer/trenchless-sewer-repair/ | Sewer | — | 73K/mo organic |
/sewer-cleaning/ | /services/sewer/sewer-cleaning/ | Sewer | 12 | — |
/sewer-line-replacement/ | Phase 2 NEW (decision 1b) | — | Page does not exist on prod | |
/drain-cleaning/ | /services/drains/drain-cleaning/ | Drains | 24 | 2nd rule for /plumbing-services/drain-cleaning/ (decision 3a) |
/water-heaters-installation/ | /services/water-heaters/water-heater-repair/ | WH | 25 | Decision 4a: both old slugs collapse |
/water-heater-repair/ | /services/water-heaters/water-heater-repair/ | WH | — | Decision 4a (2nd rule) |
/sump-pumps/ | /services/emergency/sump-pump-emergency/ | Emergency | 8 | — |
/leak-repair/ | /services/other/leak-repair/ | Other | 10 | — |
/gas-line-repair/ | Phase 2 (decision 2b) | — | Page does not exist on prod | |
Sewer Camera Inspection special case: Bricks page 8 already lives at flat slug /sewer-camera-inspection/. On May 1 cutover, that flat-slug Bricks page becomes prod content (matches existing Google Ads final URLs). Week 2 post-launch: build SEO variant at /services/sewer/sewer-camera-inspection/ + add 301 flat → nested.
Bundle: 9 RSAs need URL update + 3 RSAs need review-count sync (392+/384+ → "396+") = 11 unique RSAs touched via single GAQL mutate after cutover stabilizes Friday afternoon.
🚫 Do NOT touch paused legacy campaigns (Sewer Repair Kansas City, Sewer, Sewer #2, Russ-era). Only "BSP | Search | … | Mar 2026" campaigns are in scope per MH rule.
[ ] 1. Production backup via Hostinger (full site + DB) — must complete first
[ ] 2. Verify backup integrity (download, sample restore-test on staging)
[ ] 3. Build the 9 new Bricks pages at /services/{category}/{slug}/ on PRODUCTION (DRAFT)
[ ] 4. Build "May 1 Cutover" Category in Rank Math Redirections (11 rules), all DISABLED — NO Redirection-plugin install (B2)
[ ] 5. Build new nested menu (5 parents + 9 children + FREE Camera CTA), nav as-is publicly
[ ] 6. Publish the 9 new Bricks pages
[ ] 7. Run preflight curl: confirm new URLs return 200, old URLs still serve Oxygen
[ ] 8. Bulk-enable "May 1 Cutover" Rank Math Category — all 11 rules go active
[ ] 9. Theme switch Oxygen → Bricks
[ ] 10. Delete old flat menu items (including septic) — nav now shows new nested structure
[ ] 11. Run full redirect verification curl matrix on production
[ ] 12. Run Google Rich Results Test on 3 sample pages
[ ] 13. Submit updated sitemap to Google Search Console
[ ] 14. Bundled GAQL mutate: 9 RSA URL updates + 3 review-count syncs (→"396+")
[ ] 15. Update GMB profile URLs
[ ] 16. Monitor 4 hours: error logs, 404 spikes, ad disapprovals, ranking checks
⏱️ Rollback budget: 30 min · 3-step procedure — theme switch back, redirect group OFF, unpublish new pages. Old menu restored from backup if needed.
v3 scoped only the 9 service pages. v4 rebuilds with the canonical Apr 18 Audrey scope: 9 service + 15 location + 6 informational = 30 pages Phase 1. Sources verified via the Apr 27 staging audit + content database inventory.
| Phase | Pages | Source | Status |
|---|---|---|---|
| Phase 1 (May 1) | 9 service + 15 location + 6 informational | Apr 18 design + per-page playbooks + mining systems | Sprint runs Mon-Thu |
| Phase 2 (post-launch) | 9 NEW service per playbook §4 + Coupons + Careers + blog rebuild | 1 page/week per playbook §7 cadence | Decision-by-decision |
| Phase 3 (long-tail) | TIER B locations + blog content master rebuild | Anything not in Phase 1 or 2 | No timeline. Defer. |
Cross-links to ground-truth audits:
DECIDED 2026-04-26 by Robert: Approach 2. Bricks staging keeps FLAT slugs through rehearsal. At cutover (Fri May 1 8AM CT), all three changes — slug rename to /services/{category}/{slug}/ per Menu Page Strategy Playbook §6 + Rank Math 301s enabled + theme switch Oxygen→Bricks — fire as a single bundled cutover. Rollback path: Rank Math category bulk-disable + theme revert.
| Approach | What | Pro | Con |
|---|---|---|---|
| 1 (RECOMMENDED) | Restructure staging this week — rename all flat-slug Bricks staging pages to nested URLs | Rehearsal validates production-target URL state | Extra restructure work; multiple slug changes during testing |
| 2 | Slug rename at cutover — keep staging flat, do rename + 301s + theme switch all at once | Less staging work this week | Rehearsal validates flat-slug world; real cutover is nested-slug world; Friday surprise risk |
| 3 | Production-direct — skip staging, do directly on production | Minimal staging work | Same risk profile as Approach 2 |
Locked rationale: Approach 2 saves ~4.5 hrs of Mon-Tue restructure work and reclaims that capacity for the last-mile push layer + 30-page Phase 1 build. Friday-morning slug-rename is a single Bricks UI sweep + Rank Math bulk-enable; rehearsal validates the redirect map + theme switch separately. Three locked rules at cutover:
/services/{category}/{slug}/ in Bricks UI as cutover step 7Two flat-slug exceptions (locked Apr 26):
/sewer-camera-inspection/ stays FLAT on May 1 (Bricks page 8, matches existing Google Ads final URLs — zero RSA updates needed). Phase 2 Week 2: build SEO-optimized nested variant at /services/sewer/sewer-camera-inspection/ + 301 flat → nested per playbook §7./drain-cleaning/ flat slug 301s to /services/drains/drain-cleaning/; /plumbing-services/drain-cleaning/ (Oxygen canonical per Apr 26 audit) gets a 2nd 301 to the same nested target. Both inbound URL forms converge on canonical nested page (Decision 3a).Water Heaters consolidation (Decision 4a): /water-heaters-installation/ AND /water-heater-repair/ both 301 to /services/water-heaters/water-heater-repair/.
Mining systems already exist — DO NOT rebuild. See Content Database Inventory for full details.
| Layer | Status | Path |
|---|---|---|
| Service-page mining | ✅ shipped Apr 21 | /opt/nexus/titan/service_page_reviews_apply.py + Service Page Copy Mining System playbook |
| Location-page mining | ✅ shipped Apr 23 | /opt/nexus/titan/location_page_mining.py · 8 data sources · 9 cities (extend CITY_ZIPS for 6 more) |
| Bricks element-tree write primitive | ✅ shipped Apr 22 | POST /bsp/v3/bricks/native-save |
| Last-mile push to Bricks meta | ❌ NOT YET BUILT | nexus_populate_service_pages.py + nexus_populate_location_pages.py — wraps mining → native-save |
The build target (last-mile layer):
nexus_populate_service_pages.py • reads service-page mining brief JSON • selects Template A (emergency-style) or B (diagnostic-style) per page • generates content blocks per section • writes via POST /bsp/v3/bricks/native-save to draft post • idempotent · producer-as-verifier · ~6 hrs build nexus_populate_location_pages.py • reads location-page mining brief JSON per city • merges into location template (proven on OP 258, 146 elements) • writes via native-save · 1 invocation per city · ~6 hrs build
Reference proof: OP 258 page (146 elements, 3 snippets) was built MANUALLY using location mining brief — proving brief→Bricks works end-to-end. The last-mile layer automates that step.
Template A/B routing baked into populate_service_pages.py (Decision 5):
| Page | Template | Why |
|---|---|---|
| Sewer Repair | A · emergency | 17.2% conv rate proves emergency frame |
| Sewer Cleaning | A · emergency | backed-up sewer = urgent |
| Drain Cleaning | A · emergency | mid-clog urgency, 24 RSAs |
| Sump Pump Emergency | A · emergency | active flood |
| Leak Repair | A · emergency | active leak — stop-the-water mode |
| Trenchless Sewer Repair | B · diagnostic | 73K/mo organic = research traffic comparing trenchless vs dig |
| Water Heater Repair | B · diagnostic | tank/tankless/repair-vs-replace decision tree |
Excluded from populate (handled separately): Sewer Camera Inspection (Bricks page 8 already built manually) · Sewer Line Replacement (Phase 2 NEW) · Gas Line Repair (Phase 2).
| Day | Focus | Hours |
|---|---|---|
| Mon Apr 27 | PARALLEL council dispatch 08:00: populate_service_pages.py (Dispatch A) + populate_location_pages.py (Dispatch B) via council v2 (Strategist+Critic+Researcher+Auditor) · 11:00 native-save smoke test (10 writes) · 13:00 both scripts v1 + CITY_ZIPS extended for 6 TIER B · 17:00 trial run sewer-repair + Playwright verify · GBP location_id fix · pause /lp/drain-cleaning-special/ ads | ~6 hrs critical path |
| Tue Apr 28 | Build populate_location_pages.py · run service-page mining for 9 drafts · Template A/B decision per page · Kalen review batch 1 (service pages) | ~10 hrs |
| Wed Apr 29 | Run location-page mining for 14 cities · publish 9 service pages on prod (DRAFT) · build About + Contact + FAQ + Financing + Reviews · stage Rank Math redirect rules (DISABLED) · Kalen review batch 2 | ~10 hrs |
| Thu Apr 30 (early) | Final QA · Playwright sweeps · Kalen final approval · build nested nav · curl matrix preflight | ~6 hrs |
| Fri May 1 8AM CT | Cutover — backup, publish, redirects-on, theme-switch, monitor | ~4 hrs |
Total Mon-Thu work: ~36 hrs. Last-mile layer build = ~12 hrs. Mining + content placement = ~10 hrs (service pages) + 7 hrs (location pages). Informational pages = 6 hrs. Slug restructure = 4.5 hrs. Kalen review = ~4 hrs across batches. Slack = ~5 hrs.
Build target: /opt/nexus/nexus/scripts/bsp_kalen_review_dashboard.py · FastAPI · ~5 hrs Tue. Routes:
GET /preview/may1/ — iframe grid of all 30 pages, status badges, auto-refresh 60s, mobile responsivePOST /preview/approve/{slug} — toggles status to APPROVED · green borderPOST /preview/redline/{slug} — captures Playwright screenshot (desktop 1440px + mobile 390px), spawns Slack DM thread (Robert) tagged by slug, posts URL + screenshots + textbox contentGET /preview/gate — returns 30/30 approval count (read by cutover_rehearsal.py as gate 19)Slack DM mode: when Kalen is away from desk, every page-update auto-posts to a per-slug DM thread (Robert). Kalen's screenshot replies in thread → dashboard pulls thread → red dot on tile. SLACK_BOT_TOKEN reused, NOT #lab (Apr 26 false-positive incident).
Approval gate: cutover_rehearsal.py adds gate 19 — fails closed unless 30/30 APPROVED at Thu 17:00 CT preflight.
Council loads protocol_gates.md (curated 2026-04-25 from §1, §7.6, §11, §39) into every agent prompt. Strategist + Critic + Researcher + Auditor all see "CODEBASE PLUG-IN — MANDATORY" instructions to cite specific section IDs. Auditor flags any generic citation as HIGH severity.
Append §6 (page 8 schema · 133 elements) + §13 (Apr 21 Canonical Build SOP — read-first) + §15 (Service Page Build System 4-zone pipeline) + per-page snippet bandaid pattern (CSS Mirror · FAQ · Image Tweaks). Adds freshness gate: cron compares sha256 of doc vs cached.
| # | Blindspot | Mitigation |
|---|---|---|
| A1 | Council reads STATIC protocol_gates.md, not LIVE Bricks Codebase Doc — drifts as doc updates | Sun PM cron: fetch doc, compare sha256, alert on drift; freshness gate in council pre-flight |
| A2 | protocol_gates.md curated only §1/§7.6/§11/§39 — missing §6/§13/§15 critical for populate scripts | Append "May 1 Cutover Addendum" with §6/§13/§15 (this patch ships) |
| A3 | §13 Apr 21 protocol lock not enforced: hand-authored Bricks JSON → sanitizer rejects → 404 | Hard rule in addendum: NEVER hand-author. Use native-save with cloned page-8 trees ONLY |
| A4 | §15 4-zone pipeline (A prep · B clone+swap · C verify · D promote) not loaded — risk dispatch skips zones | Auditor checklist: dispatch must explicitly map each step to A/B/C/D zones |
| A5 | §6 page-8 schema (133 elements) not loaded — risk dispatcher generates wrong element-tree shape | Addendum loads schema reference; clone-from-page-8 enforced |
| A6 | §18 prior snippet conflict audit not loaded — dispatcher could repeat past mistakes | Add to addendum: §18 + §22 (page 157 cycle DO NOT deactivate without extraction) |
| A7 | §31/§32 gap+data audits not loaded — repeats existing gaps | Cron-refresh §31/§32 to /opt/nexus/nexus/scripts/output/bricks_codebase_canon.json weekly |
| # | Blindspot | Mitigation |
|---|---|---|
| B1 | Shared dep: POST /bsp/v3/bricks/native-save (single point of failure) | Mon 11:00 smoke test 10/10 writes against staging post 258 (canonical, baseline 146 elements verified Apr 27), rollback after |
| B2 | Council token budget doubles when running parallel dispatches | Cap $40/dispatch · stop-loss $80 total · dashboard alert at 80% spend |
| B3 | Critic ↔ Researcher disagreement deadlock | Strategist tiebreaks · escalate to Robert if score < 85 after 2 rounds |
| B4 | Auditor false-pass (Gemini-Flash rubber-stamp risk) | Independent Playwright check, NOT script self-report (Rule 1) |
| B5 | Source-precedence drift between Apr 21 vs Apr 23 mining canons | Component 9 hard-routes: service→Apr 21, location→Apr 23, schema→Bricks Codebase Doc |
| B6 | Conversion-hypothesis floor not enforced for service script | Component 10 requires Sewer Repair 17.2% baseline cite for service dispatch |
| B7 | Producer-as-verifier collapse (Apr 19 burn pattern) | Auditor reads independently via Playwright DOM probe, never trusts script stdout |
| B8 | Mining brief schema file missing Mon 08:00 → dispatch fails | Pre-req task Sun PM: write mining_brief_schema.json + smoke validate |
| B9 | Idempotency under crash (script crashes mid-run, leaves partial state) | Native-save upsert-by-slug · scripts re-runnable, partial-state-safe |
| B10 | Cache-purge timing — verification reads stale cache | LS + CF purge after every write, BEFORE any verification read (Apr 22 rule) |
| # | Blindspot | Mitigation |
|---|---|---|
| C1 | Bricks sanitizer rejects unverified element shapes (§13 incident) | Clone-from-page-8 only. Reject any generated JSON not derived from a known-good tree |
| C2 | Code Snippets PUT endpoint silent-fails at priority 32767 (§28.1) | Use DELETE+POST pattern, NEVER PUT for snippets |
| C3 | Header 105 + Footer 106 are GLOBAL templates — page-gated CSS bleeds (§14 Apr 21 footer v7 incident) | BSP Footer Global #68 unscopes; per-page CSS Mirror re-scopes by body.page-id-N |
| C4 | Page 157 cycle snippets — DO NOT deactivate without extraction (§22) | Pre-flight cycle-extraction tool; council Auditor flags any deactivate-without-extract dispatch |
| C5 | WP Media asset IDs (§6c inventory) — populate scripts must check before writing image refs | Pull §6c snapshot Sun PM; populate script reads asset IDs from snapshot, fails fast if missing |
| C6 | read-child stale cache (§28.1 Apr 24 truncation recovery) | Always /bsp/v2/cache/purge BEFORE /bsp/v2/theme/read-child |
| # | Blindspot | Mitigation |
|---|---|---|
| D1 | Bricks UI session timeout (~30 min) — script can lose auth mid-run | Native-save uses REST not UI session; refresh token via WP cookie helper Mon 07:55 |
| D2 | Bricks builder lock — concurrent writes "another user editing" | Serialize per-post writes; pre-flight check Bricks lock state via REST |
| D3 | LiteSpeed cache + Cloudflare APO — TWO purge layers, must hit both | cf_purge.py + LS purge on every write; both must return success:true |
| D4 | Hostinger Apr 14 throttle on rapid REST writes — 10/min ceiling | Throttle native-save to ≤6/min; backoff 10s between writes |
| D5 | WP REST nonce expiry on long parallel runs | Re-auth every 50 writes; council script catches 403 and re-issues nonce |
| # | Blindspot | Mitigation |
|---|---|---|
| E1 | Kalen review dashboard /preview/may1/ may need Cloudflare bypass to render iframes | Add CF Access bypass rule for /preview/* before Tue dashboard ship |
| E2 | Audrey access to dashboard — login or Slack-only? | Slack-only for Audrey (per her hard rules: no Bricks UI, no Confluence). Robert relays if needed |
| E3 | Stephanie format on dispatch errors — generic stack traces fail SOP | Wrap errors in P/I/S/D/N before posting to Slack #lab |
# /opt/nexus/nexus/scripts/bricks_codebase_freshness.py
# Sun PM cron + council pre-flight
import hashlib, json, requests
URL = 'https://morpheus.callbrightside.com/documents/BSP_Bricks_Codebase_Documentation.html'
CACHE = '/opt/nexus/nexus/scripts/output/bricks_codebase_canon.json'
live = requests.get(URL, timeout=20).text
live_sha = hashlib.sha256(live.encode()).hexdigest()
cached = json.load(open(CACHE)) if os.path.exists(CACHE) else {}
if cached.get('sha256') != live_sha:
# DRIFT DETECTED — re-extract §6/§13/§15 + refresh protocol_gates addendum
extract_and_refresh(live)
json.dump({'sha256': live_sha, 'last_check': now()}, open(CACHE, 'w'))
alert_slack('Bricks Codebase Doc drift — protocol_gates refreshed')
| # | Gap | Status |
|---|---|---|
| F1 | protocol_gates.md = 15,165 B but council_runtime.py:651 truncates at 14,000 → §6 page-8 schema + freshness gate spec DROPPED | ✅ FIXED: raised to 20,000 |
| F2 | Component 9 (source_precedence.py) has no tier for BSP_Bricks_Codebase_Documentation.html — only ranks MH chunks by date | ✅ FIXED: added BRICKS_DOC_AUTHORITY_SOURCE constant + _authority_tier field. Bricks Codebase Doc chunks now pin to TIER 0 above date-based ranking. Smoke test passes. |
| F3 | Auditor empirical-grep (auditor_tools.py:150) — scope unclear; may not grep against Bricks Codebase Doc as evidence corpus | ✅ FIXED: added BRICKS_DOC_PATH constant + _load_bricks_doc() mtime-cached lazy loader + verify_bricks_section_citation(). audit_dispatch() now extracts every §N citation in dispatch and FLAGs hallucinated sections. |
| F4 | Freshness gate was pseudocode in addendum — not actually shipped as runnable cron / pre-flight script | ✅ FIXED: /opt/nexus/nexus/scripts/bricks_codebase_freshness.py shipped (cron-ready · Slack drift alert · cache snapshot) |
| F5 | ledger_snippet truncates at 6,000 chars — recent state changes Mon AM may not load if ledger churn is high Sun PM | ⚠️ ACCEPT — raise on demand if Mon dispatch shows missing recent state |
| # | Blindspot | Mitigation |
|---|---|---|
| G1 | Internal links INSIDE body content of Bricks pages still point at flat slugs (/sewer-repair/) instead of nested (/services/sewer/sewer-repair/) | populate scripts run sed-style internal-link rewrite Pass D after content swap; Auditor greps for old-slug instances |
| G2 | Yoast meta keys (titles · descriptions) on Oxygen don't auto-migrate to Rank Math meta — different post_meta keys | Pre-cutover Wed PM: SQL migrate _yoast_wpseo_title → rank_math_title (similar for desc, og:image, canonical) for all 30 pages |
| G3 | Schema.org markup — Oxygen's LocalBusiness schema vs Bricks default — coverage gap on service pages | Wed PM: deploy Rank Math LocalBusiness schema globally + Service schema per service page; verify via Google Rich Results Test on each |
| G4 | Sitemap.xml regen after slug renames — old sitemap may have flat slugs cached | Cutover step 10 (post theme switch): GET /wp-json/rank-math/v1/sitemap-clear + force re-crawl via GSC URL Inspection |
| G5 | Hardcoded image src paths (uploads/2024/...) in old Oxygen content may not exist in Bricks media library | Mon AM: WP REST GET /media for all attached images per Oxygen page; fail fast if missing in Bricks staging |
| G6 | Custom canonical URLs on Oxygen — must replicate on Bricks pre-cutover or risk dup-content flag | Wed PM SQL audit: SELECT meta_value FROM postmeta WHERE meta_key='_rank_math_canonical_url' for each migrated post; replicate on Bricks staging post |
| # | Blindspot | Mitigation |
|---|---|---|
| H1 | GTM container ID — must inject in Bricks header via bricks/render filter (different injection path than Oxygen) | Verify GTM-XXXXXX present on Bricks staging /sewer-repair-test/ via DOM inspect Tue PM |
| H2 | GA4 event tracking — Oxygen used data-attributes; Bricks default rendering may emit different DOM tree → click tracking breaks | Re-test all 12 GA4 custom events on Bricks staging Wed PM (form_submit, phone_click, cta_click, etc.) |
| H3 | GCLID capture — landing page JS (Snippet #55 GCLID Bridge v2) must run on Bricks server-rendered page; verify session preservation across slug 301 | Curl Bricks page with ?gclid=test123 → verify _gcl_aw cookie set + bsp_lead_session POST fires |
| H4 | Conversion linker — Google Ads conversion linker must persist GCLID across slug 301 (potential drop on 30x cascade) | Test: visit /sewer-repair/?gclid=X → 301 to /services/sewer/sewer-repair/ → cookie still set? |
| H5 | Enhanced Conversions for leads — Google Ads form trigger requires Bricks Forms to emit gtag conversion event with hashed PII fields | Tue PM: confirm Bricks Forms wired to GA4 + Google Ads with EC hashed-email; test submit on staging |
| # | Blindspot | Mitigation |
|---|---|---|
| I1 | WP Media library asset IDs differ between staging and prod — populate scripts using ID-references will break on prod | populate scripts must reference assets by FILENAME or SLUG, not numeric ID; mining brief carries filename |
| I2 | Image alt text — populate scripts must inject alt from mining brief, not leave empty (a11y violation + SEO loss) | Hard rule in populate_*.py: alt="" is FATAL; mining brief must include alt per image; Auditor flags missing alt |
| I3 | Lazy-loading — Bricks default vs Oxygen's loading="lazy" may differ → above-fold images lazy-load → LCP regression | populate scripts force loading="eager" on hero image; loading="lazy" on below-fold; Playwright verify LCP < 2.5s |
| I4 | Featured image — Yoast/Rank Math og:image often falls back to featured image; Bricks may not auto-set | populate scripts MUST set _thumbnail_id post_meta for every page; Auditor SELECT meta_value as gate |
| # | Blindspot | Mitigation |
|---|---|---|
| J1 | LCP / CLS shift — Bricks renders different DOM than Oxygen; no current Core Web Vitals baseline measurement | Tue PM: Lighthouse run on 5 Bricks staging pages; commit baseline; Thu QA gate fails if LCP ≥ 2.5s or CLS ≥ 0.1 |
| J2 | Render-blocking JS — populate scripts inject snippet JS (FAQ accordion · CSS Mirror · Image Tweaks) — could block render | All injected snippets MUST use defer or async; Lighthouse render-blocking budget = 0ms |
| J3 | Keyboard navigation — Bricks accordion (FAQ snippet) may break tab order or trap focus | Manual axe-core test on FAQ accordion staging Wed AM; gate Tab-cycle through 5 questions back to button |
| J4 | Screen reader compatibility — Bricks dynamic templates may inject role="" defaults that confuse SR (announce nothing) | VoiceOver smoke test on /sewer-repair-test/ Wed PM; verify sections announce as landmarks |
| J5 | Color contrast — populate scripts swap content into existing template; new copy may break WCAG AA contrast on yellow CTA bg | Auditor automated check: every text-on-bg combo run through WCAG AA contrast calc; fail if < 4.5:1 (regular) / 3:1 (large) |
| # | Blindspot | Mitigation |
|---|---|---|
| K1 | Bricks Forms vs Oxygen Forms — different field names · submission handlers · validation behavior | Tue PM: end-to-end form test on Bricks staging contact form → verify Slack notification + GA4 form_submit event + CRM webhook fires |
| K2 | reCAPTCHA keys — staging may use different site_key than prod; cutover Friday must confirm prod keys live | Cutover step 11: curl Bricks page → verify g-recaptcha site key matches prod constant; rollback gate if mismatch |
| K3 | Email notifications — wp_mail working on Bricks staging? SMTP plugin (WP Mail SMTP / similar) must be active | Tue PM: trigger test form submit; verify email received in robert.dove@callbrightside.com inbox within 60s |
| K4 | Phone tap-to-call — tel: links must use canonical (913) 963-1029; old content may have stale numbers | populate scripts grep replace any non-canonical phone in content; Auditor regex-fail on any tel:+1[^9] |
| K5 | Conversion tracking on form submit — if Bricks Forms uses custom action URL, postback to GA4 may not fire | Tue PM: confirm Bricks Forms gtag event_name=form_submit fires; if not, add bricks/form/submit hook to inject gtag call |
| Tier | Window | Blindspots | Effort | Status |
|---|---|---|---|---|
| 0 | Sun PM (must land before Mon 08:00 CT) | L1 · N4 · L4 | ~5.5 hr | ✅ SHIPPED THIS TURN |
| 1 | Sun PM Apr 26 (shipped before Mon AM) | M1 · M2 · N1 | ~6 hr | ✅ SHIPPED · dispatcher_safety.py · populate_*.py · dispatcher_changelog_hook.py |
| 2 | Tue PM / Wed AM harden | L2 · L3 · M3 · M4 · O1 · O5 · N2 · N3 | ~17 hr | ⏳ Schedule slot (TODO_ROBERT) |
| 3 | Sun PM Apr 26 (dispatcher ship) + Phase 2 (doc-cleanup) | O5 · O1 · O2 SHIPPED · O3 · O4 deferred | ~5 hr shipped · ~7 hr deferred | ✅ 3 SHIPPED · dispatcher_intelligence.py · O3/O4 → Phase 2 |
| # | Architecture gap | Mitigation · Path | Status |
|---|---|---|---|
| L1 | protocol_gates is CURATED 1.5% excerpt of 1,275,807 B doc — 98.5% invisible to council. Mario slop audit · Theo principles · WP Media inventory · all unreachable. | Per-dispatch section extraction at query-time · /opt/nexus/nexus/scripts/bricks_doc_section_loader.py · 32+ sections indexed · scoring by query keyword overlap | ✅ SHIPPED |
| L2 | auditor_tools mtime cache invalidates on bare touch (no content change) AND misses live nginx-disk drift (CDN cache). | Cache by sha256 of content + 5-min HEAD ETag/Last-Modified revalidation against URL · Tier 2 | ⏳ Tier 2 |
| L3 | auditor regex-greps doc as plain text — citations inside <code> or screenshot captions miss. verify_bricks_section_citation currently only matches header pattern. | BeautifulSoup parse · structured {section_id: text} dict · O(1) lookup · catches all rendering contexts · Tier 2 | ⏳ Tier 2 |
| L4 | Apr 17 changelog has structured facts (133/118 element counts · removed brxe-IDs · asset 150-154) — never injected to prompts. Dispatch could cite stale 118 count, Auditor passes (§6 exists). | BRICKS_DOC_FACTS extraction → /opt/nexus/nexus/scripts/output/bricks_doc_index.json · Critic injects facts as constraint block | ✅ SHIPPED (extraction live · runtime injection Tier 2) |
| L5 | §14 Mario / §16 Slop audit are operating principles; council reads as plain text. Critic has no structured anti-slop ruleset. | Extract Mario rules → OPERATING_PRINCIPLES constant · Critic agent receives explicit slop pattern list to red-team against · Tier 2 | ⏳ Tier 2 (paired with O5) |
| # | Architecture gap | Mitigation · Path | Status |
|---|---|---|---|
| M1 | Dispatcher writes to Bricks via REST without reading first. Manual edits since last mining run get blown away. Lost work risk. | Mandatory GET /bsp/v2/db/meta-full?post_id=N before any write · diff vs expected · abort if > 5 unexpected elements added · read-patch-write-verify · Tier 1 | ⏳ Tier 1 |
| M2 | Sanitizer chain (security_check → sanitize_data → save) silently falls back if any stage nukes elements. Snippet 33 line: if (!is_array($current)) $current = $elements restores unsanitized. | Dispatcher parses response body · asserts steps array contains security_check:ok AND helpers_sanitize_data:ok · ERR aborts · Tier 1 | ⏳ Tier 1 |
| M3 | Apr 17 specificity rule (§0): blanket [id^="brxe-"] beats single-id selectors. Generated _cssCustom single-id = silent override loss. Not in agent prompts. | Critic CSS-specificity gate · every _cssCustom selector ≥ double-id where blanket exists · Tier 2 | ⏳ Tier 2 |
| M4 | Apr 17 append rule (§0): desktop @media MUST be appended AFTER mobile @media closes. Edits inside mobile block swallow desktop rules. | Structural validator on child-theme CSS edits · only append new @media at end of file · gate pre-flight on any _cssCustom with @media · Tier 2 | ⏳ Tier 2 |
| # | Architecture gap | Mitigation · Path | Status |
|---|---|---|---|
| N1 | Dispatcher actions never write back to doc. Apr 17 changelog hand-written. Mon AM 9 service + 15 location pages ship — no auto-append. Doc stale by Tue. | Post-dispatch hook writes id="apr28-changelog" section · element count delta · new asset IDs · new snippets · git commit (doc verified IN GIT this turn) · Tier 1 | ⏳ Tier 1 (git-backed) |
| N2 | MH log entries don't reciprocally link from doc. Future dispatches search doc, miss historical fix context. | Post-dispatch hook injects "MH cross-reference" footnote into relevant doc section linking bsp-aprNN-{slug} · Tier 2 | ⏳ Tier 2 |
| N3 | Doc is one HTML file. Mon AM PARALLEL dispatch (services + locations) → both emit changelog → file-level overwrite race · last writer wins. | Serialized changelog queue · file lock OR SQLite-backed log table rendered to HTML on 5-min cron · Tier 2 | ⏳ Tier 2 |
| N4 | F4 freshness check runs Sun 7PM CT cron. Robert hand-edits doc Sun 8PM → 11-hour staleness window before Mon AM dispatch. | Pre-flight re-check at council dispatch start · /opt/nexus/nexus/scripts/freshness_preflight.py:check_freshness_now(strict=True) · adds 1-2 sec · eliminates window | ✅ SHIPPED |
| # | Architecture gap | Mitigation · Path | Status |
|---|---|---|---|
| O1 | Theo test ("explain to Kalen in one sentence why each line exists") not enforced on generated code. Council emits CSS · no agent asks justification. | Critic agent gate · every emitted CSS rule / PHP snippet / JS gets one-sentence justification field · missing field = dispatch fails · Tier 2 | ⏳ Tier 2 |
| O2 | §16 self-audit ("67 snippets exist, many predate this session") never read as anti-pattern signal. Adding new snippets without flag = bloat continues. | §16 → KNOWN_TECH_DEBT constant · Critic auto-flags any dispatch adding to known debt without explicit Robert override · Tier 3 | ⏳ Tier 3 |
| O3 | Doc tracks "67 snippets / 48 active" — moving number, never auto-updates. Critic can't accurately flag "adding to or replacing 67?" | Post-dispatch hook queries snippet REST · updates doc snippet count line · Tier 3 · <TODO_ROBERT> verify /bsp/v2/db/snippets endpoint exists | ⏳ Tier 3 |
| O4 | §12 cleanup candidates flagged but never auto-cleaned. Snippets 41/42/48/52/54 should be deleted post-Audrey-footer-ship · still INACTIVE. | Post-dispatch hook cross-references §12 list · surfaces "purge candidates" to weekly Sunday review · don't auto-delete · Tier 3 | ⏳ Tier 3 |
| O5 | §0 "burned in memory" rules (Apr 17 append · specificity) live as human-readable notes. Council has no constraint mechanism. | Regex sweep "burned in memory" / "non-negotiable" / "must be appended AFTER" · build BRICKS_HARD_RULES · Critic enforces · violation = automatic rejection · Tier 2 | ⏳ Tier 2 (paired with L5) |
| File · Path | Function | Smoke test |
|---|---|---|
/opt/nexus/nexus/scripts/bricks_doc_section_loader.py | L1 · L4 · section index + BRICKS_DOC_FACTS extraction | python3 bricks_doc_section_loader.py → JSON cache written + facts populated |
/opt/nexus/nexus/scripts/freshness_preflight.py | N4 · pre-flight freshness re-check (importable) | from freshness_preflight import check_freshness_now · returns (fresh, sha, msg) |
/opt/nexus/nexus/scripts/cutover_rehearsal.py | +3 gates: step_19 doc parity · step_20 index present · step_21 preflight importable | 21 gates total (was 18) |
| Gap | File · Path | Smoke |
|---|---|---|
| M1 · M2 | /opt/nexus/nexus/scripts/dispatcher_safety.py | read_post_baseline · diff_against_baseline · assert_sanitizer_chain · 4 smoke tests pass · post 258 = 146 elements (matches expected) |
| M1 | /opt/nexus/nexus/scripts/populate_service_pages.py | 7-page dry-run sweep all clear (Template A: 5 · Template B: 2) |
| M1 | /opt/nexus/nexus/scripts/populate_location_pages.py | 15-city dry-run sweep all clear (9 TIER A + 6 TIER B) |
| N1 | /opt/nexus/nexus/scripts/dispatcher_changelog_hook.py | append + idempotency (replace not dup) + file-lock · git commit deferred until repo has initial commit (graceful skip) |
| O5 · O1 · O2 | /opt/nexus/nexus/scripts/dispatcher_intelligence.py | 15 BRICKS_HARD_RULES extracted · 67 snippets/48 active known debt · §16 slop excerpt · Theo test gate language · wired into Critic prompt |
| +3 gates | /opt/nexus/nexus/scripts/cutover_rehearsal.py | step_22 dispatcher_safety · step_23 populate scripts · step_24 dispatcher_intelligence — 24 gates total (was 21) |
E2E verified: council dispatch fire shows freshness pre-flight (auto-detected drift, refreshed cache) · protocol gates loaded · Bricks Doc sections injected (3 sections, 9942 chars) · Critic intelligence wired into critic_prompt construction.
Verdict: ✅ PHASE 1 LOCKED — 30 PAGES SHIP MAY 1
Per Robert 2026-04-26: Phase 1 = full Apr 18 Audrey scope = 9 service + 15 location + 6 informational = 30 pages. No scope cut. No slip. May 1 ship date is final.
Capacity reclaimed by Approach 2: ~4.5 hrs Mon-Tue freed from slug restructure → reallocated to last-mile push layer build + content population.
Mon-Thu execution discipline:
populate_service_pages.py (last-mile layer, ~6 hrs) · GBP location_id fix · pause /lp/drain-cleaning-special/ ads · stage Rank Math redirect rules (DISABLED) — slug restructure work removed (saved 4.5 hrs)populate_location_pages.py (~6 hrs) · run service-page mining (9 drafts) · Template A/B per page · Kalen batch 1Risk callouts (still HARD): last-mile layer must complete Mon AM or service-page mining can't write through. Location mining pipeline must extend CITY_ZIPS for 6 TIER B cities Tue. Kalen review cadence must hold to schedule — slip Kalen → slip everything.
✅ ALL 7 DECISIONS LOCKED 2026-04-26
/preview/may1/ + Slack DM thread per page. Auto-screenshot desktop+mobile · approve/redline buttons · 30/30 approval gate before cutover/plumbing-services/drain-cleaning/