AdvancedRx Salesforce · Project Plan · 2026-06-23

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

Apex builds the Rx bottle label as native ZPL, queues it as a Print_Job__c routed to the filler's workstation, and a company-owned local agent (the Advanced Rx Helper) polls Salesforce and prints to the station's existing Zebra over USB. No print cloud, no browser, no third-party vendor. Every California ship-to gets a 16 CCR 1707.5-compliant format on one universal 4×3 stock; the current label's PHI leaks are fixed for real.

Status: Draft Gated on PIC/counsel sign-off + the one-station agent POC Two repos · no print cloud · PHI stays on SF + the LAN
1
Universal label stock
4×3 for ALL patients · CA-vs-standard is a server-chosen ZPL format
0
Print clouds / browsers / vendors
SF → Print_Job__c → the station's agent (LAN) → USB printer
≥12pt
CA cluster floor
= 50 dots @ 300 dpi · today renders 9–11pt
3
Live PHI leaks fixed
iframe URL · console.log names · external QR host

§1Goal & summary

Make Rx bottle-label printing California 16 CCR 1707.5 compliant for every order shipping to California, clean of the PHI leaks in today's path, and delivered with no print cloud, no browser, and no third-party vendor, on one universal label stock for every patient.

ZPL is a text command language, so the label is just a string built in Apex — the renderer is Salesforce-native. 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. 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 (§12).

Two formats on one universal stock — both routings are server-side

FormatApplies toLayout
Standard Every non-CA ship-to (default) To-be-redesigned (nicer than today), rebuilt as ZPL with the PHI leaks removed.
CA-compliant Every CA ship-to (enforced) 1707.5 cluster (4 items, order A→B→C→D, ≥12pt sans, ≥50% area, emphasis), directions on the container, full BPC 4076 lower zone, native ^BQ QR.

One universal 4×3 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 (container-fit is being bench-tested this week, in progress). 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 agent never chooses. The native ^BQ QR removes the external bwipjs-api.metafloor.com dependency, and the separate refill-QR sticker is consolidated onto the main label.

§2Current 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 plan and earlier audit overstated both the print automation and the compliance posture. The corrected facts:

Print path is manual, not auto

c:printBottleLabel iframes /apex/PrescriptionLabel (a renderAs="pdf", @page 4in 2in page). window.print() is commented out (printBottleLabel.js:114-116); the pink "Print this page" button and the 5-second setTimeout both invoke that 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

DefectEvidence (file:line)Rule broken
Patient name at 10ptPrescriptionLabel.page:241707.5(a)(1) ≥12pt
Drug name 12pt → 11pt, height-clippedPrescriptionLabelController.cls:91-95; page:92-931707.5(a)(1) ≥12pt
Sig (directions) at 9.5 / 9ptPrescriptionLabelController.cls:97-101; page:95-961707.5(a)(1)(C); directions-on-container
Drug strength NEVER queried or renderedSOQL cls:35-50 has no strength field1707.5(a)(1)(B); BPC 4076(a)(7)
Condition/purpose NEVER renderedabsent from controller + page1707.5(a)(1)(D); BPC 4076(a)(10)
Four items not clustered / not in required ordername header, drug+sig body, no purpose/strength1707.5(a)(1) cluster ≥50% in order A→D
No emphasis treatmentno bold/color/blank-space set-off1707.5(a)(2) emphasis

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

Unencoded PHI in the iframe GET URL

leak #1

