# Product Requirements Document

**Spec ID:** 051
**Feature:** Scheduling Onboarding & Activation (Scheduling Included in All Plans)
**Owner:** Scheduling team
**Status:** Revised after Codex PRD review (2026-07-20) — ready for SDD
**Origin:** July 2026 business decision to include the Scheduling module in all BuyerKiosk plans instead of selling it as the paid Premium Scheduling add-on (Spec 035). Making it free only creates value if stores actually adopt it — and today there is no path from "scheduling exists" to "our store runs on it." A July 2026 codebase audit confirmed: no onboarding/checklist/tour infrastructure exists anywhere in the product, and the team-member invite flow generates activation tokens but never delivers them (no email, no SMS).

## Validation Checklist

- [x] All required sections are complete
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Problem statement is specific; unmeasured claims are labeled hypotheses with a baseline-capture requirement
- [x] Problem is validated by evidence (codebase audit + gate architecture review)
- [x] Context → Problem → Solution flow makes sense
- [x] Every persona has at least one user journey
- [x] All MoSCoW categories addressed (Must/Should/Future/Won't)
- [x] Every feature has testable acceptance criteria with stable IDs
- [x] Every KPI has a definition (numerator/denominator/cohort) and corresponding tracking events
- [x] Per-step completion contracts defined (no undefined checklist states)
- [x] No feature redundancy
- [x] No contradictions between sections (checklist semantics unified in M3 contract table)
- [x] Technology choices appear only as locked decisions in Constraints; design remains SDD scope
- [x] A new team member could understand this PRD

---

## Product Overview

### Vision
Every BuyerKiosk store can go from "never used scheduling" to a fully operating scheduling practice — team roster with pay rates, positions, availability, a published week on the calendar, and both mobile apps in staff pockets — by following a self-serve Getting Started experience that tracks its own progress and teaches the calendar hands-on. Activation is a deliberate opt-in moment we can measure, not a silent flag flip.

### Eligible Cohorts (definitions used throughout)
- **BK-native-eligible store:** a store whose `schedulingProvider` is BuyerKiosk-native or unset/none — i.e., not integrated with When I Work or Homebase. These stores can activate via the hub.
- **External-provider store:** `schedulingProvider` is When I Work or Homebase. These stores see the integration-aware page (M5) and are excluded from activation KPIs.
- **Grandfathered store:** a store already on BK-native scheduling before this spec ships (the former premium users). They skip activation (auto-detected complete) and their historical data counts toward step detection.
- **Active staff (denominator base):** users with an enabled account and an active assignment to the store. **Roster-completeness denominators exclude owner-role accounts** (owners are often salaried; requiring an hourly rate for them creates false incompleteness).

### Problem Statement
Scheduling is one of the most complete modules in the product (calendar, open shifts, AI fill, availability, tasks-on-shifts, positions, mobile clock-in), but it sits behind the Premium gate (Spec 035) and has zero adoption tooling:

- Non-premium stores landing on `/admin/:typeNum/schedule` see a marketing page instead of the product. When the gate drops, they will land on an empty calendar for an unconfigured store with no roster, no positions, and no guidance.
- A fully set-up store requires work across at least six surfaces (settings, positions, team members, invites, availability, calendar) in a dependency order that nothing communicates. The AI fill feature, our flagship "aha" moment, errors on a week with no open shifts — a first-time user who clicks "AI" on an empty week hits a failure, not magic.
- The team invite flow is a dead end: the invite action writes an activation token to the database and returns — **no email or SMS is ever sent**. A store owner who "invites" their staff has actually done nothing visible to the staff.
- There is no way to know or show whether employees are using the Team+ app or managers the Live app, even though usage signals exist in the database (device push registrations, mobile login sessions).
- There is no onboarding, checklist, tour, or first-run framework anywhere in the authenticated product to build on. The only guided tour in the repo lives in the logged-out marketing demo sandbox.

**Hypotheses (to be validated by the pre-launch baseline, see Success Metrics):** announcing "scheduling is now free" without this spec would produce a spike of visits to an empty calendar, a wave of "how do I set this up" support tickets, and near-zero durable adoption. These are stated as expected outcomes, not measured facts; the baseline snapshot exists so post-launch results are comparable.

### Value Proposition
A store owner clicks one announcement banner and lands on a Getting Started hub that:

- Activates scheduling for their store as an explicit, tracked choice — with integration-aware handling for stores already using When I Work or Homebase.
- Shows a checklist that completes *itself* as the store does real work (positions created, pay rates filled, apps in use, first week published) — progress is detected from live data, not self-reported.
- Actually delivers team invites by email and SMS, ending in app-download guidance.
- Teaches the calendar with an interactive walkthrough in the real UI: create a shift, create an open shift, let AI fill the week, publish — in the order the features actually require.
- Gives BuyerKiosk a measurable activation funnel for the launch.

---

## User Personas

### Primary Persona: Store Owner / Operator
- **Demographics:** Owner-operator of one to several BuyerKiosk stores. Runs the business day-to-day; limited patience for configuration. Uses the admin site weekly, the workspace daily.
- **Goals:** Get the team scheduled with as little setup friction as possible. Know what's left to do. Not pay for scheduling anymore.
- **Pain Points:** Doesn't know the module exists / assumed it costs extra. No idea what "set up" means or in what order. Staff onboarding (accounts, apps) feels like IT work.

### Primary Persona: Store Manager
- **Demographics:** Runs the floor and builds the weekly schedule. Comfortable with the admin site; will live in the calendar page and the Live app.
- **Goals:** Build and publish a week fast. Fill open shifts without phone tag. See who's available before scheduling them.
- **Pain Points:** First encounter with the calendar is a blank grid. Doesn't know open shifts must exist before AI fill can run. Doesn't know which staff have working logins.

### Secondary Persona: Hourly Employee
- **Demographics:** Front-line staff. Interacts through the Team+ app and the workspace time clock. Wide range of technical comfort; responds to texts faster than email.
- **Goals:** See their schedule on their phone. Set availability once. Claim extra shifts. Clock in without hassle.
- **Pain Points:** Never receives a working invite today (the system sends nothing). Doesn't know the Team+ app exists or that it's for them.

### Secondary Persona: BuyerKiosk Platform Team
- **Demographics:** BK internal engineering/ops running the launch.
- **Goals:** Announce the change to every store at once, watch activation and setup-completion rates, and intervene where stores stall — without manual per-store work.
- **Pain Points:** Today there is no funnel: `premiumStatus` says who paid, but nothing says who *configured* or *uses* scheduling.

---

## User Journey Maps

### Primary User Journey: Owner activates scheduling from the announcement
1. **Awareness:** Owner logs in and sees a System Alerts banner: "Scheduling is now included in your plan" with a CTA. Clicking it opens the Getting Started hub (the page that previously showed premium marketing). The banner goes to all roles (Decision D-3); copy is written to be meaningful to employees too ("ask your manager about getting on the schedule").
2. **Consideration:** The hub shows what a fully set-up store looks like and a checklist of steps with time estimates. For an external-provider store, the hub instead shows the M5 integration-aware page and does **not** offer one-click activation.
3. **Adoption:** Owner clicks "Get Started." The store's scheduling provider is set to BuyerKiosk-native, the event is tracked, and the activate step is complete. The checklist becomes the hub's centerpiece.
4. **Usage:** Owner works the checklist over days: confirms store settings, creates positions, builds the roster with pay rates and hours, sends invites. Steps completed by a manager or by employees (app logins, availability) tick themselves off automatically.
5. **Retention:** The hub remains linked from the scheduling area (permanent compact "Setup" entry after completion — Decision D-4); re-visiting shows live progress ("6 of 8 steps · 4 of 7 staff active in the app"). On completion, the hub celebrates and collapses into a compact "setup complete" state with links to going-deeper resources.

### Primary User Journey: Manager builds the first week (guided walkthrough)
1. Manager opens the "Build your first schedule" step; it deep-links to the real calendar page and offers a walkthrough.
2. The walkthrough highlights actual UI in sequence: drag-select cells → create a normal shift for a teammate → create an **open** shift with a required position → run AI fill on the remaining open shifts → review/apply the AI suggestions → publish the week. The AI beat is individually skippable; the order is enforced when performed.
3. Each action is performed by the manager on real controls (not screenshots); the walkthrough advances when the action completes and can be exited/resumed at any time.
4. On publish, staff with the Team+ app get their schedule; the checklist marks "first schedule published."

### Secondary User Journey: Employee gets invited and lands in the Team+ app
1. Owner adds the employee to the roster (name, phone/email, pay rate, target hours, position) and clicks Invite.
2. Employee receives an email and/or SMS with an activation link. The link opens account activation (set password), and the completion screen says one thing: "Download BuyerKiosk Team+" with store badges for iOS/Android.
3. Employee installs Team+, logs in with the credentials just created, and sets weekly availability in the app (whether the app proactively prompts for availability on first login is a mobile-team dependency — see Dependency Register DR-8; the availability step does not depend on a prompt existing).
4. Back on the hub, the owner sees the employee flip to "invited → active → using the app" without doing anything; the availability step progresses as staff fill theirs in.

### Secondary User Journey: BK platform team runs the launch
1. Platform team publishes the System Alerts announcement with the hub CTA — only after every launch-gated feature in the Release Manifest is live.
2. During launch week, the team monitors the activation funnel **via the tracking events** (hub views, activations, step completions, invite sends, app logins) queried from the analytics store; the dedicated funnel UI (S5) is a fast-follow, not a launch prerequisite.
3. Stores stalled at a step (e.g., activated but zero invites after 7 days) are identified from the same events for proactive outreach.

---

## Release Manifest

Single source of truth for what ships when. "Launch gate" = must be live and verified before the announcement (M2) publishes.

| Feature | Release | Launch gate | Feature flag | Rollback |
|---|---|---|---|---|
| M1 Entitlement ungating | v1, deployed first | Yes | Config-level kill switch restores gate | Re-enable gate; no data loss |
| M2 Launch announcement | v1, published **last** | — (it *is* the gate consumer) | Alert unpublish | Unpublish alert |
| M3 Hub + checklist framework | v1 | Yes | Hub route flag | Hide hub; progress data retained |
| M4 Opt-in activation | v1 | Yes | Part of hub flag | Provider values untouched by rollback |
| M5 External-provider page | v1 | Yes | Part of hub flag | Hide page |
| M6–M8 Settings/Positions/Roster steps | v1 | Yes | Part of hub flag | Hide steps |
| M9 Invite delivery (email+SMS) | v1 | Yes | Invite-send flag (independent of hub) | Disable sending; tokens still issued |
| M10 Apps step (observed use) | v1 | Yes | Part of hub flag | Hide step |
| M11 First-schedule walkthrough | v1 | Yes | Walkthrough flag (independent) | Step falls back to deep link + article |
| M12 Availability step | v1 | Yes | Part of hub flag | Hide step |
| S1 Tasks-on-shifts step | v1 if capacity, else fast-follow | No | Part of hub flag (optional step) | Hide step |
| S2 QR app handoff | Phase 2 | No | — | — |
| S3 Adoption tracker panel | Phase 2 | No | — | — |
| S4 Sample-week demo data | Phase 2/3 | No | — | — |
| S5 BK-admin funnel UI | Fast-follow after launch | No (launch monitoring uses raw events) | — | — |
| F1–F4 Future opportunities | Not committed | No | — | — |

Rollout order: M1 code lands dark behind its kill switch → M3–M12 land behind the hub/walkthrough/invite flags → flags enabled and E2E-verified on pilot stores → M2 announcement publishes. The announcement is a separately-timed manual action, never coupled to a deploy.

---

## Feature Requirements

Acceptance criteria carry stable IDs (`M1-01` …) for traceability into the SDD and test plans.

### Must Have Features

#### Feature M1: Scheduling entitlement — free for all plans
- **User Story:** As a store owner, I want every scheduling capability included in my plan with no paywall anywhere — pages, APIs, mobile, and AI — so nothing I'm told is free ever asks me to upgrade.
- **Acceptance Criteria:**
  - [ ] **M1-01:** An **entitlement inventory** is produced in the SDD enumerating every premium check that scheduling touches: web pages (calendar, settings, timesheets, and any others found), API middleware, navigation items, background jobs, AI scheduling surfaces, and mobile API premium/entitlement flags. Every scheduling-scoped entry is removed or hard-coded entitled. The inventory is the authoritative scope of this feature — "three pages" is known to be incomplete.
  - [ ] **M1-02:** The AI scheduling surfaces are entitled for all stores (the M11 walkthrough's AI beat must work for a just-activated store). Existing AI permission gates (`uri_schedule_ai` etc.) remain — entitlement removal is not permission removal.
  - [ ] **M1-03:** Mobile API responses that expose premium/entitlement state report scheduling as entitled for all stores; behavior of the current released Team+/Live client versions against this change is validated with the mobile teams (coordination via the standard backend-API update docs) before launch.
  - [ ] **M1-04:** The trial banner no longer renders for any store; user-facing trial start/activate/deactivate endpoints are disabled (Decision D-2: premium infrastructure is retained dormant — code and billing data intact, no destructive cleanup).
  - [ ] **M1-05:** No store can land on the premium marketing page; its URL serves the M3 hub.
  - [ ] **M1-06:** A config-level kill switch can restore the gate without a code deploy (launch-safety rollback).
  - [ ] **M1-07:** Whether Team Chat (also premium-bundled under Spec 035) becomes free is resolved via Open Question Q5 before M1 implementation; this spec's inventory marks chat entries as "pending Q5" rather than silently flipping them.

#### Feature M2: Launch announcement
- **User Story:** As a store owner, I want to find out in the product that scheduling is now included, with one click taking me to getting started.
- **Acceptance Criteria:**
  - [ ] **M2-01:** A System Alerts (Spec 040) announcement with a CTA to the hub can be published to all stores. Audience is **all roles** (Decision D-3: Spec 040 has no role targeting; copy is written to be safe and meaningful for every role).
  - [ ] **M2-02:** Announcement reach is measured per user via the existing acknowledgment mechanism; **announcement → hub conversion is attributed via hub visits carrying `source=alert`**, not via acknowledgments (Spec 040 shows one alert per user across stores, so acknowledgment cannot attribute per-store conversion).
  - [ ] **M2-03:** The announcement is publishable only as a manual action, sequenced after all launch-gated features are verified live (Release Manifest).
  - [ ] **M2-04:** The pre-launch baseline snapshot (see Success Metrics) is captured before the announcement publishes.

#### Feature M3: Getting Started hub with self-detecting checklist
- **User Story:** As a store owner, I want one page that shows everything needed to fully set up scheduling and tracks progress automatically, so I always know what's left.
- **Acceptance Criteria:**
  - [ ] **M3-01:** A hub page exists in the scheduling admin area at the URL previously serving premium marketing content, reachable from the scheduling navigation. After setup completes it remains as a compact "Setup" entry (Decision D-4).
  - [ ] **M3-02:** The checklist steps, their actors, prerequisites, deterministic completion rules, denominators, historical-data handling, manual/skip rules, and analytics events are exactly as defined in the **Step Contract Table** (Detailed Specifications). No step's semantics exist outside that table.
  - [ ] **M3-03:** Each step shows status (blocked / not started / in progress / complete), a plain-language description, a time estimate, the *evidence* behind its auto-detected state (e.g., "3 positions created", "2 of 7 staff missing pay rate"), and a deep link to the surface where the work happens.
  - [ ] **M3-04:** Progress is stored per store; two managers see the same progress. Walkthrough completion state is per user.
  - [ ] **M3-05:** Completion semantics: step completion is an **achievement (latched)** — once complete, it never regresses. Separately, a step whose underlying condition later degrades (e.g., all positions deleted) shows a non-blocking **health indicator** on the completed step. The only exception: switching the provider away from BK-native hides the whole flow.
  - [ ] **M3-06:** Required steps cannot be skipped toward setup completion. Only steps marked skippable in the contract table (optional steps) can be skipped. Manual "mark done" exists only for steps the contract table marks manual-eligible (heuristic detections), records who/when, and counts toward completion.
  - [ ] **M3-07:** "Setup complete" = all required steps complete (auto or eligible-manual). Optional steps never block completion.
  - [ ] **M3-08:** Detection is resilient: if a detection query fails, the step shows its last-known state with a staleness note — never a false "complete" and never a hard error page. Detection freshness NFR: state reflects reality within 5 minutes of a change or on page load, whichever is sooner.
  - [ ] **M3-09:** The checklist framework is module-agnostic (a flow definition: steps, detections, thresholds, prerequisites) so future modules can define flows without schema changes. Thresholds live in the flow definition, versioned; a threshold change applies only to not-yet-complete steps (`flowVersion` is carried on events).
  - [ ] **M3-10:** Grandfathered stores see every detectable step auto-complete on first render — they are never asked to redo setup.
  - [ ] **M3-11:** Permission matrix (reusing existing permissions, no new ones): view hub + step states = scheduling management permission; activate/switch provider = store-settings permission; roster step's wage details respect the existing sensitive-field gate (users without it see completeness counts, not amounts); invites = employee-management permission; steps whose page the current user cannot access render with a "requires an owner/admin" note instead of a broken link.

#### Feature M4: Opt-in activation (BK-native-eligible stores)
- **User Story:** As a store owner, I want turning on scheduling to be an explicit choice that I make once, safely, even if two of us click it at the same time.
- **Acceptance Criteria:**
  - [ ] **M4-01:** "Get Started" sets the store's scheduling provider to BuyerKiosk-native and records who activated and when (audit record).
  - [ ] **M4-02:** Activation is idempotent and concurrency-safe: two simultaneous activations produce one state change and one `onboarding.activated` event; an already-activated store's button is a no-op that shows the checklist.
  - [ ] **M4-03:** The provider/premium state matrix (Detailed Specifications) defines hub behavior for every combination of provider value (unset/none, BK-native, WIW, Homebase, unrecognized legacy value) × premium state (none/trial/active/expired) × configuration state (empty/partial/historically active). Unrecognized provider values are treated as external (safe default) and logged.
  - [ ] **M4-04:** No store's provider is changed by migration, announcement, or any path other than this explicit action (and M5's support-assisted path).

#### Feature M5: External-provider awareness
- **User Story:** As an owner whose store schedules through When I Work or Homebase, I want to understand what BK-native scheduling would add without any button that could break my integration.
- **Acceptance Criteria:**
  - [ ] **M5-01:** External-provider stores see an integration-aware page: what BK-native scheduling adds, what a switch would change (schedules, punches, sync, apps), and a "talk to support about switching" path.
  - [ ] **M5-02:** **v1 switching is support-assisted only** (Decision D-5): there is no self-service switch. The page's CTA opens a support contact flow that captures the store's interest (tracked). Self-service migration is future work with its own spec.
  - [ ] **M5-03:** Nothing on this page mutates provider state.

#### Feature M6: Store settings step
- **User Story:** As a store owner, I want the essential scheduling settings confirmed in minutes, with sensible defaults, so misconfiguration doesn't poison everything downstream.
- **Acceptance Criteria:**
  - [x] **M6-01 (AMENDED 2026-07-28 by product owner during self-testing):** the step covers work week start day, store timezone, store hours, and clock-in rules — and store hours are **per-day-capable, edited as three groups: Weekdays (Mon–Fri), Saturday, Sunday** (covers ~99% of stores; per-day granularity beyond that lives on the full settings page). Backed by the EXISTING Spec 018 `storeOperatingHours` model (per-day rows, NULL ⇒ default pair, per-day closed flag) via `StoreHoursService` — no new schema. Display summarizes with smart grouping ("Mon–Fri 9 AM–9 PM · Sat 9 AM–8 PM · Sun closed"). Defaults pre-populated; review-and-confirm without editing still completes the step. *Original text noted a single "store open/close hours" pair.*
  - [ ] **M6-02:** Completion is an explicit **"confirm settings" action in the step** (recorded who/when), because defaults may equal desired values and pure data-detection cannot distinguish "reviewed" from "never looked." The step additionally shows live values so the confirmation is informed. Clock-in rules are part of the confirmed set.
  - [x] **M6-03 (AMENDED 2026-07-23 by product owner during self-testing):** the step now offers **inline editing of the essential settings directly in the checklist card** — work week start day, timezone, open/close hours, and clock-in rules — posting to the EXISTING settings write API (`SchedulingController::updateConfig`; one write path, no duplicated backend editor). The deep link to the full settings page remains for advanced settings. Values render in plain language, not raw config keys. *Original text (superseded): "The step deep-links into the existing scheduling settings page sections rather than duplicating the settings UI." Rationale for the change: bouncing to a separate page mid-onboarding loses the flow; drift risk is contained by reusing the single existing write endpoint.*
  - [ ] **M6-04:** Overnight/24-hour store hours and half-hour timezones are accepted; a later timezone change surfaces the M3-05 health indicator on this step (schedules keep the store-timezone-canonical behavior that already exists).

#### Feature M7: Positions step
- **User Story:** As a store owner, I want to define my store's roles (e.g., Keyholder, Buyer, Floor) before building the roster, because positions drive shift color-coding, open-shift eligibility, and AI fill.
- **Acceptance Criteria:**
  - [ ] **M7-01:** The step explains what positions do (calendar color, open-shift qualification, swap eligibility, AI constraints) and deep-links to the existing positions management UI.
  - [ ] **M7-02:** Suggested starter positions are offered for one-click creation.
  - [ ] **M7-03:** Completion per Step Contract Table: ≥ 1 active position exists (historical/pre-activation positions count — configuration is configuration).

#### Feature M8: Team roster step (pay rates + hours)
- **User Story:** As a store owner, I want my team on the roster with pay rates, target weekly hours, and position qualifications — complete enough that labor projections and AI fill work — with the gaps shown to me by name.
- **Acceptance Criteria:**
  - [x] **M8-01 (AMENDED 2026-07-28 by product owner during self-testing — "inline-first" hub direction):** the roster step offers **inline quick-add**: create a team member directly in the checklist card with ONLY the required-to-get-started fields — first/last name, email or phone (≥1, needed for invites), hourly rate, target hours/week — via the existing canonical write path (`TeamMemberService::createTeamMember`; global users tables, never the deprecated employees table). Full detail (positions, permissions, wage history, etc.) lives on the Team Members page, reachable via a demoted "Team management →" advanced link (same pattern as the M6-03 amendment). *Original text: "The step deep-links to the existing Team Members page and its add-member wizard." Broader principle recorded: every step that CAN complete inline should — deep links become the advanced/escape path, not the primary action.*
  - [ ] **M8-02:** The step surfaces roster completeness per member: missing pay rate (hourly rate must be > 0 to count), missing hours preferences (target hours > 0), missing contact channel (no email AND no phone), missing position qualifications — building on the existing missing-field flags.
  - [ ] **M8-03:** Completion per Step Contract Table: ≥ 2 active non-owner members AND ≥ 80% of active non-owner members have pay rate and target hours set. Fractions are compared exactly (no rounding). Zero-denominator (no non-owner staff) renders the step "blocked — add your team first," not complete.
  - [ ] **M8-04:** The roster uses the canonical global users tables; nothing in this flow touches the deprecated store-level employees table.
  - [ ] **M8-05:** Staff added *after* the step completed do not un-complete it (M3-05 latching); incomplete new members appear in the step's health indicator.

#### Feature M9: Invite delivery — email and SMS
- **User Story (owner):** As a store owner, I want "Invite" to actually reach my employee — by email and/or text — with a link that gets them a working login and points them at the app.
- **User Story (employee):** As an employee, I want the invite I receive to get me from a text or email to a working app login in one sitting, on my phone, without asking anyone for help.
- **Acceptance Criteria:**
  - [ ] **M9-01:** Channel semantics: when a member has both email and phone, **both channels send by default**; the pre-send confirmation shows per-member channels and lets the sender deselect. Members with no reachable channel are flagged with a fix-contact-info link and are never silently skipped.
  - [ ] **M9-02:** Per-channel message states are tracked: queued → sent → delivered / failed / bounced (where the channel reports them), plus token-level states: active, expired, revoked, activated. The roster/hub shows per-member invite status derived from these.
  - [ ] **M9-03:** Invite records are **store-scoped and channel-scoped** (who invited whom, to which store, on which channel, when, with what outcome) — the current user-global token is not sufficient history and the SDD will design the store/member/channel model.
  - [ ] **M9-04:** Bulk invite ("invite all not-yet-invited") is idempotent per member, reports partial failures individually (N sent, M failed with reasons), and is rate-limited per run and per day (limits in SDD) to protect sender reputation.
  - [ ] **M9-05:** A member who already has an active login (e.g., an existing user newly assigned to this store) shows "active" and is offered a "notify about this store + app" message instead of a redundant credential invite.
  - [ ] **M9-06:** Activation links are single-use, expire per existing token rules, are never logged in plaintext, and an expired/revoked link renders a friendly "ask your manager to resend" page. Resend invalidates prior tokens per current behavior; a resend race (member clicks old link post-resend) gets the friendly page, not an error.
  - [ ] **M9-07:** The activation completion screen promotes the right app for the member's role (Team+ for staff, Live for manager/owner roles; dual-role members see both, Live first) with correct store badges per platform.
  - [ ] **M9-08:** SMS sends flow through the Spec 037 cost-tracking pipeline under a **new `schedulingInvite` message category** (the current Spec 037 taxonomy has no invite category — adding it is in scope). Billing treatment follows the store's existing SMS billing policy (Decision D-6); the distinct category keeps a future "BK absorbs invite costs" decision executable.
  - [ ] **M9-09:** SMS respects existing opt-out/compliance handling; invalid/landline numbers surface as failed with reason. A store with SMS unavailable degrades to email-only with a clear indicator.
  - [ ] **M9-10:** Message content is BuyerKiosk-branded and store-identified ("Casey at PLATO'S has added you…").
  - [x] **M9-11 (AMENDED 2026-07-28 by product owner — combined Team card):** invite sending never blocks roster creation, AND the hub presents roster + invites as **one combined "Build your team" card**: the inline quick-add gains a "Send invite right away" checkbox (default ON when invite sending is enabled and the member has a contact channel), so create-and-invite is a single motion; per-member invite chips render in the same card. Batch "invite everyone not yet invited" remains for build-first-invite-later workflows. **Contracts unchanged:** roster and invites remain two tracked steps (M8-03 + invites row of the Step Contract Table — completion rules, events, and KPIs untouched); this is a presentation merge only. *Original text: "Invite sending never blocks roster creation — build the full roster first, invite in one batch."*
  - [ ] **M9-12:** Channel attribution on activation: each channel's message carries a channel-tagged link (same token, distinct tracking), so `invite.activated` can attribute the winning channel even when both sent.

#### Feature M10: Apps-on-phones step (observed app use)
- **User Story:** As a store owner, I want to see which of my staff are actually *using* the app — logged in, receiving pushes — so "get the apps" isn't a step I take on faith. (Installation itself is not directly observable; observed use is the honest signal.)
- **Acceptance Criteria:**
  - [ ] **M10-01:** The step explains the two apps (Team+ for staff, Live for managers/owners) with app-store links for both platforms.
  - [ ] **M10-02:** Per-member status ladder: invited → account active → **app login observed** → push-registered, distinguishing Team+ from Live. Push registration is a bonus signal (denied notification permission must not hide a real login).
  - [ ] **M10-03:** **Signal validation is an SDD gate:** device push registrations distinguish the app; mobile login sessions may not (they are user-scoped and may not carry an app identifier). The SDD must validate that per-app login is reliably distinguishable from existing data; if not, the fallback is a backend-defined app-identifier addition to the mobile login contract (backend change + mobile apps send one field; coordinated via the standard update docs). The "no mobile work required" assumption holds only if validation passes.
  - [ ] **M10-04:** Signals are user-scoped, not store-scoped: a member's app use counts for every store they're assigned to (documented behavior, not a bug).
  - [ ] **M10-05:** Staleness: "app login observed" uses activity within the last 30 days; an old token from an uninstalled app does not tick the step (freshness window in the flow definition).
  - [ ] **M10-06:** Completion per Step Contract Table: ≥ 50% of active non-owner staff with app login observed AND ≥ 1 manager/owner with Live login observed. Manual-eligible (heuristic thresholds).
  - [ ] **M10-07:** Per-member "resend invite" is available inline.

#### Feature M11: First-schedule guided walkthrough
- **User Story:** As a manager, I want the product to teach me the calendar by doing — create a shift, create an open shift, AI-fill the week, publish — so my first week doesn't start with a blank grid and a support ticket.
- **Acceptance Criteria:**
  - [ ] **M11-01:** An interactive walkthrough runs on the real calendar page, highlighting actual controls step-by-step; each beat advances when the manager performs the real action.
  - [ ] **M11-02:** The beat sequence is: assigned shift → open shift with required position → AI fill (generate, review, apply) → publish. Order is enforced *when beats run*; the **AI beat is individually skippable** (stores that don't want AI still complete the walkthrough). Open-shift creation strictly precedes any AI beat (AI fill requires open shifts to exist).
  - [ ] **M11-03:** Exit-able and resumable at any beat; completion per user. Real shifts created during an abandoned walkthrough persist (they are real data) — the resume prompt says so.
  - [ ] **M11-04:** Prerequisite: ≥ 1 active non-owner roster member; otherwise the walkthrough refuses gracefully and points at the roster step.
  - [ ] **M11-05:** Adaptive: if the week already has shifts, already-satisfied beats are acknowledged rather than forced.
  - [ ] **M11-06:** Defined AI edge behavior: zero suggestions returned, no qualified candidate for a position, generation failure/timeout — each surfaces honestly with retry, and the walkthrough can proceed to publish without AI. A locked/already-published week routes the publish beat to acknowledge the existing publish.
  - [ ] **M11-07:** Declarative step definitions so additional walkthroughs can be authored without new engine work. The engine must support pause/resume on viewport change and meet the accessibility NFRs (keyboard-advance, focus management, screen-reader labels on coach marks).
  - [ ] **M11-08:** If the walkthrough engine is unavailable (flag off, incompatibility), the checklist step degrades to a deep link + support article — never a broken button.
  - [ ] **M11-09:** Checklist completion per Step Contract Table: first published week at-or-after activation, with or without the walkthrough.

#### Feature M12: Availability step
- **User Story:** As a store owner, I want staff availability collected before we lean on AI fill, so the schedule reflects when people can actually work.
- **Acceptance Criteria:**
  - [x] **M12-01 (AMENDED 2026-08-10 by product owner — continuation of the M8-01 inline-first sweep; owner-verified live 2026-08-10):** the manager path happens **inline in the checklist card**: for each member without availability, a grouped Weekdays/Saturday/Sunday editor (mirroring the M6-01 grouped store-hours pattern) writes through the EXISTING canonical endpoint (`PUT /api/:typeNum/schedule/availability/:employeeId`, `uri_schedule_manage`-gated; no new backend routes). Members who already have availability are never editable inline (the endpoint replaces all patterns — a grouped rewrite would destroy per-day detail); their edit path is the demoted "Availability page →" advanced link. Staff self-service in Team+ remains the preferred path and is stated on the card. *Original text: "…or a manager records it in the web availability page on their behalf."*
  - [x] **M12-02 (AMENDED 2026-08-10, same pass; owner-verified live 2026-08-10):** per-member status renders with **display names** (COALESCE displayName → first+last, "User #id" as last resort), not raw user IDs — the `memberAvailability` ladder is emitted server-side on the step payload (`{userId, name, set}`, not-set first). The apps step's `appAdoption` ladder gains the same `name` field. *Original text unchanged in substance; the 2026-07-22 build shipped the panel without a backend ladder, so it never rendered.*
  - [ ] **M12-03:** Completion per Step Contract Table: ≥ 50% of active non-owner staff with any availability recorded (recurring or exception). Manual-eligible.
  - [ ] **M12-04:** Recommended before the AI beat of M11 but never hard-blocks it; the walkthrough notes when availability coverage is low.

### Should Have Features

#### Feature S1: Tasks-on-shifts step (advanced, optional)
- **User Story:** As a manager, I want to learn that task lists can be attached to shifts, so opening/closing duties ride along with the schedule.
- **Acceptance Criteria:**
  - [ ] **S1-01:** An optional checklist step introduces shift task assignment and deep-links to the shift editor's task tab and the task-list management page.
  - [ ] **S1-02:** Completion: ≥ 1 shift with tasks assigned. Optional — skippable, never blocks setup completion.

#### Feature S2: QR app handoff (Phase 2)
- **User Story:** As a store owner, I want to hand a phone a QR code that lands on the right app store listing.
- **Acceptance Criteria:**
  - [ ] **S2-01:** Hub and invite messages include QR codes resolving to the correct app listing per platform, audience-aware (Team+ vs Live).

#### Feature S3: Adoption tracker panel (Phase 2)
- **User Story:** As a store owner, I want a dedicated adoption dashboard: sortable member list with last-app-activity and app-version visibility.
- **Acceptance Criteria:**
  - [ ] **S3-01:** Expands M10's status list into a dedicated panel (sorting, last activity, app version); lives in the post-completion "Setup" area (Decision D-4).

#### Feature S4: Sample-week demo data (Phase 2/3)
- **User Story:** As a manager exploring before my roster is ready, I want a realistic sample week on the calendar so the walkthrough and AI fill have something to show.
- **Acceptance Criteria:**
  - [ ] **S4-01:** One-click seeding of a clearly-labeled sample week for stores with an empty calendar; builds on the existing scheduling demo-data seeding script.
  - [ ] **S4-02:** Sample data is visually marked and removable with one click, leaving zero residue in payroll/timesheet paths.

#### Feature S5: BK-admin activation funnel UI (fast-follow)
- **User Story:** As the BK platform team, I want a cross-store funnel view (announced → hub → activated → steps → complete) with days-in-step, so stalled stores are visible without hand-written queries.
- **Acceptance Criteria:**
  - [ ] **S5-01:** BK-admin page (existing BK-admin permission) listing stores with flow state, current step, days-in-step; funnel counts by step for a date range.
  - [ ] **S5-02:** Launch monitoring does not depend on this UI — all underlying events ship in v1 (see Tracking).

### Future Opportunities (not committed, no acceptance criteria)

These are directions, not feature requirements; each needs its own scoping before commitment.

- **F1: Lifecycle nudges** — automated reminders to owners of stalled stores and uninvited staff, frequency-capped, via the task engine.
- **F2: CSV roster import** — bulk member import (export exists; import doesn't); most valuable for larger stores.
- **F3: Onboarding flows for other modules** — the M3 framework is deliberately generic; authoring flows for Backstock, QuickBooks, etc.
- **F4: Per-step video embeds** — short recordings complementing the walkthrough, hosted via the support-article system.

### Won't Have (This Phase)

- **Auto-flipping any store's scheduling provider** by migration or announcement — activation is always an explicit user action (locked decision, 2026-07-20).
- **Self-service provider switching for WIW/Homebase stores** — v1 is informational + support-assisted only (Decision D-5); a migration-assist flow is future work with its own spec.
- **In-app onboarding inside the Team+ / Live mobile apps** — mobile-side first-run improvements are separate mobile specs; this spec's mobile touchpoints are invite links landing well on phones, adoption detection, and the M1-03/M10-03 contract validations (coordinated via the standard inter-agent API update docs).
- **Employee-facing web onboarding** — the web flow targets owners/managers; employees onboard via invite → app.
- **Removing the Premium module code/data** — retained dormant (Decision D-2); commercial wind-down is Open Question Q1 + Spec 036 territory, not this spec.
- **New scheduling features** — this spec teaches and activates what exists. Net-new product plumbing is limited to: invite delivery (M9), the checklist framework + progress persistence + detection engine (M3), and the walkthrough engine (M11). It is *not* claimed to be "invite delivery only."

---

## Detailed Feature Specifications

### M3 — Step Contract Table (authoritative checklist semantics)

Global rules: completion is latched (M3-05); required steps are not skippable; manual completion only where marked; every first `complete` transition emits `onboarding.step_completed` exactly once (idempotent). "Post-activation" = signal timestamp ≥ activation timestamp; grandfathered stores treat all historical data as valid. Denominator base: active non-owner staff (see Eligible Cohorts); zero denominator ⇒ step state "blocked," never complete.

| Step key | Required | Actor | Permission (existing) | Hard prereqs | Complete when | Historical data | Manual/skip |
|---|---|---|---|---|---|---|---|
| `activate` | Yes | Owner/admin | store-settings | — | Provider = BK-native (event-sourced or detected for grandfathered) | Grandfathered auto-complete | Neither |
| `storeSettings` | Yes | Owner/admin | store-settings | `activate` | Explicit "confirm settings" action recorded (M6-02) | Confirmation never inferred | Neither (confirm *is* the action) |
| `positions` | Yes | Manager+ | scheduling-manage | `activate` | ≥ 1 active position | Counts | Neither |
| `roster` | Yes | Manager+ | employee-manage (wage detail: sensitive-field gate) | `activate` | ≥ 2 active non-owner members AND ≥ 80% of them have pay rate (> 0) and target hours (> 0) | Counts | Neither |
| `invites` | Yes | Manager+ | employee-manage | `activate`, `roster` in progress (≥ 1 member) | Every active non-owner member either has an active login or has ≥ 1 invite sent; members with no reachable channel don't block but are surfaced | Existing logins count | Neither (deterministic) |
| `apps` | Yes | Staff act; owner monitors | view: scheduling-manage | `invites` | ≥ 50% active non-owner staff with app login observed (30-day freshness) AND ≥ 1 manager/owner Live login observed | Grandfathered: historical counts; new stores: post-activation | Manual-eligible |
| `availability` | Yes | Staff act; manager can enter | view: scheduling-manage; edit: scheduling-manage | `activate`, `roster` | ≥ 50% active non-owner staff with any availability recorded | Counts | Manual-eligible |
| `firstSchedule` | Yes | Manager+ | scheduling-manage | `activate`, `roster`, `positions` | ≥ 1 published week at-or-after activation (any week, walkthrough or not) | Grandfathered: historical publish counts | Neither |
| `shiftTasks` | No (optional) | Manager+ | scheduling-manage | `firstSchedule` | ≥ 1 shift with tasks assigned | Counts | Skippable |
| `firstClockIn` | **No — milestone, not a step** | Staff | — | `firstSchedule` | First time punch at-or-after first post-activation publish | Post-activation only | — |

**`firstClockIn` is deliberately not part of setup completion** (Decision D-7): some stores schedule in BuyerKiosk but don't use BK time clocking (and WIW-history stores never will). It is tracked as an *operational activation milestone* (`onboarding.milestone_first_clock_in`) and shown on the completed hub as a "you're live!" moment when it happens — it never gates "setup complete."

Detection failure behavior (all steps): show last-known state + staleness note (M3-08). Duplicate detection events must not double-emit analytics (first-transition-only rule).

### M4/M5 — Provider & premium state matrix

| Provider value | Premium state | Config state | Hub renders | Get Started | Notes |
|---|---|---|---|---|---|
| unset / none | any | empty | Pre-activation pitch + checklist preview | Yes | The primary launch cohort |
| unset / none | expired trial | partial (tried during trial) | Pre-activation + "your earlier setup is preserved" | Yes | Detected steps pre-complete on activation |
| BK-native | any | any | Checklist (activate auto-complete) | Hidden | Grandfathered; M3-10 |
| WIW | any | any | M5 integration-aware page | No — support-assisted path only | Never auto-flip (ADR-5 lineage) |
| Homebase | any | any | M5 integration-aware page | No — support-assisted path only | Same |
| unrecognized legacy value | any | any | M5 page (safe default) + internal log | No | Surfaced to BK for data cleanup |

Switching effects (support-assisted, v1): documented in the support runbook, not automated — which is exactly why v1 has no self-service switch (Decision D-5).

### M9 — Invite delivery (email + SMS)

**Description:** Completes the existing invite flow, which today persists an activation token and sends nothing. Hard prerequisite for the roster, apps, and availability steps to mean anything. The SDD must design store/member/channel-scoped invite history (M9-03) — the current token storage is user-global with no store context and no delivery states.

**User Flow:**
1. Owner clicks Invite on a member (or "Invite all" for every not-yet-invited member).
2. Pre-send confirmation lists per-member channels (email / SMS / both — both preselected when both exist, deselectable) and flags unreachable members with a fix-contact-info link.
3. Messages send; roster and hub show per-member, per-channel status (M9-02) with resend controls.
4. Employee opens the link on their phone, sets a password, lands on the role-appropriate app-download screen (M9-07).

**Business Rules:** single-use tokens, existing expiry/resend/revoke semantics; per-run and per-day bulk rate limits (SDD); Spec 037 `schedulingInvite` category (M9-08); store-identified copy (M9-10); never blocks roster creation (M9-11); channel-tagged links for activation attribution (M9-12).

**Edge Cases:** no reachable channel (flagged, never silent); already-active user newly assigned to the store (notify, don't re-credential — M9-05); expired/revoked/post-resend link (friendly page); partial bulk failure (per-member outcomes); duplicate delivery callbacks (idempotent state updates); opted-out/landline numbers (failed with reason); email bounce (failed state + surfaced); queue outage (queued state visible, retry per SDD); member deactivated between send and activation (token revoked on deactivation per existing behavior).

### M11 — First-schedule guided walkthrough

**Description:** An interactive coach-mark walkthrough over the real calendar, teaching the four-beat story (shift → open shift → AI fill → publish) in the only order that works. The module's "aha" delivery vehicle.

**User Flow:** as in the journey map; beats per M11-02 with the AI beat skippable.

**Business Rules:** real controls and real data only — nothing simulated; beat order enforced when beats run; AI beat requires ≥ 1 open shift on the visible week (true by construction); per-beat skip; declarative definitions; per-user completion; checklist step completes on first post-activation published week regardless of walkthrough use (M11-09).

**Edge Cases:** empty roster (refuse → roster step, M11-04); AI failure/timeout/zero-suggestions/no-qualified-candidate (M11-06); locked/published week (acknowledge); viewport change/canceled editor popups (pause + resume); two managers concurrently (independent completion; real shifts coexist); walkthrough engine unavailable (M11-08 fallback); shifts created then walkthrough abandoned (persist; resume prompt says so).

---

## Success Metrics

### Pre-launch baseline (M2-04 launch gate)
Captured once before the announcement: count of BK-native-eligible stores; % of them with ≥ 1 position / ≥ 2 rostered members with pay rates / ≥ 1 published week in the last 60 days; current scheduling-setup support-ticket rate (taxonomy per Q4 owner); median time-to-first-publish for the grandfathered cohort (historical). This turns the problem statement's hypotheses into testable claims.

### Key Performance Indicators

Each KPI: numerator / denominator / cohort / clock. Cohort membership snapshots at announcement publish; timezone = store-local; measurement owner and cadence assigned at launch (platform team, weekly).

| KPI | Definition | Target |
|---|---|---|
| Announcement → hub conversion | Stores with ≥ 1 `onboarding.hub_viewed(source=alert)` ÷ stores where ≥ 1 user acknowledged the alert, within 7 days of publish | ≥ 60% |
| Activation (30/90d) | Stores with `onboarding.activated` ÷ BK-native-eligible stores (excl. grandfathered), within 30 / 90 days | ≥ 40% / ≥ 60% |
| Setup completion | Stores with `onboarding.flow_completed` ÷ activated stores, within 14 days of their activation | ≥ 50% |
| Invite efficacy | `invite.activated` ÷ invites with ≥ 1 channel reaching `delivered` (fallback `sent` where the channel reports no delivery state), within 7 days of send | ≥ 70% |
| App adoption | Members with `onboarding.app_adopted(app=team)` ÷ members who activated an account, within 7 days of account activation | ≥ 50% |
| First publish | Stores with `schedule.week_first_published` ÷ activated stores, within 14 days of activation | ≥ 60% |
| AI usage | Stores with ≥ 1 `ai.fill_applied` ÷ stores with ≥ 1 published week, within 30 days of activation | ≥ 40% |
| **Durable adoption** | Stores publishing a week in ≥ 3 of weeks 3–6 post-activation ÷ activated stores | ≥ 40% |
| Support load | Scheduling-setup-tagged tickets per activated store ≤ pre-launch baseline rate (Q4 taxonomy) | ≤ baseline |

### Tracking Requirements

All events carry `typeNum`, `flowVersion`, and (where applicable) `campaignId` for the announcement wave. Invite events carry stable `inviteId` (per member+store) and `messageId` (per channel attempt).

| Event | Properties | Purpose |
|-------|------------|---------|
| `onboarding.hub_viewed` | `userId`, `flowState`, `source` (alert/nav/direct) | Funnel top; conversion attribution (M2-02). |
| `onboarding.activated` | `userId`, `previousProvider` | Activation KPI. Emitted once (M4-02). |
| `onboarding.step_completed` | `stepKey`, `detection` (auto/manual), `daysSinceActivation` | Step funnel + time-in-step. First transition only. |
| `onboarding.step_skipped` | `stepKey`, `userId` | Optional-step refusal signal. |
| `onboarding.flow_completed` | `daysSinceActivation`, `manualSteps[]`, `skippedOptionalSteps[]` | Setup-completion KPI (manual vs auto visible). |
| `onboarding.milestone_first_clock_in` | `daysSinceActivation` | Operational activation (Decision D-7). |
| `onboarding.walkthrough_started` / `_completed` / `_exited` | `userId`, `walkthroughKey`, `beat`, `aiSkipped` (bool, on completed) | Walkthrough engagement + drop-off beat. |
| `invite.sent` | `inviteId`, `messageId`, `memberUserId`, `channel`, `bulk` (bool) | Invite volume; channel mix. |
| `invite.delivery_updated` | `messageId`, `state` (delivered/failed/bounced), `reason` | Delivery funnel; invite-efficacy denominator. |
| `invite.activated` | `inviteId`, `memberUserId`, `hoursSinceSend`, `winningChannel` | Efficacy KPI; channel attribution via M9-12. |
| `invite.expired_unactivated` | `inviteId`, `memberUserId` | Funnel leak; resend candidates. |
| `onboarding.app_adopted` | `memberUserId`, `app` (team/live) | App-adoption KPI (first observed app login per app). |
| `schedule.week_first_published` | `weekStart`, `daysSinceActivation` | First-publish + durable-adoption KPIs (per published week). |
| `ai.fill_generated` / `ai.fill_applied` | `weekStart`, `solverType`, `suggestionsApplied` | AI-usage KPI (reuse existing AI events if present; else add). |
| `onboarding.provider_switch_interest` | `userId`, `currentProvider` | M5 support-assisted switch demand signal. |

Announcement reach uses the existing System Alerts acknowledgment data (per user), reported as reach only — never as per-store conversion (M2-02).

---

## Dependency Register

| ID | Dependency | Owner | Status | Needed by | Fallback | Launch gate |
|---|---|---|---|---|---|---|
| DR-1 | Spec 035 premium infrastructure (gate removal target) | Scheduling | Shipped, live | M1 | — | Yes |
| DR-2 | Spec 036 billing — commercial decision for paying premium subscribers (Q1) | Product/Billing | **Open** | M2 publish | Delay announcement (gate removal can still deploy dark) | **Yes — announcement blocker** |
| DR-3 | Spec 037 SMS cost tracking + new `schedulingInvite` category | Scheduling | Category addition in scope (M9-08) | M9 | Email-only invites (M9-09 degradation) | Yes |
| DR-4 | Spec 040 System Alerts (announcement vehicle) | Platform | Shipped, live | M2 | — | Yes |
| DR-5 | Transactional email path (sender identity, domain/DNS, bounce callbacks, throughput) — Q3 | Scheduling + Ops | SDD decision | M9 | None — invite email is v1-critical | Yes (deliverability verified) |
| DR-6 | SMS provider delivery callbacks (per-message state) | Scheduling | Exists (Twilio plumbing); wiring per SDD | M9-02 | `sent`-state-only funnel (KPI fallback noted) | No |
| DR-7 | Mobile app signal contract for per-app login detection (M10-03) | Scheduling + Mobile teams | **SDD validation gate** | M10 | Backend-defined app-identifier field on login; mobile apps send it (coordinated via update docs) | Yes (either path verified) |
| DR-8 | Team+ availability entry (and whether first-login prompts for it) | Mobile team | Availability APIs exist; prompt behavior unconfirmed | M12 (journey wording only) | Step works without a prompt (manager path + staff navigating manually) | No |
| DR-9 | Walkthrough library compatibility (Syncfusion overlay, CSP, keyboard nav, responsive) | Scheduling | SDD spike | M11 | M11-08 deep-link + article fallback | No (fallback exists) |
| DR-10 | Analytics sink for tracking events + helpdesk ticket taxonomy for the support-load KPI | Platform | Events: v1 scope; taxonomy: Q4 owner | Metrics | Manual ticket tagging at launch | Partial (events yes, taxonomy no) |
| DR-11 | App-store listing URLs for Team+/Live (both platforms) | Marketing | Exist (static links in marketing pages) | M10-01 | — | Yes |

---

## Constraints and Assumptions

### Constraints
- **Locked decisions (2026-07-20, confirmed with product owner via AskUserQuestion):** (1) activation is opt-in via the hub — no auto-enable; (2) UX is a persistent checklist hub + interactive walkthroughs — not a linear wizard; (3) the walkthrough engine is the vendored driver.js library (MIT) subject to the DR-9 spike; (4) v1 plumbing scope is invite email + SMS delivery — QR handoff (S2) and sample data (S4) are deferred.
- All schema changes go through the conductor migration system; no manual SQL. Column/table naming is camelCase.
- Multi-store architecture is respected: onboarding progress is keyed by store (typeNum) in the central shared DB alongside the stores it describes.
- Provider gating uses `schedulingProvider` as the source of truth (never the legacy `wiwEnable` flag), consistent with ADR-5 lineage from Spec 049.
- Roster and invites operate exclusively on the canonical global users tables (`users`, `userStoreAssignments`); the deprecated store-level employees table is untouched.
- All new UI uses Bootstrap 5 design tokens; Syncfusion components are preferred where applicable; any modal follows the wrapper-relocation pattern (CLAUDE.md mandatory).
- The announcement rides the existing System Alerts system (Spec 040); this spec introduces no parallel announcement mechanism and requests no Spec 040 changes (Decision D-3).
- PHPStan stays clean on all touched files; existing permission gates are reused per M3-11 — no new permission URIs.
- Mobile coordination: every mobile-facing behavior change (invite landing pages, entitlement flags, app-identifier contract if DR-7 fallback triggers) is documented in the standard backend-API update docs for the Team and Live app agents.

### Assumptions
- **A1 (downgraded to validation, see M10-03/DR-7):** per-app adoption detection *may* be derivable entirely from existing data; the SDD validates this. The PRD no longer assumes it.
- **A2:** The existing transactional email plumbing (used by close reports and AI-schedule notifications) is a viable starting point for invite email; sender identity/deliverability is Q3 (SDD decision, launch-gated verification per DR-5).
- **A3:** The existing scheduling demo-data seeding script is a viable foundation for S4 when Phase 2/3 arrives.
- **A4:** Existing premium stores' configurations are valid as-is; the hub detects rather than re-runs their setup (M3-10).
- **A5:** Store owners will accept a checklist that spans days — the design assumes multi-session completion, not a single sitting.
- **A6:** The Getting Started hub replacing the premium marketing URL does not break external links worth preserving (marketing links can be redirected).

---

## Non-Functional Requirements

- **NFR-1 Hub performance:** hub renders with all step states in ≤ 2s at p95; detection queries are read-only and never lock scheduling tables.
- **NFR-2 Detection freshness:** step state reflects underlying data within 5 minutes of change or on page load, whichever is sooner (M3-08).
- **NFR-3 Invite reliability:** invite enqueue is durable (a send accepted by the UI is never silently lost); per-message states are queryable; failed sends are visible within 15 minutes.
- **NFR-4 Accessibility:** hub and walkthrough meet the product's existing accessibility bar; walkthrough specifically supports keyboard advance/exit, focus management, and screen-reader labels on coach marks (M11-07).
- **NFR-5 Supported environments:** the admin-supported browser/viewport matrix applies; the walkthrough additionally supports the calendar page's minimum supported viewport; activation-link pages are mobile-first.
- **NFR-6 PII:** wage amounts respect the existing sensitive-field permission everywhere they appear (M3-11); activation tokens never appear in logs (M9-06); invite history retention follows existing user-data retention policy.
- **NFR-7 Auditability:** activation, provider changes, settings confirmation, manual step completion, and invite sends record actor + timestamp.
- **NFR-8 Observability & rollout:** every feature flag in the Release Manifest is independently toggleable without deploy; M1 has a kill switch; error rates on new endpoints are monitored from day one.

---

## Risks and Mitigations

| Risk | Impact | Likelihood | Mitigation |
|------|--------|------------|------------|
| Announcement lands before onboarding is ready → empty-calendar spike + ticket wave | High | Medium | Release Manifest hard-sequences M2 last, as a manual action gated on verified-live launch features. |
| Entitlement inventory misses a premium check → "free" store hits an upgrade wall mid-walkthrough | High | Medium | M1-01 inventory is the SDD's first deliverable; E2E pass on a pilot store exercises calendar + AI + publish + mobile before M2. Kill switch (M1-06) if a miss ships. |
| Commercial handling of current premium payers unresolved at launch | High | Medium | DR-2/Q1 is an announcement blocker owned by product/billing; billing data left intact keeps every commercial outcome executable. |
| Invite emails/SMS marked as spam → activation funnel dies at the first hop | High | Medium | Q3 deliverability verification is launch-gated (DR-5); store-identified copy, rate-limited bulk, per-channel delivery states surfaced, resend controls; dual-channel redundancy. |
| Per-app login signals turn out indistinguishable (DR-7 fails) → M10 mis-reports adoption | Medium | Medium | M10-03 makes validation an SDD gate with a defined fallback contract; the step is manual-eligible so launch never blocks on perfect detection. |
| Wrong store switches away from WIW/Homebase | High | Low | v1 has no self-service switch at all (D-5); M5 is informational + support-assisted; unrecognized provider values default to the safe external path (M4-03). |
| Auto-detection lies → trust in the checklist collapses | Medium | Medium | Evidence shown, not just checkmarks (M3-03); latched-achievement + health-indicator model (M3-05) prevents flapping; detection failure shows staleness, never false completion (M3-08). |
| AI fill disappoints during the walkthrough (no availability, sparse roster) | Medium | Medium | Availability step ordered before AI where possible; low-coverage expectation-setting inline (M12-04); AI beat skippable and failure-tolerant (M11-06). |
| Walkthrough breaks against the live Syncfusion calendar | Medium | Medium | DR-9 compatibility spike before commitment; action-completion anchoring, pause/resume (M11-07); deep-link + article fallback (M11-08). |
| Onboarding framework over-engineered for one flow | Low | Medium | Generic *data model* only; no speculative UI for other modules (F3 uncommitted). |
| Stores activate but stall mid-checklist and churn silently | Medium | High | Time-in-step events ship day one; launch monitoring via raw events (journey 4); S5 funnel UI fast-follow; F1 nudges later. |

---

## Decisions Log

Resolved during the 2026-07-20 Codex review cycle (rationale recorded here; earlier locked decisions in Constraints):

- **D-1 (was Q2): Premium infrastructure retained dormant.** Gate bypass + kill switch only; no billing-data cleanup. Removal is a separate future decision once the launch settles. *Amended 2026-07-21 (SDD review):* one explicit carve-out — `PremiumGateMiddleware`, verified-unwired dead code that gates nothing, is deleted (SDD ADR-051-8) to prevent accidental wiring during launch. All other premium code and data remain intact.
- **D-2 = M1-04/M1-06** implementation of D-1 (trial surfaces disabled, kill switch).
- **D-3 (was Q6): Announcement goes to all roles.** Spec 040 has no role targeting and requesting it would couple this launch to a Spec 040 change; copy is written for all audiences; conversion is measured by `source=alert` hub visits, not acknowledgments (M2-02).
- **D-4 (was Q5): Hub persists after completion** as a compact "Setup" navigation entry — it becomes the home for S3's adoption tracker and future setup health.
- **D-5: External-provider switching is support-assisted only in v1.** Safe self-service switch semantics (sync jobs, punches, payroll effects) cannot be responsibly specified inside this spec; interest is captured and tracked instead (`onboarding.provider_switch_interest`).
- **D-6: Invite SMS uses a new Spec 037 `schedulingInvite` category, billed per the store's existing SMS policy.** The distinct category keeps a future "BK absorbs invite costs" change executable without data migration.
- **D-7: "First clock-in" is a tracked milestone, not a setup step.** Stores that schedule in BK but clock elsewhere must be able to reach "setup complete."

Resolved at the T0 decision gate (2026-07-21, implementation kickoff). **Status: RATIFIED by product 2026-07-21 at the T0→T1 gate** (D-8 ratified as *overridden* — chat goes free, reversing the provisional default; D-9 and D-10 ratified as recorded). Amended same day after Codex T0 review (corrected D-9 grounding; tightened lock windows; made the D-10 baseline computable):

- **D-8 (resolves Q5): Team Chat becomes FREE alongside scheduling.** *Product decision 2026-07-21 (T0→T1 gate): the product owner overrode the orchestrator's provisional stays-premium default — chat ungates with scheduling.* Implementation: `isChatEntitled(t)` remains a **separate predicate** (structural isolation retained for future flexibility) but delegates to `SchedulingEntitlement::isEntitled(t)` instead of commercial premium. Dark-deploy safety is preserved: with all flags off, `isEntitled` reduces to commercial `isPremiumActive`, so chat behavior is byte-identical to pre-051 until flags flip; when flags enable, chat ungates for all stores together with scheduling. T1A implements this branch; the S6 regression guard now asserts (a) flags-off ⇒ chat surfaces identical to pre-051 premium gating, and (b) flags-on ⇒ chat entitled wherever scheduling is entitled. Mobile `features.chat` follows the same predicate (M1-03 contract note republished). **RATIFIED by product 2026-07-21.**
- **D-9 (resolves Q3): Invite email rides the house PHPMailer/SMTP invitation-email precedent with pinned sender `noreply@buyerkiosk.com` ("BuyerKiosk"), wrapped in the `InviteEmailTransport` adapter.** Grounding (2026-07-21 audit, corrected per Codex review): the codebase has TWO transactional-email precedents — the invitation path (`EmployeeInvitationManager`: PHPMailer, SMTP-when-`SMTP_HOST`-configured, `MAIL_FROM ?? noreply@buyerkiosk.com`) and a direct SendGrid-API path (password reset, `AccountController.php:778`, sender `admin@buyerkiosk.com`). D-9 follows the invitation precedent, with two hardenings: `InviteEmailTransport` must **fail fast when SMTP is unconfigured outside dev** (never silently fall back to PHP `mail()` for invite traffic) and must **pin the sender** to `noreply@buyerkiosk.com` (not honor an arbitrary `MAIL_FROM` drift). DNS posture: buyerkiosk.com SPF authorizes the production server IPs **and** includes `sendgrid.com`; DMARC exists at `p=none` (monitoring-only — no *enforcement*, which also means no DMARC-alignment protection yet); MX healthy; DKIM signing status unverified. The T8.6 gate verifies **aligned authentication results** (Authentication-Results header: spf=pass, dkim verdict, dmarc alignment) plus inbox-vs-spam placement on Gmail + Outlook — not placement alone. Contingency if T8.6 fails: swap the adapter to the SendGrid API transport (in-repo precedent exists) + complete DKIM setup. DNS/deliverability owner: Ryan (ops). *Lock point: T3 transport implementation (T3.2); deliverability verification remains launch-gated (DR-5/T8.6).* **AMENDED 2026-07-22 (product direction at T8.6): SendGrid API is the PRIMARY invite-email engine** (house SDK precedent = password-reset path, `SENDGRID_API_KEY` env), with the PHPMailer/SMTP path retained as the secondary engine when only `SMTP_HOST` is configured; all D-9 hardenings carry over unchanged (pinned sender `noreply@buyerkiosk.com`, fail-fast when neither engine is configured outside dev, adapter interface, T8.6 aligned-authentication verification).
- **D-10 (resolves Q4): Launch detection thresholds are the PRD defaults** — ≥ 2 active non-owner members / 80% pay+hours / 50% app adoption / 50% availability / 30-day app-login freshness. Thresholds are flow-definition values (M3-09), tunable post-launch without migration; a change applies only to not-yet-complete steps. *Threshold lock point: T2.1 (they are baked into `SchedulingFlowDefinition` and its acceptance fixtures) — NOT T7.* **Ticket taxonomy (computable baseline):** no helpdesk API exists, so the support-load KPI uses the DR-10 manual fallback, made computable as follows: (1) during baseline prep (before T6.2 runs), support retroactively classifies the **trailing 60 days** of tickets with the `scheduling-setup` tag; (2) the T6.2 import CSV columns are `date, ticketId, tag, storeTypeNum` with **storeTypeNum mandatory** (a ticket that genuinely cannot be attributed to a store uses literal `unknown` and is excluded from the per-store rate, reported as a count alongside); (3) the KPI unit is **scheduling-setup tickets per activated store per week**, baseline = trailing-60-day rate over the pre-launch BK-native-eligible cohort; (4) tagging continues live from launch day. Owner: Ryan (support). *Lock points: taxonomy/CSV format before T6.2; thresholds before T2.1.*

## Open Questions

- [x] **Q1 — Commercial transition for paying premium subscribers: RESOLVED VOID 2026-07-22 (product owner):** the premium billing module was never hooked up to real billing and no store actually pays for Premium Scheduling through it. There is no commercial transition to execute — **the DR-2 announcement blocker is lifted**. Billing data remains untouched per D-1 regardless.
- [x] **Q3 — Invite email sending identity:** RESOLVED 2026-07-21 → **Decision D-9** (existing PHPMailer/SMTP path, `noreply@buyerkiosk.com`, adapter-swappable; deliverability verification stays launch-gated per DR-5/T8.6).
- [x] **Q4 — Detection thresholds + ticket taxonomy sign-off:** RESOLVED 2026-07-21 → **Decision D-10** (PRD defaults signed off; manual `scheduling-setup` ticket tagging + CSV import for the baseline).
- [x] **Q5 — Team Chat entitlement:** RESOLVED 2026-07-21 → **Decision D-8 as revised & ratified by product at the T0→T1 gate: chat becomes FREE with scheduling** (`isChatEntitled()` remains a separate predicate delegating to `SchedulingEntitlement::isEntitled()`; see the Decisions Log for the full history — the initial provisional default was stays-premium, overridden same day).
