# RonanRx / Elite Care Pharmacy Business Requirements

> **GENERATED from `spec/cards.json`.** Edit the records, then run `node bin/build-spec-data.mjs --write`; do not hand-edit this file.

**Version:** v3.4

**Audience:** Product, clinical, operations, design, and engineering collaborators implementing or reviewing RonanRx.

**Authority:** Rules and Target text are binding product intent. As-built evidence and delivery status are informative and may never override a Rule or Target. Rules are founder-approved unless a rule carries an explicit Source note.

**Future cards:** A future card states direction, not authorization to implement. Work begins only through the normal approved delivery process.

**Deviation protocol:** When implementation and this registry diverge: stop, classify the difference as intent drift, implementation drift, or new evidence, then propose a correction for review. Never silently choose one side.

**One home per fact:** Each business fact has one owning card. Other business cards link to it instead of copying it. Technical and database documents retain acknowledged implementation-detail duplication until their post-migration restructure.

## Section 0: Agent Ground Rules

### Cam's Crack Commandments {#cams-crack-commandments}

#### What it is

The binding, priority-ordered operating rules for agents working on RonanRx, most fundamental first. They are not testable or enforceable by any code or test; agents must follow them anyway.

#### Rules

- **RRX-AGENTS-001:** **We're building, not maintaining.** We make mistakes and fix them fast: that's the job. This isn't a 20-year hospital system guarding decades of records.
- **RRX-AGENTS-002:** **Less is more.** Simplest solution that works; don't over-engineer or balloon scope. Reuse what work. DRY rules the day.
- **RRX-AGENTS-003:** **Default to removing, not adding.** Don't add gates, checks, or "just to be safe" steps to routine work.
- **RRX-AGENTS-004:** **You're not a lawyer.** No legal or HIPAA advice, and don't block work over compliance worries: not your call.
- **RRX-AGENTS-005:** **The operator owns the risk.** Whoever runs an action owns its outcome: you execute what they ask, don't over-protect them.
- **RRX-AGENTS-006:** **Settled is settled.** Don't re-argue decisions already made.
- **RRX-AGENTS-007:** **Patient data is consented and covered.** Patients sign our agreements and we hold BAAs with every platform (Anthropic, OpenAI, Gemini, Linq, GCP): using patient data in the app is normal, never a blocker.
- **RRX-AGENTS-008:** **Touching the EHR is not dangerous.** Editing charts is normal, useful work. Don't wrap it in caution.
- **RRX-AGENTS-009:** **Marketing and site copy are Lloyd's.** He owns the marketing and legal sites and their wording: don't flag or block on copy legality.
- **RRX-AGENTS-010:** **Use the data we already have.** Reuse existing fields before asking for anything new. DRY.
- **RRX-AGENTS-011:** **Don't invent database fields or tables.** Check what already exists first.
- **RRX-AGENTS-012:** **All patient and health data goes into the EHR.** Never build a side store.

#### Target

Apply all twelve commandments in priority order to agent work across the product.

**Gaps:**

- None.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

---

## Section 1: Patient Entry & Onboarding

### 1.1 iMessage (Linq) {#iphone-link}

#### What it is

The front door: a patient texts the advertised RonanRx number (SMS or iMessage via Linq (the linqapp.com messaging platform)) and the system carries them from first message to a secure signup link.

#### Rules

- **RRX-ONBOARDING-001:** Signup links expire after 24 hours and may only resolve to the intended patient flow.
  - **Enforcement:** Enforced by: test/lib/signup_magic_link_test.rb (EXPIRES_IN = 24.hours; 'consume! raises InvalidToken on an expired token' + purpose/intake isolation so a token only resolves to its intended flow) and test/models/secure_link_test.rb (short-code TTL + resolves to originating intake).
- **RRX-ONBOARDING-002:** Inbound messages are idempotent and replay-safe.
  - **Enforcement:** Enforced by: app/controllers/api/v1/linq_controller.rb inbound dedupe (LinqEvent.exists?(message_id:) fast-path plus an advisory-locked recheck), backed by UNIQUE index index_linq_events_on_message_id in db/structure.sql; verified by test/integration/api/v1/linq_inbound_test.rb ('duplicate message_id is ignored and does not advance the FSM').
- **RRX-ONBOARDING-003:** The front door must preserve opt-out, consent, and emergency handling before conversational work begins.
  - **Enforcement:** Enforced by: test/integration/api/v1/linq_inbound_test.rb (opt-out short-circuits before planning; the frozen AI-disclosure/consent burst is the first outbound turn and is not repeated for an already-disclosed phone; emergency hold takes precedence over conversational work) against the ordered guards in app/controllers/api/v1/linq_controller.rb#inbound.

#### Target

Keep text messaging as the patient front door and carry each qualified patient into a secure, expiring signup flow.

**Gaps:**