printBottleLabel.js:30-44 concatenates &patientName= + &pharmacistName= (plus rx#/qty/lot/fillId) into the iframe src with no encodeURIComponent → browser history + any proxy/server URL log.

Names in console.log

leak #2

printBottleLabel.js:118-120 logs patient, pharmacist, and prescriber names to the browser console.

Patient_Fill_Id__c to an external QR host

leak #3

PrescriptionLabelController.cls:52 sends the fill id to bwipjs-api.metafloor.com on every render (page:42). Also a callout without a Named Credential.

Dead code that misled the audit

cleanup

BarQR getters (js:47-56) are unreferenced — the template (html:4) binds only src={prescriptionFrame}. And fillId is overloaded: an Rx record Id in the controller, but the business Patient_Fill_Id__c in a sibling flow variable. New code uses rxId and patientFillId as distinct values.

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

Bottle-label printing is not wired to any print-automation rail today. The new design is a company-owned local agent (§3, §4-D1), not the shipping rail and not a third-party print cloud. The shipping rail's only AdvancedRx call (ShipmentPrintLabelInvocable.cls) is shipment-scoped, reads its prefId off a zkmulti__MCShipment__c, and returns void (no status) — bending it 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).

§3Target architecture — Apex renderer + Print_Job__c queue + the agent

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 fill flow creates the job and moves on; the agent polls every few seconds, prints within seconds, and reports status back onto the job.

 SALESFORCE (advancedrx-salesforce)                                FILLER PC (Station 3)
 ──────────────────────────────────────────────────────────   ──────────────────────────────
 Filler picks station once/shift (2h expiry):
   Choose_Your_Workstation → User.Current_Workstation__c = Station 3

 Fill flow (Loop_in_an_order_Execute_Event / Generate_Rx_Label)
   print step = Apex action 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, ship-to 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, 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)│
 │  claim / lease)    │   writeback: Status__c='Printed'|'Error' │  • raw ZPL → USB printer │
 └────────────────────┘                                          │  • writes status back    │
        ▲                                                         └───────────┬─────────────┘
        │ Error / stuck Queued → alert + manual browser-PDF interim           │ USB
        │                                                                     ▼
 ┌────────────────────────┐                                        ╔═══════════════════════╗
 │ INTERIM / EMERGENCY:   │                                        ║ Zebra (existing GX430t)║
 │ remediated browser-PDF │  (PHI-clean; Rx Id only) — manual      ║ 4×3 stock, STD or CA   ║
 │ c:printBottleLabel     │                                        ║ ZPL format             ║
 └────────────────────────┘                                        ╚═══════════════════════╝
        

Component inventory — reused vs net-new

ZplBottleLabelBuilder

net-new

Pure Apex string builder; STANDARD + CA formats; native ^BQ QR. No SOQL/DML/callout — deterministic string in / ZPL out, trivially unit-testable.

BuildBottleLabel

net-new

with sharing. 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; fault connector on the insert → Error_Log__c (D5).

Print_Job__c (the queue)

net-new

ZPL__c (Long Text — PHI; purge/redact after print), Workstation__c (frozen at create), Status__c, 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.

Agent_Registration__c

net-new

One row per installed agent → its Workstation__c. SF derives a job's eligible station from the authenticated agent, never from a station Id the agent supplies (D8).

Advanced Rx Helper agent

net-new repo

advancedrx-helper — a small local service per filler PC. Polls SF for Queued jobs for its station, sends raw ZPL to the local printer over USB, writes Status__c back. v1 = exactly one job type, print_zpl. A narrow job-runner, not a remote shell (D9).

Workstation registry + per-station picker

reuse registry · new picker

Reuse Workstation__c + the 2h-expiry pick-your-station UX. Build a clean picker that writes only our binding — do not reuse Choose_Your_Printer_Screen_Flow as-is (it writes/deletes zkmulti__MCUser_Printer__c + validates via Bringg_Printer_ID__c). Current_Workstation__c is non-unique Text(150) — resolve-and-freeze a real Workstation__c Id (D8).

Remediated c:printBottleLabel + VF

remediate + keep as interim/fallback

The three PHI leaks fixed as real work; kept as the PHI-clean browser-PDF path used pre-agent and if the agent is down.

Printers — keep the existing GX430t's

reuse

The agent sends raw ZPL over USB; the GX430t prints it fine regardless of Zebra's end-of-sale status (the EOL concern only ever mattered for Browser Print). No fleet purchase. Replace with the ZD621t only as units age out.

zkmulti/Bringg/PrintNode/Browser Print/QZ Tray/Azure

DROPPED as primary

Evaluated; each kept as an "alternative" only. The company-owned agent replaces them all — see §4-D1. The label content (Apex ZPL) is identical regardless of delivery.

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.

