{
  "meta": {
    "id": "WO-2026-0705-WEYLAND-ONBOARDING-001",
    "title": "Weyland — Happy Path to Onboarding Readiness",
    "subtitle": "Sequenced execution plan · quote-first · gated · rolling-wave",
    "goal": "A new subcontractor can, on their own project documents, self-serve a correct quote (and submittal) reliably and securely — quote-first, to unlock the first paying customer (the valuation lever).",
    "generated": "2026-07-05",
    "author": "CHENG Fable 5",
    "status": "PLANNED",
    "framework": "v1.7 tickets + industry best practice (TDD, code-review gates, rolling-wave, verify-on-real-surface)"
  },
  "architecture": "Cloudflare Workers (weyland-onamerica) + D1 + R2, an operator-local extraction bridge (SABP), and a single-page app served from R2. Pipeline: extraction → affirm → materialize (trusted tier) → price → quote/submittal. Two products from one extraction: takeoff/quote (bid) and submittal (deliver).",
  "tech_stack": "JavaScript ES modules (no TS), Cloudflare wrangler, D1/SQLite, R2, pdf-lib + puppeteer, hascom CLI for ops/observability.",
  "readiness_definition": [
    "A new sub account is provisioned with correct industry access and ALL D1 seeds present (no migration-completeness gaps).",
    "Upload → extract works for BOTH hardware schedules AND door schedules.",
    "Takeoff shows the estimate on upload; any line is hand-priceable; totals roll up correctly (UOM-consistent).",
    "A correct quote PDF downloads with correct line items and totals.",
    "A submittal package PDF WITH cut sheets downloads.",
    "Cross-tenant isolation holds on EVERY session/project route (authz sweep complete).",
    "No Critical/Important review findings open on the touched surfaces."
  ],
  "milestones": [
    {
      "id": "M1",
      "name": "Minimum Onboarding",
      "legs": [
        "A",
        "B",
        "C"
      ],
      "outcome": "Quote product works end-to-end for Precision Auto Doors.",
      "why": "The paying-customer milestone → the valuation re-rate (~$6M → $10M+)."
    },
    {
      "id": "M2",
      "name": "Full Readiness",
      "legs": [
        "D",
        "E"
      ],
      "outcome": "Both products, safe for any new tenant.",
      "why": "True onboarding readiness; scale-able across trades."
    }
  ],
  "dependency_graph": "A (verify pricing) ∥ B (fix door extraction) → C (close quote loop) → ★M1 ;  then D (multi-tenant + provisioning) ∥ E (complete submittal) → ★M2. Parallel non-blocking track P runs around the critical path. Critical path to revenue = A ∥ B → C.",
  "standards": {
    "internal_mhs": [
      "v1.7 JSON ticket per leg; phases RESEARCH→DESIGN→BUILD→TEST→VERIFY→DEPLOY→CLOSE.",
      "Five-Field reporting (Status · Artifacts · Testing · Dependencies · Issues).",
      "chronicle.trace before each leg / chronicle.record after.",
      "One change, one verification — no batch rewrites tested once.",
      "Look at the data before writing code (SELECT * … LIMIT 3 first).",
      "hascom-first (query/find/brief before manual exploration).",
      "Non-destructive, manual, auditable deploy (Cybernetic Deployment Doctrine)."
    ],
    "industry": [
      "TDD for new pure logic (failing test → verify fail → implement → verify pass → commit).",
      "Verify on the REAL surface — drive the running app / real endpoint and observe. Tests necessary, not sufficient.",
      "Code-review gate before deploy for substantive changes (caught 2 shipped criticals this session).",
      "Explicit Definition of Done + an OBSERVABLE gate per leg.",
      "Risk + rollback plan per leg; feature-flag risky rollouts.",
      "Security & compliance built-in (uniform authz; SOC2/GDPR-ready posture).",
      "Rolling-wave planning — near-term legs detailed, far-term legs specced, detail just-in-time."
    ]
  },
  "global_constraints": [
    "Canonical edit tree: PAD_backup/.../Phase2/Prototype1/. The AthenaSystem twin is FROZEN (read-only).",
    "Run hascom from C:\\MHS, never C:\\MHS\\hascom. cd /c/MHS before every deploy/query.",
    "SPA deploy uses cf_api.r2_put data=<path> (NEVER file= — that 0-byte-overwrites).",
    "D1 bound-param cap ≤ 90/statement. Transient 'D1 storage operation exceeded timeout' = platform hiccup → add retry on session-write path.",
    "Observability is BROKEN: devtest.tail captured 0/8 known probes. Observe via DB before/after snapshots + devtest.http probes until fixed (P3)."
  ],
  "legs": [
    {
      "id": "A",
      "title": "Verify the pricing foundation (1a + 1b)",
      "effort": "S",
      "ticket": "WO-2026-0703 (verify)",
      "phase_focus": "VERIFY",
      "objective": "Prove the already-deployed manual-pricing (1a) and provisional-populate (1b) work on the running system. Nothing new is built; this closes the verification gate we deferred.",
      "files": [
        "weyland-worker.js — select-price ~8681, takeoff /data ~9989, rollup ~10024",
        "hardware-schedule-extractor.js — savePageExtraction ~2797 (provisional materialize)",
        "data-transformer.js — materializeAffirmedGroup ~1410",
        "deployment/public/subx.html — pricing modal ~14316, manualPriceRow/setManualComponentPrice ~14424"
      ],
      "steps": [
        "chronicle.trace + baseline snapshot (component prices NULL; provisional sets = 0).",
        "Drive 1a on the real surface: open pricing → type 125 → Set. Observe: modal renders manual input (C1 fix), unit_price=125/price_source='manual', rollup updates.",
        "Drive 1b: extract a fresh HARDWARE schedule. Observe: affirmed=0 sets appear in Takeoff before any affirm.",
        "chronicle.record; update WO-2026-0703 Five-Field (Testing)."
      ],
      "gate": "Manual price persists + rolls up; fresh hardware upload shows provisional sets — observed on the running system.",
      "risk": "Observation only (tooling) — mitigated by DB snapshots.",
      "rollback": "None (read-only verification).",
      "dev_state": "BUILT · UNPROVEN — 1a: 1/17 components priced; 1b: 0 provisional sets on live data. Deployed + review-hardened, never observed working live. This leg IS the proof."
    },
    {
      "id": "B",
      "title": "Un-break door-schedule extraction",
      "effort": "M",
      "ticket": "DX-2026-0705-WEYLAND-001",
      "phase_focus": "FIX",
      "objective": "A door-schedule upload currently no-ops — the inference-bridge config menu never presents, so openExtractionQueueFromSelection hits its 'no pages selected' gate and returns (0 writes). Restore a working door-extraction path. This is the gate to PAD's own domain even starting.",
      "files": [
        "deployment/public/subx.html — openExtractionQueueFromSelection ~11917 (gate ~11948), post-upload routing ~7960, extractAllPages ~8753 (deprecated/dead)",
        "weyland-worker.js — batch-extract ~5126 (async, routes by document_type), extract-image ~6734 (hardware-only), extract-door-schedule ~7047 (410 dead)",
        "hardware-schedule-extractor.js — routeExtraction / extractDoorScheduleHGSE (backend validated by DX-2026-0703)"
      ],
      "steps": [
        "chronicle.trace + reproduce the failing case (fresh door upload → 0 entries/0 packets).",
        "DESIGN DECISION (record in DX-2026-0705): (A) present the config menu / auto-populate full-page selection so door → batch-extract fires [recommended], vs (B) restore a synchronous edge door endpoint [fast, revives deprecated Path B + edge API cost]. Get CAPT sign-off.",
        "Build the smallest fix that makes a door upload reach batch-extract with pages. Syntax-check; code-review gate.",
        "Deploy + verify on real surface: fresh door upload → door_schedule_entries > 0 → provisional sets in Takeoff.",
        "Surface the silent-failure UX fix (loud operator error on 'no pages'); chronicle.record; close DX-2026-0705."
      ],
      "gate": "Fresh door-schedule upload → door_entries + sets populate, visible in Takeoff.",
      "risk": "Async bridge timing (respect 60s edge job TTL, depth-1); the config-menu UI.",
      "rollback": "Non-destructive (worker version + R2 revert).",
      "dev_state": "BROKEN — 3 door_entries ever; door uploads no-op (config menu absent). Backend door path validated (DX-2026-0703); the front door is dead."
    },
    {
      "id": "C",
      "title": "Close the quote loop",
      "effort": "S–M",
      "ticket": "WO-2026-0703 (2b) + FX quote-verify",
      "phase_focus": "BUILD+VERIFY",
      "milestone_after": "M1",
      "objective": "Turn 'sets exist and are priced' into 'a correct quote PDF downloads.' Unify the UOM factor, confirm the affirmation gate, verify the download.",
      "files": [
        "weyland-worker.js — /data UOM (PR→2 else 1) ~10030, generate-quote UOM (PR=2,SET3=3) ~11858, affirm gate WHERE affirmed=1 ~11826, quote persist ~11997",
        "takeoff-quote-generator.js — generateQuoteHtml UOM ~781"
      ],
      "steps": [
        "chronicle.trace + look at the data (SELECT DISTINCT uom FROM hardware_components).",
        "TDD: write a failing uomFactor(uom) unit test (PR|PAIR→2, SET/3|SET3→3, EA→1) + a rollup-parity case (PR + SET lines). Verify FAIL.",
        "Implement one shared uomFactor() used by BOTH the SQL rollup and the JS quote path. Verify PASS.",
        "Confirm generate-quote excludes affirmed=0 provisional sets and discloses the excluded count.",
        "Deploy + verify on real surface: upload → extract → price → DOWNLOAD the quote PDF; totals match on-screen Takeoff for a set with a PR and a SET line.",
        "Code review + chronicle.record."
      ],
      "gate": "Upload → extract → price → download a correct quote PDF (UOM-consistent, gate-correct). ★ = M1: quote product onboarding-ready. Put PAD on it; convert to paying.",
      "risk": "UOM edge cases; provisional leakage into the quote.",
      "rollback": "Non-destructive.",
      "dev_state": "RUNS-EMPTY — 4 quotes, all $0; UOM factor inconsistent (/data vs generate-quote). The path exists end-to-end but delivers nothing until A/B land + UOM is unified."
    },
    {
      "id": "D",
      "title": "Onboard a new customer safely (multi-tenant + provisioning)",
      "effort": "M",
      "ticket": "task #16 + founder-industry FX + migration-completeness audit",
      "phase_focus": "BUILD (spec-level; detail JIT)",
      "objective": "A brand-new subcontractor can be provisioned safely — uniform tenant isolation, correct industry access, fully-seeded D1.",
      "files": [
        "weyland-worker.js — session-scoped routes: settings ~10545, line-items ~10642, materialize ~10851, quotes ~12291/12355; project-scoped /data ~10224, generate-quote ~12044",
        "hardware-schedule-extractor.js — routeExtraction vs resolveConstraints key-case (I4)"
      ],
      "steps": [
        "Authz sweep: factor assertSessionOwner(env, sessionId, user); apply to ALL session/project routes (4 already fixed inline — make it uniform). Enumerate routes programmatically, don't eyeball.",
        "Founder / industry access: assign an industry (or founder any-industry) so constraint resolution isn't silently defaulting.",
        "Front-correctness I4: routeExtraction camelCase tenantId/industryId vs resolveConstraints snake_case → tenant constraints dropped + always ind_doors. Fix before multi-industry.",
        "Migration-completeness audit: diff migrations/seeds original weyland_db vs venture D1; seed all missing; provisioning must run the complete seed set."
      ],
      "gate": "A new sub account with correct industry access + all seeds; authz matrix shows a non-owner 404'd on EVERY session/project route.",
      "risk": "Missing a route in the sweep (security-critical) → enumerate programmatically; deny-by-default helper.",
      "rollback": "Non-destructive; authz changes are additive (deny-by-default).",
      "dev_state": "PARTIAL — 4 authz holes fixed; sweep not uniform; founder-industry access + migration-completeness gaps open."
    },
    {
      "id": "E",
      "title": "Complete the submittal product",
      "effort": "L",
      "ticket": "task #14 + submittal FX cluster",
      "phase_focus": "BUILD (spec-level; detail JIT)",
      "milestone_after": "M2",
      "objective": "Affirmed sets → a downloadable submittal package PDF WITH cut sheets. The biggest remaining build; the data-model schism is the core.",
      "files": [
        "submittal-assembler.js — assembleSubmittalPackage ~767 (reads DEAD Model #1: session_cut_sheet_matches → product_documents, 0 rows)",
        "component-matcher.js — persistSessionMatches ~395 (ON CONFLICT(session_id,cut_sheet_id) with no matching unique index)",
        "weyland-worker.js — affirm-batch (affirmation_log, currently 0 rows); /assemble ~18365; /submittal/download; extract-affirmed ~14061",
        "deployment/public/subx.html — generateSubmittalPdf ~22488 (window.print stub)"
      ],
      "steps": [
        "Repoint the assembler onto Model #2 (product_mappings / affirmation_log / catalogues page-ranges) with page-range copyPages (not whole-PDF).",
        "Persist the spine: fix the affirm-batch contract (SPA ↔ endpoint ↔ preview); replace notes-LIKE linkage with a structured session_id column.",
        "Fix the upsert: CREATE UNIQUE INDEX on session_cut_sheet_matches(session_id, cut_sheet_id).",
        "Wire the SPA: generateSubmittalPdf → POST /assemble then GET /submittal/download (retire the print stub).",
        "PDF polish: TOC page-number drift; swallowed merge failures → result.errors; memory ceiling on large merges."
      ],
      "gate": "Drive one real cut-sheet affirm → assemble → download a package PDF CONTAINING cut sheets. ★ = M2: full onboarding readiness.",
      "risk": "Largest surface; Model migration; PDF at scale → sequence sub-steps each with its own gate.",
      "rollback": "Non-destructive; keep Model #1 read as a fallback until Model #2 verified.",
      "dev_state": "BROKEN — affirmation_log=0, cut_sheet_matches=0, product_documents=0; Model #2 has 1,089 mappings but 0 affirmed. Matching works; the affirm→persist→assemble spine does not. Largest build."
    }
  ],
  "parallel_track": [
    {
      "id": "P1",
      "title": "Catalogue coverage",
      "ticket": "task #19",
      "note": "Ingest auto-operator OEM catalogues (Horton/Camden/BEA) + repair 550 orphaned variants → manual pricing becomes auto-rollup. Manual pricing bridges this, so NOT a blocker for M1."
    },
    {
      "id": "P2",
      "title": "Front-correctness I1–I3",
      "ticket": "task #17",
      "note": "Single total_sets_extracted writer; re-extraction unaffirm symmetry (via unaffirmMaterializedGroup); approved_from_page update on upsert. Do before SCALING provisional; not blocking first customer."
    },
    {
      "id": "P3",
      "title": "Fix observability",
      "ticket": "tooling FX",
      "note": "devtest.tail doesn't attach (0/8 probes). Repair/replace so we can observe live traffic; add retry-with-backoff on the session-write path for the transient D1 reset."
    },
    {
      "id": "P4",
      "title": "Procore integration polish",
      "ticket": "distribution",
      "note": "Post-M1; the warm-channel distribution thesis from the deck."
    }
  ],
  "risks": [
    {
      "risk": "Door fix breaks other extraction paths",
      "leg": "B",
      "severity": "High",
      "mitigation": "Reproduce-first; code-review gate; non-destructive deploy."
    },
    {
      "risk": "Missing a route in the authz sweep",
      "leg": "D",
      "severity": "High (security)",
      "mitigation": "Enumerate routes programmatically; deny-by-default helper."
    },
    {
      "risk": "Submittal Model migration corrupts data",
      "leg": "E",
      "severity": "High",
      "mitigation": "Keep Model #1 read as fallback; per-sub-step gates; snapshot before writes."
    },
    {
      "risk": "Can't observe → false 'done'",
      "leg": "all",
      "severity": "Medium",
      "mitigation": "DB snapshots + devtest.http; fix tail (P3); verify on real surface."
    },
    {
      "risk": "Transient D1 timeout on session write",
      "leg": "all",
      "severity": "Low",
      "mitigation": "Retry-with-backoff on the write path (P3)."
    },
    {
      "risk": "Over-planning far legs (waste)",
      "leg": "D/E",
      "severity": "Low",
      "mitigation": "Rolling-wave: detail just-in-time before each leg."
    }
  ],
  "execution_model": [
    "Run subagent-driven, ONE leg at a time, with a review gate between legs.",
    "1 — chronicle.trace the leg → generate its bite-sized plan just-in-time.",
    "2 — Implement task-by-task (TDD for new logic; one change, one verification).",
    "3 — Deploy non-destructively → VERIFY on the real surface (observe the gate).",
    "4 — Code-review gate → chronicle.record → close the leg's ticket (Five-Field).",
    "5 — Do not start the next leg until the current gate is observed."
  ],
  "live_maturity": {
    "assessed": "2026-07-05",
    "source": "Direct query of live weyland-onamerica D1 + worker state (not memory).",
    "scale": "WORKING · BUILT-UNPROVEN · PARTIAL · DATA-GAP · RUNS-EMPTY · BROKEN",
    "pipeline": [
      {
        "stage": "Upload → session",
        "rating": "WORKING",
        "evidence": "33 sessions; 10 extracted."
      },
      {
        "stage": "Hardware extraction (bridge)",
        "rating": "WORKING",
        "evidence": "10 sessions extracted; convergence byte-verified."
      },
      {
        "stage": "Door extraction",
        "rating": "BROKEN",
        "evidence": "3 door_entries total, ever. Uploads no-op (DX-2026-0705)."
      },
      {
        "stage": "Affirm → materialize (trusted tier)",
        "rating": "WORKING",
        "evidence": "5 hardware_sets, all affirmed=1."
      },
      {
        "stage": "Provisional populate (1b)",
        "rating": "BUILT · UNPROVEN",
        "evidence": "0 provisional (affirmed=0) sets on live data despite deploy."
      },
      {
        "stage": "Manual pricing (1a)",
        "rating": "BUILT · UNPROVEN",
        "evidence": "1 of 17 components priced; never exercised live."
      },
      {
        "stage": "Quote generation",
        "rating": "RUNS-EMPTY",
        "evidence": "4 takeoff_quotes, all $0 (nothing priced upstream)."
      },
      {
        "stage": "Catalogue pricing data",
        "rating": "DATA-GAP",
        "evidence": "944 variants / 943 active across 21 catalogues — domain-mismatched to auto-operators."
      },
      {
        "stage": "Cut-sheet matching (Model #2)",
        "rating": "PARTIAL",
        "evidence": "1,089 product_mappings, but 0 affirmed."
      },
      {
        "stage": "Submittal spine + assembly",
        "rating": "BROKEN",
        "evidence": "affirmation_log=0, session_cut_sheet_matches=0, product_documents=0. Assembler reads the dead model."
      },
      {
        "stage": "Multi-tenant / authz",
        "rating": "PARTIAL",
        "evidence": "4 IDOR/authz holes fixed this session; sweep not yet uniform."
      }
    ],
    "summary": "The FRONT of the pipeline (upload → hardware extraction → affirm → materialize) is WORKING. Every VALUE-DELIVERY endpoint is unproven or broken: pricing unused (1/17), provisional never fired (0), quotes all $0 (4/$0), door extraction barely runs (3 entries), submittal spine never persisted (0 across all link tables). The plan's legs target exactly these gaps — evidence, not opinion."
  }
}