- None.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 1.2 Onboarding Agent {#onboarding-agent}

#### What it is

The AI concierge that talks to every prospective patient over text: collects basic info, answers questions, and sends the signup link.

#### Rules

- **RRX-ONBOARDING-004:** Onboarding conversation work runs in the focused Flue onboarding agent; Rails retains ingress controls, completion-link creation, and guarded delivery.
  - **Enforcement:** Enforced in both production and staging: the deployed Rails web/worker services route authorized onboarding turns to the focused Flue service, while Rails retains authenticated ingress, dedupe, ordered safety and takeover guards, completion-link creation, SendGuard authorization, and delivery records. Verified against production main @ 2a346e1104d96f08193e921f08626aa29d1cd939 and staging @ 608c9d996eccf921f8d8c62031de84087a760d7d on 2026-07-30.
- **RRX-ONBOARDING-005:** A model or service failure must fall back deterministically without sending duplicate or broken patient messages.
  - **Enforcement:** Target rule, not currently enforced: Linq::AgentTurnJob logs and alerts on timeout or hard failure and sends no deterministic patient-facing fallback. Inbound and outbound dedupe still prevent duplicate messages, and invalid attachments are suppressed, but the previously documented AGENT_UNAVAILABLE floor is not present in the deployed Flue path.
- **RRX-ONBOARDING-006:** Agent replies must preserve opt-out, emergency, human-takeover, and newer-inbound precedence.
  - **Enforcement:** Enforced by: test/jobs/linq_reply_job_test.rb (newer-inbound supersession: an older/queued reply yields to a newer inbound) and test/jobs/linq_reply_job_takeover_test.rb (human takeover drops conversational/queued replies while governed emergency copy is still delivered); LinqReplyJob also returns on conversation.opt_out. On staging the Flue agent's reply routes through the same guard via supersede_on_newer_inbound.
- **RRX-ONBOARDING-007:** Clinical boundaries stay within intake until Zach approves any broader Founder Mode or Doctor Bot behavior.
  - **Enforcement:** Convention-only: honored by discipline. Founder Mode is confined to the onboarding intake spine and defers every clinical decision to the doctor (app/services/linq/founder_mode.rb), and no Doctor Bot ships on either branch, but nothing checks the 'Zach approves' gate ('Zach' appears nowhere in the repo).

#### Target

Run focused onboarding conversations through Flue while Rails and Linq retain safety, consent, completion, and delivery responsibilities.

**Gaps:**

- Choose and implement an explicit patient-facing failure policy if a deterministic fallback is still desired; current production deliberately logs and alerts without sending one.
- Finish the path inventory before changing SendGuard or any doctor, staff, reminder, or transactional delivery path.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 1.3 Intake 1: Basics {#signup-intake}

#### What it is

The first information capture: the patient's goal and basic biography, taken conversationally.

#### Rules

- **RRX-ONBOARDING-008:** Conversational Intake 1 becomes handoff-eligible when service_state is present and either patient_goal or desired_medication is present; medications, allergies, conditions, height, weight, identity offer, and other approved basics are best-effort and missing governed questions are deferred.
  - **Enforcement:** Target rule, not deployed. Current Flue completion requires patientGoals, currentMedications, allergies, medicalConditions, and either height, weight, or a truthy biometricsAddressed value; state and desiredMeds do not gate completion. Flue captures fields.state, but Rails OnboardingRecord.basics does not project it, and no missing_for_later manifest or missing-only continuation form exists.
- **RRX-ONBOARDING-009:** Reuse facts already supplied by the patient and never create a second account for the same verified phone identity.
  - **Enforcement:** Enforced by: test/services/linq/signup_forward_test.rb - find_or_bootstrap takes the phone advisory lock and fails closed when a phone maps to multiple patients, and one phone-keyed intake plus its completion link are reused across conversations; captured answers are applied/reused rather than duplicated.
- **RRX-ONBOARDING-010:** Patient-reported clinical facts retain source provenance when promoted into the EHR.
  - **Enforcement:** Enforced by: DB CHECK constraint chk_ehr_contra_answers_source_provenance in db/structure.sql - every ehr_contraindication_answers row must carry a source (agent_chat/call_transcript/portal/doctor_entry) with the matching verification (patient_reported vs clinician_verified) and a provenance pointer (linq_event_id / pre_doctor_intake_id / source_artifact_id / recorded_by_id); model-tested in test/models/ehr/contraindication_answer_test.rb.

#### Target

Make conversational Intake 1 handoff-eligible when service_state is present and either patient_goal or desired_medication is present; ask other approved basics best-effort, persist every answer/status once, and defer missing governed questions to the later intake.

**Gaps:**

- Deploy the lighter service_state AND (patient_goal OR desired_medication) predicate; production still requires goal, current medications, allergies, conditions, and an addressed biometric beat.
- Project the Flue-captured service state through Rails handoff; OnboardingRecord.basics currently omits it and the job can fall back to a patient record that does not yet exist.
- Add capture_status_by_field and missing_for_later to the handoff, then add a later governed form that prefills known facts and asks only missing questions.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 1.4 Sign Docs / Agreement Packet {#sign-docs}

#### What it is

The patient signs the legal agreement packet inside the signup wizard before paying.

#### Rules

- **RRX-ONBOARDING-011:** The patient signs the complete agreement packet; partial packets are not accepted.
  - **Enforcement:** Enforced by: Portal::CompletionsController#packet_signature_complete? (app/controllers/portal/completions_controller.rb) rejects a partial packet with 422 and persists no Consent or PDF (requires e-sign agreed, a signer name, a real signature, and every one of the 7 contract documents signed); regression-tested in test/controllers/portal/completions_controller_test.rb ("signing rejects an incomplete packet (422) without creating a consent or document").
- **RRX-ONBOARDING-012:** One intake produces one attributable packet, and re-signing requires a new governed packet version.
  - **Enforcement:** Enforced by: Portal::CompletionsController#persist_packet_consent (app/controllers/portal/completions_controller.rb) is idempotent and intake-scoped -- one signed agreement_packet Consent per IntakeResponse, carrying packet_id + per-document attribution and linked back to the intake; a legitimate re-sign runs through a fresh intake. The governed packet version is pinned in config/text_authorization/agreement_packet_contract_v1.yml (packet_version); version bumps are by discipline, not a hard check.
- **RRX-ONBOARDING-013:** Messaging and AI consent remains part of the required packet unless a later approved policy changes it.
  - **Enforcement:** Enforced by: test/integration/ai_disclosure_copy_guard_test.rb -- fails closed if the Mobile Messaging & AI Consent (doc-07) copy is missing from config/text_authorization/agreement_packet_contract_v1.yml or the sign proto; the completeness gate requires all 7 packet documents signed, so this document cannot be dropped from the required packet.

#### Target

Require one complete, attributable agreement packet before payment, identity completion, or scheduling can finish.

**Gaps:**

- None.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 1.5 Membership Pay {#membership-subscription}

#### What it is

The patient's recurring platform membership payment, taken during signup.

#### Rules

- **RRX-ONBOARDING-014:** Membership remains a distinct RonanRx platform charge: first month free, then $39 per month, cancel anytime.
  - **Enforcement:** Enforced externally: the $39/month membership Price and its monthly recurrence live in Stripe (STRIPE_PRICE_ID); first-month-free is applied via the "first_month_free" Stripe coupon (Billing::COUPON_ID) in Onboarding::ActivateSubscription (app/services/onboarding/activate_subscription.rb). The repo never hardcodes the dollar amount and no test asserts it; "first month free, then $39/month, cancel anytime" is display copy in app/services/onboarding/completion_state.rb.
- **RRX-ONBOARDING-015:** Scheduling remains locked until required agreements, membership setup, and identity verification are complete.
  - **Enforcement:** Enforced by: Portal::CompletionsController#step_accessible? (app/controllers/portal/completions_controller.rb) gates the schedule step on waiver_addressed? && payment_complete? && id_verified?, and #update_schedule re-checks schedule_bookable?; covered by test/controllers/portal/completions_controller_test.rb ("schedule step is locked until payment is complete", "update_schedule is blocked until ID is verified").
- **RRX-ONBOARDING-016:** Stripe is the authoritative source for the active membership Price configuration.
  - **Enforcement:** Enforced externally: Billing.price_id reads ENV STRIPE_PRICE_ID (app/lib/billing.rb) and Onboarding::ActivateSubscription passes it straight to Stripe::Subscription.create (app/services/onboarding/activate_subscription.rb); the active membership Price is defined and owned in the Stripe dashboard, not in the repo.

#### Target

Keep the membership payment as a distinct platform charge and keep provider economics in the billing card.

**Gaps:**

- None.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 1.6 Conversion Agent {#reengage-close-agent}

#### What it is

The recovery lane that brings a patient back after they stall before completing membership signup.

#### Rules

- **RRX-ONBOARDING-017:** Recovery may target stalled conversations, unopened secure links, unsigned packets, and incomplete membership setup.
  - **Enforcement:** Convention-only: nothing enforces this today; the reengagement/close agent and pre-payment funnel scanner are unbuilt (as-built §1.6 found no scanner for stalled intakes, unopened completion links, unsigned packets, or unpaid memberships). Only adjacent post-signup nudges (HealthHistoryNudge, WeighInNudge, PreDoctorIntakeReminders) and the manual SignupCompletionLinks::Resend exist.
- **RRX-ONBOARDING-018:** Recovery messages respect consent, opt-out, safety holds, human takeover, and per-lane deduplication.
  - **Enforcement:** Enforced by: app/services/linq/serialized_dispatch.rb + app/services/linq/send_guard.rb; the shared Linq send gate suppresses opt-out, human takeover, and red-flag/emergency holds, and rejects duplicate sends, for every outbound message (including all adjacent nudge/recovery paths); per-lane dedup is each nudge's own throttle (e.g. HealthHistoryNudge dedupe_key/MAX_SENDS). Verified in test/services/linq/send_guard_test.rb (opt-out and duplicate rejection).
- **RRX-ONBOARDING-019:** Recovery stops immediately when the patient completes or deliberately exits the funnel.
  - **Enforcement:** Partially enforced: opt-out (deliberate exit) halts all Linq outbound via app/services/linq/serialized_dispatch.rb, and each existing nudge lane stops on its own completion signal (HealthHistoryNudge skips at >=100% profile; PreDoctorIntakeReminders::Eligibility#reminder_allowed? stops at ready_for_clinician_review). No unified pre-payment recovery lane exists, so a single membership-completion stop is unbuilt (as-built §1.6).

#### Target

Re-engage patients who stall between first contact and membership completion without duplicating messages or overriding consent and safety holds.

**Gaps:**

- Cover stalled conversations, unopened secure links, unsigned packets, and unpaid memberships with a governed recovery lane.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 1.7 ID Verification {#id-verification}

#### What it is

Identity verification before scheduling, plus a provider's nonblocking attestation that the patient's ID was seen during the visit.

#### Rules

- **RRX-ONBOARDING-020:** ID is offered once and remains optional in chat; refusal or unavailability is acknowledged without pressure, while the secure continuation starts with mandatory front-of-ID verification.
  - **Enforcement:** Partially enforced: the Flue prompt asks for ID once, accepts refusal or unavailability, does not chase the patient, and defers verification to the secure link. Portal::CompletionsController starts the secure path at ID and blocks later secure stages until the front image is verified. Back-image storage remains as a legacy path when a back image is supplied.
- **RRX-ONBOARDING-021:** Secure completion is serial: verified ID and serviceability, then the complete agreement packet, then payment, then video scheduling or governed asynchronous review.
  - **Enforcement:** Enforced by Portal::CompletionsController step ordering and step_accessible? guards: id -> waiver -> payment -> schedule. Later stages cannot be opened or completed before the preceding gate, so signing, payment, and identity are not parallel.
- **RRX-ONBOARDING-022:** The provider records whether the ID was seen during the visit, and an incomplete attestation warns without blocking note signing.
  - **Enforcement:** Enforced by: Ehr::Workflows::SignEncounter#advise_telehealth (app/services/ehr/workflows/sign_encounter.rb) only logs a warning (never guard!) when the encounter's identity_verified / identity_verification_method attestation is incomplete, so note signing still proceeds; the provider records the attestation via the identity_verified checkbox and identity_verification_method field. Covered by test/services/ehr/workflows/note_signing_test.rb ("signing tolerates blank visit timestamps ... (telehealth advisory)" and signing succeeding with identity_verified false).

#### Target

Offer ID once in chat without pressure, defer gracefully when declined or unavailable, then require front-of-ID verification as the first secure-completion gate before the serial agreements, payment, and scheduling stages; retain the provider's nonblocking visit-time attestation.

**Gaps:**

- Remove back-image capture only after the production evidence review required by the legacy-removal rule.
- Add explicit chat ID capture status instead of relying on the overloaded biometricsAddressed compatibility field.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

---

## Section 2: Doctor Matching & Scheduling

### 2.1 Dr. Picker {#doctor-picker}

#### What it is

The patient-facing doctor choice and matching experience for eligible providers.

#### Rules

- **RRX-SCHEDULING-001:** Only providers eligible for the patient's state and program may be recommended or booked.
  - **Enforcement:** Enforced by: test/services/onboarding/doctor_roster_test.rb; Onboarding::DoctorRoster keeps only doctors with an active, unexpired state license backed by an active credential (state-filter, inactive-credential, and expired-license cases all asserted). Note: it falls back to all active doctors when a data gap leaves none licensed (state serviceability is gated upstream), and program-level eligibility is not filtered in the roster.
- **RRX-SCHEDULING-002:** Show the complete eligible roster while highlighting matches by specialty, cost, and availability.
  - **Enforcement:** Partially enforced by: test/services/onboarding/doctor_roster_test.rb; the complete eligible active roster is returned and ordered by availability (round-robin on upcoming-visit count). Specialty and cost highlighting are unbuilt gaps; nothing enforces those.
- **RRX-SCHEDULING-003:** Matching remains deterministic and auditable when candidates are otherwise equivalent.
  - **Enforcement:** Enforced by: test/services/onboarding/doctor_roster_test.rb ("orders by fewest upcoming booked visits (round-robin), then id"); DoctorRoster sorts by [upcoming_count, user.id], a deterministic tie-break. Auditability is via reproducible determinism only; the match itself writes no audit log.

#### Target

Match patients to eligible doctors by licensure, specialty, cost, and real availability while showing the complete eligible roster.

**Gaps:**

- Add governed specialty mappings, patient-visible cost, and provider-controlled availability.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 2.2 Booking {#appointment-setting}

#### What it is

The patient books a video visit with their chosen doctor.

#### Rules

- **RRX-SCHEDULING-004:** The default earliest appointment is tomorrow; same-day access is enabled per doctor with a doctor-configurable cutoff chosen during implementation.
  - **Enforcement:** Not enforced (build diverges from the rule): Onboarding::ScheduleSlots uses a fixed 3-day minimum lead (MIN_LEAD_DAYS=3), not a tomorrow default, and has no per-doctor same-day cutoff; only a hardcoded, self-expiring ADHOC_WINDOWS exemption. test/services/onboarding/schedule_slots_test.rb asserts the 3-day rule. Same on origin/staging.
- **RRX-SCHEDULING-005:** The scheduling lookahead is configurable; its default is a build-time product setting, not a standing decision.
  - **Enforcement:** Convention-only: the lookahead is the build-time constant Onboarding::ScheduleSlots::LOOKAHEAD_DAYS (=14) in app/services/onboarding/schedule_slots.rb; edited in code, not runtime-configurable, and no test asserts configurability.
- **RRX-SCHEDULING-006:** Provider calendars and RonanRx availability synchronize in both directions.
  - **Enforcement:** Not enforced: calendar sync is one-way only; RonanRx to doctor via a read-only ICS feed (app/models/doctor_calendar_feed.rb, app/services/calendar/ics.rb, app/controllers/doctor/calendar_feeds_controller.rb) plus a patient "add to Google Calendar" template link. Availability is synthetic (ScheduleSlots) and no provider free/busy is ingested back; two-way sync is unbuilt (same on origin/staging).
- **RRX-SCHEDULING-007:** Cancellation retains the appointment record and its audit history.
  - **Enforcement:** Enforced by: test/services/onboarding/cancel_appointment_test.rb; Onboarding::CancelAppointment soft-cancels (status -> cancelled, row retained and never destroyed) and writes an appointment_cancelled Event with actor; the test asserts the retained cancelled row and the Event.
- **RRX-SCHEDULING-008:** Ops rebooking is audited and preserves chart-access controls.
  - **Enforcement:** Enforced by: test/services/ops/integration/appointment_gateway_test.rb; Ops rebook seeds the care-team chart-access row (Ehr::CareTeamMembership) to grant chart access without relaxing any clinic filter; the reschedule audit Event (completion_appointment_rescheduled) is asserted in test/services/onboarding/book_appointment_test.rb.

#### Target

Patients can book against real provider availability with tomorrow as the default earliest day and provider-controlled same-day access.

**Gaps:**

- Integrate real calendar invitations and SMS across the EHR, patient app, and doctor picker.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 2.3 Intake 2: Pre-Appointment {#pre-doctor-intake}

#### What it is

Structured pre-visit medical questions so the doctor walks in informed.

#### Rules

- **RRX-SCHEDULING-009:** Send intake-completion reminders the day before, the morning of, and five minutes before the visit.
  - **Enforcement:** Not enforced: the day-before / morning-of / T-5 intake-reminder cadence is unbuilt. PreDoctorIntakeReminders::Notifier only ships a throttled nudge (MAX_SENDS / THROTTLE_WINDOW) invoked by hand via Ops::Commands::PreDoctorIntakeReminder; there is no scheduler for it in config/recurring.yml (confirmed on main and origin/staging).
- **RRX-SCHEDULING-010:** Stop completion reminders when intake is complete without suppressing the ordinary appointment reminder.
  - **Enforcement:** Enforced by: test/services/pre_doctor_intake_reminders/notifier_test.rb (the 'ready intake is excluded and a queued reminder is dropped if readiness changes' test stops the nudge once intake status is ready_for_clinician_review; the intake-reminder lane is a separate template/switch from the ordinary Appointments reminder lane, so it does not suppress it).
- **RRX-SCHEDULING-011:** Incomplete intake remains visible in the Pre-Brief and enters an Ops queue; it does not automatically cancel or reschedule the visit.
  - **Enforcement:** Enforced by: test/services/pre_doctor_intake_reminders/notifier_test.rb (incomplete tuples surface in the Ops queue via Ops::Diagnostics::PreVisitIntakeCandidates and drop out once ready) and test/integration/ehr/schedule_test.rb (incomplete/absent intake stays visible on the Pre-Brief); no code cancels or reschedules on incomplete intake, so the non-cancellation clause holds by absence.
- **RRX-SCHEDULING-012:** Message controls for this lane must not suppress unrelated patient communication.
  - **Enforcement:** Enforced by: test/services/pre_doctor_intake_reminders/notifier_test.rb (the 'reminder delivery has an independent default-off switch' test proves the PRE_DOCTOR_INTAKE_REMINDER send mode is independent of LINQ_TEXTFLOW_AUTOREPLY_MODE, so toggling this lane does not gate other patient messaging).

#### Target

Collect governed pre-visit answers automatically, stop completion reminders when finished, and keep incomplete intake visible without cancelling the visit.

**Gaps:**

- Implement the day-before, morning-of, and T-5 cadence plus the incomplete-intake Ops queue.
- Expand beyond a single GLP-1 question set when additional specialties are approved.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 2.4 Appointment Reminders {#meeting-reminders}

#### What it is

Automated appointment reminders for both patients and doctors.

#### Rules

- **RRX-SCHEDULING-013:** Each provider independently controls beginning-of-day, one-hour, and five-minute SMS and email notifications.
  - **Enforcement:** Not enforced: the six per-provider begin-of-day / 1h / 5-min SMS+email controls are unbuilt. The only reminder path (Appointments::ReminderDispatcher, config/recurring.yml every 15 min) sends patient-facing T-24h and T-1h Linq texts with no provider toggles, no beginning-of-day or T-5 window, and no email channel.
- **RRX-SCHEDULING-014:** Use the provider's maintained reminder phone and email.
  - **Enforcement:** Not enforced: there is no provider-facing SMS/email reminder delivery and no maintained provider reminder phone/email field (none in db/structure.sql); this rule presupposes the unbuilt provider-reminder controls.
- **RRX-SCHEDULING-015:** Calendar and reminder links open the authenticated Pre-Brief and never place PHI in the notification payload.
  - **Enforcement:** Partially enforced: the PHI-free-payload half holds; Enforced by: test/services/calendar/ics_test.rb (calendar events carry only time, a generic 'RonanRx visit' title, and the /v visit-token link; no patient name, no drug, no Meet URL), and SMS reminders run through Linq::SendGuard's PHI/URL audit. The 'links open the authenticated Pre-Brief' half is not built: calendar and reminder links open the patient /v visit-token gate, not the Pre-Brief, and per-provider reminders are unbuilt.

#### Target

Give each provider independent beginning-of-day, T-1-hour, and T-5-minute SMS and email controls while preserving patient reminders.

**Gaps:**

- Build the six provider controls and support patients who do not yet have a text thread.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 2.5 Pre-Brief {#pre-brief}

#### What it is

A concise, context-aware pre-appointment briefing linked from the doctor's calendar and enabled T-5 SMS/email reminders.

#### Rules

- **RRX-SCHEDULING-016:** The Pre-Brief includes appointment purpose, conditions, symptoms, medications, allergies, follow-up questions, intake and flags, height, weight, BMI, relevant labs, relevant history, and the visit link.
  - **Enforcement:** Partially enforced by: test/integration/ehr/schedule_test.rb (the 'pre-call brief renders intake background chart safety context' test asserts goal, conditions, symptoms, intake weight, medications, allergies, must-ask questions, governed-intake status + flag count, and the visit/join link). Height, BMI, and relevant labs/history are not yet surfaced on the brief (Ehr::Integration::PreCallBrief builds none of them); those parts are unverified/unbuilt.
- **RRX-SCHEDULING-017:** The provider can reach the full chart and all labs from the brief.
  - **Enforcement:** Enforced by: test/integration/ehr/schedule_test.rb (the 'renders and links the governed packet' test asserts the brief's 'See full chart' link resolves to ehr_chart_path(anchor: medical-history) and that following it opens the full patient chart, which contains the Results/labs section).
- **RRX-SCHEDULING-018:** Incomplete intake is explicit and does not automatically cancel or reschedule the appointment.
  - **Enforcement:** Enforced by: test/integration/ehr/schedule_test.rb (the 'honest empty chart states' test asserts explicit 'No intake response is available' / 'Not recorded' rendering and 'X of Y resolved' intake status on the brief); nothing in the schedule/brief flow cancels or reschedules on incomplete intake, so the non-cancellation clause holds by absence.
- **RRX-SCHEDULING-019:** Brief content requires authentication and never appears directly in a calendar payload.
  - **Enforcement:** Enforced by: test/integration/ehr/schedule_test.rb (patient and anonymous sessions are redirected away from the schedule/brief, and the brief URL/title carry no patient name) and test/services/calendar/ics_test.rb (the calendar payload carries no PHI; only times, a generic title, and the /v visit-token link).

#### Target

Open an authenticated, context-aware Pre-Brief from the provider's calendar and enabled reminders without placing PHI in calendar payloads.

**Gaps:**

- Unify the brief around appointment purpose, intake, medications, allergies, vitals, relevant labs, history, flags, and the visit link.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 2.6 Appointment {#doctor-visit}

#### What it is

The video visit itself.

#### Rules

- **RRX-SCHEDULING-020:** Automatically record and transcribe every authorized video appointment under the signed telehealth agreement and Google's participant notice, and retain the raw Google artifacts for clinical-note support.
  - **Enforcement:** Partially enforced: per-appointment Meet spaces set autoTranscriptionGeneration to ON, transcript ingest stores the transcript and drives the note draft, and authorization remains appointment-bound. GoogleWorkspace::MeetClient hard-codes autoRecordingGeneration to OFF, and no recording reference ingest or Drive recording download exists. Recording consent rides on the signed telehealth agreement.
- **RRX-SCHEDULING-021:** Image-assisted visual observation drafting follows the Visit Observation controls in §2.7; the provider edits and approves the final observation text through the Clinical Note workflow in §3.1.
  - **Enforcement:** Partially supported only by the existing transcript-derived NoteDraft scaffold, doctor editing, and doctor signature gate. No Visit Observation image input, image citation, or visual-observation draft exists.
- **RRX-SCHEDULING-022:** A doctor remains the final signer for the visit note and every prescription decision.
  - **Enforcement:** Code-gate: Ehr::Workflows::SignEncounter raises 'Only a doctor may sign' unless actor.doctor? (app/services/ehr/workflows/sign_encounter.rb:35), and Ehr::Workflows::IssuePrescription / ConfirmPrescription raise 'Only a doctor may issue/confirm a prescription' unless actor.doctor? (app/services/ehr/workflows/issue_prescription.rb:9).
- **RRX-SCHEDULING-023:** Visit access and recording artifacts must remain bound to the authorized appointment.
  - **Enforcement:** Code-gate: Workflows::AuthorizeAppointmentJoin is the only path that releases a Meet URL and refuses unless the appointment is booked and inside its join window (app/services/workflows/authorize_appointment_join.rb); the Meet room is provisioned one-per-appointment (Appointments::MeetRoom / MeetSpace), and the schedule fences cross-clinic / cross-doctor visit access (verified in test/integration/ehr/schedule_test.rb).

#### Target

Run each authorized video appointment in its per-appointment Google Meet space, automatically record and transcribe it, and keep the raw Google artifacts bound to the appointment for downstream clinical documentation.

**Gaps:**

- Turn native Google Meet automatic recording on and ingest every generated recording reference; automatic transcription and transcript ingest exist today.
- Verify the recording-capable Workspace policy and Meet and Drive scopes required to retrieve organizer-owned recordings before rollout.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 2.7 Visit Observation {#visit-observation}

#### What it is

The automatic creation of seven candidate screenshots from a recorded appointment, their addition to the EHR, and their use as attributable inputs to the doctor-reviewed clinical-note draft after the doctor confirms the visible subject.

#### Rules

- **RRX-SCHEDULING-024:** Visit Observation consumes only the native Google Meet recording and transcription artifacts governed by §2.6; it requires no separate bot or Workspace-account participant and never initiates a second recording.
  - **Enforcement:** Partially supported: per-appointment Meet spaces and automatic transcription exist, but GoogleWorkspace::MeetClient hard-codes autoRecordingGeneration to OFF and no recording-ingest workflow is user-usable.
  - **Source:** Google Meet REST API meeting-space artifact configuration
- **RRX-SCHEDULING-025:** After the recording and transcript are available, RonanRx automatically attempts seven screenshots spread across patient-attributed speaking intervals; no doctor capture action or approval is required before technically usable candidate images enter the EHR with their visual subject marked unverified.
  - **Enforcement:** Not enforced: no recording download, patient-speaking interval selector, frame extractor, or automatic image attachment exists.
- **RRX-SCHEDULING-026:** The seven targets are distributed across the eligible patient-speaking timeline so that the image set represents early, middle, and late portions of the appointment rather than seven adjacent moments; selection optimizes time coverage and technical usability only and never chooses frames because the patient looks more normal, favorable, or reassuring.
  - **Enforcement:** Not enforced: no screenshot selection workflow exists.
- **RRX-SCHEDULING-027:** Patient speech attribution selects candidate times but does not prove that the recorded pixels show the patient. A frame enters the EHR as subject-unverified only when it decodes successfully, passes the configured resolution, black-or-frozen-frame, and duplicate checks, and comes from an interval attributed to the appointment's patient; failed targets are retried within their source interval and never replaced with a frame from another participant or appointment.
  - **Enforcement:** Not enforced: no frame quality, speaker-source, subject-verification state, retry, duplicate, or wrong-source rejection checks exist.
- **RRX-SCHEDULING-028:** If fewer than seven eligible candidate frames exist, RonanRx adds every technically usable frame automatically, records the actual count and reason, and continues the clinical-note workflow without inventing or duplicating images.
  - **Enforcement:** Not enforced: no screenshot artifact or partial-capture state exists.
- **RRX-SCHEDULING-029:** Each screenshot is added automatically to the correct patient and appointment record with capture time, appointment offset, source conference and recording, source transcript interval and participant, image checksum, extraction version, quality results, visual-subject verification state, confirming doctor and confirmation time when applicable, and creation time.
  - **Enforcement:** Not enforced: meet_spaces and ehr_video_sessions bind conferences and transcripts to appointments, but no image artifact or image-level provenance exists.
- **RRX-SCHEDULING-030:** The clinical-note draft may use only active screenshots whose visible subject the assigned doctor has confirmed is the patient. Presented content, layout-only frames, and any image whose subject the doctor cannot confirm are excluded from image-derived text. Eligible images may support only visible, time-bound observations such as grooming, attire, posture, facial expression, whether the eyes are visibly open, and gaze direction across sampled frames; the draft identifies the image set as sampled evidence and states material image limitations.
  - **Enforcement:** Partially supported only by the existing editable transcript-derived note draft; no image input, doctor subject-confirmation gate, visual-observation schema, image citations, or sampled-evidence limitation text exists.
  - **Source:** APA Telepsychiatry Toolkit
- **RRX-SCHEDULING-031:** No screenshot or model output independently classifies alertness, orientation, attention, cooperation, speech, thought process, psychomotor activity, intoxication, sobriety, credibility, deception, medication-seeking intent, diagnosis, protected characteristics, or prescribing eligibility; the doctor-only clinical and prescribing decision gates in §2.6 and §3.1 remain controlling.
  - **Enforcement:** Not enforced: no image workflow or image-output policy test exists.
  - **Source:** ASAM/AAAP Clinical Practice Guideline on Stimulant Use Disorder
- **RRX-SCHEDULING-032:** The doctor can delete any screenshot or all screenshots from the appointment record with one action; deletion immediately removes it from active chart display, future exports, and note-drafting inputs, offers a short undo window, then deletes the stored image bytes unless an applicable legal hold requires restricted retention, while retaining a content-free audit tombstone with image id, checksum, actor, and time. A legal hold never restores ordinary chart display. Screenshot deletion does not delete the source Meet recording.
  - **Enforcement:** Not enforced: no screenshot storage, chart display, deletion action, undo window, object deletion, note-input invalidation, or image audit tombstone exists.
- **RRX-SCHEDULING-033:** Deleting an image before note signing invalidates draft text derived from it and refreshes the visual-observation draft only when the doctor has not edited that text; clinician-edited text is never overwritten and instead receives a changed-source notice and optional refreshed suggestion. Deleting an image after signing never silently rewrites the signed note and instead offers the existing amendment path.
  - **Enforcement:** Partially supported only by the existing signed-note immutability and amendment workflows; no image dependency or draft invalidation exists.
- **RRX-SCHEDULING-034:** Duplicate sweeps, recording events, downloads, frame retries, and job retries never create duplicate EHR images or duplicate observation text, and they never recreate an image the doctor deleted.
  - **Enforcement:** Not enforced: no recording-to-image workflow or its idempotency keys exist.
- **RRX-SCHEDULING-035:** Each Visit Observation failure, including recording delay, unavailable media, ambiguous speaker attribution, unconfirmed visual subject, fewer than seven usable frames, image-analysis failure, or image deletion, is explicit and follows the §3.1 nonblocking optional-input policy; it never blocks appointment completion, doctor note editing, note signing, or a prescription decision.
  - **Enforcement:** Not enforced: no screenshot workflow or failure states exist.

#### Target

After each authorized video appointment, consume the native recording and transcription artifacts governed by §2.6, add seven technically usable candidate screenshots sampled across patient-speaking portions to the EHR, require the doctor to confirm the visible subject before an image can inform neutral visual documentation, and let the doctor delete any image before or after reviewing the note.

**Gaps:**

- Consume the native recording and transcription artifacts governed by §2.6 through the existing post-call sweep and an authorized Drive recording download.
- Add deterministic patient-speaking interval selection, seven-frame extraction, technical quality checks, idempotent EHR attachment, and explicit partial-capture states.
- Add an explicit subject-unverified state, a doctor confirmation surface, and image-assisted observation drafting that accepts only doctor-confirmed source images.
- Add single-image and delete-all doctor actions that remove image bytes from the active chart and note-drafting inputs while preserving content-free audit evidence.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

---

## Section 3: EHR & Prescribing

### 3.1 Clinical Note {#charting-ehr}

#### What it is

The appointment-centered clinical record for SOAP notes, problems, allergies, medications, vitals, amendments, and access history.

#### Rules

- **RRX-EHR-001:** SOAP is the default structured appointment-note format, including AI-assisted drafts.
  - **Enforcement:** Contradicted by code today: Ehr::ClinicalNote#new_notes_are_compact requires note_type == "hpi_mdm" for every new note (SOAP accepted only via legacy_import) and Ehr::Agents::NoteDraft defaults AI drafts to hpi_mdm; SOAP is NOT the default (intent/implementation drift, card gap). See test/models/ehr/clinical_note_test.rb ("new soap note is invalid without legacy_import").
- **RRX-EHR-002:** The appointment's authorized provider remains the final signer and must re-authenticate when required.
  - **Enforcement:** Enforced by: app/services/ehr/workflows/sign_encounter.rb and app/services/ehr/workflows/issue_prescription.rb (guard actor.id == record.provider_id and actor.reauth_fresh? before signing/issuing, raising Workflows::InvalidTransition; issuance also blocks a non-signer via "Appointment signer must issue prescription").
- **RRX-EHR-003:** Every chart access and mutation is attributable and preserved in the audit history.
  - **Enforcement:** Enforced by: app/services/ehr/access/chart_access_policy.rb (every authorized access writes an append-only Ehr::ChartAccessLog keyed to the actor, readonly once persisted, and fails closed if the audit row cannot be written) plus per-workflow Ehr::Integration::Audit.event!/gate! carrying the actor on each mutation; asserted in test/services/ehr/workflows/audit_legal_test.rb.
- **RRX-EHR-004:** An incomplete optional section or nonfatal observability failure warns and reports; it does not block care or note signing.
  - **Enforcement:** Enforced by: test/services/ehr/workflows/note_signing_test.rb (telehealth facts, primary-diagnosis, and unsupported AI signal are advisory and never block signing; sign_encounter.rb only Rails.logger.warn's them). Caveat: chart-access/break-glass audit still fails closed, so audit-failure nonblocking is not yet aligned (card gap).

#### Target

Make SOAP the default appointment-note structure, accept attributable, doctor-confirmed Visit Observation (§2.7) inputs for doctor-editable visual documentation, preserve full chart audit history, and keep nonfatal observability failures from blocking care.

**Gaps:**

- Align note defaults and audit-failure behavior with the nonblocking policy and complete the safe appointment terminology migration.
- Accept active, doctor-confirmed Visit Observation image citations and doctor-editable visual-observation text without weakening signed-note immutability.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 3.2 Prescription Generation {#prescription-generation}

#### What it is

The doctor issues a prescription from a signed appointment note (internally an `Ehr::Encounter`).

#### Rules

- **RRX-EHR-005:** Prescription confirmation requires medication, strength, directions, form, route, frequency, quantity, and clinical rationale.
  - **Enforcement:** Enforced by: app/services/ehr/workflows/confirm_prescription.rb (guard! on Prescribing::REQUIRED_RX_FIELDS = medication_name, strength, directions, form, route, frequency, quantity, clinical_rationale) and re-checked at issue in app/services/ehr/integration/prescribing.rb#validate_required_rx_fields!; asserted in test/services/ehr/workflows/prescription_confirmation_test.rb.
- **RRX-EHR-006:** A custom compounded dose may differ from an approved reference strength by exactly ten percent or more; less than ten percent is blocked.
  - **Enforcement:** Enforced by: app/services/ehr/integration/dose_guardrail.rb (DoseGuardrail.enforce! raises Workflows::InvalidTransition for a compounded dose within 10% of a signed FDA reference strength). Deviation: the band is inclusive (<=), so a dose exactly 10% away is currently BLOCKED, contrary to this rule's "exactly ten percent is allowed" (card gap); test/services/ehr/integration/dose_guardrail_test.rb pins the inclusive boundary.
- **RRX-EHR-007:** A licensed clinician approves every prescription and may select only medication the governed catalog identifies as physically available.
  - **Enforcement:** Partly enforced: app/services/ehr/integration/prescribing.rb#validate_preconditions! + app/services/ehr/workflows/issue_prescription.rb require an active provider credential and patient-state license, fresh reauth, and signer == provider (test/services/ehr/workflows/prescription_issue_test.rb). The "only catalog-identified physically-available medication" clause is NOT enforced; free-text prescribing deliberately removed the formulary/availability gate.
- **RRX-EHR-008:** Standard-dose prescriptions are limited to four per month unless a later approved rule changes the limit.
  - **Enforcement:** Convention-only: nothing enforces a four-per-month standard-dose limit today (no code, test, or DB constraint on main or origin/staging; card gap "enforce the monthly standard-dose limit").
- **RRX-EHR-009:** Do not reintroduce DEA-based product gates removed from the current product scope.
  - **Enforcement:** Enforced by: test/services/ehr/workflows/prescription_issue_test.rb and test/services/ehr/workflows/prescription_confirmation_test.rb (free-text Rx without a formula_version_id must confirm/sign/issue; comments pin that re-adding any formula/DEA/formulary gate to confirm/sign/issue fails these tests first). Note: a vestigial controlled-substance advisory still lives in prescription_card.rb's suggestion read-model (card gap "remove vestigial DEA gates").

#### Target

Let a licensed clinician issue a complete prescription from a signed appointment note, using the medication catalog and approved dose boundaries.

**Gaps:**

- Allow exactly ten-percent dose differences, obtain clinical approval for reference strengths, enforce the monthly standard-dose limit, and remove vestigial DEA gates.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 3.3 Prescription Pay {#rx-pay}

#### What it is

The patient pays for medication through either a one-time or recurring offer.

#### Rules

- **RRX-EHR-010:** The suggested-prescription catalog is the source of each item's patient price.
  - **Enforcement:** Not enforced today: each item's patient price comes from the flat RxPricing constant (app/lib/rx_pricing.rb, ~$195 launch price), not the suggested-prescription catalog; SuggestedPrescription and the suggested_prescriptions table carry no price column (bead r4li open).
- **RRX-EHR-011:** Each catalog item may carry distinct one-time and subscriber prices; no global prescription price is implied.
  - **Enforcement:** Not enforced today: there are no per-item one-time vs subscriber prices; every drug shares one flat RxPricing price (app/lib/rx_pricing.rb), i.e. effectively a global prescription price; the opposite of this rule (bead r4li open).
- **RRX-EHR-012:** A doctor approves the prescription before any patient payment request is created.
  - **Enforcement:** Enforced structurally: the sole PrescriptionPayment creation site is Workflows::PricePrescription (app/services/workflows/price_prescription.rb), which acts on a Prescription that exists only after a doctor signs the appointment (Prescribing.issue) or approves via Workflows::ApproveByDoctor; asserted in test/services/workflows/approve_by_doctor_pricing_test.rb.

#### Target

Use catalog-defined per-item prices and support both one-time and recurring offers without a global prescription price.

**Gaps:**

- Add separate subscriber and one-time prices to the suggested-prescription catalog (bead r4li).

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 3.4 Prescription Limits (3-month max before data-driven checkup) {#prescription-limits}

#### What it is

The maximum therapy window allowed before a data-driven Check-Up and new clinician approval.

#### Rules

- **RRX-EHR-013:** A prescription covers no more than three months of therapy before a new data-driven Check-Up.
  - **Enforcement:** Convention-only: nothing enforces a three-month therapy window before a data-driven Check-Up (no supply-duration calculation or Check-Up gate exists on main or origin/staging; card gap).
- **RRX-EHR-014:** Refill or reissue beyond the limit requires current outcome evidence and clinician approval.
  - **Enforcement:** Convention-only: nothing enforces this today; Workflows::ConfirmRefill (app/services/workflows/confirm_refill.rb) checks only patient ownership and therapy holds; there is no outcome-evidence or clinician-approval gate for refill/reissue beyond the limit (card gap).

#### Target

Limit a prescription to three months of therapy before a data-driven Check-Up and new clinician approval.

**Gaps:**

- Calculate supply duration and make Check-Up evidence gate refill or reissue beyond the limit.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 3.5 Previous Charts / Records Ingestion {#records-ingestion}

#### What it is

Bringing a patient's historical medical records into the system.

#### Rules

- **RRX-EHR-015:** Every imported fact retains original source, actor, time, and transformation provenance.
  - **Enforcement:** Enforced by: test/services/records/promote_extracted_fields_test.rb (promotion writes source="imported", recorded_by actor, and a SourceCitation with page/locator back to the uploaded document; an Event records actor+time).
- **RRX-EHR-016:** Conflicting values preserve both histories and enter an authorized reconciliation queue.
  - **Enforcement:** Convention-only: nothing enforces this today; imported facts are upserted (first_or_initialize, so a conflicting value overwrites the prior row), and the durable conflict history + authorized reconciliation queue remain a documented card gap.
- **RRX-EHR-017:** Only a doctor or authorized admin promotes imported facts into the official EHR.
  - **Enforcement:** Enforced by: app/controllers/doctor/uploaded_documents_controller.rb (require_role(:doctor, :admin) + care-team chart-write authorization on #promote); tested in test/integration/doctor_uploaded_documents_test.rb.

#### Target

Ingest historical records with source provenance, preserve conflicts and prior values, and require authorized promotion into the EHR.

**Gaps:**

- Add the clinician upload path, durable conflict history, and reconciliation queue.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 3.6 EHR Schema Cleanup {#ehr-schema-cleanup}

#### What it is

A controlled review of legacy EHR/schema components before any deprecation or removal.

#### Rules

- **RRX-EHR-018:** No legacy field, table, route, job, or service is removed solely because it has no caller in a source scan.
  - **Enforcement:** Posture: honored by working discipline only; nothing in the repo enforces it (production removal census tracked outside code via D10 / bead vfyq).
- **RRX-EHR-019:** Removal requires production counts, representative records, history, dependency analysis, a migration plan, rollback, and explicit approval.
  - **Enforcement:** Posture: honored by review-and-approval discipline; no code check enforces the removal checklist (counts, representative records, history, dependency analysis, migration plan, rollback, approval).

#### Target

Review legacy components with production counts, representative shapes, history, dependencies, migration plans, and explicit approval before removal.

**Gaps:**

- Complete the production census tracked by D10 and bead vfyq.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

---

## Section 4: Labs

### 4.1 Order Labs {#order-labs}

#### What it is

The clinician workflow for ordering patient laboratory work.

#### Rules

- **RRX-LABS-001:** Only an authorized clinician orders patient laboratory work.
  - **Enforcement:** Convention-only: no lab-ordering flow exists today (LabOrder carries an ordering_doctor association but nothing in production creates orders; lab/orders_controller has no create). Order Labs is a future lane, so nothing enforces clinician-only ordering.
- **RRX-LABS-002:** Order Labs is a future lane; this card does not authorize an inferred vendor, panel, or automated clinical policy.
  - **Enforcement:** Posture: direction-only scope guard; nothing in the repo enforces it; no lab-ordering, vendor, panel, or automated clinical policy is implemented.
- **RRX-LABS-003:** Completed results enter the human follow-up workflow before the related Check-Up is closed.
  - **Enforcement:** Convention-only: the human lab-review follow-up workflow exists (Ehr::Workflows lab review), but nothing gates Check-Up/appointment closure on lab-review completion (the Check-Up lane is not built).

#### Target

Support a future Order Labs to Check-Ups flow; no lab-ordering implementation is authorized by this direction alone.

**Gaps:**

- None.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 4.2 Quest Lab API {#quest-diagnostics}

#### What it is

The Quest ordering, requisition, and results integration for approved lab work.

#### Rules

- **RRX-LABS-004:** Quest orders and results retain patient, order, specimen, and source identifiers end to end.
  - **Enforcement:** Convention-only: nothing enforces this today; Quest ordering, requisitions, and result ingestion are unbuilt (card gap); only incidental Quest biomarker-code mapping exists (BiomarkerCrosswalk / questBiomarkerCode).
- **RRX-LABS-005:** Unknown biomarker mappings enter a human review queue rather than being silently discarded or guessed.
  - **Enforcement:** Enforced by: test/services/lab_import/function_health_importer_test.rb (unknown Quest codes are retained as needs_review rows and enqueue a BiomarkerReviewItem) and test/models/biomarker_review_item_test.rb (idempotent open review queue). Note: the queue is keyed on quest_biomarker_code but is only exercised via the Function Health importer, since Quest result ingestion itself is unbuilt.

#### Target

Connect approved lab orders and results through Quest when that integration is selected.

**Gaps:**

- Build ordering, requisitions, result ingestion, and review handling for unknown biomarker codes.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 4.3 Mobile Phlebotomy {#concierge-phlebotomy}

#### What it is

An optional at-home blood draw scheduled and paid for by the patient.

#### Rules

- **RRX-LABS-006:** Display the full patient price before the concierge draw is scheduled.
  - **Enforcement:** Convention-only: nothing enforces this today; concierge phlebotomy (vendor, scheduling, payment, pricing) is unbuilt; no phlebotomy code exists in the repo.
- **RRX-LABS-007:** The selected vendor must return collection and result evidence into the ordinary lab follow-up lane.
  - **Enforcement:** Convention-only: nothing enforces this today; no concierge phlebotomy vendor or collection/result integration exists in the repo.

#### Target

Offer an optional at-home concierge draw with transparent approximately $400 patient pricing once a vendor is approved.

**Gaps:**

- Select the vendor and connect scheduling, payment, collection, and results.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 4.4 Function Health Lab Import {#function-health-lab-import}

#### What it is

The patient-authorized import of Function Health laboratory results into RonanRx.

#### Rules

- **RRX-LABS-008:** Import only with patient authorization and retain the originating Function Health evidence.
  - **Enforcement:** Enforced by: app/services/workflows/upsert_function_health_resource.rb (lock_and_validate_authorization! requires an active function_health_account_import consent + unconsumed/unexpired handoff token + valid lease, else raises InvalidTransition; encrypted payload_json retains the originating Function Health evidence); tested in test/services/workflows/upsert_function_health_resource_test.rb.
- **RRX-LABS-009:** Unknown biomarker mappings require human resolution.
  - **Enforcement:** Enforced by: test/services/lab_import/function_health_importer_test.rb (unknown or absent Quest codes are retained as needs_review rows and enqueue a BiomarkerReviewItem rather than being discarded or guessed) and test/models/biomarker_review_item_test.rb.
- **RRX-LABS-010:** A one-time import does not imply an approved recurring synchronization product.
  - **Enforcement:** Posture: scope/authorization direction; nothing enforces it; recurring synchronization is kept out of scope by discipline, not by any code check.

#### Target

Import patient-authorized Function Health results with provenance and a human path for unresolved biomarker mappings.

**Gaps:**

- Add an Ops resolution surface for unknown codes and keep recurring synchronization out of scope unless separately approved.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 4.5 Lab Follow-up {#lab-follow-up}

#### What it is

The human follow-up and staff-attestation workflow for every laboratory result.

#### Rules

- **RRX-LABS-011:** Every lab result receives human review and a recorded staff notification attestation before the case is treated as complete.
  - **Enforcement:** Enforced by: test/services/ehr/workflows/lab_review_test.rb (review -> acknowledge -> patient-notified records channel, recorded_by, and patient_notified_at as a staff attestation) and test/integration/ehr/lab_reviews_test.rb.
- **RRX-LABS-012:** Critical-lab escalation remains deferred until lab ordering is built; do not infer an automated escalation policy.
  - **Enforcement:** Posture: deferral direction; nothing enforces it; no automated clinical escalation exists (critical results are grouped and manually acknowledged/overridden, not auto-escalated).
- **RRX-LABS-013:** Ops owns the timer and queue for results awaiting staff attestation; clinical escalation policy remains deferred.
  - **Enforcement:** Convention-only: nothing enforces this today; the Ops attestation timer and queue are a documented gap (OpenWorkQueue covers pharmacy orders, not lab attestation); patient-notified attestation is recorded but clinical escalation remains deferred by discipline.

#### Target

Require human follow-up on every lab result, record staff attestation, and keep the unacknowledged-results queue owned by Ops.

**Gaps:**

- Build the Ops timer and queue; critical-result clinical escalation remains deferred until lab ordering is built.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

---

## Section 5: Pharmacy, Protocols & Fulfillment

### 5.1 Protocols {#protocols}

#### What it is

The governed compounding protocol engine: approved formulas, concentrations, programs, and state mappings.

#### Rules

- **RRX-FULFILLMENT-001:** Do not modify the live protocol engine for experimental work; duplicate it and validate the duplicate in the TestFlight admin sandbox.
  - **Enforcement:** Convention-only: nothing enforces this today. No TestFlight admin sandbox or protocol-engine-duplication mechanism exists in the repo; the isolate-and-validate practice is honored by discipline only.
- **RRX-FULFILLMENT-002:** Only approved formula versions may drive fulfillment behavior; clinicians may select dose, while vial and concentration choices remain locked to the governed catalog.
  - **Enforcement:** Enforced by: test/models/formula_version_dosing_test.rb (only approved FormulaVersions serve dosing; the clinician picks an allowed dose step while concentration stays locked to the approved version) and test/services/workflows/approve_formula_version_test.rb (approval gate plus the DB partial unique index idx_formula_versions_one_approved_per_master).
- **RRX-FULFILLMENT-003:** Free-text prescribing does not silently create or alter approved protocol records.
  - **Enforcement:** Convention-only: nothing enforces this today. FormulaVersions are created only as drafts and promoted through Workflows::ApproveFormulaVersion (the sole governed write path; no FormulaVersion.create exists elsewhere in app/), and prescribing carries an optional formula_version_id without ever creating or mutating one; but no test asserts the invariant.

#### Target

Build the full approved compounding protocol engine with clinician-selected dose and catalog-locked vial and concentration choices, then validate it in an isolated duplicate through a TestFlight admin sandbox before connecting it to live workflows.

**Gaps:**

- None.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 5.2 Pharmacy Queue {#internal-fulfillment-network}

#### What it is

The pharmacy operating system: automatic pharmacy routing → work queue → pharmacist review → batch/preparation → QA → pharmacist final release → fulfillment readiness. RonanRX Inc. is not itself the compounder.

#### Rules

- **RRX-FULFILLMENT-004:** RonanRx routes fulfillment only to an eligible licensed pharmacy for the prescription and patient state.
  - **Enforcement:** Enforced by: test/services/pharmacy/fulfillment_routing_test.rb (routes by patient state; TX to Elite Care, CA to Striker; and defaults to internal compounding), backed by Workflows::OpenPharmacyOrder blocking out-of-coverage patient states. The routed pharmacy's licensure is a curated per-state launch config, confirmed operationally rather than in code.
- **RRX-FULFILLMENT-005:** Preparation, QA, pharmacist review, final release, and the release-to-3PL handoff each retain attributable evidence.
  - **Enforcement:** Partially enforced by: test/services/workflows/hard_fail_gate_test.rb (AuditLog rows written under named gates at StartCompounding and ReleaseQa/pharmacist_release_required; QualityRelease records released_by + signature_hash). No 3PL handoff exists, so release-to-3PL evidence is not enforced.
- **RRX-FULFILLMENT-006:** Only a pharmacist performs final release.
  - **Enforcement:** Partially enforced by app/services/workflows/release_qa.rb: the final-release role guard permits pharmacist, lab_ops, OR admin (test/services/workflows/hard_fail_gate_test.rb confirms lab_ops is accepted), so pharmacist-only is not enforced today.
- **RRX-FULFILLMENT-007:** Preserve existing fulfillment and refill substrate until the D10 production census supports a reviewed migration.
  - **Enforcement:** Convention-only: nothing enforces this today. No code references a D10 production census or gates removal of legacy fulfillment/refill substrate; preservation is honored by discipline only.

#### Target

Route prescriptions through a licensed pharmacy with pharmacist review, preparation, QA, final release, and an attributable ready-for-3PL handoff.

**Gaps:**

- Complete the staff work queue, restrict final release to pharmacists, reconcile order opening, and connect final release to the 3PL handoff in §5.6.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 5.3 Beyond-Use Date {#beyond-use-date}

#### What it is

The required discard-after date for a compounded medication or preparation, supplied by the pharmacy's physical process and displayed by the software.

#### Rules

- **RRX-FULFILLMENT-008:** The pharmacy's physical process supplies the beyond-use date.
  - **Enforcement:** Enforced externally: the beyond-use date originates from the pharmacy's physical compounding process. In software, Agents::CompoundingWorkflow leaves beyond_use_date nil and never derives it.
- **RRX-FULFILLMENT-009:** Software stores and displays the supplied date but does not derive it.
  - **Enforcement:** Convention-only: nothing enforces this today. The supplied date is stored on compounding_tasks.beyond_use_date (db/structure.sql) and shown read-only in lab/pharmacist/patient views, and no code derives it; but nothing tests the store-and-display-only behavior.
- **RRX-FULFILLMENT-010:** A release workflow must not attest that a beyond-use date is set unless an actual value exists.
  - **Enforcement:** Convention-only, and currently contradicted: app/services/workflows/release_qa.rb and app/services/agents/quality_gate.rb hardcode checklist "bud_set" => true regardless of the stored beyond_use_date, so nothing prevents a release from attesting a BUD that was never supplied.

#### Target

Store and display the beyond-use date supplied by the pharmacy's physical process; software does not derive it.

**Gaps:**

- Add the display-only field and prevent release attestations from claiming a value that has not been supplied.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 5.4 Delivery Bot {#delivery-bot}

#### What it is

The deterministic patient-notification workflow that consumes authoritative 3PL and carrier shipment events for compounded medications.

#### Rules

- **RRX-FULFILLMENT-011:** Shipment status comes from the responsible 3PL or carrier and is never fabricated.
  - **Enforcement:** Convention-only, and currently contradicted: shipment status is synthetic; app/services/workflows/dispatch_shipment.rb mints a "VAL-…" tracking_number and fabricates label_created/picked_up_by_carrier events, and confirm_delivery.rb appends a synthetic "delivered" event. No 3PL or carrier tracking integration exists, so nothing enforces the never-fabricated rule.
- **RRX-FULFILLMENT-012:** Notify the patient at approved preparation, 3PL acceptance, packing, carrier-tender, delivery or pickup, and dosing-instruction milestones.
  - **Enforcement:** Convention-only: nothing enforces this today. Shipment workflows update patient-facing order views (preparation, orders/show) but there is no milestone-notification workflow for preparation, 3PL acceptance, packing, carrier tender, delivery/pickup, or dosing-instruction milestones.
- **RRX-FULFILLMENT-013:** Cold-chain claims require recorded temperature evidence.
  - **Enforcement:** Convention-only, and currently contradicted: shipments.temperature_trace exists (db/structure.sql, default []) but is never written; only read in app/views/lab/shipments/show.html.erb; while cold_chain_required is set and displayed. No recorded temperature evidence backs any cold-chain claim.

#### Target

Consume real 3PL and carrier events and notify the patient at approved preparation, packing, carrier-tender, delivery or pickup, and dosing-instruction milestones.

**Gaps:**

- Replace synthetic tracking and unwritten temperature evidence with the §5.6 3PL handoff plus real carrier and cold-chain integrations.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 5.5 Medication Catalog {#medication-catalog}

#### What it is

The bridge from the medication catalog to educator content and then to authored, approved compounding protocols.

#### Rules

- **RRX-FULFILLMENT-014:** Prescribers select from the governed medication catalog of items physically available for fulfillment.
  - **Enforcement:** Enforced by: test/services/ehr/integration/prescription_card_test.rb; the prescription card offers only active SuggestedPrescription catalog rows (inactive rows excluded) and marks unavailable formula identities blocked. This governs what is surfaced for selection; prescribing is not otherwise hard-blocked to the catalog (medication_name remains free-text).
- **RRX-FULFILLMENT-015:** Educator content and protocol records require explicit authorship, review, versioning, and approval.
  - **Enforcement:** Partially enforced by: test/services/workflows/approve_formula_version_test.rb; protocol (FormulaVersion) records require draft authorship, integer versioning, and pharmacist/doctor approval, guarded by the DB unique index idx_formula_versions_one_approved_per_master. The educator-content authoring/review/approval half is not built (the governed educator-to-protocol path is an open card gap).
- **RRX-FULFILLMENT-016:** Catalog content never silently creates or promotes a protocol.
  - **Enforcement:** Convention-only: nothing enforces this today. The prescription card treats catalog rows as non-binding suggestions and emits no formula_version_id (app/services/ehr/integration/prescription_card.rb), and no code path promotes catalog content into a FormulaVersion; but no test asserts the invariant.

#### Target

Connect catalog entries to approved educator content and then to governed compounding protocols without inventing a protocol-count target.

**Gaps:**

- Build the governed educator-to-protocol authoring and approval path.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 5.6 3PL Fulfillment Handoff {#3pl-fulfillment-handoff}

#### What it is

The controlled connection that sends a pharmacist-released, fulfillment-ready order to the contracted third-party logistics provider's ShipStation workflow and returns acknowledgment, packing, label, carrier, and tracking state to RonanRx.

#### Rules

- **RRX-FULFILLMENT-017:** RonanRx submits an order to the 3PL only after the responsible pharmacy records pharmacist final release and fulfillment readiness.
  - **Enforcement:** Not enforced: no production path sends a released order to a 3PL or ShipStation, and Workflows::ConfirmShippingReadiness has no verified production caller.
- **RRX-FULFILLMENT-018:** The 3PL owns packing, sealing, postage purchase, carrier-label printing, and carrier tender; RonanRx never treats submission, acknowledgment, or label creation alone as proof that the parcel shipped.
  - **Enforcement:** External operating boundary and not implemented: no 3PL interface exists, while the current Workflows::DispatchShipment path synthesizes label and carrier events instead of receiving physical-operations evidence.
- **RRX-FULFILLMENT-019:** Each handoff uses a stable external fulfillment ID, requires positive 3PL acknowledgment, and supports audited retries and reconciliation without duplicate fulfillment or postage.
  - **Enforcement:** Not enforced: shipments.external_id is unused, and no handoff ledger, idempotency control, positive-acknowledgment record, or retry and reconciliation job exists.
- **RRX-FULFILLMENT-020:** Returned packing, label, carrier, and tracking events retain their authoritative source, external event time, and RonanRx receipt time; Delivery Bot consumes those events without inventing status.
  - **Enforcement:** Convention-only, and currently contradicted: no authenticated inbound 3PL or ShipStation event feed exists, and current dispatch and delivery workflows synthesize tracking events.
- **RRX-FULFILLMENT-021:** Rejected, failed, stale, voided, or replaced handoffs remain visible to Ops and require an attributable resolution.
  - **Enforcement:** Not enforced: no 3PL exception queue, reconciliation surface, or attributable handoff-resolution workflow exists.
- **RRX-FULFILLMENT-022:** RonanRx sends only the fulfillment and shipping data the 3PL requires and excludes unrelated clinical information.
  - **Enforcement:** Not enforced because no 3PL transport exists. The outbound payload contract and acceptance tests must verify data minimization before release.

#### Target

Reliably hand pharmacist-released orders to the contracted 3PL through its ShipStation workflow, reconcile positive acknowledgment, and return real packing, label, carrier, and tracking evidence to RonanRx for Ops and Delivery Bot.

**Gaps:**

- Define and implement the authenticated outbound contract, stable external IDs, positive acknowledgment, and retry and reconciliation behavior.
- Ingest authoritative 3PL, ShipStation, and carrier events, surface exceptions in Ops, and feed Delivery Bot.

#### Open decisions

- **D11** (status: open; owner: Operations + Engineering; opened: 2026-07-30): Will RonanRx connect directly to the 3PL's ShipStation account and webhooks or use a 3PL-owned API or feed, and what authenticated event contract is available?

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

---

## Section 6: Billing Architecture

### 6.1 Stripe 💳 {#billing-architecture}

#### What it is

The billing model that keeps the patient's membership, prescription charge, RonanRx fee, and doctor payout explicit.

#### Rules

- **RRX-BILLING-001:** RonanRx Inc is merchant of record and collects the full patient amount.
  - **Enforcement:** External (Stripe account ownership): merchant-of-record is a Stripe/legal configuration, not a repo control. Today membership is collected on RonanRx's own Stripe account (app/lib/billing.rb) while the per-prescription charge routes to the Elite Care (TX) / True Nano (CA) pharmacy accounts (app/lib/rx_billing.rb), so a single 'RonanRx collects the full amount' is not yet what the code does (gap m5l2).
- **RRX-BILLING-002:** RonanRx retains a flat $10 fee and absorbs Stripe processing from that fee.
  - **Enforcement:** Not enforced today: the flat $10 RonanRx fee and Stripe-processing absorption are unbuilt (gap m5l2, Stripe Connect). Membership price lives in Stripe (STRIPE_PRICE_ID, app/lib/billing.rb) and the flat $195 med charge in app/lib/rx_pricing.rb; neither encodes a $10 fee split.
- **RRX-BILLING-003:** The doctor receives a flat $29 payout through Stripe Connect.
  - **Enforcement:** Partially external: the $29 provider-fee default is set in code and contract (app/services/provider_agreement_packet/cover_fields.rb = 2900 cents; config/text_authorization/provider_agreement_packet_contract_v1.yml, 'the Provider Fee can be settled by Stripe'), but the actual Stripe Connect payout is unbuilt (gap m5l2).
- **RRX-BILLING-004:** Membership and prescription charges remain distinct and identify the responsible billing entity.
  - **Enforcement:** Enforced by: test/lib/rx_billing_test.rb - asserts the per-prescription charge carries its own account key and never the global membership Stripe key, and routes the responsible billing entity by state (CA -> True Nano 'ca', else -> Elite Care 'tx'). Membership (app/lib/billing.rb) and prescription (app/lib/rx_billing.rb / PrescriptionPayment) charges are separate Stripe account families.

#### Target

RonanRx Inc remains merchant of record, collects the full amount, absorbs Stripe processing from its $10 fee, and pays the doctor a flat $29 through Stripe Connect.

**Gaps:**

- Complete Stripe Connect and the $29 payout implementation (bead m5l2).

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

---

## Section 7: Async Care Loop

### 7.1 Follow-Up Appointment {#check-ups}

#### What it is

A doctor-gated review of patient progress, adherence, outcomes, and relevant health evidence about every three months.

#### Rules

- **RRX-CARE-001:** Check-Ups occur about every three months and remain distinct from patient Outcome Check-ins.
  - **Enforcement:** Not enforced today: the doctor-gated ~3-month Check-Up is unbuilt - no Check-Up model, table, or quarterly cadence exists. Only the patient OutcomeCheckIn (app/models/outcome_check_in.rb; weekly/biweekly/monthly) is built, so the two are 'distinct' only because the Check-Up does not yet exist (gap).
- **RRX-CARE-002:** The doctor reviews current weight, adherence, outcomes, and relevant timeline evidence before approving continuation.
  - **Enforcement:** Convention-only (operational): nothing in code gates continuation on a documented review of weight, adherence, outcomes, and timeline; the doctor one-action review surface is unbuilt (gap). Spec designates this operational-review-confirmed.
- **RRX-CARE-003:** Check-Up approval is attributable and gates therapy beyond the prescription limit.
  - **Enforcement:** Not enforced today: no Check-Up approval exists and Prescription carries no refill/dispense-limit field (confirmed in db/structure.sql), so nothing gates 'therapy beyond the prescription limit.' Therapy holds (app/services/therapy_holds/guard.rb) gate refills, but not a Check-Up-based limit (gap).

#### Target

Run a doctor-gated Check-Up about every three months using weight, adherence, outcomes, and relevant timeline evidence.

**Gaps:**

- Connect Check-Ups to prescription limits, deliver patient prompts over SMS, and give doctors a one-action review surface.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 7.2 Refills {#refills}

#### What it is

Future medication-continuity functionality intended to keep patients from running out while preserving clinician control.

#### Rules

- **RRX-CARE-004:** Refill eligibility is calculated from quantity, dose, frequency, days supply, start date, and expected depletion.
  - **Enforcement:** Not enforced today: refill scheduling is a hardcoded 28-day cadence (app/services/agents/refill.rb sets next_refill_date = today+28; app/services/workflows/confirm_delivery.rb sets due_date = today+28). The refill_tasks table has no quantity/dose/frequency/days-supply/start-date/depletion fields, so eligibility is not computed as the rule describes (gap).
- **RRX-CARE-005:** A clinician approves refill or reissue, and active therapy or safety holds remain authoritative.
  - **Enforcement:** Partially enforced by code-gate: TherapyHolds::Guard.ensure_clear! (app/services/therapy_holds/guard.rb) raises Workflows::InvalidTransition to block a refill under an active therapy/safety hold, applied in Workflows::ConfirmRefill (app/services/workflows/confirm_refill.rb) and covered by test/services/agents/refill_test.rb. But there is no clinician refill/reissue APPROVAL step - refills are patient-confirmed - so that half of the rule is unenforced.
- **RRX-CARE-006:** No patient message promises compounding or shipment until the corresponding fulfillment action exists.
  - **Enforcement:** Convention-only: nothing ties compounding/shipment language to an existing fulfillment record. Linq outbound is restricted to approved templates by Linq::SendGuard (app/services/linq/send_guard.rb), which blocks arbitrary prose, but no check verifies a promise against a real PharmacyOrder/Shipment (e.g., the portal flash 'Pharmacy will queue your next compounding run' fires on patient confirm).

#### Target

Compute refill eligibility from the prescription and expected depletion while preserving clinician approval and therapy holds.

**Gaps:**

- Verify historical refill usage before redesign and connect approved refills to real fulfillment behavior.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 7.3 Clinical Rounds {#doctor-newsfeed}

#### What it is

The doctor-facing feed of meaningful patient progress, messages, refills, shipments, and clinical flags.

#### Rules

- **RRX-CARE-007:** Use Doctor Newsfeed as the canonical name for the core provider surface.
  - **Enforcement:** Convention-only: the Doctor Newsfeed surface is unbuilt - no 'newsfeed'/'news_feed' reference exists in app, lib, test, or origin/staging. The canonical name is honored by documentation discipline only (gap).
- **RRX-CARE-008:** Feed events are attributable, patient-specific, and linked to their owning record or action.
  - **Enforcement:** Not enforced today: the Doctor Newsfeed is unbuilt, so there are no feed events to attribute. The underlying Event model (app/models/event.rb) is polymorphic/attributable (eventable + actor, type allowlist) and linked to an owning record, but nothing assembles it into an attributable per-patient doctor feed (gap).
- **RRX-CARE-009:** The Doctor Newsfeed remains distinct from The Daily Compound and from internal patient-graph history.
  - **Enforcement:** Not enforced today: neither the Doctor Newsfeed nor 'The Daily Compound' exists in the codebase (no reference in app, lib, test, or origin/staging), so the required distinction is definitional only (gap).

#### Target

Give doctors a patient-progress feed for Check-Ups, refills, messages, shipments, clinical flags, and meaningful health changes.

**Gaps:**

- Build the Doctor Newsfeed as a distinct core surface and keep it separate from The Daily Compound.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 7.4 Patient Chat {#doctor-patient-texting}

#### What it is

Doctors (and staff) texting patients directly.

#### Rules

- **RRX-CARE-010:** Every outbound path has an identified owner, consent check, delivery policy, and observable failure path.
  - **Enforcement:** Enforced by: app/services/linq/send_guard.rb (opt-out consent check, template+mode delivery policy, and observable reasons[] failure path applied on every LinqReplyJob-routed send) plus test/architecture/linq_takeover_sender_classification_test.rb, which inventories every Linq sender origin against an owner classification and fails on any unclassified or bypassing path. The dedicated doctor-to-patient lane itself is not yet built (card gap).
- **RRX-CARE-011:** Human takeover suppresses agent replies until an explicit resume or expiry under the approved policy.
  - **Enforcement:** Enforced by: test/services/workflows/linq_takeover_test.rb (strict 24h expiry boundary and explicit release clearing the claim) and test/services/linq/serialized_dispatch_takeover_concurrency_test.rb (an active takeover winning the chat lock blocks suppressible agent dispatch).
- **RRX-CARE-012:** The onboarding Flue cutover does not replace doctor, staff, reminder, or transactional messaging paths.
  - **Enforcement:** Enforced by: test/architecture/linq_takeover_sender_classification_test.rb - the transactional/staff/reminder senders (appointments notifier, health-history/weigh-in nudges, pre-doctor reminders, logins, admin messages, rx-ready-to-pay) are enumerated as an 'exempt' lane distinct from the conversational (onboarding) lane, so a cutover that removed or absorbed them fails the inventory. No 'Flue' code exists in this Rails repo (onboarding brain lives outside it).

#### Target

Keep doctor, staff, reminder, and transactional messaging on explicit Rails and Linq paths. Flue owns the onboarding reply brain only.

**Gaps:**

- Build the missing doctor-to-patient surface without treating the onboarding Flue cutover as a replacement for this lane.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 7.5 Medical Support Chat {#urgent-text-escalation}

#### What it is

The human escalation and audited resolution path for urgent patient messages.

#### Rules

- **RRX-CARE-013:** Urgent messages are classified before ordinary conversational handling and may place an immediate safety hold.
  - **Enforcement:** Enforced by: test/integration/api/v1/linq_red_flag_detector_test.rb (the red-flag detector provably runs before the typing/comprehension/voice agents and a tier-1 match latches an emergency hold + persists governed match metadata before normal planning) and test/services/linq/emergency_detection_test.rb (red-flag phrases classify to a tier-1 emergency hold reply).
- **RRX-CARE-014:** The responsible doctor, pharmacist, or Ops responder receives an actionable notification with an acknowledgement deadline.
  - **Enforcement:** Escalation ladder and acknowledgement-deadline scheduling are exercised by test/jobs/on_call_escalation_job_test.rb (5-min live call, 10-min ops fallback, halted once a clinician acknowledges), but actual paging delivery is external and NOT wired today: OnCall::Dispatch::ADAPTERS (app/services/on_call/dispatch.rb) is empty, ON_CALL_TRANSPORT is unset, and the boot guard fails closed - so a real responder notification is not delivered (card gap: connect a real paging transport).
- **RRX-CARE-015:** Detection, contact attempts, acknowledgements, decisions, and hold release remain visible in the Ops audit trail.
  - **Enforcement:** Enforced by: test/jobs/on_call_escalation_job_test.rb (detection ladder, contact/page attempts, and 'stops once acknowledged' recorded as AdverseEventEscalationEntry kinds) and test/controllers/staff/adverse_event_escalations_controller_test.rb (a same-clinic clinician acknowledgement is recorded to the encrypted escalation ledger and visible in Ops).
- **RRX-CARE-016:** Therapy-hold release requires resolution confirmation and a fresh safety review.
  - **Enforcement:** Enforced by: test/services/workflows/therapy_hold_workflows_test.rb, which drives app/services/workflows/release_therapy_hold.rb: release requires explicit trigger_resolved + resolution_confirmation AND a current passing glp1_fill_safety run, with missing/post-call/aged/reopened-chart evidence failing closed and confirmed pancreatitis never releasing.

#### Target

Classify urgent patient messages, place appropriate holds, notify the responsible human team, and record every action through resolution.

**Gaps:**

- Connect a real paging transport and build the clinician queue for acknowledgements, adverse events, and hold release.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

---

## Section 8: App Data & Health Profile

### 8.1 Device Data {#app-data-collection}

#### What it is

Continuous patient data from approved devices and HealthKit, limited to information RonanRx can display and interpret responsibly.

#### Rules

- **RRX-DATA-001:** HealthKit access is read-only and begins only after an in-app explanation and affirmative patient step before Apple's authorization dialog.
  - **Enforcement:** Enforcement: to be confirmed in audit - HealthKit read-only access and the in-app explanation before Apple's authorization dialog are iOS-client behaviors with no code in this Rails repo (absent on main and origin/staging; the the 2026-07-17 snapshot implementation was removed and the backend is unbuilt per the card gap).
- **RRX-DATA-002:** Ingest only data types RonanRx can display and interpret responsibly.
  - **Enforcement:** Enforced for the only live device path (Oura) by app/services/oura/webhook_processor.rb (SUPPORTED_DATA_TYPES allowlist ignores any unsupported data_type) and app/services/oura/daily_sync.rb (only a curated set of interpretable metrics is normalized; rows carry the 'adjunctive wellness trend, not diagnostic' provenance label). The HealthKit app-data ingestion path is unbuilt (card gap).
- **RRX-DATA-003:** Synchronize on foreground entry and through HealthKit observer delivery; do not invent hourly polling.
  - **Enforcement:** Convention-only in this repo: the only wearable sync (Oura/Withings) uses daily recurring jobs (config/recurring.yml, 05:00/05:30 CT) plus provider webhooks (observer/push) - no hourly polling - but the HealthKit foreground-entry and observer synchronization the rule describes is iOS/app behavior that is unbuilt here (card gap); nothing in this repo enforces it.
- **RRX-DATA-004:** Device data retains source, time, units, deduplication identity, and patient authorization state.
  - **Enforcement:** Enforced by: test/services/workflows/ingest_wearable_daily_metrics_test.rb (one row per patient/provider/day; concurrent inserts reconciled) backed by the DB unique index idx_wearable_daily_metrics_patient_provider_date in db/structure.sql for deduplication identity. Source (provider + wearable_connection), time (measured_on), and source_ref are columns and authorization is the active wearable_connection + logged PhiAccess consent, but 'units' are not separately modeled (opaque metrics jsonb).

#### Target

Ingest approved wearable and HealthKit data read-only after an in-app explanation and affirmative patient authorization.

**Gaps:**

- Build a current backend and wire foreground and HealthKit observer synchronization; the removed 2026-07-17 snapshot implementation is historical only.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 8.2 Intake 3: Medical History {#health-history-questionnaire}

#### What it is

The deep patient health repository: full health-history questionnaire, goals, dose and side-effect logs.

#### Rules

- **RRX-DATA-005:** Patient-reported health history belongs in the official EHR with a patient-reported label and full provenance.
  - **Enforcement:** Enforced by: test/services/ehr/health_history_projector_test.rb, which drives app/services/ehr/health_history_projector.rb: completed answers project into Ehr::MedicalHistory with source 'patient_reported' and a clinical_snapshot carrying full provenance (source health-history id, questionnaire version, revision number, completed_at, projection fingerprint) plus an 'ehr_health_history_projected' audit event.
- **RRX-DATA-006:** Conflicting values preserve both histories and enter a doctor or authorized-admin reconciliation queue.
  - **Enforcement:** Convention-only: nothing enforces this today. Ehr::HealthHistoryReview (pending_review) is a projection-review sidecar, not a value-level conflict queue; the projector overwrites the source label and retires prior patient-reported facts rather than preserving both, and the governed promotion + doctor/authorized-admin conflict-reconciliation queue is explicitly unbuilt (card gap).
- **RRX-DATA-007:** Do not ask the patient for a fact the governed record already contains unless confirmation is required.
  - **Enforcement:** Enforced by: test/services/health_history/intake_prefill_test.rb - facts already supplied at signup are mapped into confirmation-only Health Story prefills carrying source_label ('From your secure signup') and source_reference, so known facts are re-surfaced for confirmation rather than asked again; deferred sentinels/blanks are suppressed.

#### Target

Promote patient-reported health history into the EHR with provenance, conflict history, and authorized reconciliation.

**Gaps:**

- Build the governed promotion and conflict queue and make approved reminders deliver by default.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

---

## Section 9: AI / Agent Layer

### 9.1 Agent Router {#agent-router}

#### What it is

The routing layer that sends each conversation to a focused agent while preserving safety, consent, and human-control boundaries.

#### Rules

- **RRX-AGENTS-013:** Safety, opt-out, and human-takeover branches run before conversational agent routing.
  - **Enforcement:** Enforced by: test/integration/api/v1/linq_takeover_suppression_test.rb (asserts human-takeover, red-flag, and dosing safety branches run before and outrank conversational agent routing: generative agents never run and no reply is enqueued during a hold); ordering lives in app/controllers/api/v1/linq_controller.rb#inbound.
- **RRX-AGENTS-014:** Each Flue agent owns one focused conversational responsibility.
  - **Enforcement:** Convention-only: each Agents:: subclass declares a single artifact_kind/event_action by the BaseAgent pattern, but nothing tests that an agent owns exactly one conversational lane.
- **RRX-AGENTS-015:** Routing expansion requires an explicit owner and boundary for the new lane.
  - **Enforcement:** Convention-only: routing/lane expansion has no owner-or-boundary gate; the only adjacent guard is test/architecture/linq_takeover_sender_classification_test.rb, which forces new Linq send sites to be explicitly classified but does not enforce lane ownership or scope.

#### Target

Route conversational agent work through focused Flue agents, beginning with onboarding, without weakening the Rails safety and delivery boundary.

**Gaps:**

- None.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 9.2 Patient Support {#patient-support}

#### What it is

AI-assisted customer service, including cancellation handling.

#### Rules

- **RRX-AGENTS-016:** RonanRx does not cancel a membership automatically.
  - **Enforcement:** Convention-only: no automated membership-cancellation path exists; the sole cancel path (Onboarding::CancelSubscription) is a deliberate onboarding action for non-serviceable states during the free first month. Nothing enforces the never-automatic invariant.
- **RRX-AGENTS-017:** Serviceability is resolved before payment activation whenever possible; later issues go to staff for deliberate handling.
  - **Enforcement:** Convention-only: serviceability is computed by Onboarding::Serviceability and shown in the completion wizard, but since the payment-ungated hotfixes (#659/#661) it gates nothing at payment (per the Onboarding::PaymentEligibility comment); non-serviceable cases are handled deliberately by staff via Onboarding::CancelSubscription. No hard gate enforces resolution-before-payment.
- **RRX-AGENTS-018:** Patient support can escalate to a human without losing conversation or decision history.
  - **Enforcement:** Convention-only: Agents::PatientSupport can set escalate/human_approval_required and every run is preserved as an Artifact+Event, but no staff reply/escalation surface is wired yet (card gap), so human escalation is not enforced end-to-end.

#### Target

Provide patient support and deliberate cancellation handling without automatically cancelling a paid membership.

**Gaps:**

- Add patient and staff cancellation flows, a staff reply surface, and a focused support agent with human escalation.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 9.3 Retention Agent {#retention-agent}

#### What it is

The focused agent that helps a patient during an explicit cancellation flow.

#### Rules

- **RRX-AGENTS-019:** Retention help begins only from an explicit cancellation flow and never prevents the patient from completing cancellation.
  - **Enforcement:** Convention-only: the retention agent is unbuilt on main and origin/staging (future card; the cancellation flow it depends on is not built). Nothing enforces this today.
- **RRX-AGENTS-020:** The lane remains focused on retention and hands clinical, billing, or safety issues to their owning workflows.
  - **Enforcement:** Convention-only: the retention agent is unbuilt on main and origin/staging (future card); nothing enforces its lane focus or hand-offs today.

#### Target

Offer governed retention help when a patient explicitly enters a cancellation flow, without obstructing cancellation.

**Gaps:**

- Build the cancellation flow first, then add the focused retention lane.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 9.4 Founder Mode / Dr. Bot {#founder-mode}

#### What it is

An intended patient product that captures the patient's goal/treatment interest, captures provider willingness/qualification, and matches patients to appropriate providers and programs.

#### Rules

- **RRX-AGENTS-021:** Founder Mode and Doctor Bot remain approved intent but must not be implemented until Zach defines the clinical boundaries.
  - **Enforcement:** Convention-only: enforced by governance (open decision D5, owner Zach), not code; nothing prevents implementation. Note: a distinct trigger-gated Linq::FounderMode demo-intake variant exists, but the Dr. Bot matching product this rule governs is deferred.
- **RRX-AGENTS-022:** Any implementation belongs on the Flue and rrx-agents architecture rather than reviving the deleted Ruby path unchanged.
  - **Enforcement:** Convention-only: an architecture directive (any implementation belongs on Flue/rrx-agents, not the deleted Ruby path); nothing enforces where a future implementation lives.

#### Target

Keep Founder Mode and Doctor Bot as product intent and reintroduce it on the Flue and rrx-agents architecture after its clinical boundaries are approved.

**Gaps:**

- None.

#### Open decisions

- **D5** (status: open; owner: Zach; opened: 2026-07-21): Define the clinical boundaries for Founder Mode and Doctor Bot on the Flue and rrx-agents architecture. Do not implement until answered.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 9.5 Medical Research Agent {#medical-research-agent}

#### What it is

The focused agent that answers clinical research questions from governed evidence with traceable citations.

#### Rules

- **RRX-AGENTS-023:** Every clinical answer is grounded in the governed evidence corpus and carries traceable citations.
  - **Enforcement:** Convention-only: Agents::ResearchSynthesis prompts the LLM for citations and an evidence grade but is not wired to the governed evidence corpus (the research/ ingestion pipeline is separate); nothing enforces corpus grounding today (card gap: connect synthesis to the corpus).
- **RRX-AGENTS-024:** Evidence quality and conflicts are visible to the human reviewer.
  - **Enforcement:** Convention-only: the evidence_packet artifact carries an evidence_grade field, but no reviewer surface exposing evidence quality or conflicts is built (card gap); nothing enforces visibility.
- **RRX-AGENTS-025:** No research-agent output becomes patient care or prescribing action without human approval.
  - **Enforcement:** Enforced by: app/services/workflows/approve_by_doctor.rb (gate "ai_never_makes_final_prescribing_decision" plus a hard actor.role == "doctor" guard before any Prescription is created); the research agent's output is a non-actioning reference artifact.

#### Target

Answer clinical research questions from a governed evidence corpus with traceable citations and mandatory human review.

**Gaps:**

- Connect synthesis to the corpus, grade evidence, expose a review surface, and then enable ingestion.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 9.6 Health Coach {#nutrition-fitness-agent}

#### What it is

The focused nutrition and fitness coaching agent for approved programs.

#### Rules

- **RRX-AGENTS-026:** Nutrition and fitness coaching stays inside the approved program and clinical boundaries.
  - **Enforcement:** Convention-only: no nutrition/fitness agent exists on main or origin/staging (future card); nothing enforces the program/clinical boundary today.
- **RRX-AGENTS-027:** Clinical, safety, or medication questions hand off to the responsible care workflow.
  - **Enforcement:** Convention-only: no nutrition/fitness agent exists on main or origin/staging (future card); nothing enforces clinical/medication hand-off today.

#### Target

Provide focused nutrition and fitness coaching for approved programs within explicit clinical and escalation boundaries.

**Gaps:**

- Define and build the complete focused agent.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 9.7 Greptile Watchdog {#drift-layer}

#### What it is

The review layer that compares implementation with the approved suite and presents drift to humans.

#### Rules

- **RRX-AGENTS-028:** Drift produces an explainable diff, owner, and human review request.
  - **Enforcement:** Convention-only: no drift-detection/self-checking layer exists on main or origin/staging (future card; gap: build alerts, generated diffs, ownership, close-and-verify). Nothing produces a drift diff/owner/review request today.
- **RRX-AGENTS-029:** A missing spec edit alone does not automatically reject a merge.
  - **Enforcement:** Convention-only: no spec-vs-code merge gate exists, so a missing spec edit cannot block a merge; the non-blocking behavior holds only because the drift layer is unbuilt, not because anything enforces it.
- **RRX-AGENTS-030:** Closing a drift finding requires a recorded fix or an approved registry update plus verification.
  - **Enforcement:** Convention-only: the drift layer is unbuilt (future card); nothing enforces that closing a drift finding requires a recorded fix or approved registry update plus verification.

#### Target

Detect drift between code and the reviewed suite, present a diff to humans, and never block a merge solely because the spec was not updated.

**Gaps:**

- Build review alerts, generated diffs, ownership, and a human close-and-verify workflow.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 9.8 Agent Census {#agent-census}

#### What it is

The maintained census of agents, their runtime type, wiring state, and responsibility.

#### Rules

- **RRX-AGENTS-031:** The census covers every production-relevant Ruby and Flue agent, its model or deterministic type, callers, wiring state, and owner.
  - **Enforcement:** Convention-only: no maintained cross-runtime agent census exists (open decision D10). Partial machine-checked inventories exist (clinical_agent_fallbacks_test lists 8 real-LLM agents; linq_takeover_sender_classification_test inventories send sites) but none records model/callers/wiring-state/owner, and nothing enforces completeness.
- **RRX-AGENTS-032:** Historical counts are labeled by branch and date; they never stand in for the D10 production census.
  - **Enforcement:** Convention-only: a governance rule tied to D10 (historical counts must be labeled by branch and date and never substitute for the production census); nothing in code enforces it.

#### Target

Maintain a cross-runtime census of focused agents, models, callers, wiring state, and ownership.

**Gaps:**

- Complete the production census in D10 and distinguish Ruby agents from the Flue onboarding agent.

#### Open decisions

- **D10** (status: open; owner: Engineering (bead vfyq); opened: 2026-07-21): Complete the card-by-card legacy production census before enabling automated spec enforcement.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

---

## Section 10: RonanRX iOS & Scope Boundaries

### 10.1 RonanRX iOS {#ronanrx-ios}

#### What it is

The native RonanRX iOS patient app: the HealthKit data-feed leg plus (eventually) the patient's daily companion.

#### Rules

- **RRX-PLATFORM-001:** The app's product name is RonanRX and its bundle identifier is `com.ronanrx.app`.
  - **Enforcement:** Enforced in the separate RonanRX_iOS app (Xcode bundle identifier com.ronanrx.app); not present in the ronanrx-core Rails repo.
- **RRX-PLATFORM-002:** HealthKit follows the read-only authorization and synchronization rules in App Data Collection.
  - **Enforcement:** Enforced in the RonanRX_iOS app (HealthKit read-only authorization); the Rails-side mobile sync API was removed (2026-07-17 snapshot) and is not present in ronanrx-core today (only Oura/Withings webhook feeds exist).
- **RRX-PLATFORM-003:** Existing app infrastructure is reviewed against current code and intent before removal or replacement.
  - **Enforcement:** Convention-only: nothing enforces this today.

#### Target

Ship the native RonanRX patient experience and a current, supported backend for its approved HealthKit and care flows.

**Gaps:**

- Rebuild or restore the required mobile APIs against current main and staging; do not treat the removed 2026-07-17 snapshot implementation as current substrate.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 10.2 The Daily Compound {#the-daily-compound}

#### What it is

This source-of-truth pass covers `ronanrx-core` and `RonanRX_iOS`. The separate Timeline product is not being approved, status-badged, or fact-checked in this pass.

#### Rules

- **RRX-PLATFORM-004:** The Daily Compound is outside this suite and is not the Doctor Newsfeed.
  - **Enforcement:** Convention-only: The Daily Compound does not appear in ronanrx-core; nothing enforces this today.
- **RRX-PLATFORM-005:** Do not import runtime, privacy, launch, or scheduling claims until a separate audit admits them.
  - **Enforcement:** Convention-only: nothing enforces this today.

#### Target

Keep The Daily Compound outside this suite and distinct from the Doctor Newsfeed until a separate audit admits it.

**Gaps:**

- None.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 10.3 ronanrx-brain {#ronanrx-brain}

#### What it is

Do not include RonanRX Brain in this source-of-truth reconciliation. It is a separate company-operations system, not the patient/doctor product in `ronanrx-core` or the RonanRX iOS app.

#### Rules

- **RRX-PLATFORM-006:** ronanrx-brain is outside the patient and doctor product scope.
  - **Enforcement:** Convention-only: ronanrx-brain does not appear in ronanrx-core; nothing enforces this today.
- **RRX-PLATFORM-007:** Do not import governance, deployment, fleet, or data claims until a separate audit admits them.
  - **Enforcement:** Convention-only: nothing enforces this today.

#### Target

Keep ronanrx-brain outside the patient and doctor product scope until a separate audit admits it.

**Gaps:**

- None.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 10.4 Elite Care Pharmacy Site {#elite-care-pharmacy-site}

#### What it is

The separate pharmacy marketing-site repository is outside this `ronanrx-core` + `RonanRX_iOS` reconciliation pass. This does not remove Elite Care Pharmacy from the core fulfillment and billing requirements in §§3, 5, and 6.

#### Rules

- **RRX-PLATFORM-008:** The Elite Care Pharmacy marketing site is outside this suite.
  - **Enforcement:** Convention-only: the Elite Care Pharmacy marketing site does not appear in ronanrx-core (only pharmacy fulfillment/billing references exist); nothing enforces this today.
- **RRX-PLATFORM-009:** Its exclusion does not remove pharmacy fulfillment, billing, or licensing requirements owned by core cards.
  - **Enforcement:** Convention-only: nothing enforces this today.

#### Target

Keep the Elite Care Pharmacy marketing site outside this suite while retaining pharmacy fulfillment and billing requirements here.

**Gaps:**

- None.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

### 10.5 TestFlight Protocol Sandbox {#protocol-sandbox}

#### What it is

An isolated TestFlight admin sandbox for validating duplicated protocol behavior before any connection to live workflows.

#### Rules

- **RRX-PLATFORM-010:** The sandbox uses a duplicate of protocol behavior and never mutates the live engine.
  - **Enforcement:** Convention-only: the TestFlight protocol sandbox is not built in ronanrx-core; nothing enforces this today.
- **RRX-PLATFORM-011:** Connection to live workflows requires passing evidence and explicit approval.
  - **Enforcement:** Convention-only: the sandbox-to-live promotion gate is not built; nothing enforces this today.

#### Target

Validate duplicated protocol behavior in an isolated TestFlight admin sandbox before any live connection.

**Gaps:**

- Build the isolated sandbox and its approval evidence.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

---

## Section 11: Provider Onboarding

### 11.1 Provider Onboarding {#provider-onboarding}

#### What it is

How a doctor (or NP/PA) gets onto the platform: invited or self-enrolled, agreement-signed, credentialed, licensed, approved, and linked to a clinic. **Provider onboarding** is the canonical term; it is not the patient's pre-doctor intake (§2.3).

#### Rules

- **RRX-PROVIDER-001:** Provider onboarding is invite-gated for launch and uses the complete approved agreement packet.
  - **Enforcement:** Enforced by: app/services/workflows/create_provider_from_invite.rb (guards a valid invite and a sealed agreement packet) and test/services/workflows/create_provider_from_invite_test.rb
- **RRX-PROVIDER-002:** New provider credentials and licenses remain inactive until a human approves them.
  - **Enforcement:** Partially enforced: provisioning runs only from an admin-minted, security-re-authenticated ProviderInvite (app/controllers/admin/provider_invites_controller.rb), but the resulting credentials and licenses are set status:active on provisioning (app/services/ehr/integration/providers.rb), not held inactive pending a separate human approval.
- **RRX-PROVIDER-003:** Provider agreements use the approved $10 RonanRx fee and $29 doctor payout model.
  - **Enforcement:** Enforced by: test/services/provider_agreement_packet/render_and_seal_test.rb (asserts $29 default provider fee, $10 flat platform fee, $39 total)
- **RRX-PROVIDER-004:** Collect and verify the provider identity, NPI, licenses, malpractice coverage, contact details, payout identity, prescribing preferences, and program participation.
  - **Enforcement:** Partially enforced: the required floor (name, contact email, professional designation, NPI, at least one state license) is guarded in app/services/workflows/create_provider_from_invite.rb; malpractice, payout, prescribing-preference, and program fields are captured into the encrypted prefill blob (app/services/provider_invites/create.rb) but not verified (capture-only in v1).
- **RRX-PROVIDER-005:** Providers attest that they saw the patient's ID during the appointment under the nonblocking EHR policy.
  - **Enforcement:** Enforced by: test/services/ehr/workflows/note_signing_test.rb (identity attestation is recorded on the encounter and the note still signs when identity is unverified; nonblocking)

#### Target

Keep provider onboarding invite-gated, agreement-complete, and inactive until human credential activation, using the approved $10 RonanRx fee and $29 doctor payout copy.

**Gaps:**

- Wire Stripe Connect payout behavior without changing the approved agreement economics.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).

---

## Section 12: Ops Console & Oversight

### 12.1 Ops Console {#ops-console}

#### What it is

The admin-gated staff control room for audited interventions across conversations, appointments, payments, prescriptions, and charts.

#### Rules

- **RRX-OPS-001:** Every Ops surface is admin-gated and every clinical or patient-facing intervention is attributable.
  - **Enforcement:** Enforced by: test/controllers/ops/console_access_test.rb (admin-only access) and test/controllers/ops/audit_spine_test.rb (fail-closed per-view audit with actor attribution)
- **RRX-OPS-002:** Human takeover suppresses conversational agent replies while preserving approved transactional communication.
  - **Enforcement:** Enforced by: test/jobs/linq_reply_job_takeover_test.rb (an active human takeover drops conversational agent replies while emergency, appointment, and transactional traffic remain deliverable)
- **RRX-OPS-003:** Manual messages preserve consent, safety holds, secure-link policy, delivery result, and audit history.
  - **Enforcement:** Enforced by: app/services/linq/admin_messages/send.rb (opt-out, safety-hold, and secure-pointer/PHI guards, with dedupe and audit) and test/services/linq/admin_messages/send_test.rb
- **RRX-OPS-004:** The Ops chat surface supports payment actions and multiple prescriptions for one patient.
  - **Enforcement:** Enforced by: test/controllers/api/ops/v1/rx_payment_mutations_test.rb (payment-link/reconcile actions) and test/controllers/api/ops/v1/payment_diagnostics_test.rb (per-patient prescription inspection over the prescriptions array)

#### Target

Give the Ops Console a chat surface that can handle payment actions and multiple prescriptions for one patient.

**Gaps:**

- None.

#### Open decisions

None.

#### Status

Current delivery status and since-date live in [`spec/status.json`](status.json).