§4Key decisions

D1

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

ChosenA 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.
Dropped — Browser Print / QZ TrayDriving a localhost agent from the browser drags in CSP / Locker/LWS + a self-signed localhost cert (Browser Print) or licensing (QZ). No documented clean LWC↔localhost success story; Browser Print would have forced new ZD621t printers.
Dropped — zkmulti/Bringg/PrintNode/Azurezkmulti/Bringg: shipment-scoped, void return, unproven contract. PrintNode: a third-party cloud → a HIPAA BAA gate. Azure: a cloud that also can't reach the USB printer — strictly more work.

The unavoidable constraint: a cloud cannot reach a USB printer behind the pharmacy firewall, so something must run on the filler's PC. Our agent keeps PHI on SF + the LAN (no print-vendor BAA), removes the browser/CSP surface entirely (server-to-agent poll, not browser-to-localhost), works with the existing GX430t's, and gives us a platform (D9). The durable queue is Print_Job__c; the agent polls on backoff + jitter (≈30s floor → 120s cap, dropping to a 5-min heartbeat once the nudge is active) under a /limits budget that hard-stops polling at 95% of the org's daily API allocation, optionally woken by a Platform Event "nudge" — the nudge is the wake signal, never the durable store. QZ Tray / PrintNode remain drop-in delivery swaps if owning fleet software ever proves untenable.

D2

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

ChosenPure Apex ZplBottleLabelBuilder builds the ZPL for both formats. The agent never builds labels — it only relays a finished ZPL string to the printer.
RejectedAssembling labels in the agent — changing a label would mean redeploying to every PC.

Label content + compliance live in one versioned, testable place where the data is. Font sizing is auditable in dots: at 300 dpi, font height in dots = pt × (300/72), so 12pt = 50 dots (^A0N,50,50) — the ≥12pt floor is a reviewable number. ZPL injection must be neutralized: ^FD excludes the command prefixes ^ and ~, so the builder hex-encodes every field value with ^FH (or strips/escapes) before insertion — unit-tested with a sig containing ^ and ~.

D3

One universal 4×3 stock for ALL patients; CA-vs-standard is a FORMAT, not media

ChosenEvery patient's label prints on one universal 4×3 thermal stock — the same blank in every printer. CA and standard are two ZPL formats the server renders onto it. No stock swapping, no dedicated CA printer, no media enforcement.
DroppedThe old dual-stock design (4×2 standard + larger CA stock) and its silent-wrong-blank enforcement problem.

One blank dissolves the stock-mismatch problem — there is no wrong media to load. The law specifies ≥50% area + ≥12pt, not a physical size; 4×3 holds the CA cluster at ≥50% plus the full BPC 4076 lower zone. Container-fit is being bench-tested this week (in progress). Fixed-stock overflow rule: the label can't grow, 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. Counsel must confirm a split cluster still satisfies 1707.5 (OQ-1).

D4

FORMAT routing is server-enforced by ship-to state; the agent/LWC never choose

ChosenBuildBottleLabel selects the format from Order__r.Shipping_Address__r.State, with a hard CA guard as the last statement touching format — a CA ship-to can never be downgraded. The choice is stamped onto the Print_Job__c before the ZPL is built; nothing downstream can change it.
RejectedAgent- or operator-selected format with no enforced guard.

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, and 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

ChosenThe Apex action inserts a Print_Job__c (Queued) and continues. The agent processes it through a claim/lease lifecycle: an atomic locked Queued→Printing transition (FOR UPDATE, stamps Claimed_By__c / Claimed_At__c / Attempt__c); print → writeback Printed/Error; a scheduled sweeper re-queues lease-timed-out jobs once; Attempt__c caps retries. At-least-once with an explicit reprint control — duplicates are caught by the pharmacist verification (D7).
RejectedA bare status flip (two agents claim the same job) or pretending exactly-once.

