{
  "for_llm": "This page documents ONE part of LeadLift's coded OS: the onboarding form (its own Cloudflare worker) and what a linked form starts on the Modal server, then the human steps in the app up to 100 - Onboarded. To change it, open the repo in code.repo and read the stage file and code.files. change_policy: green = the app can write it; amber = a named business rule with a test (change rule + test together, merge through the gate); red = a contract other parts call (routes, tables, step names, the call-done database trigger), a setting or secret. Facts are as of main e0a8709 (12 Oct 2026).",
  "system": "LeadLift Onboarding",
  "unit": {
    "index": 2,
    "of": 3,
    "node": "Onboarding form to onboarded"
  },
  "platform": "code (Cloudflare form worker + Modal)",
  "component": "Onboarding form: onboarding.leadliftagency.co/form (also leadlift-onboarding.pages.dev/form), Cloudflare Pages project leadlift-onboarding",
  "code": {
    "repo": "https://github.com/Icarus-Growth/leadlift-os",
    "ref": "main e0a8709",
    "stage": "os/process/ll-onboarding-flow.md",
    "boxes": [
      "16",
      "17",
      "18",
      "19",
      "20",
      "21",
      "22",
      "23",
      "26"
    ],
    "files": [
      "os/onboarding-form/spec/questions.json",
      "os/onboarding-form/worker/worker.js (submit, completeProcess, finishFiling, slackMessages, askModalToInvite)",
      "os/onboarding-form/tools/deploy.py",
      "deploy/handlers/onboarding.py (POST /onboarding/slack-people, /onboarding/form-finish, /onboarding/call-done)",
      "os/api/onboarding_legs.py (form_leg, ad_account_message, ad_account_text, finish_form, give_drive_access)",
      "os/api/search_client.py",
      "os/api/client_picker.py",
      "os/api/calls.py (stamp_call_steps)",
      "os/api/onboarding.py (call_done, AFTER_CALL_STEPS)",
      "os/db/migration-2026-09-24-onboarding-deadlines.sql",
      "os/db/v2.sql (v_onboarding_steps_overdue)",
      "os/app/src/areas/client/index.tsx",
      "os/app/src/lib/actions.ts (stampStep, confirmClose)",
      "os/app/src/lib/ladders.ts (HUMAN_STEPS, STEP_MOVES, ONBOARDING)",
      "os/app/src/areas/tables/index.tsx (OnboardingQueue)"
    ]
  },
  "trigger": {
    "type": "client presses Submit on the onboarding form",
    "also": [
      "a pick in the #onboarding-forms client picker (POST /slack/interactions -> start_form_step)",
      "a matched Fathom call (stamps the two call steps)",
      "a person in the app (Mark done, Confirm & close, Move to)"
    ]
  },
  "reads": [
    {
      "table": "onboarding_form_drafts",
      "why": "per-page saves; the client's own link id"
    },
    {
      "table": "clients",
      "why": "find the client (link id, email, form_email, phone+name, names, fuzzy, AI)"
    },
    {
      "table": "onboarding_steps",
      "why": "step state, late steps via v_onboarding_steps_overdue"
    },
    {
      "table": "call_recordings",
      "why": "the onboarding call and its (empty) brief"
    }
  ],
  "writes": [
    {
      "table": "onboarding_form_submissions",
      "why": "one row on Submit; client_id when linked"
    },
    {
      "table": "clients",
      "why": "form_email, stand-in company_name, empty first/last name, onboarding_status"
    },
    {
      "table": "onboarding_steps",
      "why": "Message sent to connect Viktor..., the three human steps, fired_at on the two after-call steps"
    },
    {
      "table": "call_recordings",
      "why": "extraction_confirmed + confirmed_by_id on Confirm & close"
    }
  ],
  "notifies": [
    {
      "slack": "#onboarding-forms (form's ONBOARDING_SLACK_CHANNEL): 'Onboarding form submitted by ...', every Q and A in the thread, tags the form worker's ALERT_SLACK_ID (Sheni)"
    },
    {
      "slack": "client's #onboarding thread: Viktor message + steps (tags Fidel and Paulo), 'Not yet' line for Paulo, one \u2713/\u2717 line per invite"
    },
    {
      "slack": "client channel: Slack Connect invites (conversations.inviteShared), max 10 per form"
    },
    {
      "slack": "#sentry: a filing that Modal could not finish, or a refused Slack post"
    }
  ],
  "schedules": [
    {
      "rule": "database trigger onboarding_steps_call_done -> POST /onboarding/call-done",
      "when": "about 60 s after 'Onboarding call takes place' becomes complete",
      "does": "sets fired_at on 'Transcript captures details' and 'Client knowledge bank updated' (24-hour window)"
    }
  ],
  "edges": {
    "upstream": [
      {
        "node": "Payment to onboarding started",
        "unit": 1,
        "via": "the welcome email's own form link; the client channel the Slack leg made"
      },
      {
        "node": "Contract (reserved slot 04, pending)",
        "unit": 4,
        "via": "runs alongside by hand; gates nothing"
      }
    ],
    "downstream": [
      {
        "node": "Editing Pipeline (sibling system)",
        "href": "../editing-pipeline/",
        "via": "the client uploads footage to Raw Footage"
      },
      {
        "node": "OS walkthrough (sibling system)",
        "href": "../os-walkthrough/",
        "via": "the app screens and Slack channels"
      }
    ]
  },
  "capabilities": [
    "Four-page form plus review; each Continue saves a server-side draft; one link per client; two people can fill it; after Submit the link shows it was submitted",
    "Finds the client: own link first, then email, phone+name, names, fuzzy, AI (OpenRouter, 0.85 or more); otherwise a client picker in the #onboarding-forms thread",
    "Posts the form and every answer in #onboarding-forms",
    "For a linked form (not a guess): fills a stand-in company and an empty name, shares the client folder with the form email (Make 6585401), posts the Viktor message on 'Yes, it is done' or a Paulo line on 'Not yet', sends up to 10 Slack Connect invites",
    "App: Mark done on the three human steps (booked -> 40 - Call Booked, takes place -> 50 - Call Taken with a confirm, ad account access obtained), Confirm & close -> 100 - Onboarded, Move to the next stage",
    "A matched Fathom call stamps 'Onboarding call booked' and 'Onboarding call takes place' for a client whose steps are laid out"
  ],
  "boundaries": [
    "Box 16 knowledge bank: not built; 'Client knowledge bank created' has no code and no app button",
    "Boxes 20-21 brief extraction and KPI baseline draft: not built; extracted_brief stays empty ('STEP 2'); 'Transcript captures details' and 'Client knowledge bank updated' have no code and no app button",
    "Box 26: as built, the resolver (search_client) reads LeadLift's own database (clients, client_contacts), not knowledge-bank about.md folders as the stage file says",
    "A form linked by a guess (fuzzy or AI) skips the form leg; a form linked in the app (Link to this client) writes only client_id and does not start the form leg",
    "'Ad account access obtained' is never stamped by code; a person marks it when Viktor can read the account",
    "The contract steps have no app button either"
  ],
  "change_policy": {
    "green": [
      "onboarding_steps status/completed_at for the three HUMAN_STEPS (Mark done)",
      "clients.onboarding_status (Move to / stage list)",
      "call_recordings.extraction_confirmed + confirmed_by_id (Confirm & close)",
      "clients provisioning: ad_account_access, ad_account_id, ads_manager_url, pixel_id, viktor_connected, module_3_complete",
      "clients targets: target_cost_per_lead, target_leads_per_week, expected_quality_mix, drift_threshold, baseline_confirmed",
      "onboarding_form_submissions.client_id (link a lost form)"
    ],
    "amber": [
      "os/onboarding-form/spec/questions.json (validate_spec.py + worker tests, then tools/deploy.py)",
      "SLACK_PEOPLE_LABEL and AD_LABELS in os/api/onboarding_legs.py, pinned to questions.json by a test",
      "SAME_DAY_HOURS = 24 for team steps (Fidel, 2026-09-24, docs/DECISIONS.md)"
    ],
    "red": [
      "form address onboarding.leadliftagency.co/form and /api/onboarding/* routes",
      "route POST /onboarding/slack-people and POST /onboarding/form-finish",
      "ONBOARDING_LEG_SECRET (Modal, the form's Cloudflare worker, Make)",
      "route.onboarding_step_stamp / route.onboarding_confirm_close (Supabase PATCH)",
      "onboarding_step_name enum and the tables onboarding_form_submissions / onboarding_form_drafts / onboarding_steps / call_recordings / clients",
      "database trigger onboarding_steps_call_done -> POST /onboarding/call-done (server address written into the database)",
      "who is tagged: the form worker's ALERT_SLACK_ID (Sheni), ONBOARDING_SLACK_PEOPLE_SLACK_ID (Paulo), ONBOARDING_FIDEL_SLACK_ID (Fidel)",
      "os/onboarding-form/tools/deploy.py (no main check, no rollback, rewrites live settings)"
    ]
  },
  "known_issues": [
    "Five steps have no code and no app button (2 contract, 3 knowledge bank/transcript); they show late after their 24 hours",
    "A filing Cloudflare stops for its own reasons stays 'filing' and nobody is told",
    "onboarding_form_submissions does not save how its client was found",
    "The form's release has no main check and no rollback",
    "The #onboarding-forms post still tags Sheni (form worker ALERT_SLACK_ID)",
    "Transcript (Loom 3) says the team won't be able to read the backend code: wrong as built, LeadLift receives the repo"
  ],
  "recovery": {
    "git": "every change is a commit on main, merged through the gate",
    "rollback": "modal app rollback leadlift-os (server); the form has no rollback command"
  }
}