---
title: CA-compliant bottle labels — Apex ZPL + the Advanced Rx Helper print agent
description: Build the Rx bottle label as native ZPL in Apex, queue it as a Print_Job__c routed to the filler's workstation, and have a company-owned local agent (the Advanced Rx Helper — its own repo) poll Salesforce and print to the station's Zebra over USB. No print cloud, no browser, no third-party vendor. Enforce a California 16 CCR 1707.5-compliant format for every CA ship-to on one universal 4×3 stock; fix the current label's PHI leaks for real.
status: Draft
owner: Kyle
updated: 2026-06-23
last_verified: 2026-06-23
verification_type: code+web
verification_ttl_days: 14
---

# CA-compliant bottle labels — Apex ZPL + the Advanced Rx Helper print agent

> **Pattern 3 plan.** `plan.md` is authoritative; `visualization.html` is derived and must stay in sync (see `.claude/rules/project-plans.md`).
> **Status is tracked only in `docs/plans/README.md`.**
> **Two repos:** `advancedrx-salesforce` (renderer + print-job queue) and a NEW **`advancedrx-helper`** repo (the workstation agent). **No print cloud, no browser, no third-party print vendor.** The renderer is Apex building a ZPL string; delivery is a **company-owned local agent** that polls Salesforce for jobs routed to its workstation and prints to the station's Zebra over USB. PHI never leaves Salesforce + your own network.
> **Superseded alternatives (all evaluated, all dropped as primary):** Azure render service; `zkmulti`/Bringg/PrintNode cloud rails; Zebra Browser Print / QZ Tray (browser→localhost). Why each lost → §4-D1. The label *content* (Apex ZPL) is identical regardless of delivery.

---

## §1 — Goal & summary

Make Rx **bottle-label** printing (a) **California 16 CCR 1707.5 compliant** for every patient whose order ships to California, and (b) **clean of the PHI leaks** in today's label path, while (c) delivering it with **no print cloud, no browser, and no third-party vendor**, on **one universal label stock** for every patient.

The renderer is **Salesforce-native**; the delivery is a **company-owned agent**. ZPL is a text command language, so the label is just a string built in Apex. The genuinely hard part was never *drawing* the label — it is **getting bytes to a USB printer on a filler's PC**, which no cloud (Salesforce, Azure, PrintNode, Bringg) can reach behind the pharmacy firewall without *something running on that PC*. So we run our own: the **Advanced Rx Helper**, a small local agent (its own repo) that **polls Salesforce** for print jobs routed to its workstation and prints to the station's Zebra over USB. The path is **SF → `Print_Job__c` queue → the station's agent (on your LAN) → the USB printer**. **No print cloud, no browser, no third-party vendor — PHI never leaves Salesforce + your own network.** This label is v1 of the agent; it grows into a platform one narrow, audited job type at a time (D1, D9).

Two label **formats** on **one universal 4×3 stock** — the CA-vs-standard difference is a **server-chosen ZPL format**, not a media change:

- **Standard format** (default) — every **non-CA** ship-to. To-be-redesigned (nicer than today), rebuilt as ZPL with the PHI leaks removed.
- **CA-compliant format** — every **CA** ship-to. A 16 CCR 1707.5 patient-centered label whose four mandated items are clustered in the required order at ≥12-point sans serif in ≥50% of the label area, with the mandatory emphasis treatment, **directions printed on the container** (never "see leaflet"), and the full BPC 4076 element set in the lower zone.

**One universal 4×3 label stock for ALL patients.** No stock swapping, no dedicated CA printer, no media-enforcement problem — the same blank goes in every printer, and the server picks which ZPL format to render onto it. (Physical container-fit of the 4×3 stock is being **bench-tested this week** — in progress, not an open question.)