Error contract: failure is signaled by the Apex action throwing (incl. internal DML failure); the flow's fault connector on the action routes to Error_Log__c. There is no LWC→Apex print call to mis-wire. The agent's print outcome lives on Print_Job__c (better than the dropped rail's void return); stuck/aged jobs raise an alert; the remediated browser-PDF path is the manual fallback. Because pharmacist verification backstops the physical label, at-least-once + reprint is the right, simple target.

D6

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

ChosenReuse the existing GX430t printers. The agent sends raw ZPL over USB, which the GX430t prints fine — its end-of-sale status is irrelevant to our own agent. No fleet purchase. As units fail, replace with the Zebra ZD621t (300 dpi, thermal transfer — the named GX430t successor).
DroppedBuying a ZD621t fleet up front (the cost the Browser-Print path implied — Browser Print is dropped, so its support matrix is moot).

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. Media: wax-resin ribbon on coated 4×3 (move to resin + synthetic for max durability); thermal transfer keeps labels legible for the medication's life — qualify by test (Phase 0).

D7

No post-print scan; consolidated refill QR

ChosenNo post-print scan step — the existing pharmacist verification step is the compliance backstop (it checks the printed label on the bottle against the Rx before ship). The separate refill-QR sticker is consolidated onto the main label, drawn natively in ZPL.
DroppedA separate post-print scan-to-verify step.

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. The exact refill-QR encoding is an open input (OQ-3).

D8

PRINTER routing is by workstation, with the station identity derived server-side

ChosenEach Print_Job__c carries a target Workstation__c Lookup, resolved and frozen at job creation. The agent only ever receives jobs for the station Salesforce binds to its authenticated identity — it never passes a station Id the server trusts. 5 stations = 5 registered agents, each served only its own jobs.
RejectedRouting on the non-unique Current_Workstation__c text, a shared filter, or reusing Choose_Your_Printer_Screen_Flow as-is (its zkmulti side effects contradict "no zkmulti").

User.Current_Workstation__c is Text(150) holding a non-unique workstation Name, so BuildBottleLabel must resolve it to a specific Workstation__c Id (with an "ambiguous → error" rule) — or add a real User → Workstation__c lookup. Per-agent identity means each agent authenticates as its own principal (a JWT/cert integration identity — the credential); Agent_Registration__c is the station binding, not the credential. So a compromised agent cannot name another station's jobs — SF derives the lane from the auth context. The printer is a property of the physical station, not the person.

D9

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

ChosenThe 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.
Out of scope (the trap)A generic "execute arbitrary command" / clipboard automation / "copy data into another app" — the unreviewable remote-control surface. Never.

A company-owned, SF-connected workstation agent is a genuine platform — each new capability gets cheaper once it's trusted. Two guardrails keep it safe: (1) the platform emerges feature-by-feature, and compliance stays decoupled (the CA label is content in Apex; it ships 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 on a PHI workstation that could take arbitrary actions is an RCE surface, so it only ever runs explicitly-coded, reviewed job types.

§5California compliance — 16 CCR 1707.5 + BPC 4076, sourced

Every requirement is quoted verbatim from a primary or authoritative source. The citations are load-bearing — paraphrasing the law loosely is exactly what the review caught.

The patient-centered cluster — 16 CCR 1707.5(a)(1)

“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.” — Cornell LII / CA Board Order of Adoption

Emphasis is mandatory — 16 CCR 1707.5(a)(2)

“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).” — Cornell LII. On a monochrome printer we use the permitted bold typeface + blank space (color is unavailable).

Remaining elements must not interfere — 16 CCR 1707.5(a)(3)

“The remaining required elements for the label specified in section 4076 of the Business and Professions Code … shall be printed so as not to interfere with the legibility or emphasis of the primary elements … These additional elements may appear in any style, font, and size typeface.” — Cornell LII.

The full BPC 4076(a) lower-zone set

BPC 4076(a)(1)–(11) (FindLaw): (1) manufacturer trade name OR generic + manufacturer; (2) directions for use; (3) patient name; (4) prescriber name; (5) date of issue; (6) pharmacy name + address + Rx number; (7) strength of the drug; (8) quantity dispensed; (9) expiration date; (10) condition/purpose if indicated; (11) physical description (outpatient only) — with an exemption for medications lacking a description in a commercially available database (the citable basis to omit it for compounded preparations; confirm with PIC/counsel).

Translated directions — the correct current language list

16 CCR 1707.5(b): “…translation of the directions for use … into at least five languages other than English.” The regulatory floor is five, but the Board currently publishes SIX standardized-direction language sets — Chinese, Farsi, Korean, Russian, Spanish, Vietnamese — covering 15 standardized directions. The prior plan's "five … not six" was stale. Drive the language set from data/config, never a literal. Translated directions are on patient request and may go on a supplemental document; when on the container/label, the English version must also appear (BPC 4076.6 — re-verify against leginfo before build; a 2024 amendment, AB 1902, is in the lineage).

CA label layout on the universal 4×3 stock

≥50% area cluster · ≥12pt sans (=50 dots @ 300 dpi) · bold + blank-space emphasis
(A) JANE Q. PATIENT
(B) Drug name + STRENGTH
(C) Directions for use (full sig — on the container)
(D) Condition / purpose (if on the Rx)
Lower zone — BPC 4076, any font/size, must not interfere manufacturer/generic · prescriber · date of issue · pharmacy name + address + Rx# · qty · expiration/discard · physical description (or 4076(a)(11) exemption) · native ^BQN,2,5 QR (Patient_Fill_Id__c) + consolidated refill QR · USP-795 BUD/lot + "compounded by the pharmacy" (16 CCR 1735.4)

Supplemental document (on request)

  • Translated directions in any of the 6 Board languages
  • English sig also stays in the on-container cluster
  • Never the mandated English directions — the cluster keeps those

Compounding overlay: 16 CCR 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).