**Two independent routings, both server-side:** the **format** is chosen by ship-to **state** (CA → compliant); the **printer** is chosen by **workstation** (the filler's current station). Apex sets both; the LWC/agent never choose (D4, D8).

The QR is drawn **natively in ZPL** (`^BQ`) — removing the external `bwipjs-api.metafloor.com` dependency that currently leaks `Patient_Fill_Id__c` off-org on every label render. The separate refill-QR sticker is **consolidated onto the main label** (the exact refill-QR encoding is an open input — OQ-3).

---

## §2 — Current state (corrected — verified against code 2026-06-23)

Today's bottle label is a server-rendered **Visualforce PDF** iframed by an LWC, printed **manually** from the PDF viewer. The prior version of this plan and the earlier audit **overstated** both the print automation and the compliance posture. The corrected facts:

### Print path is manual, not auto

- `c:printBottleLabel` renders nothing itself — it **iframes** `/apex/PrescriptionLabel` (`printBottleLabel.html:4` → `<iframe src={prescriptionFrame}>`; URL built at `printBottleLabel.js:28-44`).
- `/apex/PrescriptionLabel` is a Visualforce `renderAs="pdf"` page, `@page size: 4in 2in` (`PrescriptionLabel.page:1,5`), controller `PrescriptionLabelController`.
- **`window.print()` is commented out** (`printBottleLabel.js:114-116`: `handleClick() { //window.print(); }`). `connectedCallback` (`:117-124`) logs three names then `setTimeout(handleClick, 5000)` — which invokes the no-op. The pink "Print this page" button (`printBottleLabel.html:7`) also calls the no-op. **The operator prints manually from the PDF viewer.** (Cross-checked: `CORRECTIONS.md` row 35.)

### CA non-compliance is undercounted in the old audit

Every one of these is below the 16 CCR 1707.5 **≥12-point** floor or is a missing mandated element:

| Defect | Evidence | 1707.5 / BPC 4076 rule broken |
|---|---|---|
| Patient name renders at **10pt** | `PrescriptionLabel.page:24` (`font-size:10pt; font-weight:bold`) | (a)(1) ≥12pt floor |
| Drug name **12pt → 11pt** on long names, and height-clipped | `PrescriptionLabelController.cls:91-95` (`<40 chars → 12pt else 11pt`); rendered `PrescriptionLabel.page:93` inside `overflow:hidden; height:30px` (`:92`) | (a)(1) ≥12pt floor |
| Sig (directions) renders at **9.5pt / 9pt** | `PrescriptionLabelController.cls:97-101`; rendered `PrescriptionLabel.page:96` inside `height:40pt; max-height:50pt; overflow:hidden` (`:95`) | (a)(1)(C) ≥12pt; directions-on-container |
| Drug **strength NEVER queried or rendered** | Controller SOQL `PrescriptionLabelController.cls:35-50` has no strength field; page renders only `{!medicationName}` (`:93`) | (a)(1)(B) "name of the drug **and strength**"; BPC 4076(a)(7) |
| **Condition/purpose NEVER queried or rendered** | absent from controller + page | (a)(1)(D); BPC 4076(a)(10) (when indicated on Rx) |
| Four items **not clustered, not in required order** | name in header (`:24`), drug + sig in body (`:93,:96`), no purpose, no strength | (a)(1) cluster in one ≥50% area in order A→B→C→D |
| **No emphasis treatment** | no bold/color/blank-space set-off of the four items | (a)(2) emphasis |

### Three live PHI leaks (production, today, on the path that survives as fallback)

1. **Unencoded PHI in the iframe GET URL.** `printBottleLabel.js:30-44` concatenates `&patientName=` (`:34`, from `patientNameConverted` `:69-74`) and `&pharmacistName=` (`:39`, from `pharmacistNameConverted` `:62-67`) plus rxNumber/quantity/lotNumbersList/dosageForm/fillId into the iframe `src` with **no `encodeURIComponent`**. PHI lands in the URL → browser history, any proxy/server URL log.
2. **Patient/pharmacist/prescriber names in `console.log`.** `printBottleLabel.js:118-120`: `console.log(this.patientName)`, `console.log(this.pharmacistName)`, `console.log(this.PrescriberName)`.
3. **`Patient_Fill_Id__c` sent to an external QR host.** `PrescriptionLabelController.cls:52`: `qrCode = 'https://bwipjs-api.metafloor.com/?bcid=qrcode&text=' + medPresc.get(0).Patient_Fill_Id__c + ...`; rendered as `<apex:image url="{!qrCode}">` at `PrescriptionLabel.page:42`. A patient fill identifier egresses to `metafloor.com` on **every** label render. (Also a callout-without-Named-Credential.)

### Dead code that misled the old audit

- **BarQR is defined but not rendered.** `printBottleLabel.js:47-56` defines `urlGenerateCode` / `urlGenerateQRCode` (BarQR pages), but the template (`printBottleLabel.html:4`) binds only `src={prescriptionFrame}`. The BarQR getters are dead; the QR that actually prints is the external metafloor one.
- **`fillId` is overloaded.** The controller param `fillId` is an **Rx record Id** (`PrescriptionLabelController.cls:27`, queried `WHERE Id =:fillId` `:49`). But in `Loop_in_an_order_Execute_Event` a flow variable `FillId` is assigned `Patient_Fill_Id__c` (the business value) while the screen-component input `fillId` is bound to the Rx Id. Same name, two meanings. New code uses `rxId` and `patientFillId` as distinct values.

### Delivery today is manual browser-print; there is no print rail for bottle labels

- Bottle-label printing is **not** wired to any print-automation rail today — the operator prints manually from the PDF viewer (above). The new design is a **company-owned local agent** (§3, §4-D1), not the shipping rail and not a third-party print cloud.
- **Why not the shipping rail (`zkmulti`/Bringg):** the only AdvancedRx call (`ShipmentPrintLabelInvocable.cls`) is shipment-scoped, reads its `prefId` off a `zkmulti__MCShipment__c`, and returns `void` (no status). Bending a shipment-scoped managed rail to non-shipment bottle labels meant an unproven contract + a cloud hop. **The constraint that drove the whole design:** a cloud cannot reach a USB printer behind the pharmacy firewall — *something must run on the filler's PC.* Every option (Browser Print, QZ Tray, PrintNode, our agent) is just a different "thing on the PC"; we choose to **own** it (D1).

---

## §3 — Target architecture (Apex renderer + Print_Job__c queue + the Advanced Rx Helper agent)

### End-to-end happy path

```
 ┌───────────────────── SALESFORCE (advancedrx-salesforce) ─────────────────────┐   ┌──── FILLER PC (Station 3) ────┐
 │                                                                                │   │                               │
 │  Filler connects to station once/shift (2h):                                   │   │                               │
 │   Choose_Your_Workstation → User.Current_Workstation__c = Station 3            │   │                               │
 │                                                                                │   │                               │
 │  Fill flow (Loop_in_an_order_Execute_Event / Generate_Rx_Label)               │   │                               │
 │        │  print step = Apex action (or BeforeSave) that CREATES a record       │   │                               │
 │        ▼                                                                        │   │                               │
 │  BuildBottleLabel (Apex, with sharing, WITH USER_MODE):                        │   │                               │
 │    1. SOQL Rx by rxId: name, drug+STRENGTH, sig, purpose, qty, BUD, lot,       │   │                               │
 │       prescriber, dates, Order__r.Shipping_Address__r.State, patientFillId     │   │                               │
 │    2. format = (state=='CA') ? CA : STANDARD   ── server-enforced (D4)         │   │                               │
 │    3. zpl = ZplBottleLabelBuilder.build(format, fields)  (native ^BQ QR)       │   │                               │
 │    4. INSERT Print_Job__c{ ZPL__c, Workstation__c = Current_Workstation,       │   │                               │
 │            Status__c='Queued', Rx__c, Patient_Fill_Id__c }   (D8)              │   │                               │
 │        │  (fault connector on the INSERT → Error_Log__c)                       │   │                               │
 │        ▼                                                                        │   │                               │
 │  ┌─────────────────────┐        poll: "Queued jobs for Station 3?"             │   │  ┌─────────────────────────┐  │
 │  │ Print_Job__c queue  │◀───────────────────────────────────────────────────────────│ Advanced Rx Helper agent │  │
 │  │ (Workstation-keyed) │────────  job (ZPL) ────────────────────────────────────────▶│  • polls SF (its station)│  │
 │  └─────────────────────┘        writeback: Status__c='Printed' | 'Error'      │   │  │  • raw ZPL → USB printer │  │
 │        ▲                                                                        │   │  │  • writes status back    │  │
 │        │  Status__c='Error' or stuck 'Queued'                                  │   │  └─────────────────────────┘  │
 │        │     → alert + the manual browser-PDF interim is always available      │   │            │ USB              │
 │  ┌─────────────────────────┐                                                   │   │            ▼                  │
 │  │ INTERIM/EMERGENCY:      │                                                   │   │   ╔═════════════════════╗     │
 │  │ remediated browser-PDF  │  (PHI-clean; Rx Id only) — manual print           │   │   ║ Zebra (existing      ║     │
 │  │ c:printBottleLabel      │                                                   │   │   ║ GX430t), 4×3 stock,  ║     │
 │  └─────────────────────────┘                                                   │   │   ║ STD or CA ZPL format ║     │
 │                                                                                │   │   ╚═════════════════════╝     │
 └────────────────────────────────────────────────────────────────────────────┘   └───────────────────────────────┘
```

The label never leaves Salesforce + your own network: **SF → `Print_Job__c` → the station's agent (on your LAN) → the USB printer.** No print cloud, no browser, no third-party SaaS. Printing is **asynchronous** (the agent polls every few seconds and prints within seconds); the fill flow creates the job and moves on, and the agent reports status back onto the job.

### Component inventory — reused vs net-new

| Component | Repo / status | Notes |
|---|---|---|
| `c:printBottleLabel` + `/apex/PrescriptionLabel` + `PrescriptionLabelController` | SF — **Remediate (PHI fixes) + keep as interim/emergency fallback** | The three PHI leaks (§2) are fixed here as real work. Becomes the manual browser-PDF path used pre-agent and if the agent is down. |
| `ZplBottleLabelBuilder` (pure Apex string builder; STANDARD + CA formats; native `^BQ` QR) | SF — **NET-NEW** | No SOQL, no DML, no callout — deterministic string in / ZPL out, trivially unit-testable. |
| `BuildBottleLabel` (Apex, `with sharing`) | SF — **NET-NEW** | SOQL the Rx by `rxId` under `WITH USER_MODE`; pick format by ship-to state (D4); build ZPL; **INSERT a `Print_Job__c`** routed to the filler's `Current_Workstation__c` (D8). Invoked from the fill flow as an action. Fault connector on the insert → `Error_Log__c` (D5). |
| `Print_Job__c` (custom object — the queue) | SF — **NET-NEW** | Fields: `ZPL__c` (Long Text — **PHI; purge/redact after print, see §7/D5**), `Workstation__c` (Lookup, **frozen at create** — D8), `Status__c` (Queued/Printing/Printed/Error), **`Claimed_By__c`, `Claimed_At__c`, `Attempt__c`** (claim/lease — D5), `Rx__c`, `Patient_Fill_Id__c`, `Error__c`, `Printed_At__c`. The agent's inbox + an audit trail (metadata retained; PHI body purged). |
| `Agent_Registration__c` (custom object — agent↔station binding) | SF — **NET-NEW** | One row per installed agent → its `Workstation__c`. Salesforce derives a job's eligible station from the **authenticated agent**, never from a station Id the agent supplies (D8, Codex #2). |
| `Workstation__c` + `User.Current_Workstation__c` | SF — **Reuse registry; new picker** | Reuse `Workstation__c` as the station registry and the *2h-expiry pick-your-station UX*. **Do NOT reuse `Choose_Your_Printer_Screen_Flow` as-is** — it writes/deletes `zkmulti__MCUser_Printer__c` and validates via `Bringg_Printer_ID__c` (managed-package side effects that contradict "no zkmulti"). Build a clean picker that writes only our binding (D8, Codex #4). Note `Current_Workstation__c` is `Text(150)` (the workstation *Name*), and Name/Computer_ID/Printer_ID are **not unique** — resolve-and-freeze a `Workstation__c` Id at job creation (Codex #3). |
| Refill lot-selection (`Undo_By__c=null`, earliest `Expiration_date__c`) | SF — **Reuse as-is** | Already in both fill flows. |
| Edits to `Loop_in_an_order_Execute_Event` + `Generate_Rx_Label` | SF — **NET-NEW (edits)** | Add the `BuildBottleLabel` action (create the job) + a "sent to `<station>`" confirmation screen; keep the browser-PDF as the interim/fallback path. |
| `BuildBottleLabelTest` + `ZplBottleLabelBuilderTest` | SF — **NET-NEW** | Outcome-asserting per CLAUDE.md §8 (CA-state → CA format; non-CA → STANDARD; CA can't be downgraded; the job is created with the right Workstation + ZPL). |
| **Advanced Rx Helper agent** (`advancedrx-helper` repo) | **NET-NEW (own repo)** | A small local service per filler PC. Polls SF for `Queued` `Print_Job__c` for *its* station, sends raw ZPL to the local printer over USB, writes `Status__c` back. **v1 implements exactly one job type — `print_zpl`.** A narrow job-runner, not a remote shell (D9). |
| Agent ↔ SF auth (**per-agent authenticated principal** + least-privilege) + a **claim** Apex endpoint | SF — **NET-NEW** | Each agent authenticates as its **own principal** — a JWT/cert-based integration identity (this is the *credential*), on the `Minimum Access - <Integration>` pattern. **`Agent_Registration__c` is the station *binding*, not the credential**: it maps that authenticated identity → a `Workstation__c`. The agent calls a **claim endpoint** that atomically transitions a job `Queued→Printing` (D5) and returns only jobs for the station Salesforce **derives from the caller's authenticated identity** (D8) + writes status. It cannot read patient records broadly or name another station. |
| Universal **4×3** thermal label stock | **NET-NEW (single media)** | One blank for ALL patients; CA-vs-standard is the server-chosen ZPL format. Container-fit bench-tested this week (in progress). |
| Printers | **Reuse existing GX430t's** | The agent sends raw ZPL over USB; the GX430t prints ZPL fine regardless of Zebra's end-of-sale status (the EOL concern only ever mattered for *Browser Print* SDK support, which we dropped). **No fleet purchase.** Replace with **Zebra ZD621t** (300 dpi, thermal transfer — the named GX430t successor) only as units age out; pair with a wax-resin ribbon on coated 4×3 (resin/synthetic for durability), qualified by test. |
| **`zkmulti`/Bringg/PrintNode/Browser Print/QZ Tray/Azure** | **DROPPED as primary** | Evaluated; each kept as an "alternative" only. The agent replaces them. See §4-D1. |

There is **no external callout in the print path** and **no print cloud**. The native `^BQ` QR removes the only outbound HTTP the current label makes. If a future archival need arises, write a `ContentVersion` directly in Apex — no Named Credential, no external service.

---

## §4 — Key decisions

### D1 — Delivery is a **company-owned local agent** (the Advanced Rx Helper), polling Salesforce

**Decision.** A small service on each filler PC (`advancedrx-helper`, its own repo) **polls Salesforce** for `Print_Job__c` records routed to its workstation and prints the ZPL to the local Zebra over USB. **No print cloud, no browser, no third-party vendor.**

**Rationale — why every alternative lost.** The unavoidable constraint: a cloud cannot reach a USB printer behind the pharmacy firewall, so *something must run on the filler's PC.* Given that, the choice is only "whose agent + cloud or not":

- **Zebra Browser Print / QZ Tray (browser→localhost):** put the agent on the PC, but drive it from the browser — which drags in Salesforce CSP / Lightning Locker/LWS + a self-signed localhost cert (Browser Print) or licensing (QZ). Deep research found **no fully-documented public "clean" LWC↔localhost Browser Print success story**, and Browser Print's support tracking would have forced new ZD621t printers. Fragile + hardware cost.
- **`zkmulti`/Bringg:** shipment-scoped, `void` return, an unproven non-shipment contract, a managed-package boundary we don't control.
- **PrintNode (direct):** clean, but a third-party cloud → a HIPAA **BAA** gate, and an external dependency whose updates we don't control. (Bringg is PrintNode under the hood, so PHI already transits it for shipping — but we'd rather own the rail.)
- **Azure render service:** Azure is also a cloud that can't reach the USB printer either; it'd add the relay *and* still need a per-PC agent. Strictly more work.

**Our agent** keeps PHI on SF + the LAN (no BAA needed beyond Salesforce), removes the browser/CSP surface entirely (it's a server-to-agent poll, not a browser-to-localhost call — which also dissolves the earlier `@InvocableMethod`/`@AuraEnabled`/Flow-fault confusion: there is **no LWC print path**, just "create a record; the agent prints it"), works with the **existing GX430t's**, and gives us a platform to build on (D9).

**Wake-and-poll, with a real API budget (Codex #7/#8).** The **durable queue is `Print_Job__c`** (the source of truth). To avoid burning the org-aggregate REST budget (a naive "poll every few seconds × 5 stations" is 10k–100k+ idle calls/day, and limits are org-wide, not per-user), the agent **polls on a backoff with jitter** and a `/limits` budget check, optionally **woken by a Platform Event "nudge"** so it polls *now* when a job lands and idles otherwise. Platform Events are the wake signal, **not** the durable store (72-hour retention + replay edge cases) — the agent always reconciles against `Print_Job__c` and polling is the fallback if a nudge is missed.

**Concrete budget (proposed defaults — confirm in build, OQ-4).** Per station: idle poll every **30s** baseline, **exponential backoff to a 120s cap** after consecutive empty polls, **±20% jitter**; **when the Platform Event nudge is active, idle drops to a 5-minute heartbeat** (the event drives real-time wake, so steady state is event-driven, not poll-driven). **Hard ceiling ≈ 2,880 calls/station/day** (~one/30s) → **alert if exceeded**. **`/limits` gate** before each poll cycle: at **≥80%** of the org daily API allocation, widen to event-only / 5-min heartbeat + alert; at **≥95%**, **stop polling** entirely (event-only) + alert. Worst case (nudge channel down, all 5 stations pinned at the 30s floor) ≈ **14.4k calls/day** total — bounded, and well under a typical Enterprise org's daily allocation; steady state with nudges is a few thousand/day. These are the proposed starting ceilings; **`/limits` is the real hard stop** regardless of tuning.

**Fallbacks if we ever want them:** the browser-PDF path (interim/emergency, always available); or, if owning fleet software ever proves untenable, QZ Tray (local) or PrintNode (cloud + BAA) become drop-in delivery swaps — the Apex renderer + `Print_Job__c` queue are unchanged.

### D2 — Renderer lives in Apex; the agent is a dumb pipe (label logic is NOT in the agent)

**Decision.** `ZplBottleLabelBuilder` (pure Apex) builds the ZPL for both formats. The agent **never builds labels** — it only relays a finished ZPL string to the printer.

**Rationale.** Label logic belongs in one versioned, testable place where the data lives. If the agent assembled labels, changing a label would mean redeploying to every PC. Keep the agent dumb (receive ZPL → print → report status); all label content + compliance lives in Apex, identical regardless of delivery. **Font sizing is auditable in dots:** at 300 dpi, ZPL font height in dots = point size × (300/72), so **12pt = 50 dots** (`^A0N,50,50`) — the ≥12pt floor is a reviewable number in the template (Labelary; the printers are 300 dpi).

**ZPL injection must be neutralized (Codex #6).** Patient name / drug / sig / address are untrusted text going into ZPL command stream. ZPL `^FD` field data **excludes the command-prefix characters `^` and `~`**, and a sig containing them would corrupt or inject commands. `ZplBottleLabelBuilder` **must hex-encode every field value with `^FH`** (or strip/escape `^`/`~` and control chars) before insertion — no raw patient/Rx text concatenated into the ZPL. Unit-test with a sig that contains `^` and `~`.

### D3 — One universal **4×3** stock for ALL patients; CA-vs-standard is a server-chosen FORMAT, not media

**Decision.** Every patient's label prints on **one universal 4×3 thermal stock** — the same blank in every printer. The CA and standard labels are two **ZPL formats** rendered onto that media. **No stock swapping, no dedicated CA printer, no media enforcement.**

**Rationale.** The old dual-stock design (4×2 standard + larger CA) created a stock-enforcement problem: the wrong blank silently produces a non-compliant label even when the right ZPL is sent. One 4×3 blank **dissolves that problem** — there is no wrong media to load. The law specifies ≥50% area + ≥12pt for the four-item cluster, not a physical size; 4×3 holds the CA cluster at ≥50% plus the full BPC 4076 lower zone, and comfortably holds the (nicer, to-be-redesigned) standard format too. **Container-fit of the 4×3 stock is being bench-tested this week** — in progress, not an open question. (Corrects the prior plan's "see leaflet" handling of directions by giving every format room for directions on the container.)

**Fixed-stock overflow rule.** The stock is fixed at 4×3 — the label **cannot grow**. Cluster fonts are pinned at ≥12pt, so a long sig **wraps within a fixed max-line budget**; a sig that still overflows prints a **continuation/auxiliary 4×3 label** rather than shrinking below 12pt or dropping a required element. The builder enforces the budget; the budget is set from the **actual longest production sigs at 12pt** — a Phase 3 build/test task. **Counsel must confirm (OQ-1)** that splitting directions across a continuation label still satisfies 1707.5's single-contiguous-cluster requirement (Codex #9) — if not, those long-sig fills need a larger single stock.

### D4 — **Format** routing is server-enforced by ship-to state; the agent/LWC never choose

**Decision.** `BuildBottleLabel` selects the format from the Order's ship-to state (`Order__r.Shipping_Address__r.State`):

```apex
String format = ('CA'.equalsIgnoreCase(shipToState)) ? FORMAT_CA : FORMAT_STANDARD;
// HARD GUARD: a CA ship-to can never be downgraded.
if ('CA'.equalsIgnoreCase(shipToState)) { format = FORMAT_CA; }
```

The choice is made before the ZPL is built and stamped onto the `Print_Job__c`. Nothing downstream (the agent, any UI) can change it. CA patients **always** get the compliant format; non-CA get the standard format. The guard is the **last** statement touching `format`.

**Rationale.** 16 CCR 1707.5 is mandatory for CA dispensing; a silent downgrade is a compliance failure with patient-safety stakes. Encoding the choice in Apex makes "CA always gets the compliant format" a property of the system. Because both formats print on the same universal stock, no media-mismatch path can undo it.

### D5 — Async job model with an explicit **claim/lease** contract; status on `Print_Job__c`

**Decision.** The fill flow's `BuildBottleLabel` **Apex action inserts a `Print_Job__c`** (`Status__c='Queued'`) and continues. The agent processes it through a **claim/lease** lifecycle, not a bare status flip (Codex #1):

1. **Claim:** the agent calls a Salesforce Apex endpoint that does an **atomic, locked transition** `Queued→Printing` (`FOR UPDATE`), stamping `Claimed_By__c` (the agent identity), `Claimed_At__c`, and incrementing `Attempt__c`. Two agents can't claim the same job.
2. **Print → writeback:** on success the agent sets `Printed`/`Printed_At__c`; on failure `Error`/`Error__c`.
3. **Lease timeout:** a job stuck in `Printing` past a lease window (agent crashed mid-print) is **re-queued** by a scheduled sweeper for one bounded retry; `Attempt__c` caps retries so a poison job ends in `Error`, not a loop.
4. **At-least-once, with a guard:** the model is at-least-once (the "printed OK but the agent died before writeback" case can reprint). A duplicate label is a recoverable annoyance, not a safety event — the **pharmacist verification step** (D7) catches a wrong/missing label before ship. Provide an explicit **reprint** control rather than pretending exactly-once.

**Error contract (Codex #10), made precise.** Failure is signaled by the **Apex action throwing** — including if its internal DML fails — and the fill flow's **fault connector on the `BuildBottleLabel` action** routes to `Error_Log__c` (per `.claude/rules/flow.md`; you fault-connect the *action*, which surfaces the internal insert failure by throwing). There is **no LWC→Apex print call** to mis-wire (which also dissolves the earlier `@InvocableMethod`/`@AuraEnabled` ambiguity). The agent's print outcome lives on `Print_Job__c` (better than the dropped rail's `void` return); stuck `Printing`/`Error`/aged-`Queued` jobs raise an alert; the remediated browser-PDF path is the manual fallback.

**Rationale.** A distributed print queue needs a real concurrency contract — atomic claim, lease, attempt cap, crash handling — or you get duplicate prints, lost status, and poison loops. Because the pharmacist verification backstops the physical label (D7), **at-least-once + reprint** is the right, simple target; we don't need exactly-once.

### D6 — Printers: **keep the existing GX430t's**; replace with ZD621t only as they age out

**Decision.** Reuse the **existing GX430t** printers at the fill stations. The agent sends **raw ZPL over USB**, which the GX430t prints fine — its end-of-sale status is irrelevant to our own agent (it only mattered for *Browser Print* SDK support, now dropped). **No fleet purchase.** As units fail, replace with the **Zebra ZD621t** (300 dpi, thermal transfer — Zebra's named GX430t successor).

**Rationale.** Owning the agent means we drive the printer directly; we don't depend on a vendor's printer-support matrix. Keeping the GX430t's removes the ~$1.2k-per-station cost the Browser Print path implied. **Media:** wax-resin ribbon on coated-paper 4×3 (move to resin + synthetic for max durability); thermal transfer keeps Rx labels legible/scannable for the medication's life — qualify by test (Phase 0).

### D7 — No post-print scan; consolidated refill QR

**Decision.** **No post-print scan step.** The existing **pharmacist verification step** is the compliance backstop — the pharmacist checks the printed label on the bottle against the Rx before the order ships. The **separate refill-QR sticker is consolidated onto the main label**, drawn natively in ZPL (exact refill-QR encoding is an open input — OQ-3).

**Rationale.** A post-print scan would duplicate a control the pharmacist already performs; the human verification already compares the physical label to the Rx, which is the meaningful compliance check (and the backstop that makes async printing safe — D5). Consolidating the refill QR removes a second sticker and a second print step.

### D8 — **Printer** routing is by workstation, with the station identity **derived server-side**

**Decision.** Each `Print_Job__c` carries a **target `Workstation__c` Lookup**, resolved and **frozen at job creation** (D4 timing). The agent only ever receives jobs for the station **Salesforce binds to its authenticated identity** — it never passes a station Id the server trusts (Codex #2). Three corrections from Codex's live describe (Codex #3):

- **`User.Current_Workstation__c` is `Text(150)` holding the workstation *Name*, not a lookup**, and `Workstation__c` `Name` / `Computer_ID__c` / `Printer_ID__c` are **not unique**. So `BuildBottleLabel` must **resolve that Name to a specific `Workstation__c` record and stamp its Id** on the job (with a defined tie-break / "ambiguous → error" rule) — *or* we add a real `User → Workstation__c` lookup field and set that at pick-time. Do **not** route on the non-unique text.
- **Per-agent authenticated principal, not a shared filter.** Each installed agent **authenticates as its own principal** — a JWT/cert-based integration identity; **this is the credential.** A per-agent **`Agent_Registration__c` row binds that authenticated identity → a `Workstation__c`** — it is the station *map*, **not** the auth mechanism. The claim endpoint (D5) returns only jobs for *that* principal's bound station; a compromised/misconfigured agent **cannot** ask for another station's jobs because it never names the station — Salesforce derives it from the auth context.
- **Reuse the picker *pattern*, not `Choose_Your_Printer_Screen_Flow` as-is (Codex #4).** That flow creates/deletes `zkmulti__MCUser_Printer__c` records and validates via `Bringg_Printer_ID__c` — managed-package side effects that contradict "no zkmulti." Build a clean workstation-picker (same 2-hour-expiry UX) that writes only our binding.

**Rationale.** The printer is a property of the *physical station*, not the person, so the filler's pick-your-station step tells SF where they are; SF freezes that to a concrete `Workstation__c` Id on the job, and the agent is told its lane by SF (never the reverse). That closes the spoofing gap, survives the non-unique text reality, and keeps the only new user-facing behavior the same "pick your workstation" step packers already do. **5 stations = 5 registered agents, each served only its own jobs.**

### D9 — Build it as a **narrow job-runner**, never a remote shell; let the platform emerge

**Decision.** The agent polls SF for **typed jobs**; **v1 implements exactly one type — `print_zpl`**. The reusable platform is the *framework* (poll, auth, job lifecycle, status writeback, deploy, auto-update). Future capabilities are **new, individually security-reviewed job types** (e.g. `print_pdf`, `read_scale`, `scan_barcode`, `carrier_label`) — **never a generic "execute arbitrary command."**

**Rationale.** A company-owned, SF-connected workstation agent ("Advanced Rx Helper") is a genuine platform: once deployed + trusted, each new capability gets cheaper. But two guardrails keep it from biting us: **(1) the platform emerges feature-by-feature** — don't design it up front, and keep **compliance decoupled** (the CA label is *content* in Apex; it can ship via the manual PDF path even before the agent, so the platform build never blocks the deadline); **(2) no arbitrary local execution** — a cloud-driven agent that can take arbitrary actions on a PHI workstation is a remote-code-execution surface, so the agent only ever does explicitly-coded, reviewed job types. "Replace Bringg / do carrier integration ourselves" is a *future direction the platform unlocks* (the agent only solves the print last-mile; the carrier brain — rating/labels/tracking/manifest — is a separate large project), not a v1 promise.

---

## §5 — California compliance (16 CCR 1707.5 + BPC 4076), precise and sourced

> Every requirement below is quoted verbatim from a primary or authoritative source. **Do not paraphrase the law loosely** — the citations are load-bearing.

### What 1707.5 requires of the patient-centered cluster

**The four items, clustered, ordered, sized — 16 CCR 1707.5(a)(1)** (verbatim, Cornell LII; cross-checked against the CA Board Order of Adoption PDF):

> "Each of the following items, and only these four items, shall be clustered into one area of the label that comprises at least 50 percent of the label. Each item shall be printed in at least a 12-point sans serif typeface, and listed in the following order: (A) Name of the patient (B) Name of the drug and strength of the drug … (C) The directions for the use of the drug. (D) The condition or purpose for which the drug was prescribed if the condition or purpose is indicated on the prescription."

**Emphasis is mandatory — 16 CCR 1707.5(a)(2)** (verbatim, Cornell LII):

> "For added emphasis, the label shall also highlight in bold typeface or color, or use blank space to set off the items listed in subdivision (a)(1)."

**Remaining elements must not interfere — 16 CCR 1707.5(a)(3)** (verbatim, Cornell LII):

> "The remaining required elements for the label specified in section 4076 of the Business and Professions Code, as well as any other items of information appearing on the label or the container, shall be printed so as not to interfere with the legibility or emphasis of the primary elements specified in paragraph (1) of subdivision (a). These additional elements may appear in any style, font, and size typeface."

### The full BPC 4076(a) lower-zone element set

**BPC 4076(a)(1)–(11)** (verbatim, FindLaw) — the remaining required elements, which must appear without interfering with the cluster's legibility/emphasis:

> (1) "either the manufacturer's trade name of the drug or the generic name and the name of the manufacturer"; (2) "The directions for the use of the drug"; (3) "The name of the patient or patients"; (4) "The name of the prescriber"; (5) "The date of issue"; (6) "The name and address of the pharmacy, and prescription number or other means [of identifying the prescription]"; (7) "The strength of the drug or drugs dispensed"; (8) "The quantity of the drug or drugs dispensed"; (9) "The expiration date of the effectiveness of the drug dispensed"; (10) "The condition or purpose for which the drug was prescribed if the condition or purpose is indicated [on the prescription]"; (11) "the physical description of the dispensed medication, including its color, shape, and any identification code that appears on the tablets or capsules" — with exemptions including veterinary prescriptions, new drugs in the first 120 days, and **medications lacking a description in a commercially available database**, and applying to **outpatient pharmacies only**.

### Translated directions — the correct current language list

**16 CCR 1707.5(b)** (verbatim, Cornell LII):

> "the board shall publish on its Web site translation of the directions for use listed in subdivision (a)(4) into at least five languages other than English"

**Corrected fact (Codex was right):** the regulatory *floor* is "at least five," but the Board currently **publishes SIX** standardized-direction language sets — **Chinese, Farsi, Korean, Russian, Spanish, Vietnamese** — covering **15 standardized directions for use** (`https://www.pharmacy.ca.gov/publications/translations.shtml`). The prior plan's "five … not six" was **stale**. Drive the language set from data/config, never a `5`/`6` literal — the Board adds languages "as necessary."

Translated directions are **on patient request** and may be printed on the container, the label, **or a supplemental document**; when on the container/label the **English** version must also appear. (BPC 4076.6 — re-verify the operative text against leginfo before build; a 2024 amendment (AB 1902, eff. 2025-01-01) is in the lineage.)

### How each element is satisfied on the CA 4×3 ZPL label

| Requirement | Where | How (ZPL) |
|---|---|---|
| Patient name | Cluster zone (top of ≥50% block) | `^A0N,50,50` (=12pt at 300 dpi), bold; item (A) first |
| Drug name **and strength** | Cluster zone | `^A0N,50,50`; **strength added** — never queried today (`PrescriptionLabelController.cls:35-50`). Verify the strength field API name via live FieldDefinition SOQL (OQ-2). Item (B) |
| Directions for use (sig) | Cluster zone, **on the container** | `^A0N,50,50`; **no shrink below 12pt**, **no "see leaflet"**. Item (C). On the **fixed 4×3** the sig wraps within a defined max-line budget; a sig exceeding it prints a **continuation/auxiliary 4×3 label** — never below 12pt, never dropping a required element (D3) |
| Condition/purpose (if on Rx) | Cluster zone | Item (D); **new data element** — verify the Rx source field via live SOQL (OQ-2). Required only when indicated on the Rx |
| Cluster ≥50% of label **area** | Cluster geometry | The four `^FO`/`^FB` blocks occupy ≥50% of the 4×3 printable **area** — the builder asserts cluster **width × height ≥ 50% of label area**, not dot-height alone (Codex #9: height-only is wrong if the cluster isn't full-width). Auditable from the `^FO`/`^FB` coordinates. |
| Emphasis (a)(2) | Cluster zone | **Blank space + bold** — on a monochrome printer "color" is unavailable, so use the (a)(2)-permitted **bold typeface** for the field values **and blank space** to set off the four items |
| Manufacturer/generic, prescriber, date of issue, pharmacy name+address, Rx#, qty, expiration/discard date, physical description (or 4076(a)(11) exemption) | Lower zone (BPC 4076) | Any size/font (a)(3); printed so as **not to interfere** with the cluster's legibility/emphasis |
| QR (`Patient_Fill_Id__c`) | Lower zone | **Native `^BQN,2,5` + `^FDHA,<patientFillId>^FS`** — H error-correction, automatic mode, magnification 5 (reliable at 300 dpi). **No external service.** |
| Translated directions (6 languages, on request) | Supplemental document | English sig stays in the cluster; translated text on a supplement is explicitly permitted by 1707.5(a)(3)/(b) and BPC 4076.6 |
| USP-795 compound provenance (BUD / lot / "compounded by the pharmacy") | Lower zone + per 16 CCR 1735.4 | AdvancedRx is a compounding pharmacy; 1735.4(b) requires the 4076 + 1707.5 info **on top of** the compound elements — flag to PIC/counsel for the CA cohort (OQ-1) |

**The size-independent defects (sub-12pt name/drug/sig, missing strength, missing purpose, no emphasis, no cluster) are fixed in the renderer for the CA format.** Compliance sign-off (PIC + counsel) is a Phase 0 gate, not an engineering deliverable (OQ-1).

---

## §6 — Build phases

Each phase is a shippable increment. **Compliance is decoupled from the agent** (D9): the CA label content can ship via the manual browser-PDF path before the agent exists, so the platform build never blocks the deadline. The agent is the "spit right out" automation on top.

### Phase 0 — Non-engineering critical path (gates everything)

- **PIC + compliance counsel (BLOCKING):** confirm 1707.5 applies to the CA-ship-to cohort; approve the CA label layout (cluster ≥50%/12pt/order/emphasis, directions-on-container); rule on USP-795 / 16 CCR 1735.4 compound elements; confirm the translated-directions workflow (6 languages, on request, supplemental document), the BPC 4076(a)(11) physical-description exemption for compounds, and **USP `<17>` + the state board's adopted label rules**.
- **Pharmacy ops (in progress this week):** bench-test the universal **4×3** stock — confirm it **physically wraps real AdvancedRx containers** with the cluster on a flat-enough face. (In-progress, not an open question.)
- **Media + ribbon (qualify by test):** coated-paper 4×3 + wax-resin ribbon (resin + synthetic for durability); verify smudge/fade/abrasion (incl. an alcohol-wipe test).
- **Printers:** none to buy — reuse existing GX430t's (D6).

### Phase 1 — Compliance via the manual path (ship CA-compliant labels without the agent)

Goal: a CA ship-to gets a **fully 1707.5-compliant label** the operator can print today, decoupled from the agent build.

- **`ZplBottleLabelBuilder` + a CA 4×3 render** — and, for the manual interim, a `renderAs="pdf"` `@page 4in 3in` CA layout in the remediated VF page.
- **PHI remediation (real work, fixes live exposure):** pass only the Rx Id to the VF page; query PHI server-side under `WITH USER_MODE`; remove the `console.log` of names (`printBottleLabel.js:118-120`); replace the external metafloor QR (`cls:52`, `page:42`) with an in-org BarQR / inline QR; delete the dead BarQR getters (`js:47-56`).
- **Exit criteria:** a CA ship-to prints a 1707.5-compliant 4×3 label (manually) that passes PIC/counsel review (OQ-1); no PHI in any GET URL, `console.log`, or external callout (verified via the rendered `src` + network panel).

### Phase 2 — The Advanced Rx Helper agent + the `Print_Job__c` queue (POC: one station)

Goal: a real fill on one station produces a `Print_Job__c` that the station's agent prints automatically — proving the poll/auth/print/status loop end to end.

- **SF:** `Print_Job__c` object; `BuildBottleLabel` Apex action (build ZPL + insert the job routed to `Current_Workstation__c`); the least-privilege Connected App + integration user (read Queued jobs for a station, write status).
- **`advancedrx-helper` repo (NET-NEW):** the agent — auth to SF, poll for its station's `Queued` jobs, send raw ZPL to the local GX430t over USB, write `Status__c` back. v1 = the single `print_zpl` job type (D9). Decide + document the deploy/auto-update mechanism.
- **One station:** install the agent on one filler PC, register its `Workstation__c`, run a real fill.
- **Exit criteria:** a fill on Station X creates a job, the Station-X agent prints a **scannable native-`^BQ` CA label** within seconds with no click, and `Status__c` flips to `Printed`; a forced failure (agent down / printer offline) leaves the job `Queued`/`Error` and the manual fallback still works.

### Phase 3 — Both formats + the fill-flow cutover

- **`ZplBottleLabelBuilder`:** finish STANDARD (nicer, redesigned) + CA formats; enforce the max-line budget + continuation-label overflow (D3); test the **longest production sigs** at 12pt on the 4×3. **Print-quality validation:** verify the QR/barcode scan grade to **ISO/IEC 15415 (2D) / 15416 (1D) Grade B or better**; confirm **USP `<17>`** legibility.
- **SF:** wire `BuildBottleLabel` into `Loop_in_an_order_Execute_Event` (`Print_Bottle_Screen_0`) + `Generate_Rx_Label` (`Display_Bottle_Label`) — replace the manual screen with the action + a "sent to `<station>`" confirmation; fault connector on the insert → `Error_Log__c`; keep the browser-PDF as the interim/fallback. Add the filler **pick-your-workstation** step — reuse the picker **pattern** + `Current_Workstation__c` + the 2h-expiry UX, but as a **clean new picker, NOT `Choose_Your_Printer_Screen_Flow` as-is** (that flow writes/deletes `zkmulti__MCUser_Printer__c` + validates `Bringg_Printer_ID__c` — managed-package side effects that contradict "no zkmulti"; see D8/§3).
- **Exit criteria:** both formats print via the agent; CA ship-tos get the compliant format and cannot be downgraded; the fallback is reachable.

### Phase 4 — Fleet rollout + hardening

- Install the agent + register `Workstation__c` on all 5 fill stations; confirm routing isolation (each station prints only its jobs). Monitoring/alerting on stuck/`Error` jobs. Document the agent's deploy/auto-update/security runbook. `print_bottle_label_flow` (Draft) stays untouched.
- **Exit criterion:** all stations auto-print to their local GX430t; stuck/failed jobs alert; the manual fallback is reachable everywhere.

---

## §7 — Security / PHI

- **PHI stays on Salesforce + your own network.** The label bytes go **SF → `Print_Job__c` → the station's agent (on your LAN) → the USB printer.** No print cloud, no third-party SaaS, no off-network hop — so **no print-vendor BAA question** (the dropped PrintNode/Bringg paths would have needed one; the agent does not).
- **This plan fixes EXISTING production PHI exposure.** The three leaks (§2) are live today and survive on the fallback path; Phase 1 remediates all three as **real work**: Rx-Id-only URL + server-side `WITH USER_MODE` query; remove the name `console.log`; native `^BQ` QR (new path) + in-org BarQR/inline (fallback). **No PHI leaves the org.**
- **Agent ↔ SF is least-privilege AND per-agent (Codex #2).** Each agent has its own credential bound to a station via `Agent_Registration__c`; the claim endpoint returns only jobs for the station Salesforce derives from that auth — **the agent never supplies a station Id the server trusts**, so a compromised/misconfigured agent cannot pull another station's jobs. It cannot read patient records broadly; it sees only the ZPL on its claimed jobs.
- **`Print_Job__c.ZPL__c` is a new PHI store — give it a retention/redaction policy (Codex #5).** The ZPL contains patient name/sig/address, and **Shield is not licensed**, so it's plaintext at rest. Policy before build: **purge or redact `ZPL__c` after `Printed`** (keep non-PHI audit metadata — who/where/when/attempts), lock field-level access to the integration user + admins, and set an explicit retention window. The agent likewise does not persist job bodies/verbose logs.
- **No raw patient text in the ZPL (Codex #6).** `ZplBottleLabelBuilder` hex-encodes (`^FH`) / escapes every field value before insertion — `^FD` excludes `^`/`~`, so an un-escaped sig could corrupt or inject ZPL (D2).
- **The agent is a narrow job-runner, not a remote shell (D9).** It executes only explicitly-coded, reviewed job types (v1: `print_zpl`). No "run arbitrary command" — that boundary is what keeps a cloud-driven workstation agent from being an RCE surface as it grows.
- **Local-workstation surface — harden, not ignore.** The ZPL transits the agent and the Windows print spooler on the filler's own machine. Treat it like any local print job: the agent should not persist job bodies / verbose logs (or scrub them), run under a locked-down service account, and clear the spooled job after printing.
- **No PHI in logs.** Neither the Apex (`BuildBottleLabel` / `ZplBottleLabelBuilder`) nor the agent logs patient name/sig/address or the ZPL body. Log opaque ids (`rxId`, `patientFillId`, `Print_Job__c` Id) only.
- **Conventions:** `with sharing` on the new Apex; `WITH USER_MODE` on the patient-data SOQL; no hardcoded user/profile/RT IDs; no caller-supplied SOQL; outcome-asserting tests; agent secrets in a secret store, not in source.

---

## §8 — Risks & mitigations

| Risk | Severity | Mitigation |
|---|---|---|
| **We now own fleet software forever** — the agent must be deployed, code-signed, auto-updated, monitored, secured, and supported on every filler PC; bus-factor if its author leaves | **High (the real cost)** | Keep v1 tiny + boring (one job type, a poll loop); document a deploy/auto-update/runbook in the `advancedrx-helper` repo; treat it as a maintained product, not a script. The narrow job-runner (D9) keeps the surface small. |
| **Agent down / offline / printer jam** → label doesn't print | Med | Async status on `Print_Job__c` (stuck `Queued` / `Error` alerts); the **pharmacist verification step catches a missing label** before ship (D7); the manual browser-PDF fallback is always available. |
| **Agent security** — a cloud-driven workstation agent could become an RCE surface | High | **No arbitrary execution** — only explicitly-coded, reviewed job types (D9); least-privilege SF integration user; signed agent builds; locked-down service account. |
| **CA patient silently downgraded to the non-compliant format** | High | Server-enforced routing with a **hard CA guard as the last statement** touching `format` (D4); the format is stamped on the job before the agent sees it; unit test asserts a CA ship-to cannot produce STANDARD. |
| **Compliance miss** (cluster <50%, sub-12pt, wrong order, no emphasis, directions off-container, wrong language list) | High | §5 quotes the verbatim rules; the builder asserts dot-height/cluster-area; **PIC + counsel sign-off is a hard Phase 0/3 gate**; language set is data-driven (6 languages, not a literal). |
| **Existing PHI leaks persist on the fallback** | High | Phase 1 remediates all three as real work and ships first; exit criterion verified against the rendered URL + network panel. |
| **Long sig overflow on the fixed 4×3** | Med | Fixed max-line budget + a continuation/auxiliary 4×3 label (never sub-12pt, never drop a required element — D3); longest production sigs tested in Phase 3. |
| **ZPL geometry / unscannable QR** | Med | Labelary preview for proofing; native `^BQ` at magnification 5; **scannable QR is a Phase 2 exit criterion**; ISO Grade-B verification in Phase 3. |
| **Wrong-station routing** | Med | Route by `Workstation__c` resolved from `Current_Workstation__c` (D8); agent bound to its station; routing-isolation tested in Phase 4. |
| **Platform scope-creep delays the label** | Med | Compliance is decoupled (ships via the manual path, Phase 1); the agent stays one job type until the label is done (D9). |
| **Duplicate / lost-status prints** (no claim/lease) | High | Atomic claim + lease + `Attempt__c` cap + sweeper for stuck `Printing` (D5); at-least-once with an explicit reprint; pharmacist verification (D7) catches a wrong/missing physical label. |
| **`Print_Job__c.ZPL__c` = new plaintext PHI store** (no Shield) | High | Purge/redact `ZPL__c` after `Printed`; FLS-lock the field; explicit retention window (OQ-5); agent doesn't persist bodies (§7). |
| **ZPL command injection** via patient/sig text | High | `^FH` hex-encode / escape all field data (`^FD` excludes `^`/`~`); unit-test a sig with `^`/`~` (D2). |
| **Polling burns the org-aggregate API budget** | Med | Backoff + jitter, `/limits` monitoring, an explicit budget; a Platform Event "nudge" wakes the agent so idle polling is rare (D1, Codex #7/#8). |
| **Agent spoofs another station** | Med | Per-agent credential + `Agent_Registration__c`; SF derives the station server-side; the agent never names a station (D8, Codex #2). |

---

## §9 — Open questions (the few that truly need humans)

Research + the agent decision have resolved the rest (delivery rail, station routing, printer, the CSP/Browser-Print and PrintNode-BAA questions — all moot now). These require a human decision or live verification:

| # | Question | Owner | Blocks |
|---|---|---|---|
| OQ-1 | **PIC + counsel sign-off** on the CA label: 1707.5 layout, USP-795 / 16 CCR 1735.4 compound elements, BPC 4076(a)(11) physical-description exemption, translated-directions workflow (6 languages, supplemental document), USP `<17>` + the state board's adopted rules. | **Counsel + PIC** | Phase 0, Phase 1, Phase 3, go-live |
| OQ-2 | Live FieldDefinition SOQL: exact API name of the **drug strength** field and the **condition/purpose** field on `HealthCloudGA__EhrMedicationPrescription__c` (Health Cloud managed; not in retrieved metadata; `EhrMedicationPrescription.md` is past TTL). | **SF schema / Kyle** | Phase 1, Phase 3 |
| OQ-3 | What does the **refill QR encode**? The separate refill-QR sticker is being consolidated onto the main label — confirm the exact payload / URL. | **Kyle / pharmacy ops** | Phase 3 |
| OQ-4 | **Agent build decisions** (in the `advancedrx-helper` repo): language/runtime, deploy + auto-update across filler PCs, **per-agent SF auth** (JWT/cert) + the `Agent_Registration__c` station binding, the **claim/lease** endpoint + lease window, and the wake (Platform Event) vs poll-budget tuning (D1/D5/D8). | **Kyle / eng** | Phase 2 |
| OQ-5 | **`Print_Job__c.ZPL__c` retention/redaction policy** — purge-after-print timing, what audit metadata is retained, FLS, and retention window (Shield not licensed, so the ZPL is plaintext PHI at rest). | **Kyle / PIC** | Phase 2 |

---

## §10 — Response to the Codex review

Each of the 11 findings, mapped to accepted/fixed (how) or rejected (why):

| # | Sev | Finding | Disposition |
|---|---|---|---|
| 1 | HIGH | "Permanent fallback" preserves PHI leaks (iframe GET URL `printBottleLabel.js:30/41`; `console.log` names `:118`; external QR `PrescriptionLabelController.cls:52` / `PrescriptionLabel.page:42`) | **ACCEPTED / FIXED.** §2 documents all three with file:line; **§6 Phase 1** remediates them as real work (pass Rx Id only + server-side `WITH USER_MODE` query; remove name `console.log`; native `^BQ` QR + in-org BarQR/inline for the fallback). §7 states explicitly this fixes existing production exposure. |
| 2 | HIGH | Compliance design fails 1707.5 — directions must be ON the container (not "see leaflet"); emphasis omitted; BPC 4076 lower zone under-specified; "must not interfere with legibility" | **ACCEPTED / FIXED.** §5 quotes (a)(1) (cluster, order, ≥50%, ≥12pt), **(a)(2) emphasis** (bold + blank space), **(a)(3)** non-interference, and the full **BPC 4076(a)(1)–(11)** lower zone, all verbatim. **Directions are an in-cluster container item (C)** — the "see leaflet" handling is corrected (D3, §5). |
| 3 | HIGH | Current-label audit undercounts noncompliance — name 10pt, med 11pt, sig 9/9.5pt, strength not queried, items not clustered/ordered | **ACCEPTED / FIXED.** §2 "CA non-compliance is undercounted" table cites each with file:line. |
| 4 | HIGH | `zkmulti` reuse not proven — only call is shipment-scoped | **MOOTED.** The `zkmulti`/Bringg rail is **dropped** (§4-D1). Delivery is the company-owned agent to a USB printer — no cloud rail, no non-shipment-print contract to prove. |
| 5 | HIGH | Automatic fallback overpromised — `printLabels` returns no status; failure-signal fidelity deferred | **MOOTED / FIXED.** The `void`-return rail is dropped. Failure is observable on `Print_Job__c.Status__c` (the agent writes back); stuck/`Error` jobs alert; the pharmacist verification step + manual fallback backstop it (D5/D7). |
| 6 | MED | Error contract inconsistent — can't have both a Flow fault connector AND a success/false return | **ACCEPTED / FIXED.** **D5** is one model: the fill flow **inserts a `Print_Job__c`** with a **fault connector** → `Error_Log__c`; the agent reports print status onto the job. There is **no LWC→Apex print call** to mis-wire (which also dissolves the new-round #2/#3). |
| 7 | MED | `fillId` overloaded (Rx Id vs `Patient_Fill_Id__c`) | **ACCEPTED / FIXED.** §2 documents the overload; new code uses **`rxId`** (record Id) and **`patientFillId`** (the QR payload) as distinct values (§3, §6). |
| 8 | MED | Azure claims unverifiable in this repo | **MOOTED.** Azure is **dropped** (D1). |
| 9 | MED | "2-click pink button" description wrong — `window.print()` commented out | **ACCEPTED / FIXED.** §2 "Print path is manual, not auto" states it (`js:114-116`) + cross-checks `CORRECTIONS.md` row 35. |
| 10 | LOW | BarQR claim unsupported — JS defines BarQR URLs but the template renders only `src={prescriptionFrame}` | **ACCEPTED / FIXED.** §2 "Dead code" notes the BarQR getters are unreferenced; Phase 1 deletes them. |
| 11 | LOW | AGENTS.md points to `.Codex/rules`; `EhrMedicationPrescription.md` may be stale | **ACCEPTED.** Flagged for a cheap parallel cleanup (repoint AGENTS.md to `.claude/rules/`); `EhrMedicationPrescription.md` is past its 14-day TTL, so OQ-2 verifies field names via **live FieldDefinition SOQL** rather than trusting the doc. |

*(Subsequent Codex rounds: the `@InvocableMethod`/Flow-fault conflation and the fixed-stock-vs-"grow-the-label" contradiction are resolved by the async `Print_Job__c` model (D5, no LWC print path) and the fixed-stock overflow rule (D3). The ZD621t-vs-ZD421t correction and the Browser-Print fragility findings are moot now that the agent — not Browser Print — is the delivery path; the GX430t's are reused (D6).)*

*(**Final review (2026-06-23) — the four remaining edges are filed:** agent **auth wording** clarified (the credential is a per-agent authenticated principal; `Agent_Registration__c` is the station *binding*, not the credential — §3 / D8 / §7); the **Phase 3 picker contradiction** fixed (a clean new picker, never `Choose_Your_Printer_Screen_Flow` as-is — D8 / §6); the **API budget made numeric** (30s→120s backoff, 5-min heartbeat with nudge, ~2,880 calls/station/day ceiling, `/limits` 80%/95% gates — D1); and this stale Pattern-3 "pending" note removed. The items Codex left open — PIC/counsel sign-off, the ZPL retention policy, and the agent build decisions — are **human gates (OQ-1 / OQ-5 / OQ-4)**, not plan defects, so the plan is design-complete and stays **Draft** until those clear.)*

---

## §11 — Effort & sequencing

| Phase | Engineering | Notes |
|---|---|---|
| 0 — non-eng critical path | — (wall-clock) | PIC/counsel sign-off + 4×3 container-fit bench test (in progress) + media qualification. No printer procurement. **Gates everything.** |
| 1 — compliance via manual path | ~2–4 d | `ZplBottleLabelBuilder` + CA VF-PDF interim + PHI remediation. Ships CA compliance decoupled from the agent. |
| 2 — agent + queue (one-station POC) | SF ~2–3 d · agent ~5–8 d | NET-NEW `advancedrx-helper` repo (poll/auth/print/status) is the bulk; SF side is the `Print_Job__c` object + `BuildBottleLabel` + integration user. **Proves the loop.** |
| 3 — both formats + flow cutover | ~3–5 d | Finish STANDARD + CA ZPL + overflow + barcode/USP-17 validation; wire the fill flows + pick-your-station. Gated on OQ-1/2/3. |
| 4 — fleet rollout + hardening | ~2–3 d + ops | Install on all 5 stations; routing isolation; monitoring; deploy/update runbook. |

**True critical path:** Phase 0 sign-offs + the Phase 2 agent build dominate. The label *content* is modest (a string builder); the new commitment is **owning the agent** (a maintained product, D9 + §8) and the **compliance sign-off**. Because compliance ships via the manual path in Phase 1, the agent build never blocks the CA deadline.

---

## §12 — Platform roadmap: the Advanced Rx Helper beyond the bottle label

The bottle label is **platform phase 1** — it stands up the agent + the `Print_Job__c`/claim framework with a single job type (`print_zpl`). **Platform phase 2** is the backlog below: each item is a **new, individually security-reviewed job type** on the same framework (D9 — never a generic "run a command"). Grounded in current AdvancedRx workstation work; build one at a time, compliance-decoupled, after the label ships.

### Phase 2a — Quick wins (ride the `print_zpl` framework)

| Job type | Grounds in | Notes |
|---|---|---|
| `PRINT_PACKING_LIST` | `printPackingListv2` (browser print today) in `Print_one_order_Puller_view` | **Top quick win** — same shape as the label; kills browser-print fragility on the pack line |
| `PRINT_RECEIPT` | `printReceipt*` + `ControllerOrderSummary` receipt lookup | Natural second print type |
| `PRINT_CLAIM_FORM` | `printClaimFormListTestFlow` (browser print) | PHI-heavy; narrow print job fits |
| `PRINT_SHIPPING_LABEL_LOCAL` | `Prepare_Order_for_Shipping` → `ShipmentPrintLabelInvocable` | Closes the shipping loop locally (respect Zenkraft attachment/printer behavior) |
| `PRINT_AUX_WARNING_LABELS` | bottle-label warning fields; `PrescriptionLabel.page` compound text | Cheap once the ZPL path exists |
| `LOCAL_BARCODE_RENDER` | the **bwip-js/metafloor** external QR (`PrescriptionLabelController:52`, `printPackingListv2`) | **Security hardening** — render barcodes locally, killing external-identifier egress across *all* labels (the bottle label already does this via native `^BQ`) |

### Phase 2b — First agent→SF (capture) primitives

| Job type | Grounds in | Notes |
|---|---|---|
| `CAPTURE_VERIFICATION_SCAN` | `barcodeInputValidator` / `scanBarcodeTextFieldFinalVerification` (focus-sensitive textbox scans) | First capture primitive; **only while an expected scan job is open** — no keyboard injection |
| `VERIFY_SHIPPING_TRACKING_SCAN` + `PACKAGE_TYPE_SCAN` | `Prepare_Order_for_Shipping` tracking/package scan screens | Safety/status loop after local print |
| `WORKSTATION_PRINTER_HEALTHCHECK` | `Workstation__c` + the 2h printer-assignment expiry | Ops value; no PHI |
| `ORDER_READY_DESKTOP_ALERT` | `shippingView` / filler queue controllers | OS notification; keep PHI-minimal (order id, not patient name) |

### Phase 2c — Ambitious (compounding bench; need device/SOP confirmation)

| Job type | Grounds in | Notes |
|---|---|---|
| `SCALE_CAPTURE_COMPOUND_WEIGHT` | `Compound__c`/`Compound_Ingredient__c`/`Lot__c` (BUD, ingredient qty) | Potentially high patient-safety value; needs a real USB scale + SOP tolerances |
| `SCAN_TO_RX_IMAGE` | `Rx_Image__c` doc fields; Azure intake | Rescan paper/fax to SF; whitelist file types — never arbitrary upload |
| `LABEL_BARCODE_QA` | fill-id QR + final-verification scans | Decode-and-verify the printed QR (pass/fail), not photo retention |
| `PAYMENT_TERMINAL_JOB` | `Capture_Payment` / `PaymentCaptureAction` | PCI-sensitive — token/status only, never raw PAN in the agent or SF |

### Out of scope (the trap)

**No generic "copy data into another app," clipboard automation, or "run this command"** — that is the unreviewable remote-control surface D9 forbids. Softphone/fax bridges are plausible but need real workflow confirmation first. SFMC/marketing tasks aren't workstation-local — they stay server/SF-side.

**Top 3 after the label:** `PRINT_PACKING_LIST` (quick win, same shape) → `CAPTURE_VERIFICATION_SCAN` (first capture primitive — makes the platform feel real) → `PRINT_SHIPPING_LABEL_LOCAL` + `VERIFY_SHIPPING_TRACKING_SCAN` (closes the shipping loop). Pair `LOCAL_BARCODE_RENDER` with the label work as security hardening.

---

## Appendix — Evidence anchors

- **Current label (corrected facts):** `PrescriptionLabel.page:1,5` (`renderAs=pdf`, `@page 4in 2in`), `:24` (name 10pt), `:42` (external QR image), `:92-93` (drug clip + render), `:95-96` (sig render); `PrescriptionLabelController.cls:35-50` (SOQL, no strength/purpose), `:52` (metafloor QR with `Patient_Fill_Id__c`), `:91-95` (drug 12→11pt), `:97-101` (sig 9.5/9pt); `printBottleLabel.js:28-44` (PHI in iframe URL), `:47-56` (dead BarQR getters), `:114-116` (`window.print()` commented), `:118-120` (`console.log` names); `printBottleLabel.html:4` (single iframe), `:7` (pink button); `CORRECTIONS.md` row 35.
- **Why not the shipping rail (dropped):** `ShipmentPrintLabelInvocable.cls:7` (`void`, `List<Id> shipmentIds`), `:8-14` (`MCShipment` query), `:18` (Attachment `WHERE ParentId IN`), `:30` (`printLabels(attachments, prefId-from-shipment, userId)`); `ShipmentPrintLabelInvocableTest.cls:5-12` (Attachment parented to shipment). Shipment-scoped + `void` return = why the bottle label goes to a company-owned agent instead.
- **Reused station-routing pieces:** `Choose_Your_Printer_Screen_Flow.flow-meta.xml` (per-user `zkmulti__MCUser_Printer__c` write + 2h re-prompt); `Workstation__c/fields/{Bringg_Printer_ID__c,Computer_ID__c,Printer_ID__c,Type_of_Printer__c}.field-meta.xml`; `User.Current_Workstation__c`. Launch points: `Loop_in_an_order_Execute_Event.flow-meta.xml` (`Print_Bottle_Screen_0`), `Generate_Rx_Label.flow-meta.xml` (`Display_Bottle_Label`), `print_bottle_label_flow.flow-meta.xml` (Draft).
- **CA law (verbatim, primary/authoritative):** 16 CCR 1707.5(a)(1)/(a)(2)/(a)(3)/(b) — Cornell LII `https://www.law.cornell.edu/regulations/california/16-CCR-1707.5` + CA Board Order of Adoption `https://www.pharmacy.ca.gov/laws_regs/1707_5_oa.pdf`; BPC 4076(a)(1)–(11) — FindLaw `https://codes.findlaw.com/ca/business-and-professions-code/bpc-sect-4076/`; translations (6 languages incl. Farsi, 15 directions) — `https://www.pharmacy.ca.gov/publications/translations.shtml`; 16 CCR 1735.4 (compounded) — `https://www.law.cornell.edu/regulations/california/16-CCR-1735.4`; BPC 4076.6 (translated directions) — `https://codes.findlaw.com/ca/business-and-professions-code/bpc-sect-4076-6/`.
- **ZPL / hardware:** native QR `^BQN,2,5` + `^FDHA,<data>^FS` — `https://zplpdf.com/en/zpl-barcodes-guide`; font dots = pt × DPI/72 (12pt = 50 dots @ 300 dpi) — `https://labelary.com/zpl.html`; Zebra ZD621t (300 dpi, thermal transfer; named GX430t successor) — `https://www.zebra.com/us/en/products/printers/desktop/zd621.html`; barcode print-quality grades ISO/IEC 15415 (2D) / 15416 (1D); USP `<17>` prescription-container labeling.