Cluster ≥50% of label AREA: the four ^FO/^FB blocks occupy ≥50% of the printable area — the builder asserts cluster width × height, not dot-height alone (height-only is wrong if the cluster isn't full-width). Native QR: ^BQN,2,5 with ^FDHA,<patientFillId>^FS — H error-correction, automatic mode, magnification 5 (reliable at 300 dpi), no external service. 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).

§6Build 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 automation on top.

0
Non-engineering critical path (gates everything)

PIC + counsel (BLOCKING)

  • 1707.5 applicability + layout (cluster ≥50%/12pt/order/emphasis, directions-on-container)
  • USP-795 / 16 CCR 1735.4 compound elements
  • Translated-directions workflow (6 langs, supplemental doc); 4076(a)(11) exemption; USP <17>

Pharmacy ops (this week)

  • Bench-test the universal 4×3 stock
  • Confirm it physically wraps real containers (in progress)

Media + printers

  • Coated 4×3 + wax-resin ribbon; smudge/fade/abrasion + alcohol-wipe test
  • No printers to buy — reuse existing GX430t's (D6)
1
Compliance via the manual path (ship CA-compliant labels without the agent)

Renderer

  • ZplBottleLabelBuilder + a CA 4×3 render
  • For the interim, a renderAs=pdf @page 4in 3in CA layout in the remediated VF page

PHI remediation (real work)

  • Pass only the Rx Id; query PHI server-side WITH USER_MODE
  • Remove name console.log (js:118-120)
  • Replace metafloor QR (cls:52, page:42) with in-org BarQR/inline; delete 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
Exit: a CA ship-to prints a fully 1707.5-compliant 4×3 label (manually), PIC/counsel-approved, with all three PHI leaks closed on the surviving fallback path.
2
The Advanced Rx Helper agent + the Print_Job__c queue (POC: one station)

Salesforce

  • Print_Job__c object; BuildBottleLabel action (build ZPL + insert the job routed to Current_Workstation__c)
  • Least-privilege Connected App + integration user (read Queued jobs for a station, write status)

advancedrx-helper repo (net-new)

  • 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: 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 leaves the job Queued/Error and the manual fallback still works.
3
Both formats + the fill-flow cutover

Renderer

  • Finish STANDARD (nicer, redesigned) + CA formats; enforce the max-line budget + continuation-label overflow (D3)
  • Test the longest production sigs at 12pt; verify QR/barcode to ISO/IEC 15415/15416 Grade B+; confirm USP <17>

Flow cutover

  • Wire BuildBottleLabel into Loop_in_an_order_Execute_Event + Generate_Rx_Label — replace the manual screen with the action + a "sent to <station>" confirmation; fault connector → Error_Log__c
  • Add the pick-your-workstation step; keep the browser-PDF fallback

Gated on

  • OQ-1 (compliance sign-off), OQ-2 (strength + purpose field API names), OQ-3 (refill-QR payload)
Exit: both formats print via the agent; CA ship-tos get the compliant format and cannot be downgraded; the fallback is reachable.
4
Fleet rollout + hardening

Rollout

  • Install the agent + register Workstation__c on all 5 fill stations
  • Confirm routing isolation (each station prints only its jobs)

Hardening

  • Monitoring/alerting on stuck/Error jobs
  • Document the agent's deploy/auto-update/security runbook

Untouched

  • print_bottle_label_flow (Draft) stays as-is
Exit: all stations auto-print to their local GX430t; stuck/failed jobs alert; the manual fallback is reachable everywhere.

§12Platform 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"). Build one at a time, compliance-decoupled, after the label ships.

Phase 2a — Quick wins (ride the print_zpl framework)
Job typeGrounds inNotes
PRINT_PACKING_LISTprintPackingListv2 (browser print today)Top quick win — same shape as the label; kills browser-print fragility on the pack line
PRINT_RECEIPTprintReceipt* + ControllerOrderSummaryNatural second print type
PRINT_CLAIM_FORMprintClaimFormListTestFlowPHI-heavy; a narrow print job fits
PRINT_SHIPPING_LABEL_LOCALShipmentPrintLabelInvocableCloses the shipping loop locally (respect Zenkraft behavior)
PRINT_AUX_WARNING_LABELSbottle-label warning fieldsCheap once the ZPL path exists
LOCAL_BARCODE_RENDERthe bwip-js/metafloor external QRSecurity hardening — render barcodes locally, killing external-identifier egress across all labels
Phase 2b — First agent→SF (capture) primitives
Job typeGrounds inNotes
CAPTURE_VERIFICATION_SCANbarcodeInputValidator / final-verification scansFirst capture primitive; only while an expected scan job is open — no keyboard injection
VERIFY_SHIPPING_TRACKING_SCAN + PACKAGE_TYPE_SCANPrepare_Order_for_Shipping scan screensSafety/status loop after local print
WORKSTATION_PRINTER_HEALTHCHECKWorkstation__c + 2h printer-assignment expiryOps value; no PHI
ORDER_READY_DESKTOP_ALERTshippingView / filler queue controllersOS notification; PHI-minimal (order id, not patient name)
Phase 2c — Ambitious (compounding bench; need device/SOP confirmation)
Job typeGrounds inNotes
SCALE_CAPTURE_COMPOUND_WEIGHTCompound__c / Lot__c (BUD, ingredient qty)High patient-safety value; needs a real USB scale + SOP tolerances
SCAN_TO_RX_IMAGERx_Image__c doc fields; Azure intakeRescan paper/fax to SF; whitelist file types — never arbitrary upload
LABEL_BARCODE_QAfill-id QR + final-verification scansDecode-and-verify the printed QR (pass/fail), not photo retention
PAYMENT_TERMINAL_JOBCapture_Payment / PaymentCaptureActionPCI-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 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) → PRINT_SHIPPING_LABEL_LOCAL + VERIFY_SHIPPING_TRACKING_SCAN (closes the shipping loop). Pair LOCAL_BARCODE_RENDER with the label work as security hardening.

§8Risks & open questions

RiskSevMitigation
We now own fleet software forever — the agent must be deployed, code-signed, auto-updated, monitored, secured, supported on every filler PC; bus-factorHIGH — the real costKeep v1 tiny + boring (one job type, a poll loop); document a deploy/auto-update/runbook in advancedrx-helper; treat it as a maintained product. The narrow job-runner (D9) keeps the surface small.
Agent down / offline / printer jam → label doesn't printMEDAsync status on Print_Job__c (stuck Queued / Error alerts); the pharmacist verification step catches a missing label (D7); the manual browser-PDF fallback is always available.
Agent security — a cloud-driven workstation agent could become an RCE surfaceHIGHNo arbitrary execution — only explicitly-coded, reviewed job types (D9); least-privilege SF integration user; signed builds; locked-down service account.
CA patient silently downgraded to the non-compliant formatHIGHServer-enforced routing with a hard CA guard as the last statement touching format (D4); stamped on the job before the agent sees it; unit test asserts CA can't produce STANDARD.
Compliance miss (cluster <50%, sub-12pt, wrong order, no emphasis, off-container directions, wrong language list)HIGH§5 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 fallbackHIGHPhase 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×3MEDFixed max-line budget + a continuation/auxiliary 4×3 label (never sub-12pt, never drop an element — D3); longest production sigs tested in Phase 3.
ZPL geometry / unscannable QRMEDLabelary preview; native ^BQ at mag 5; scannable QR is a Phase 2 exit criterion; ISO Grade-B verification in Phase 3.
Duplicate / lost-status prints (no claim/lease)HIGHAtomic claim + lease + Attempt__c cap + sweeper for stuck Printing (D5); at-least-once with explicit reprint; pharmacist verification (D7).
Print_Job__c.ZPL__c = a new plaintext PHI store (no Shield)HIGHPurge/redact ZPL__c after Printed; FLS-lock the field; explicit retention window (OQ-5); the agent doesn't persist bodies.
ZPL command injection via patient/sig textHIGH^FH hex-encode / escape all field data (^FD excludes ^/~); unit-test a sig with ^/~ (D2).
Polling burns the org-aggregate API budgetMEDBackoff + jitter, /limits monitoring, an explicit budget; a Platform Event "nudge" wakes the agent so idle polling is rare (D1).
Agent spoofs another stationMEDPer-agent credential + Agent_Registration__c; SF derives the station server-side; the agent never names a station (D8).
Platform scope-creep delays the labelMEDCompliance is decoupled (ships via the manual path, Phase 1); the agent stays one job type until the label is done (D9).

Open questions — the few that truly need humans

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

§Codex review — addressed

A prior round of this plan was reviewed; all 11 findings are folded into the current design. The headline dispositions:

#FindingDisposition
1Fallback preserves PHI leaksFIXED §2 documents all three with file:line; Phase 1 remediates them as real work; §7 of the plan states it fixes existing exposure.
2Compliance fails 1707.5 (directions on container, emphasis, BPC 4076, non-interference)FIXED §5 quotes (a)(1)/(a)(2)/(a)(3) + full BPC 4076 verbatim; directions are an in-cluster container item (D3).
3Current-label audit undercounts noncomplianceFIXED §2 table: name 10pt, drug 11pt, sig 9/9.5pt, no strength, no purpose, not clustered, no emphasis — each with file:line.
4zkmulti reuse not proven (shipment-scoped only)MOOTED The zkmulti/Bringg/PrintNode rail is dropped (D1). Delivery is the company-owned agent to a USB printer.
5Automatic fallback overpromised; void-return signalMOOTED The void rail is dropped. Failure is observable on Print_Job__c.Status__c; stuck/Error jobs alert; pharmacist verification + manual fallback backstop it (D5/D7).
6Error contract inconsistent (fault AND boolean)FIXED D5 is one model: the flow inserts a Print_Job__c with a fault connector → Error_Log__c; the agent reports status. No LWC→Apex print call to mis-wire.
7fillId overloaded (Rx Id vs Patient_Fill_Id__c)FIXED New code uses rxId (record Id) and patientFillId (QR payload) as distinct values.
8Azure claims unverifiable in this repoMOOTED Azure is dropped (D1).
9"2-click pink button" wrong (window.print() commented)FIXED §2 states it (js:114-116) + cross-checks CORRECTIONS.md row 35.
10BarQR claim unsupported (getters unreferenced)FIXED §2 notes the dead getters; Phase 1 deletes them.
11AGENTS.md path / stale Rx docACCEPTED Flagged for cheap cleanup; OQ-2 verifies field names via live FieldDefinition SOQL rather than the stale doc.

Subsequent 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).