---
last_verified: 2026-09-14
verification_type: cross-repo-reconciliation; 2026-09-14 (launch day, plus the same-day Invalid-id incident — cause read from the class and flow XML on disk, reproduction and fix verified by Marissa through the Flow Builder debug panel in production, with the fix then confirmed by her on real production traffic (SkippedNoPortalUser rows written, error emails stopped), and the org-wide invocable check run against force-app) — steps 2, 3, 4, the G14 canary and the same-morning cohort opening (all patients, both channels, from about 11:00 ET) confirmed by Marissa in production; Kyle's authorisation of the widening recorded from her relay of his go-ahead, not from his own written sign-off (PSG grant, then the switch, then the Testing_Mode flip, then a passing one-patient canary a little after 11:00 ET); PSG coverage recorded from her statement that every portal User holds the permission set active or not, not from a cohort query, and no count or duration was captured; the 13:48–13:50Z mint/redeem activity attributed to her own testing by her confirmation, not by telemetry correlation; G10's non-settlement recorded from her statement that Kyle is revising the runbook, not from his ledger, which is unchanged since 2026-09-04; Kyle's readiness check, the 13:48–13:50Z unattributed mint/redeem activity and the Practitioner-sync failure read from advancedrx-document-integration #440 (merged 2026-09-14), not from an Azure or org query; gate states below read from his gates ledger at commit 00c90a3, whose last substantive edit is 2026-09-11 — so every launch-morning fact is ahead of that ledger; earlier 2026-09-11 (evening) — GO decided by Marissa; the gate positions in the decision note read from the advancedrx-document-integration gates ledger and streaming plan at that moment (G10 open, G5 drill accepted / SQL readback outstanding, G4 browser record open, G9 alert-proof closed, disconnect-fix runtime proof done), not from an org query; earlier 2026-09-11 — the PSG grant moved from 9/11 to 9/14 (Marissa's decision, with the switch held until coverage is verified) recorded from her statement, not from an org query; earlier 2026-09-10 — the message-path pass, the switch-to-9/14 decision and the no-real-patient-mint state confirmed by Marissa in production; G13 / G11 sign-off state from Kyle's ledger (advancedrx-document-integration #427–#429); earlier 2026-09-09 — 9/9 step 0, G11 flip and the post-flip login proof (both channels, `LoginHistory.Application = Magic Link Bridge`) confirmed by Marissa in production; earlier 2026-09-02 — items 10 + 11 build state and the unfiltered / Sent-write-back-in design confirmed by Marissa in production MC
verification_ttl_days: 14
---

# Magic-link — the go-live sequence

> **Status:** first cut, 2026-09-01. **Owner:** Marissa. **Purpose:** put every remaining item into
> one of four phases, with the dependencies that force the order.
> **✅ C2 PASSED 2026-09-01 (Marissa + Kyle), all six criteria** — through a real Marketing Cloud send, so
> the wrapped `click.advancedrx.email` href was the one followed. **The assumption this document was
> written on is now a fact, and E2's blocking pre-production gate is closed: the email lane is unblocked
> and stays in the sequence.** The *If C2 fails* section below is retained as the record of the fallback
> that was not needed.
> **Update 2026-09-02 (Marissa):** **both go-live journeys — items 10 (email) and 11 (SMS) — are BUILT**,
> saved and validated in production MC, not activated. The Marketing Cloud build is complete. **The
> go-live journeys carry no test-patient filter
> (the separate test journeys do) and include the `Status__c = 'Sent'` write-back from the start.** That
> collapses item 11a to a single switch — old journey off, go-live journey on, same sitting — and removes
> the 9/9 "activate test-scoped alongside old" step. The day-by-day, Phase 1, Phase 3 and hard-ordering
> rules 1–3 are rewritten below to match; the reasoning is at `magic-link-send-wiring.md` item 11a.

## Target dates — go-live 9/14, decided by Marissa 2026-09-01

**Holding 9/14, and the plan requires no weekend work.** That makes **end of day Friday 9/11 a hard
go/no-go**, which is where the decision belongs anyway.

### The calendar, honestly

| | |
|---|---|
| Calendar days 9/1 → 9/14 | 13 |
| Working days | 9 |
| **Marissa available** | **5 — 9/2, 9/3, 9/9, 9/10, 9/11** |
| Out | **9/4**, **9/8** (Marissa), **9/7** (Labor Day) |
| Shape | a **five-day gap, 9/4 → 9/8**, sits in the middle |

**Every one of those five days is committed. There is no recovery day.** This is a best case that holds
if both sides hit every date and nothing surfaces after 9/10 — recorded as such rather than as a
comfortable plan.

### 🔑 The one resequencing that makes it reachable

**Phase 2 does not depend on Phase 1 finishing.** The Salesforce deploy — G3, the flow set, the CMDT — is
independent of building Marketing Cloud journeys. **So Phase 2 lands on 9/3, before the gap, not 9/9
after it.** Otherwise Phase 3 collapses to two days.

**Leaving the deploy inert across the gap is safe:** `Testing_Mode__c = true` means no real patient mints,
the go-live journeys are not activated, and pre-G11 Azure still points at the `ClaudeTest` cache, so even a
test-patient mint cannot redeem. The redaction flow (11b) ships in the same set, so the SMS writeback is
covered from the moment anything can write to it.

### Day by day

| Date | Day | Phase | Marissa | Kyle |
|---|---|---|---|---|
| **9/2** | Wed | 1 | Production journeys (**items 10 + 11 ✅ built**), **retention ON at creation** (✅ both DEs), question 24 (✅ closed), birthdate coverage (✅ answered), invocable tests (✅ 90.7%), **Full deploy ✅ clean**, DOB redesign ✅ handed off | G9, G5 |
| **9/3** | Thu | **2** | ✅ **DONE 2026-09-03** — **G3's 9/3 half** (prod ECA, cert, manual `SetupEntityAccess`, permset to test users — ~~into the PSG~~ that is 9/11) · **deploy the flow set** · **verify CMDT** (landed `false`/`true` — correct; see run-sheet step 2) · **test-patient mint (proves the credential chain)** · item 17 production half | G9, G5 |
| 9/4 · 9/7 · 9/8 | — | — | *out* | **G5, G8, G12, G10** — the gap is his run |
| **9/9** | Wed | **3** | ✅ **DONE 2026-09-09** — step 0 closed in the morning (`Mint_Enabled__c` on, principal-access permset on Marissa + Kyle + Sarah, scheduled-path identity already covered, bridge checks re-run, tuple handed over via a shared 1Password vault); **post-G11 redeem proof PASSED on both paths** — a flow-debug mint for the test patient, the SMS-style link straight to `/my-home` signed in, the email-style link via the DOB page to `/my-home` signed in, **`LoginHistory.Application = Magic Link Bridge`** (the measurement question is answered). **Not yet run: the message path through the test journeys** (a real text and email carrying a link) — that plus the QA-C cases is 9/10–9/11. Go-live journeys stayed inactive | ✅ **G11 DONE 2026-09-09** (Kyle, solo, after Marissa's demo) — pause → kill epoch → zero live links → five keys → sentinel → health green → minting resumed |
| **9/10** | Thu | **3** | ✅ **DONE 2026-09-10** — **message path PASSED on both channels through the test journeys** (a real text and a real email carried working links; each logged the test patient in — QA-C **C1** a day early). ~~**The switch (item 11a):** old journeys off, go-live journeys on — same sitting, per channel.~~ **NOT run — Marissa's decision 2026-09-10: the go-live journeys stay inactive until go-live, so the switch moves to 9/14.** Old journeys still sending; `Testing_Mode__c` still `true`; no `Magic_Link__c` rows for real patients. Before/after numbers → Kristen, with Kyle | ✅ **G13 DONE 2026-09-10** — part 1 passed 14:58Z (fresh mint, byte-identical replay, revoked-identity 410); part 2 (DOB cap) closed on evidence — the cap was hit live 9/3 and 9/9. Kyle signed his half of G11 |
| **9/11** | Fri | **3** | QA-C ~~C1~~ *(done 9/10)* **C3, C4, C5** · ~~item 13~~ *(→ 9/14, after the switch)* · ~~PSG grant~~ *(→ 9/14, ahead of the switch — Marissa 2026-09-11)* · ✅ **GO — decided 2026-09-11**, with C4's revoke half unrun and G10 open; see the decision note | G8 *(closed 9/4)*, **G10 still open**; G9 alert-proof closed, G5 drill accepted, disconnect-fix proof done |
| 9/12–9/13 | — | — | *no work* | *no work* |
| **9/14** | Mon | **4** | **PSG grant first, and wait for coverage** *(moved from 9/11)* → **the switch (11a)** with `Testing_Mode__c` still `true` → verify real patients get today's message and no rows appear for them → **flip `Testing_Mode__c = false`** · **G14 canary**, one channel · item 13 after the switch | Operators present, monitoring watched live |

**G11 moved to 9/9, earlier than "last."** It still precedes everything it must, but running it on 9/10
would leave G13 and all four QA-C cases on 9/11 — and 9/11 has to stay light enough to be a decision day.

### Day by day, expanded

> **🔑 Deployment is by change set, ClaudeTest → Full → Production. Nothing deploys from this repo,
> ever** *(Marissa, 2026-09-01)* — the repo is the record, not the pipeline. **Full gets its own deploy
> and verify pass**, so promotion is two hops, not one. Where a step below says "deploy," read it as
> "promote by change set and verify in the target."
>
> **The day assignments come from the dependencies above. The ordering *within* each day is reasoned
> rather than sourced** — it is the order that satisfies the constraints, not an order anyone has run
> before. Reorder freely where operational reality disagrees; the only sequences that are load-bearing
> are the ones called out in *Hard ordering rules*.

#### Wed 9/2 — build the two real journeys

1. ✅ **BUILT 2026-09-02 — saved, validated, not activated.** Build the **email journey** in Marketing
   Cloud: entry event on `Refill_Reminder__c`, ~~filtered to test patients only
   (`Contact__r.Special_Patient_Type__c = 'Test Patient'`)~~ **with no test-patient filter — that belongs
   to the separate test journey** *(Marissa, 2026-09-02)*, pulling the URL through the lookup —
   **`Magic_Link__r → Magic_Link_URL__c`**, not `Magic_Link__c` off the reminder's own field list, which
   hands back an Id that looks like success. **Include the `Status__c = 'Sent'` write-back** — safe,
   because this journey is never active alongside the old one.
2. ✅ **BUILT 2026-09-02 — saved, validated, not activated.** Build the **SMS journey** the same way — **and it also needs
   `Contact__r.MobileConnect_Phone__c`**, which the email journey does not carry. Easy to lose by copying
   that one.
3. **Turn retention on for both new entry data extensions as you create them** — 30 days, individual
   records, reset-on-import **off**. Free today; never again once rows accumulate. ✅ **Confirmed on both
   DEs 2026-09-02.**
4. ~~**Write down whatever column names Marketing Cloud generates.** That is the exact text the AMPscript
   has to read.~~ **Retired 2026-09-02.** Nothing changes between the test and go-live journeys here; the
   only question was whether the go-live DE's column carries the exact string the shared templates read —
   `Refill_Reminder__c:Magic_Link__r:Magic_Link_URL__c` — and it does. Confirmed by inspection; nothing
   to record.
5. ✅ **CLOSED 2026-09-02 — RULED by Kyle: both buttons on the one magic link is fine, no template
   change.** ~~Decide~~ **Question 24** — the "Shop Now" button. Both buttons land on `/my-home`, which
   is accepted as designed: the link logs the patient in and they navigate from there. **Question 16 is
   unaffected** — the credential still reaches click-tracking data under two wrapped hrefs.

6. ✅ **ANSWERED 2026-09-02 — total by construction; see the last sub-bullet.** ~~🔴~~ **Confirm the
   production Azure identity can actually read birthdates across the portal cohort.**
   The `ClaudeTest` fix used a **criteria-based sharing rule**, proven at **n = 1** — that establishes
   the *mechanism*, not the *coverage*. A criteria-scoped rule can cover most portal-user parent contacts
   and **quietly miss some**, where View All Records fails or passes cleanly. The plan already says *"the
   sharing rule may therefore be sandbox-only — confirm that before anyone replicates it to
   production,"* **and nobody had picked it up.** #174 suggests production is already fine by an existing
   route (270,905 birthdates cached, 2 nulls), but that was never confirmed.
   - **Why it is a launch item:** a patient whose birthdate the identity cannot read **fails the DOB
     step-up and cannot complete an email magic-link login** — silently, for a subset, with no obvious
     signature.
   - **It is a query, not a project.** Run it 9/2 so a coverage gap surfaces on a Wednesday, since the
     fix would be a production sharing change.
   - **✅ ANSWERED 2026-09-02 (Marissa, read-only SOQL in production):** `Azure_Integration` grants
     Contact **Read + View All Records** plus `Contact.Birthdate` read; no View All Data. Every portal
     user's parent contact is readable — no rule, no gap, nothing to count. **The n = 1 worry was a
     sandbox artefact:** ClaudeTest's copy of the permission set lacks View All Records, so the sharing
     rule there is confirmed **sandbox-only** and must not be replicated. Production being wider than the
     sandbox is now **question 25** in `magic-link-send-wiring.md` — Marissa's permission-set review,
     not a v1 item.

7. ✅ **DONE 2026-09-02 — 90.7%, 17/17 passing.** ~~🔴~~ **Raise `MagicLinkMintInvocable` test coverage
   to 75% or more.** Measured too low (Marissa, 2026-09-01). **This gates the 9/3 production promotion**,
   not the Full one — production deploys enforce the 75% org-wide gate and sandbox deploys do not — but
   fix it *before* building the change set, so Full validates the same code that goes to production.
   - **✅ The diagnosis held: it was the save-failure paths, not the callout paths.** The existing 13
     tests already covered every callout outcome — 200, 410, 500, a thrown callout, no-verdict,
     ineligible, no-portal-user, same-key replay, blank key, bulk ordering. What no test reached was
     `MagicLinkMintInvocable.cls:184`–`239`, where `Database.upsert` comes back with errors. *(The
     2026-09-01 guess of "callout-failure and `FailureEntry` branches" was half right — the
     `FailureEntry` half is where the gap was.)*
   - **Four tests added (Marissa, 2026-09-02), each asserting an outcome:**
     `mintedResultIsDowngradedWhenAttemptRowCannotSave` — the class's least obvious correctness
     property: a `Minted` attempt whose row cannot save is reported as `Failed` with the URL withheld,
     because the flow then cannot populate its lookup and the patient gets a blank link;
     `nonMintedStatusStandsWhenAttemptRowCannotSave` — the mirror, where a lost telemetry row does not
     change the patient's outcome, and which also proves the save-failure log fires independently of
     `SkippedIneligible` being deliberately unlogged; `nullRequestInListFailsThatEntryOnly` — batch
     isolation and input-order preservation; `nullRequestListReturnsEmptyAndWritesNothing`.
   - **How the save failure is forced, since it is not obvious:** a valid-shaped Contact Id that does not
     exist, so the required `Contact__c` lookup fails `INVALID_CROSS_REFERENCE_KEY`. Because the
     invocable upserts with `allOrNone = false`, that is a **per-row** error — which is exactly the
     branch under test. The mock still returns 200, so the test distinguishes *"the mint failed"* from
     *"the mint succeeded and we could not record it."*
   - **One branch is left uncovered, deliberately: the `catch` around the whole upsert
     (`MagicLinkMintInvocable.cls:219`, ~9 lines).** It needs the entire `Database.upsert` call to throw
     rather than return row errors, and no lever for that is known with confidence — the candidate
     (two requests carrying the same idempotency key in one call) may produce row errors instead, and
     was not tested. **Recorded so a future reader knows it is a judgment call, not an oversight.**
     Worth re-testing if a lane ever batches duplicate keys, which is the real-world shape of it.
   - **The gap is uncovered paths, not absent testing.** `MagicLinkMintInvocableTest` is already a
     strong class by this org's standards — 518 lines, ~13 methods, 81 assertions, in a codebase where
     `TEST-ASSERTION-AUDIT.md` records 60% of sampled tests having **zero**. What is missing is almost
     certainly the callout-failure and `FailureEntry` branches, which need `Test.setMock` work rather
     than more assertions on the happy path.
   - **🔴 Do not pad it.** `.claude/rules/apex.md` forbids `increaseCoverage()` no-op methods, and the
     org already carries two live examples (`IntegrationHelper`, `HandleMCSubscriber`) that are slated
     for removal. **"Get to 75%" is precisely the instruction that produces coverage gaming** — and on
     the class that mints login credentials, coverage that overstates real verification is worse than a
     number that reads low. Every new test needs an outcome-asserting `System.assert*`.
     **Honoured:** no `increaseCoverage()`, no `@TestVisible` loops, no assertions on setup data. The
     three highest-value assertions are behavioural rather than structural — the URL is withheld on a
     failed save, a non-`Minted` status stands, and one null entry does not poison its batch.

**Afternoon — promote to Full and verify.** ✅ **EXECUTED 2026-09-02, clean.** Change set
**ClaudeTest → Full**, then **verify in Full.** This is its own pass, not a formality *(Marissa,
2026-09-01)*: does the set deploy cleanly, did every component arrive, did the CMDT land with the
right values.

**What the pass established, beyond "it arrived":**

- **The fifteen-component set is the one that promotes tomorrow.** Nothing new to discover in
  production: two Apex, the `Magic_Link_Setting__mdt` type + its two fields + the `Default` record +
  its layout, four flows (mint, the `Magic_Link__c` clean-up pair, and 11b's SMS redaction), the
  External and Named Credentials, and both permission sets.
- **The credential components survive a deploy with the principal still enabled** on
  `Magic_Link_Mint_External_Credential_Principal_Access` — the mapping flagged as not always
  surviving cleanly. It does.
- **🔑 Custom metadata carries its values — rehearsed rather than assumed.** Both switches landed
  `true`, so `Mint_Enabled__c` had to be turned *off* in Full afterwards. That is the F5a tripwire
  demonstrated a day early: tomorrow's *"verify the CMDT landed `true`/`true`"* is a real check, not
  a formality, because the source record's values genuinely travel. *(Proven a second time 2026-09-03:
  production landed `false`/`true`, because it promoted from Full, where this `false` had just been set.
  The check caught exactly what it was designed to catch — and the value was the intended one.)*

**Two things deliberately removed from the set on 2026-09-02 before uploading**, both worth keeping
out of the production promotion for the same reasons:

- **`SharingCriteriaRule/Account.Account_Sharing_for_Azure_Integration`** — the `ClaudeTest`-only
  workaround. Production reaches Contact records by permission (`Azure_Integration` carries Contact
  Read + View All Records, confirmed 2026-09-02), so the rule is unnecessary there; see question 25.
  It would have ridden the promotion all the way.
- **`ExternalClientApplication/Magic_Link_Bridge`** — redeem-side, so it does nothing in Full, and it
  is the component most likely to throw the two known deploy errors. A change set is all-or-nothing,
  so carrying it risked failing the fourteen that mattered. **It also raises a question that must be
  answered before production:** the ECA metadata carries its signing certificate inline, so a
  deployed copy carries whatever `ClaudeTest` has bound. **Does Azure sign production JWTs with the
  same Key Vault certificate it uses for `ClaudeTest`?** One certificate and deploying is fine; two
  and a deployed ECA fails at the bridge with a signature mismatch, surfacing on 9/9 with no slack.
  **Ask Kyle alongside the secret** — the answer decides whether the production ECA is deployed or
  stood up by hand, which is what step 1 below implies anyway.

**Post-deploy state in Full:** `Mint_Enabled__c` **off** (tidiness — with Marketing Cloud severed from
Full and no secret entered, an accidental firing could only have produced an error row, never a live
credential), `Testing_Mode__c` **on**, principal enabled. **Do not retrieve CustomMetadata from Full**
— it would pull that `false` over the source, and rule 5 below would fire on the next deploy.

**🔑 Do NOT mint here — changed 2026-09-02 (Marissa).** ~~Prove the mint credential chain works here,
where a real mint is safe to attempt.~~ The proof moves to a **test-patient mint in production on 9/3**,
which is strictly better evidence and removes a hazard:

- **Better evidence.** A Full mint exercises a *sandbox copy* of the credential chain. The 9/3 test
  exercises the **production** External Credential, Named Credential and secret — the ones that
  actually matter.
- **The hazard it removes.** A Full sandbox is refreshed *with data*, so its record Ids are
  **production** Ids. A Full-minted link therefore carries a real patient's contact and user
  identifiers, and `dbo.MagicLinkLogins` stores no Salesforce org — so after G11 those identifiers
  would match the production cache. Timing alone would have covered it (72 h expiry from 9/2 dies 9/5,
  G11 is 9/9, and G11 step 3 confirms zero live links regardless), but there is no reason to create
  the class of artefact at all.
- **A consequence worth knowing:** with no mint here, entering the client secret in Full proves
  nothing — an empty or wrong one is undetectable without a callout. **So the secret is a 9/3 need,
  not a 9/2 one.** Still ask Kyle today; today is simply not blocked on his reply.

**🔑 The client secret must be entered by hand in Full too, before the mint test can pass.** *(Added
2026-09-02 — this was written up for 9/3 only, and it applies identically here.)* Change sets carry the
External Credential and Named Credential **records**; they never carry the **secret**, in any deployment
mechanism. So the chain arrives in Full with an empty secret and the mint fails on auth until it is
typed in. Three things follow:

- **Add all three components to the change set explicitly** — External Credential `Magic_Link_Mint`,
  Named Credential `Magic_Link_Mint`, and the permission set
  `Magic_Link_Mint_External_Credential_Principal_Access`. They are separate components and easy to miss.
- **Enter the secret** in Full: Setup → Named Credentials → External Credentials → `Magic_Link_Mint` →
  the `Mint_Caller` principal → Authentication Parameters. **Then check the permission set still shows
  the principal enabled** — the record travels, that enablement is worth an eyeball before trusting the
  mint test.
- **It is the same secret in all three orgs**, not one per org: it belongs to the Entra app
  `MagicLink-Salesforce-Client`, which is already granted `MagicLink.Mint` and allowlisted, so nothing
  Azure-side changes per org. **Azure does not need repointing for a mint from Full** — mint and redeem
  are separate chains, and only redeem is governed by the G11 tuple. A Full-minted link therefore
  **mints successfully and will not open**, which is the correct result today: redeem still resolves
  against the `ClaudeTest` cache.

**⚠ Those Full mints are live 72-hour rows in the production Azure SQL, and `dbo.MagicLinkLogins`
carries no Salesforce org marker.** Keep the test to one or two, and **tell Kyle** — G11's
"advance the kill epoch → confirm zero live links" pre-step on 9/9 is what neutralises them, and it
should not meet unexplained rows. Same class of artefact as the 2026-08-20 proof link and the 2026-09-01
item-9 session links already listed under G11.

**🔴 Retrieving the secret has lead time — check it today, not tomorrow.** Entra shows a secret's value
**once, at creation**; afterwards the portal shows only its description, expiry and a masked hint. So
Kyle's 1Password item is the only readable copy. The 2026-08-18 one-time share link **expired**
(2026-09-02), which killed the *delivery*, not the secret — **ask Kyle to re-share the same item.** If he
no longer holds it, his only option is to create a **new** secret on the same app; that is safe, since an
Entra app can hold several at once and `ClaudeTest` keeps working on the old one, but it is a bigger ask
than a re-share. Expiry is not a concern: created 2026-08-18 with a 24-month term.

**Build the journeys but do not switch them on.** Activation is what creates each journey's `JBSystemFlow_*`
pair on `Refill_Reminder__c`, and there is no reason for those to exist while Marissa is away.
*(Flagged 2026-09-01 and not objected to.)*

**Kyle in parallel:** G9, G5.

**Also today: redesign the email DOB step-up page** (Marissa, with Claude Design). **This ships with the
release** — decided 2026-09-01. The page is Azure-served (`MagicLinkDobPages.cs`), so **design it today
and hand it to Kyle, who deploys it during the 9/4–9/8 gap.** It gets verified as part of the 9/11 email
test, so the canary on 9/14 sees the real page rather than the current one.

#### Thu 9/3 — promote Full → Production and verify · 🔴 the day that decides 9/14

> **📋 RUN SHEET — the order to actually work in, agreed with Marissa 2026-09-02.** The numbered steps
> below this box are the *reference*: what each item is and why it matters. **This is the running
> order.** It differs from the numbering because dependencies force it — nothing can mint until the
> flow set that carries the mint has been deployed.
>
> **⚠️ Added the morning of 2026-09-03 — Kyle's session comes first.** Kyle pulled his Friday work
> forward to today: the **Function release** (#385 redirect + #395 DOB redesign + 15-guess cap —
> [runbook cleared 7:50am](https://github.com/AdvancedRxPharmacy/advancedrx-document-integration/pull/398))
> and the **streaming pre-stage**
> ([#400](https://github.com/AdvancedRxPharmacy/advancedrx-document-integration/pull/400), draft, runs
> same day after the release). Neither names Marissa as a participant, but three inputs on his path are
> hers alone, and none of them depend on the change set — so they go **before step 1**, not after 12.
>
> - **K1. Grant `View All Users` to `Azure_Integration` by hand, now.** This is ~~step 13~~, moved.
>   #400 Part N runs the production nightly copy's **first run "only with Marissa's production
>   permissions"** — its precondition read is literally *"Kyle → Marissa: has her 9/3 item landed?"*
>   Without it he publishes and waits. Do **not** also grant View Setup and Configuration (reference
>   step 4). Step 1's sixteenth component touches the same permission set — after the deploy, re-check
>   `PermissionsViewAllUsers` is still `true` rather than trusting the file. **✅ Landed via step 1 —
>   Marissa had ticked it on Full's copy 2026-09-02, so the deploy carried it. Post-deploy query:
>   `PermissionsViewAllUsers = true`, `PermissionsViewSetup = false`. Kyle told.**
> - **K2. Mint one `ClaudeTest` Email link for his B8 probe, and hand it over securely.** Runbook
>   Precondition 5: **unexpired**, shape `https://login.advancedrx.net/r/<20 chars>`, plus **one valid
>   eight-digit `MMDDYYYY` date deliberately known NOT to match that test patient.** Neither value goes
>   in chat, shell history, tickets, or the execution record — he pastes both hidden (`Read-Host
>   -AsSecureString`). B8 submits **one wrong guess** on the link, so its lifetime count becomes
>   nonzero: **it cannot double as B9's link.** Produce it the same way as 9/1's item 9 pass.
>   **✅ 2026-09-03 — minted and handed over with the wrong-date value.** Neither recorded here, by design.
> - **K3. Do NOT mint B9's link this morning.** B9 (the lifetime-cap proof) runs **last**, after his SQL
>   and package, and Precondition 6 says mint it **immediately before**: no prior misses, ≥35 minutes
>   remaining, a **populated test-cache `Birthdate`** for that patient (Kyle can confirm from the
>   `ClaudeTest` cache table), and explicit approval to burn it — B9 takes 30+ minutes and destroys the
>   link. Expect a call later today; keep the flow-debug window handy.
> - **K4. Select `User` for Change Data Capture in production — one-line confirm with Kyle first.** His
>   9/2 evening decision (#396) says Stage 0 "starts now, Marissa flips User CDC"; his streaming plan's
>   Stage 0 packet still reads **2026-09-15**. Gate A is signed with all-User scope, so the flip is
>   authorized — but confirm which date he means before putting every User's change events on the bus.
>   **✅ 2026-09-03 — confirmed with Kyle, `User` selected for Change Data Capture in production.** Stage 0
>   counting runs from today; his streaming plan's Stage 0 packet (still dated 9/15) is his to reconcile.
> - **K5. ~~Still owed him by EOD, unchanged:~~ the production bridge tuple** — consumer key,
>   `SetupEntityAccess` confirmed, test-user assignments confirmed. That is steps 8–12. **Two of three
>   confirmed today (SEA one row; one test patient assigned); the key moved to next week at Kyle's
>   request — see step 12.**
>
> **Deploy and prove the mint**
> 1. **Deploy the change set** — sixteen components: the fifteen proven in Full, plus
>    `PermissionSet/Azure_Integration`. **Not the ECA** (see step 1 below for why). **✅ DONE
>    2026-09-03 — validated first, then deployed. Clean.**
> 2. **Verify both switches landed `true`/`true`** — `Mint_Enabled__c`, `Testing_Mode__c` — **before
>    anything else runs.** They arrive **on**; this is a check, not an enablement. Proven in Full
>    2026-09-02, where a `Mint_Enabled__c` had to be turned *off* afterwards. **If `Testing_Mode__c`
>    landed `false`, production is live to everyone with no flip and no warning.**
>    **✅ CHECKED 2026-09-03 — landed `false`/`true`, not `true`/`true`, and that is correct.** The
>    "expect `true`/`true`" above was written against `ClaudeTest`'s values; the set promoted from
>    **Full**, where `Mint_Enabled__c` was turned off after the 9/2 pass — so `false` traveled, which
>    is precisely the mechanism this step exists to catch. It is also Marissa's intent: **minting
>    stays off for now.** `Testing_Mode__c = true` was the one that mattered, and it held.
> 3. **Confirm the principal is still enabled** on `Magic_Link_Mint_External_Credential_Principal_Access`.
>    It survived the Full deploy, but it is the mapping that does not always. **✅ 2026-09-03 — enabled.**
> 4. **Enter Kyle's secret** — `Magic_Link_Mint` External Credential → `Mint_Caller` principal →
>    Authentication Parameters. This is the only place it goes. ~~⏸ HELD~~ **✅ ENTERED 2026-09-03,
>    afternoon** — held through the morning by choice, entered once the bridge block was closed. Client
>    Id is the same Entra app as `ClaudeTest` (`b030cfdb-…`); per-Entra-app, not per-org.
> 5. **Mint for a test patient, via flow debug.** Expect a `Magic_Link__c` row **carrying a URL** — not
>    merely a row, since a `Failed` row is also a row. **The only check all week that proves the
>    production credential chain.** ~~⏸ NOT RUN~~ **✅ PASSED 2026-09-03 — `Minted` row with a URL.
>    The production credential chain is proven:** External Credential, secret, Named Credential, Entra
>    token, allowlist, callout — all in production, five days before the flip. The link itself cannot
>    redeem (Azure still points at `ClaudeTest`) and dies 9/6. Prerequisite that made it possible:
>    Marissa assigned herself `Magic_Link_Mint_External_Credential_Principal_Access` first — flow debug
>    runs as the debugger.
> 6. **Run a contact with `Special_Patient_Type__c` blank, via flow debug.** Expect **two** negatives:
>    no `Magic_Link__c` row for that reminder, **and** nothing in `B2B_Custom_Exception__c`. One
>    assertion alone records a leak-then-throw as a pass. ~~⏸ NOT RUN~~ **✅ PASSED 2026-09-03 — both
>    negatives held.** Item 17's production half is done.
> 7. **Decide about `Mint_Enabled__c` for the gap.** Leaving it on is this plan's assumption and is
>    contained. **Turning it off is defensible** — the mint lives in the flow, not the journey, so a
>    test patient hitting a scheduled path during the five days away would mint unattended (harmless:
>    cannot redeem pre-G11, expires in 72 h, swept by G11 step 3). **If it goes off it MUST go back on
>    before 9/9's login proof** — write that into 9/9 rather than trusting memory across five days.
>    **DECIDED 2026-09-03: OFF for the gap** — it arrived off, was switched on only for steps 5–6, and
>    was **switched back off and confirmed** the same afternoon. **→ 9/9 step 0.**
>
> **🔴 Found 2026-09-03, answering "who needs the principal-access permset?" —
> `Magic_Link_Mint_External_Credential_Principal_Access` is assigned to NOBODY in production.** The
> permission set deployed and its principal is enabled (step 3), but `PermissionSetAssignment` rows do
> not travel in a change set — same class as `SetupEntityAccess`. Two identities need it: **Marissa
> herself**, for the flow-debug tests in steps 5–6; and **whatever runs the real scheduled path** — the
> Automated Process user, or the Default Workflow User if one is set (Azure plan §7.8 "execution-context
> trap"; send-wiring step 3 records both fixes as workable on 2026-08-19 but not which one `ClaudeTest`
> uses). **Marissa's decision 2026-09-03: assign it to all admins, pharmacists and fillers** — that
> covers every human who might debug or test. **She assigned herself first, and steps 5–6 then ran
> clean under it — the human half is proven.** **The wider staff assignment is pushed to next week
> (Marissa, 2026-09-03) — nothing mints this week and she holds it herself; → 9/9 step 0.** **The scheduled-path identity is a separate assignment
> and a 9/9 precondition** (→ 9/9 step 0): check `ClaudeTest`'s Manage Assignments and Process
> Automation Settings for which fix is live, mirror it in production. Without it, 9/9 looks exactly like
> reference step 3 warned — works under a person, 401s from the flow. Also: this permission set is **not
> in the repo** (`permissionsets/` holds only `Bridge_Access` and `View_All`).
>
> **Build the bridge by hand**
> 8. **Create the External Client App** — `Magic Link Bridge` / `Magic_Link_Bridge` /
>    ~~`kyle@advancedrx.co`~~ **`techhelp@advancedrx.co`** / Local. **✅ 2026-09-03.** The contact
>    email is only Salesforce's notice address for the app; a shared mailbox outlives either owner. The
>    repo's `externalClientApps/Magic_Link_Bridge.eca-meta.xml` still says `kyle@` — reconcile on the
>    next retrieve, and make sure someone reads `techhelp@`.
> 9. **Tick Enable JWT Bearer Flow, then upload the certificate as PEM.** Callback
>    `https://login.salesforce.com/services/oauth2/success`; scopes Api, Web, RefreshToken, OpenID;
>    PKCE on. **Refresh-token rotation and the 30-day idle TTL will be forced on** — expected, and
>    harmless because JWT Bearer issues no refresh token. **✅ 2026-09-03 — bound cert reads
>    `CN=magiclink-eca-signing-prod`, expires 25 May 2028 19:01:39 GMT.** Full SHA-256, recorded
>    un-elided for the first time: `12:98:B2:F8:67:40:7C:E3:CE:13:9D:CA:29:F1:4B:F8:E8:CF:62:03:8D:60:06:4E:DB:5B:BE:5E:E1:CF:95:D6`.
>    - **🔴 Trap found today — this step used to say "upload the `.crt`", and the obvious `.crt` is the
>      wrong one.** `force-app/main/default/certs/Magic_Link_Bridge.crt` is a **placeholder** from the
>      May Track A scaffold (#101): `CN=MagicLinkBridge-Placeholder`, DER not PEM, SHA-256
>      `BC:4D:2D:3C…`. Uploading it binds a cert Azure has never signed with, and nothing complains
>      until 9/9. **The real cert is the `<certificate>` base64 in
>      `extlClntAppGlobalOauthSettings/Magic_Link_Bridge.ecaGlblOauth-meta.xml`** — decode with the
>      runbook's one-liner (the tag is split across lines, so match `<certificate\s*>`), convert DER →
>      PEM, check the fingerprint above before uploading. **Placeholder flagged for removal; not fixed
>      in flight.**
>    - ~~Open: which file went into Full on 9/2?~~ **✅ Answered 2026-09-03 (Marissa): the same
>      certificate as production.** Full's rehearsal was the real thing end to end; the placeholder has
>      never touched an org. No footnote needed.
> 10. **Grant `Magic_Link_Bridge_Access` access to the app — THEN set Permitted Users to
>     admin-approved.** That grant is `SetupEntityAccess`; it never deploys. **Reversed, the save
>     fails** — observed 2026-09-02. Run the verification query after. **✅ 2026-09-03 — one row.**
> 11. **Assign `Magic_Link_Bridge_Access` to test users only.** Not the group; that is 9/11. **✅ 2026-09-03
>     — one test patient. More when that phase opens.**
> 12. **Get the Consumer Key and send it to Kyle.** Key only, never the secret. **→ DEFERRED to next week
>     — Kyle, on the call, 2026-09-03.** His gap-week runbook's "bridge tuple by EOD 9/3" row is superseded
>     by his own word. The key exists and reads from the app any time; nothing on his side waits on it
>     before the 9/9 flip. Carried to 9/9 step 0, together with the two production endpoints he will need
>     alongside it (token, single-access) — he names the hostnames, she reads the key.
>
> **Close the loop**
> 13. ~~**Confirm `View All Users` came across with the permission set, and tell Kyle.**~~ **Done as K1,
>     by hand, before step 1** *(moved 2026-09-03 — his first run is today, not Friday)*. What remains
>     here: after step 1's deploy, re-check `PermissionsViewAllUsers` is still `true` on
>     `Azure_Integration`. "Tell Kyle" is moot — he is in the room.
> 14. **The before-numbers**, if there is room. Hard deadline 9/10, and this is the fullest of the five
>     days. **→ PENDING 2026-09-03 — method changed, work reassigned.** Marissa chose **Salesforce
>     reports built in production** over the paste-and-run SOQL the baseline doc describes: reports
>     re-run on 9/10 and after launch, where a doc of pasted numbers is a one-time snapshot. A build
>     sheet — folder `Magic Link — Before Numbers`, ten reports covering §2, §3.1, §5, §6, §7, plus a
>     Users report standing in for the unreportable `LoginHistory` — went to the reporting team. **The
>     9/10 deadline is unchanged**; the folder is cloned as "After" that day. §4.2 and §5.4 of the
>     baseline are deliberately left out (join / bug-sizing, not before-picture). The baseline doc's §1
>     method is superseded for this purpose; its §8 synthesis still applies to whatever the reports show.
>
> **Two standing rules.** Never retrieve `CustomMetadata` after the deploy — it pulls the switch values
> over the source, and hard rule 5 fires on the next deploy. And **the ECA is a hand-build in both
> directions**; it does not go into a change set either way.


1. **Build the login bridge by hand in production** — the `Magic_Link_Bridge` External Client App, its
   certificate, and the `Magic_Link_Bridge_Access` permission set. **Rehearsed end to end in Full on
   2026-09-02; the values are in the repo's four ECA metadata files and the certificate is extractable
   from `extlClntAppGlobalOauthSettings` without Azure access** — see
   [`cert-rotation-runbook.md`](magic-link-portal-login-azure/cert-rotation-runbook.md) → *Binding
   through Setup*.
   - **Two things the Setup path needs that the runbook did not have:** tick **Enable JWT Bearer Flow**
     to reveal the certificate upload, and upload the certificate as **PEM** (`.crt`), not the DER the
     metadata field stores. Feeding it DER gives *"the uploaded file isn't a certificate"*, which looks
     exactly like the Track A mistake and is not.
   - **Expect two settings you cannot turn off:** refresh token rotation and the 30-day idle refresh
     token TTL are now forced on newly created apps, where `ClaudeTest`'s May-era app has rotation off.
     **Harmless here — JWT Bearer issues no refresh token** — but it is real drift between the orgs and
     is recorded so nobody re-derives it from a diff. Same class as the 2026-08-31 permission-set
     licence validator: a platform enforcement change, not a configuration mistake.
   - **Do the permission-set grant BEFORE setting Permitted Users to admin-approved**, or you are asking
     the platform to save an app nobody can use.
   - **✅ The certificate question is ANSWERED, and the answer was in this repo the whole time**
     *(2026-09-02; [`magic-link-portal-login-azure/cert-rotation-runbook.md`](magic-link-portal-login-azure/cert-rotation-runbook.md))*.
     **There is one Key Vault and one signing certificate** — `advancedrxkeyvaulteast2`, cert
     **`magiclink-eca-signing-prod`** — and **`ClaudeTest`'s ECA already has that production certificate
     bound**, verified 2026-05-26 as byte-identical to the Key Vault's, SHA-256 `12:98:B2:F8…95:D6`,
     public half only. Salesforce holds the public cert and only *verifies*; Azure signs. The runbook
     states outright that the public cert is not a secret.
   - **So this is a promotion, not a hand-build.** ~~the production Key Vault certificate~~ — there is no
     separate production certificate to obtain, and a deployed ECA carries the correct one. *(The
     question raised on 2026-09-02 — whether production and `ClaudeTest` share a certificate — should
     have been checked here before it was put to Kyle. His reply that day concerned a **spare**
     certificate for rotation, which is a different thing and is post-launch; see below.)*
   - **🔴 BUILD IT BY HAND. DO NOT PUT THE ECA IN A CHANGE SET — established the evening of 2026-09-02
     by trying, in Full.** ~~hop it into Full first~~ Three separate reasons, each proven rather than
     reasoned:
     1. **Its OAuth components are not change-set-deployable.** `ExtlClntAppGlobalOauthSettings`,
        `ExtlClntAppOauthSettings` and `ExtlClntAppOauthConfigurablePolicies` do not appear in the
        component picker at all. A set containing only `ExternalClientApplication` **validates and
        deploys clean** — and lands an app with OAuth off and no certificate. It fails silently, which
        makes it worse than an error.
     2. **The deployed app could not then be repaired.** Editing it failed with *"We couldn't save the
        external client app Magic Link Bridge"* — including a save with **no changes at all**. It
        arrives as a partial record and stays one.
     3. **It carries the source org's identity.** `<orgScopedExternalApp>` embeds an org Id
        (`00DWJ000008dUUf:Magic_Link_Bridge`), which the target org cannot resolve.
     **Deleting it and building from scratch in Setup worked first time.** So step 1 is a hand-build, as
     it was originally written — the certificate being shared between orgs was the right finding, but it
     did not make the app promotable.
   - **Verify the binding after it lands anywhere:** re-retrieve and confirm the `<certificate>` base64
     decodes to subject `CN=magiclink-eca-signing-prod` with that SHA-256. An error reading *"the
     uploaded file isn't a certificate"* means the value is a name rather than content — the original
     Track A mistake.
2. **Assign `Magic_Link_Bridge_Access` directly to the test users only — NOT to the portal Permission Set
   Group.** *(Changed 2026-09-01, Marissa.)* Nothing needs it until a link is redeemed, and through
   Phase 3 that is test patients only. Direct-assigning keeps the ~140,000-user group recalculation off
   the deploy day, and means a wrong app or certificate config is discovered against five users rather
   than after recalculating a group for nothing. **The group addition moves to 9/11** — see that day.
3. **🔴 Finish the mint credential chain — the secret cannot be promoted.** *(Corrected 2026-09-01. An
   earlier revision said "build by hand, it is not in source." That reasoning was wrong: nothing deploys
   from the repo, so what is or is not in git has no bearing on what a change set carries. **What
   genuinely does not travel is the secret** — in any deployment mechanism.)* The chain is three
   artifacts, and the External Credential's **client secret must be entered in the target org by hand**:
   - **External Credential `Magic_Link_Mint`** — OAuth 2.0, Client Credentials with Client Secret, named
     principal `Mint_Caller`. **The client secret comes from Kyle** — the same value entered in Full on
     9/2; it is per-Entra-app, not per-org, so there is nothing new to request for production.
   - **Named Credential `Magic_Link_Mint`** — URL `https://mint.advancedrx.dev`, linked to the External
     Credential; Generate Authorization Header **on**, merge fields in body/header **off**.
   - **Permission set `Magic_Link_Mint_External_Credential_Principal_Access`** — External Credential
     Principal Access, enabling the principal.
   - ~~**Why this is the dangerous one: it is invisible on 9/3.**~~ **✅ Made visible and PROVEN
     2026-09-03** — the test-patient mint (run-sheet step 5) produced a `Minted` row with a URL in
     production. *(Original reasoning, kept for the record:)* `Testing_Mode__c` is `true` and the day's
     verification is a *negative* test, so an incomplete chain produces no error. ~~**It would fail on 9/9, at the first real test-patient mint** — the day with no slack. **This is why
     the Full pass on 9/2 matters: prove a real mint there.**~~ **Superseded 2026-09-02:** the Full mint
     was dropped and the proof moved into this day as **step 9's test-patient case**, which is why that
     step exists and why it runs early.
4. **🔴 Grant `View All Users` to `Azure_Integration` — Kyle's Friday run fails silently without it.**
   *(Added 2026-09-02. This task existed only in his gap-week runbook and was absent from this
   document, which is how a Thursday deliverable goes missing.)* He publishes the nightly portal-user
   job on Friday and runs it once by hand; **without this the run reports success with zero rows.**
   Checked live 2026-09-02 — `PermissionsViewAllUsers = false` on both `Azure_Integration` and the
   `Minimum Access - Azure Integration` profile.
   - **His ask was broader than what is actually needed. Two of the three parts are already done:**
     reading birthdates is live (`Azure_Integration` carries Contact **Read + View All Records** and
     `Contact.Birthdate` FLS, queried 2026-09-02), and **the sharing rule he referenced is a
     `ClaudeTest` workaround for the View All Records that org lacks** — production does not need it.
     See question 25.
   - **Do NOT also grant `View Setup and Configuration`.** It is `false` in production and should stay
     so: `ClaudeTest` needs it because its pipeline selects `Profile.Name`; the production pipeline
     omits that field. See question 22.
   - **The drift runs both ways, which is why "just copy the sandbox" is the wrong instruction.**
     Production is *wider* than `ClaudeTest` on Contact (View All Records) and *narrower* on User.
     `ClaudeTest`'s `Azure_Integration` already carries `ViewAllUsers`, so the grant is known to save
     on that permission set — it should not trip the Salesforce Integration license validator that
     blocked an edit on 2026-09-01.
5. **Do the `SetupEntityAccess` entry by hand.** It does not come across in a deploy — proven, PR #169 —
   then run its verification query.
6. **Promote the flow set Full → Production by change set:** the mint flow, `Magic_Link__c`, the eligibility fields, and the **SMS redaction
   flow** (item 11b).
8. **Before anything else runs, check both switches** — ~~came across as ON — `Mint_Enabled__c = true`~~
   *(corrected 2026-09-03: `Mint_Enabled__c` legitimately arrives however the source org left it — from
   Full that was `false`, and `false` is the intended gap state; the safety check is the second switch)*
   `Testing_Mode__c = true`. **If testing mode landed `false`, production is live to everyone with no
   flip and no warning.**
9. **Run two reminders, not one. Do the first EARLY in the day** — added 2026-09-02, and the ordering
   is the point.
   - **🔴 "Early" cannot mean "before the deploy." The numbering above is a checklist, not a running
     order** *(clarified 2026-09-02)*. A mint fires from `Refill_Reminder_Scheduled_Path_Send_Date_Time`
     calling `MagicLinkMintInvocable`, **both of which arrive in step 6** — so nothing can mint until the
     flow set is in and the switches are verified. The real running order is **6 → 8 → 3 → 9**: deploy,
     check the switches, finish the credential chain, then prove it. Steps 1, 2, 4 and 5 are
     independent of that chain and can go around it.
   - **🔑 A TEST patient first — the positive case.** With `Testing_Mode__c = true` a test patient
     **does** mint (the ON + test-patient case from item 17's 2×2). Expect a `Magic_Link__c` row
     **carrying a URL**. This is the only check all week that proves the **production credential
     chain** — External Credential, Named Credential, secret, Entra app, allowlist, callout. **Do it
     immediately after building the chain in step 3**, so a wrong or stale secret leaves time to reach
     Kyle rather than surfacing at 6pm.
   - **Then a NON-test patient — the negative case** (item 17's production half): no `Magic_Link__c`
     row, nothing in `B2B_Custom_Exception__c`, and the flow demonstrably ran.
   - **Why both.** The negative case proves the safety switch; it cannot prove the credential chain,
     because a chain that is broken and a switch that is working look identical — no mint either way.
     That blind spot is why the plan previously minted in Full; this closes it in the org that counts.
   - The test-patient link is a live 72-hour credential. Minted 9/3 it dies 9/6, well clear of G11 on
     9/9, and G11's step 3 confirms zero live links regardless.
10. **Do not pull metadata down from the org after this** — a CMDT retrieve would overwrite the source
   switch values.

**Two Kyle handoffs, neither needing a joint session:**

- **From Kyle, ~~before 9/3~~ before the 9/2 Full pass — moved earlier 2026-09-02:** the **Entra client
  secret** for the mint API, to build the External Credential. The non-secret values — token endpoint,
  client Id, scope — are already recorded in the Azure plan §7. **It is needed a day earlier than this
  line said**, because the Full mint proof cannot run without it, and the ask is *"re-share the 8/18
  1Password item"* rather than *"send a secret"* — the original one-time link expired 2026-09-02, and
  Entra cannot re-display the value. **Same secret for Full and production; it is per-Entra-app, not per
  org.** See the 9/2 afternoon block.
- **To Kyle, ~~on or after 9/3~~ on 9/9:** the **production ECA's Client Id**, which does not exist until
  the app is created, for `EcaClientId` in Azure App Config. He can apply it any time before G11 on 9/9.
  **The app was created 2026-09-03; Kyle asked for the key next week (call, 2026-09-03) — 9/9 step 0.**
- **To Kyle, on 9/3 — confirmation that `View All Users` is granted** *(added 2026-09-02)*. His Friday
  publish-and-run of the nightly portal-user job depends on it and **fails silently without it**, so
  telling him is part of the step, not a courtesy. See step 4. **✅ Granted and told, 2026-09-03.**
- ~~**Still unanswered from 2026-09-02, and it decides step 1:** does Azure sign **production** logins
  with the same Key Vault certificate as `ClaudeTest`?~~ **✅ ANSWERED from the repo, not from Kyle
  (2026-09-02).** One Key Vault, one certificate, already bound in `ClaudeTest` and already the
  production one — see step 1. **Nothing is owed by anyone on this**, ~~and the ECA can be promoted~~
  *(superseded the same evening — the certificate is shared, but the ECA is still a hand-build; see
  step 1's 🔴 block. Built by hand in production 2026-09-03 with that certificate.)*

**Three deploy gotchas already hit in `ClaudeTest` (Azure plan §7, table of symptoms) — they transfer:**
the `ExtlClntAppGlobalOauthSettings` deploy fails if `<certificate>` names a Certificate record instead
of carrying the body (the repo carries the body, so this is handled); `ExtlClntAppOauthConfigurablePolicies`
rejects `AdminApproved` and `Relax` as enum values (the repo uses `AdminApprovedPreAuthorized` and
`Bypass`, also handled); and the `SetupEntityAccess` insert errors with
`Field is not writeable: SetupEntityAccess.SetupEntityType`.

**Also today, and it has a hard deadline: capture the baseline metrics.** See *Measuring whether this
worked* below. ~~**~30 minutes of paste-and-run queries, and**~~ it **must happen before 9/10** — that is
when sending behaviour changes and the clean before-picture stops existing. *(Method changed 2026-09-03:
Salesforce reports in production, built by the reporting team — see run-sheet step 14.)*

**🔴 End of day: if the bridge, the credential chain and the flows are not in, this falls into the
five-day gap and 9/14 is gone.** And the credential chain now has its own evidence — a test-patient
mint that produced a URL — rather than being assumed from a clean deploy.

#### Fri 9/4 · Mon 9/7 · Tue 9/8 — Marissa out

Labor Day plus two days off. **Kyle's clear run at G5, G8, G12 and G10** — three uninterrupted days, the
best window he gets. *(Revised by Kyle 2026-09-03: he pulled the #385/#395 Function release and the
streaming pre-stage — nightly copy publish, streaming SQL, #390 listener Off — forward to Thursday, so
Friday's list is lighter than his gap-week runbook shows.)*

#### Wed 9/9 — switch on, and point Azure at production · 🔴 tripwire 2

> **📋 RUN RECORD 2026-09-09 — the day as it actually went.** Kyle's message the evening before asked
> for three values (Consumer Key, production token endpoint, production single-access endpoint), the
> two step-0 items, and Marissa's pause-tool acceptance run filed as evidence. All delivered by early
> afternoon:
>
> - **Step 0 done in the morning.** `Mint_Enabled__c` → `true` (Testing Mode still `true`). Principal-access
>   permset assigned to Marissa, Kyle and Sarah — **not yet the wider admins / pharmacists / fillers grant
>   decided 9/3; that is now a choice, not a leftover** (nothing this week needs it). Scheduled-path
>   identity confirmed already covered. Both 9/3 bridge checks re-run: one `SetupEntityAccess` row, one
>   test patient on `Magic_Link_Bridge_Access`.
> - **Scheduled-path finding, for the record:** a `ClaudeTest` query showed the principal-access permset
>   assigned to exactly three humans (Marissa, Kyle, Sarah) and not to the Automated Process user — so the
>   sandbox's working fix is the Default Workflow User route, and production was checked against that.
> - **Tuple handed over.** Endpoints in chat (`https://advancedrx3mc.my.salesforce.com/services/oauth2/token`;
>   `https://www.advancedrx.net/services/oauth2/singleaccess` — the production site sits at the root of the
>   custom domain, so no `/vforcesite` path, unlike the sandbox value). Consumer Key via a shared
>   1Password vault Kyle opened for Marissa as a guest; never in chat.
> - **Stage 0 reminder-peak confirmation** filed in Kyle's repo (his #421, merged): 9/4 normal, 9/5–9/6
>   low, 9/7 none, 9/8 roughly double. He had already pulled the later usage rows himself.
> - **Acceptance run re-run and filed** (his #422, merged): 13:28–13:33Z, both operation ids, kill epoch
>   unchanged. G10 now points at it; Sarah's run is the outstanding one.
> - **Demo** to Sarah, Kristen (reporting), Leiley (support) and Tori (training), midday — live sandbox
>   link, message templates as screenshots. Kyle held the flip until it finished.
> - **G11 — Kyle ran it solo after the demo**, no joint call needed: pause → kill epoch → zero live links →
>   five keys written and read back → sentinel → health green → minting resumed. His word: *"Switch is
>   done, health is green, minting is back on. Production only from now on, no sandbox mints."*
> - **Step 4 redeem proof PASSED — via flow debug, not via a journey send.** Marissa minted for the test
>   patient through flow debug (the same method as 9/3 step 5); `Magic_Link__c` row `Minted` with a URL.
>   **The SMS-style path landed on `www.advancedrx.net/my-home` signed in; the email-style path showed the
>   redesigned DOB page, accepted the birthdate, landed on `/my-home` signed in.** Nothing odd on either.
>   **What this proves:** production mint → production redeem → production session, on both paths, after
>   the flip. **What it does not prove yet:** the message path — no real text or email carried a link to a
>   phone or inbox today. That end-to-end run through the test journeys, plus the QA-C cases (expired link
>   → normal login page, ineligible patient gets the plain reminder, link works twice then dies when
>   cancelled, URL clears after an hour), is 9/10–9/11 work. Kyle's message asked for the journey sends;
>   tell him plainly which proof he is signing G11 against.
> - **Measurement question answered:** `LoginHistory.Application` reads **`Magic Link Bridge`** for both
>   logins — magic-link logins are distinguishable from password logins, so adoption can be counted.
> - **Carried to 9/10 (added to that day's list):** the journey-send end-to-end on both channels, and the
>   remaining QA-C cases already scheduled for 9/11.
> - **Docs corrected the same day** (#226, merged): the "login failures undercounted" caveat was wrong in
>   its broad form — email-code failures **do** land in `LoginHistory`; only wrong-DOB attempts do not.
> - **Tripwire 2 read, honestly:** G8 closed, G12 executed; G9 enabled with five of six rules untested;
>   G10 runbook drafted, sign-off pending; G5 nightly running, streaming steps open. Two closed, three
>   part-way. **SMS-only would not have helped** — those three are Azure spine either way — so the lever
>   was not taken; the open question for Kyle is whether G9 and G10 close by 9/11 or are accepted open
>   like G7. **G10's stated closure ("week of 9/15") collides with G14 on 9/14 requiring G1–G13 closed** —
>   flagged, not resolved.
>
> Remaining for 9/9: both sign G11 in the gates ledger (Kyle's repo).

0. **Preconditions carried from 9/3** *(added 2026-09-03)* — all three are invisible until a mint is
   tried, which is why they are written here rather than trusted to memory across five days:
   - **`Mint_Enabled__c` back to `true`.** It arrived `false` from Full and was left off for the gap
     (9/3 step 7). Nothing mints until this flips; step 4's login proof needs it. **✅ 2026-09-09 morning.**
   - **`Magic_Link_Mint_External_Credential_Principal_Access` assigned to the scheduled-path identity**
     — `autoproc` via an API-inserted `PermissionSetAssignment`, or the Default Workflow User — mirroring
     whichever fix `ClaudeTest` carries. **Also the staff grant** — admins, pharmacists and fillers —
     pushed here from 9/3; it covers people, not the path, and both are needed before minting resumes.
     `PermissionSetAssignment` never deploys. **✅ 2026-09-09 — path covered; humans = Marissa, Kyle,
     Sarah (wider staff grant deferred by choice; see run record).**
   - ~~If 9/3 steps 4–6 did not run later that day, the production credential chain is still
     unproven.~~ **✅ Resolved 2026-09-03 afternoon — steps 4–6 ran and passed; the production
     credential chain is proven.** Nothing to do here; only the two bullets above and the tuple remain.
   - **Hand Kyle the production bridge tuple** — the `Magic_Link_Bridge` Consumer Key (read it from the
     app; key only), plus the production token and single-access endpoints, which he names and Marissa
     confirms. Deferred here from 9/3 at Kyle's request; it feeds `EcaClientId` in the five-key write in
     step 3, so it must be in his hands before that step, not during it. **✅ 2026-09-09 — delivered
     (vault + chat); Kyle read all five back before the sentinel.**
1. **First thing: ask Kyle how many of his five he finished.** Two or more still open and 9/14 will not
   hold — **this is the last clean chance to drop email and ship SMS only.** **✅ Answered from his
   ledger 2026-09-09: two closed, three part-way; SMS-only not taken (see run record).**
2. ~~**Switch both new journeys on**, still test-patients-only. The old journeys keep running, so test
   patients get two of everything. Harmless — they are internal.~~ **Struck 2026-09-02** — the go-live
   journeys have no test-patient filter, so they **stay inactive** until the old ones are switched off
   tomorrow. The test journeys (filtered, already active) are today's harness.
3. **With Kyle (G11), in this order and no shortcuts:** pause minting → advance the kill epoch → confirm
   zero live links → point Azure at production. **✅ DONE 2026-09-09 — Kyle ran it solo after the demo;
   Marissa's part (supply the three values, confirm the read-back, run the channel tests after resume)
   needed no call.**
4. Send a test on both channels **through the test journeys** and **confirm the link actually logs you
   in.** It could not before today — pre-flip those identifiers did not exist in the production cache — so
   this is what G11 unlocks. **✅ REDEEM HALF PASSED 2026-09-09 via a flow-debug mint — both paths
   landed on `/my-home` signed in; email-style via the redesigned DOB page;
   `LoginHistory.Application = Magic Link Bridge`. ⏭ The "send a test on both channels through the test
   journeys" half — a real message carrying the link — is NOT yet run; moves to 9/10 with the QA-C cases.**

#### Thu 9/10 — open it to everyone, safety still on · 🔴 the riskiest day

> **📋 RUN RECORD 2026-09-10 — the day as it actually went.**
>
> - **Message path PASSED, both channels, through the test journeys (Marissa + Sarah).** A test-patient
>   reminder ran the real scheduled path, minted, entered the test journeys, and a real text and a real
>   email arrived carrying working links; each one logged the test patient in. This is the "send a test on
>   both channels" half of 9/9 step 4, and it is QA-C **C1** done a day early (SMS end to end through a
>   real Marketing Cloud send). It is also the storefront confirmation that G11's ledger row lists as
>   Marissa's half.
> - **🔑 DECISION (Marissa, 2026-09-10): the go-live journeys stay inactive until go-live.** The switch
>   (item 11a — old journeys off, go-live journeys on, one sitting per channel) does **not** run today; it
>   moves to **9/14**, ahead of the `Testing_Mode__c` flip. Consequences: the four-day full-cohort
>   rehearsal this section described collapses to launch morning (switch with Testing Mode still `true`,
>   verify real patients get today's message and no `Magic_Link__c` rows appear for them, then flip); the
>   old journeys keep sending as they are until then; **item 13** (delete the test journeys) moves to 9/14
>   *after* the switch, since the test journeys are the only pre-switch harness; and the before-numbers
>   picture holds until 9/14 rather than ending today. Hard-ordering rules 3 and 10 are unaffected.
> - **Testing Mode on, no real-patient mints, regular reminders flowing.** The old journeys are still
>   sending ordinary refill reminders; no `Magic_Link__c` row exists for anyone but the test patient.
> - **Reporting moved:** the before/after numbers (9/3 step 14 → Salesforce reports) are now Kristen's,
>   working with Kyle, separate from this sequence.
> - **Kyle's side, from his ledger (document-integration #427–#429):** **G13 part 1 PASSED 14:58Z** — fresh
>   mint, byte-identical replay, revoked-identity 410, synthetic ids, the final caller. **Part 2 (the
>   15-miss DOB cap) CLOSED ON EVIDENCE:** a synthetic id has no portal-cache row, so `PortalRowMissing`
>   sends it to the dead-link transport before the first miss counts; the cap was already hit live on 9/3
>   14:51Z and 9/9 17:33Z (`DobVerifyExhausted`, lifetime 15), and his ask for a test-patient link was
>   withdrawn. That failed synthetic redeem also closed G11's "failed-link destination" qualification — it
>   landed on `https://www.advancedrx.net/login` with the UTM + `ref` contract. Kyle recorded his G11
>   sign-off. Separately: Stage 0 closed; the portal-user feed opened in LogOnly at 12:15Z and exposed two
>   listener defects (User `Birthdate` 400s; the Contact stream silently dead since 9/5); fix #425 + #426
>   deployed 14:07Z and proven 14:35–14:38Z (18 WouldApply, zero poison). Apply is not before 9/11.
> - **Tripwire 3 PASSED:** G11 flipped (9/9) and G13 passed (9/10). Friday stays a decision day.
>
> - **Two alerts fired for real at 18:31Z and 18:35Z — audit-write (Sev1) and redeem bridge failure (Sev0) —
>   both benign.** Sarah tapped one SMS test link about 30 times in four minutes; Azure saw about 60 requests
>   and 55 completed redeems, and `LoginHistory` agrees (57 `Magic Link Bridge` rows and 57 `OAuth OTP Login`
>   handoff rows for the day, two of them Marissa's message-path test). Five requests were abandoned by her
>   browser mid-bridge (HTTP 499); on those the audit row could not be written and one was classified as an
>   unhandled bridge failure. Zero SQL or Salesforce failures in the window. Kyle's evidence record:
>   document-integration #430. **Finding: about two redeems per tap on her phone.** A text link redeems on
>   any GET, and the function has no guard for speculative prefetch/prerender requests (the `Sec-Purpose` /
>   `Purpose` headers), so a phone that pre-loads the link logs in twice. **Decision (Marissa, 2026-09-10):
>   read each `Magic Link Bridge` row as "at least one tap" and count adoption as distinct users per day.**
>   The prefetch guard and the cancelled-request fix are Kyle's, post-launch, not gates. G9 now has three
>   rules left to live-fire.
>
> Remaining for 9/10: nothing on this lane. The joint G11 signature is a formality — Kyle's half is
> recorded and Marissa's proof is in hand.

**Step 1 is one sitting per channel.** *(Rewritten 2026-09-02 — the 2026-09-01 steps 1–3 were "add the
`Sent` element, remove the test-patient filter, switch the old off," which assumed a filter the go-live
journeys were built without.)*

1. **The switch: deactivate the old journey, activate the go-live journey — per channel, same sitting.**
   The `Status__c = 'Sent'` write-back is already in the go-live journeys, so there is nothing to edit in
   Journey Builder today — **confirm it is present before activating**, since the switch cannot add it.
   If a go-live journey were activated while the old one still ran, real patients would get two messages;
   that is the whole reason for one sitting. **⏭ MOVED TO 9/14 (Marissa, 2026-09-10) — see the run
   record; the go-live journeys stay inactive until go-live.**
2. **Leave `Testing_Mode__c` ON.** Every real patient now flows through the go-live journeys, mints
   nothing, and receives exactly the message they would have gotten anyway. **A full-cohort rehearsal
   with no credentials in flight.** **✅ Holding 2026-09-10 — Testing Mode on; real patients are still on
   the old journeys, so the rehearsal itself moves to 9/14 with the switch.**
3. **Watch for `Magic_Link__c` rows against non-test patients. There should be none** — that is the
   rehearsal's actual assertion, not a side check. **✅ 2026-09-10 — none.**
4. **With Kyle (G13):** the production smoke test on synthetic identifiers — fresh mint, byte-identical
   replay, terminal 410. **Do not redeem a synthetic link into Salesforce.** **✅ 2026-09-10 — part 1
   passed 14:58Z; part 2 closed on evidence; see the run record.**

**🔴 End of day: Azure pointed at production and the smoke test passed — or Friday stops being a decision
day.** **✅ Both held (Azure 9/9, smoke 9/10).**

#### Fri 9/11 — prove it, then decide

1. **C1** — real text to a real phone; tap it; confirm it logs in. **✅ PASSED 2026-09-10 through the test
   journeys (a real Marketing Cloud send), email likewise.**
2. **C3** — an ineligible test patient gets the plain reminder: no dead link, nothing blank.
3. **C4** — a link works twice inside 72 hours, and stops working once revoked.
4. **C5** — the URL clears off the record about an hour after it is created, and the record itself stays.
5. **Item 13** — delete both test journeys **and their leftover `JBSystemFlow_*` pairs**, or they keep
   firing against nothing. **⏭ MOVED TO 9/14, after the switch (2026-09-10)** — with the go-live journeys
   inactive until launch, the test journeys are the only harness through Friday.
6. ~~**Add `Magic_Link_Bridge_Access` to the portal Permission Set Group** and let it recalculate.
   *(Moved here from 9/3, Marissa 2026-09-01.)* By now the app is proven against real test redemptions
   from 9/9 and 9/10, so the ~140,000-user recalculation is not being run on faith. Starting it Friday
   gives a large async job the weekend to settle rather than running it on go-live morning — and since
   the permission set grants nothing, it is harmless across the weekend even if go/no-go comes back NO.
   **Start it and leave it; this is not a watch-and-verify step.**~~
   **⏭ MOVED TO 9/14 (Marissa, 2026-09-11) — the grant runs on launch morning, and the switch waits
   for it to finish.** Marissa's decision, taken with the weekend-settle reasoning above on the table.
   The containment is the wait: no real patient can be handed a link before the switch, and the switch
   does not run until coverage is complete, so an unfinished recalculation cannot present as a broken
   link. **What the move costs is launch-morning time, and the length is unmeasured** — the sandbox
   rehearsal recalculated a two-user group; production is ~142,000 active portal Users (nightly cache
   count, 2026-09-09), and the job now sits ahead of the switch, the `Testing_Mode__c` flip and G14.
   See 9/14 step 2.
   - ⚠ **This is the step that must not be forgotten.** Skip it and every real patient at general launch
     gets `Failed: Not approved for access` — which reads as a broken link, not a missing permission.
7. Confirm Kyle has closed **G8 and G10**. **G8 closed 9/4. G10 open** — see the decision note below.
8. ~~**Check `Testing_Mode__c` is still on** after all that testing (item 16).~~ **✅ Confirmed on 2026-09-11
   (Marissa): still on.**
9. ✅ **GO — decided 2026-09-11 by Marissa.** Holding the decision here is what keeps the weekend clear.

> **📌 What the GO was taken alongside.** Recorded so Monday morning inherits the position rather than
> rediscovering it. **This is a statement of repo state at the moment of the decision, not a claim about
> what was or was not squared verbally** — where Marissa and Kyle have settled something between
> themselves, this note is the thing to correct.
>
> | Item | State at the GO | Whose |
> |---|---|---|
> | **QA-C C4, revoke half** | **Not run.** The replay half passed 9/11. The revoke half needed the jumpbox and Kyle was in it. Kyle proved a revoked identity is refused on 9/10, but on synthetic ids at the Azure boundary — not a real link killed through `usp_RevokeMagicLinksByContact`, which is the action support would take | Marissa |
> | **G10** — operator / incident-response readiness | **Open.** Section 9 unsigned; Sarah's acceptance run outstanding; Kyle's identity-chain correlation of Marissa's 9/9 run pending. His written target is the week of 9/15 | Kyle |
> | **G5** — portal-user cache | Deactivate/restore drill **accepted 2026-09-11** (the 9/3 live deactivation counts; no staged drill). **SQL-side readback of the 9/9 scheduled run still not done** | Kyle |
> | **G4** — DOB step-up | Cap evidence closed. **Browser evidence record for the five styled states still open** | Kyle |
>
> **🔴 The standing rule is that G14 requires G1–G13 all closed, and G10 sits inside that range.** So the
> canary either waits on Kyle's sign-off, or G10 takes the **accepted-open, named-owner** treatment G7
> already has (Kyle, 2026-09-02, as the named risk owner). **That choice is not recorded anywhere yet and
> is the first thing to settle on Monday morning** — before the PSG grant, since it decides whether the
> day proceeds at all.
>
> **Closed on 9/11 after the decision list was written**, so the GO is better-supported than the list
> above implies: the client-disconnect fix's runtime proof (13:50–13:58Z, Kyle's own mint — he did not
> need a link from Marissa), the mint-auth alert live-fire (12:54:07Z, leaving G9's alert-proof item
> closed for launch with four fired and two accepted as query-reviewed), and the portal feed reaching
> Apply (12:06:39Z).

#### Mon 9/14 — go live

> **📋 RUN RECORD 2026-09-14 — the day as it actually went.** *(Written while the day runs. Two blanks
> are marked `⬜` and are the only things this record is waiting on: the cohort-coverage count and the
> canary outcome.)*
>
> - **🔴 Step 0 — G10 was settled neither way, and the day went ahead.** Kyle is revising the
>   incident-response runbook this morning rather than signing section 9, and no formal *accepted open
>   with a named owner* was recorded. **G10 therefore stands open by default rather than by decision** —
>   precisely the outcome step 0 was written to prevent. Recorded here rather than quietly dropped,
>   because the asymmetry matters: **G7 carries an explicit written carve-out for the canary
>   ("G14 may proceed with G7 open under this acceptance; opening beyond the canary may not"), and G10
>   carries none.** Kyle's ledger row is unchanged since 2026-09-04 and still reads *target week of 9/15*.
> - **Step 2 — the PSG grant ran first and the switch waited for it**, as decided on 9/11. **Coverage is
>   a superset, not a match: every portal User holds `Magic_Link_Bridge_Access`, active or not** (Marissa,
>   2026-09-14). G3's exit bar — *"cohort query shows 100% permission-set coverage for eligible Users"* —
>   is therefore met by containment rather than by a count matching the active-User count, which is the
>   stronger result and the one to record. Granting past the active cohort is harmless: an inactive
>   Salesforce User cannot authenticate at all, so the extra holders can never redeem. **No count and no
>   duration were captured** — the recalculation completed quickly enough not to be the blocking wait it
>   was budgeted as. The 9/11 worry about an unmeasured job at ~142,000 users did not materialise, and
>   there is now no measurement of it; if that job is ever run again at size, time it.
> - **🔑 G3's provisioning half, stated precisely, because it is easy to over-claim.** The grant went on
>   the **portal Permission Set Group**, so future portal Users provisioned into that group receive the
>   permission set **by construction** — which is the mechanism G3 asks for. What has *not* been run is
>   the ledger's demonstration of it: *"create/provision a test User and show automatic assignment."*
>   Mechanism structural, demonstration outstanding.
> - **Step 3 — the switch ran.** Old journeys off, go-live journeys on, one sitting per channel, with
>   `Testing_Mode__c` still `true`.
> - **Step 4 — `Testing_Mode__c` off.** The first moment a real patient can receive a link.
> - **✅ Step 5 — G14 canary PASSED**, one consenting patient, one channel, a little after 11:00 ET
>   (Marissa, 2026-09-14). **This is the first magic link a real patient has ever received.**
> - **🔴 Step 6 — the cohort opened the same morning: all patients, both channels, from about 11:00 ET**
>   (Marissa, 2026-09-14). That is the same clock time as the canary, so **the canary was not a stage
>   separated in time from general launch**, and *watch it, then decide* did not happen as a discrete
>   window. Two consequences, both recorded rather than adjudicated:
>   - **G7's acceptance was the one condition written against this, and Kyle waived it on the day.** His
>     2026-09-02 ruling reads *"G14 may proceed with G7 open under this acceptance; **opening beyond the
>     canary may not**,"* and G7 is open with a stated target of the week of 9/15. **Kyle authorised the
>     widening — *go ahead* — relayed by Marissa on 2026-09-14.** So this is a decision taken, not a
>     condition skipped. **Recorded secondhand, with the gap named:** it is not Kyle's own written
>     sign-off, the time and stated basis are not captured, and **the G7 row in his ledger still reads as
>     scoped to the one-patient canary**. Extending it to the full cohort is his edit; the 2026-09-02
>     entry is the model — named risk owner, dated, basis written out.
>   - **The ramp record does not exist.** The gates ledger requires cohort size, channel, start and end
>     time, owner, stop conditions and outcome before widening. What is known is *all patients, both
>     channels, from about 11:00 ET*. **The org-stamp deferral is now the whole cohort's control**, not
>     one patient's: a link names no Salesforce org, so the only thing keeping a sandbox mint from
>     redeeming against a real account is `Mint_Enabled__c = false` in `ClaudeTest`.
> - **Kyle's side, read from his repo rather than reported** (`advancedrx-document-integration` #440,
>   merged 2026-09-14): a read-only **launch-day readiness check, 11:38–11:53Z**, green on every line —
>   both listener channels subscribed and in `Apply`; zero severity 3+ traces since Friday 15:15Z; zero
>   alert instances subscription-wide since Friday 15:00Z; six magic-link rules enabled; health 200/200
>   on both Front Door hosts, and 26,737 weekend health requests all 200; the twelve launch App
>   Configuration keys unchanged since Friday, sentinel still `g11-production-tuple-20260909`; nightly
>   portal sync Succeeded 9/12, 9/13 and 9/14 (142,731 → 142,747 → 142,747). Four gRPC stream drops over
>   the weekend, each self-recovering from bookmark in 5–20 s, **origin undetermined — either side can
>   raise those codes.** Observability workbook usage panels deployed 14:00:37Z.
> - **✅ Resolved — the 13:48–13:50Z activity was Marissa's own launch-morning testing** (confirmed
>   2026-09-14). Kyle's row records it as unattributed (*"who sent them was not confirmed when this row
>   was written"*); **his ledger still needs that line**, or the first person to audit the day will find
>   two mints and four redeems against a production patient cohort with no owner. The shape matches the
>   known ~two-redeems-per-tap prefetch behaviour: two links, two taps, four redeems.
> - **Separate and pre-existing, no launch impact:** `Sync_SFDC_Practitioner_Incremental` has failed every
>   night since at least 2026-08-31 (`Staging_SalesforcePractitionerCache` does not exist). Prescriber
>   matching is off. It is unowned rather than unnoticed.
> - **🔴 First production defect, found and fixed the same day — `System.StringException: Invalid id:` at
>   `Mint_Magic_Link`.** `MintRequest.portalUserId` was typed `Id`; Flow resolves
>   `$Record.Contact__r.Portal_User__c` to an **empty string** when the contact has no portal user, and the
>   platform's coercion threw at the action boundary — so the class's own `SkippedNoPortalUser` branch was
>   **unreachable, and had never executed in production**. **Nothing about the mint changed at 11:00; what
>   changed was who reaches it** — under `Testing_Mode__c = true` only Test Patients (all of whom had portal
>   users) routed to that action, so the whole cohort met it for the first time when the flip happened.
>   **No patient was blocked:** the fault connector carries the flow to `Create_PRRs`, so affected patients
>   got their ordinary reminder without a link. It writes no `Error_Log__c` row, so the emails are the only
>   signal — there is no in-org count of how many failed. Fixed in `7979f78` (`String` + `String.isBlank`,
>   payload unchanged), verified at the Flow boundary **in production, because `ClaudeTest` was unavailable**
>   under Kyle's standing "production only, no sandbox mints." **The tests were 17/17 green throughout** — the
>   skip-branch test passes a typed `Id` null from Apex, which never coerces, and Flow is the one caller a
>   unit test cannot reach. Org-wide check the same day found **no other exposed invocable input**. **✅ Confirmed on real
>   traffic the same day: `SkippedNoPortalUser` rows are being written and the error emails have stopped** — the
>   branch went from never-executed to live within hours. The portal-less lane is countable for the first time,
>   which also means any cohort sizing done from `Magic_Link__c` before the fix is short by exactly that
>   population. Full write-up: `business-context/integrations/magic-link.md`; the generalised rule is now in
>   `.claude/rules/apex.md`.
> - **Leadership told.** Plain-language note to the tech, support, reporting and training leads on launch
>   morning: what changes for patients on each channel, what does not change (the existing login stays,
>   ineligible patients get today's reminder, an expired link lands on the normal login page), that today
>   is one patient rather than the cohort, and that adoption is counted as patients per day rather than
>   link opens.

0. ~~**🔴 Settle G10 first — before anything else runs.**~~ **NOT SETTLED — see the run record.** It was
   open at Friday's GO, and G14 requires G1–G13 all closed. Either Kyle signs section 9, or G10 is
   formally **accepted open with a named owner**, the way G7 was on 2026-09-02. **Decide it out loud and
   write it down; do not let the day proceed on the assumption it resolved itself over the weekend.**
   *(Added 2026-09-11 with the GO. The day proceeded without the decision being taken either way — which
   is the failure mode this step named, so it is left standing rather than struck.)*
1. Confirm **everything else is closed** and Kyle has people watching — rollback and pause operators
   present, monitoring live.
2. **🔑 Add `Magic_Link_Bridge_Access` to the portal Permission Set Group, and let it finish before
   step 3.** *(Moved here from 9/11, Marissa 2026-09-11 — see that day's item 6.)* **This is the
   must-not-forget step, and on 9/14 it is also a blocking one.** Start the grant, then verify coverage
   before touching the journeys: every eligible portal User holds the permission set, count steady and
   matching the active portal-User count. **G3's exit bar is 100% cohort coverage, and this is where it
   is met.**
   - ⚠ **Do not start the switch while the recalculation is still running.** Skipping or pre-empting it
     gives real patients `Failed: Not approved for access`, which reads as a broken link rather than a
     missing permission. The wait IS the control that replaces the weekend settle.
   - ⏱ **Budget unknown time here.** ~142,000 active portal Users; the only rehearsal was a two-user
     sandbox group, so no measured duration exists. Everything below queues behind it.
   - ✅ **DONE 2026-09-14** — the grant ran first and the switch waited for it, as the 9/11 decision
     required. **Every portal User holds the permission set, active or not**, so coverage is a superset
     of the eligible cohort and G3's 100% bar is met by containment. Recalculation was quick; neither a
     count nor a duration was captured, so the feared unmeasured wait produced no measurement either.
3. **The switch (item 11a) — moved here from 9/10 (Marissa, 2026-09-10).** With `Testing_Mode__c` still
   `true`: confirm the `Status__c = 'Sent'` write-back is present in each go-live journey, then per channel
   in one sitting, old journey off, go-live journey on. Real patients get today's message through the new
   journeys; only the test patient mints. **Before the next step, check that no `Magic_Link__c` rows exist
   for non-test patients** — the rehearsal that 9/10 was going to provide, compressed to the morning.
   ✅ **DONE 2026-09-14.**
4. **Turn `Testing_Mode__c` off.** That is the moment a real patient can receive a link. Last, not first
   (rule 10). ✅ **DONE 2026-09-14.**
5. **G14** — send to **one real person who has agreed to it, one channel only.** ✅ **PASSED 2026-09-14**,
   a little after 11:00 ET (Marissa) — the first magic link a real patient has ever received. **Kyle's
   ledger carries no G14 row yet**; it needs one, and the row is the project's closing evidence.
6. Watch it. Then decide about opening up to the full cohort. **🔴 OPENED 2026-09-14 — all patients, both
   channels, from about 11:00 ET, the same clock time as the canary.** So this step did not run as written;
   there was no separated watch-then-decide window. The conditions it names stood at the time and are
   recorded here as unmet rather than struck:** Kyle's G7 acceptance is explicit that *opening beyond the canary may not* proceed with G7 open,
   and his ledger's *Before widening beyond the canary* section adds four more — hold at the smallest
   cohort while alert routing and plain-reminder rollback are watched; measure scanner bridge volume and
   Salesforce API headroom (the D7 cap/throttle/geo decision, deliberately measurement-driven); prove
   cache freshness over at least one real provisioning cycle; and record cohort size, channel, start/end,
   owner, stop conditions and outcome. The org-stamp durable fix belongs here too.
7. **Item 13** — delete both test journeys and their leftover `JBSystemFlow_*` pairs, now that the switch
   has run. ⬜ **Still to run 2026-09-14.**
8. **⬜ QA-C C4, revoke half — carried in from 9/11 and now more load-bearing, not less.** The replay half
   passed 9/11; the revoke half needed the jumpbox and Kyle was in it. From the canary onward a real
   patient carries a live 72-hour link, and killing a real link through `usp_RevokeMagicLinksByContact`
   is the action support takes when a patient rings to say stop. Proven on synthetic ids at the Azure
   boundary (Kyle, 9/10); never run against a real link. *(Added 2026-09-14.)*

### 🔴 Three tripwires, with dates — so a slip shows on 9/3, not 9/11

1. **End of 9/3 — is G3 done and the flow set deployed?** If not, Phase 2 falls into the gap and restarts
   9/9, which collapses Phase 3 to two days and 9/14 is gone. **This is the most schedule-critical item on
   Marissa's side.** **✅ PASSED 2026-09-03** — flow set deployed, bridge built and wired, credential
   chain proven with a real mint. G3's remaining bar (100% cohort coverage via the PSG) is 9/11 by design.
2. **Start of 9/9 — how many of G5, G8, G9, G10, G12 has Kyle closed?** He has the gap to himself. **If
   two or more are still open on the morning of 9/9, 9/14 is not going to hold** — and that is the last
   moment the SMS-only fallback can be taken cleanly.
3. **End of 9/10 — has G11 flipped and G13 passed?** G11 → G13 → G14 is the critical path with zero
   slack. If either is open, 9/11 becomes a work day rather than a decision day, and the weekend is the
   only buffer left. **✅ PASSED 2026-09-10** — G11 flipped 9/9; G13 part 1 passed 14:58Z and part 2
   closed on evidence in Kyle's ledger.

### The lever if a tripwire fires

**Launch SMS-only on 9/14 and let email follow.** Per **E2** this is already the standing alternative. It
leaves the already-built go-live email journey inactive, drops the email half of the switch and question
16 for v1 — and SMS carries the same Azure spine, so
it buys schedule without buying risk. **Tripwire 2 (morning of 9/9) is the last clean moment to take it.**

---

## What this is, and what it is not

**It is a dependency-ordered sequence.** Every remaining checklist item, go-live gate and open question
placed in exactly one phase, with the ordering rules that were previously scattered across two repos and
half a dozen sections pulled into one list.

**It is now also a schedule** — dates were added 2026-09-01 against Marissa's 9/14 target; see *Target
dates* above. **The order came first and the dates were fitted to it, not the reverse**, which is why the
tripwires sit where the dependencies bind rather than at even intervals. Kyle's side is dated from his
gate dependencies rather than from any stated capacity, so **his dates are the ones to confirm**.

**Where "production" means two different things — this is the most common confusion.** AdvancedRx has
**one Marketing Cloud account, the production one**, so the journeys and templates are *built in
production MC* — the **test** journeys scoped to test patients, the **go-live** journeys unfiltered and
inactive until the switch, with `Testing_Mode__c` (F5a) doing the real-patient containment. The
Salesforce metadata — flows, fields, the ECA,
permission sets — ~~is **not in production at all yet**, deliberately~~ *(true when written; **deployed
2026-09-03**, with `Mint_Enabled__c` off and `Testing_Mode__c` on)*. So "build the production journey"
is Phase 1 work, while "production deploy" is Phase 2 and means Salesforce.

## The dependency spine

Read from the gates ledger, not inferred:

```
G3 (prod Salesforce bridge) ─┐
G1 (client, CLOSED) ─────────┴─→ G11 (kill epoch + App Config flip) ─┐
                                                                     ├─→ G13 (prod smoke) ─→ G14 (canary)
G9 (alerting) ──→ G12 (crypto cutover) ──────────────────────────────┘         ▲
                                                                                │
G5, G8, G10 ────────────────────────────────────────────────────────────────────┘
   (G14 requires G1–G13 ALL closed)
```

**G14 is the only real-patient send, and it needs every other gate shut.** That single line does more to
size the remaining work than any item count.

---

## Phase 1 — Sandbox testing and Marketing Cloud build

*Everything here is doable now. No Salesforce production deploy required.*

| Item | Owner | Note |
|---|---|---|
| **QA-C C2** — emailed link lands on the DOB step-up | Marissa runs; Kyle owns the mechanism | **2026-09-01 session.** Unblocked by the F1 WAF fix going live. Assumed passing below |
| **Item 10** — build the production **email** journey in MC | Marissa | ✅ **BUILT 2026-09-02** — saved, validated, **not activated**. ~~Test-patient-scoped,~~ **No test-patient filter** (the test journey carries it); `Magic_Link__r` traversal; **`Sent` write-back built in**; entry-DE retention on |
| **Item 11** — build the production **SMS** journey in MC | Marissa | ✅ **BUILT 2026-09-02** — saved, validated, **not activated**. Same design as item 10, plus `Contact__r:MobileConnect_Phone__c`; entry-DE retention on |
| **Entry-DE retention ON at creation** for both new journeys | Marissa | Free now, never again. **Not** G15 — see *Not blocking v1*. ✅ Both DEs confirmed 2026-09-02 (individual records, 30 days, reset-on-import off) |
| ~~**Question 24** — the "Shop Now" second button~~ | ~~Marissa~~ Kyle | ✅ **CLOSED 2026-09-02 — RULED by Kyle: both buttons on the same magic link is fine, no template change.** Accepted as designed; q16's doubled click-tracking surface is unchanged |
| ~~**Question 16 / PR #182** — magic-link URL in MC click data~~ | Kyle | ✅ **CLOSED 2026-09-02 — ACCEPTED**, approved by merge. The templates as built are correct; the *suppress* branch that would have meant rebuilding three was not taken |
| **Item 3** — email CTA copy sign-off | Kyle + Sarah | SMS half approved 2026-08-31. **✅ Email half CLOSED 2026-09-10 — seen and accepted by Sarah and Kyle. Item 3 complete; wording lives with the templates, not in this doc** |
| **Redesign the email DOB step-up page** | **Marissa** designs; **Kyle ships** | ✅ **DESIGNED 2026-09-02 — five states, handed off in [`advancedrx-document-integration` #392](https://github.com/AdvancedRxPharmacy/advancedrx-document-integration/pull/392)**, which carries the drop-in stylesheet, the slash-mask script and its CSP token, the inline logo, and all five state bodies. 🔴 **The deadline is 9/3 EVENING, not the gap** — Kyle's [gap-week runbook](https://github.com/AdvancedRxPharmacy/advancedrx-document-integration/blob/main/docs/plans/magic-link-gap-week-runbook-2026-09-04.md) step 2 rides it on the same Function release as #385's redirect, and it must pass **Codex review + tests (style-hash pinning)** first; miss that and it ships after the canary sees the old page. *(Corrected 2026-09-02: this row previously read "deploys it in the 9/4–9/8 gap", which is a day later than his runbook allows.)* **Two decisions sit with him inside the PR:** whether the branded dead-link page lands (it would change one of his step-2 probes), and whether the error state echoes the entered date back |

**Already done in this phase:** QA-A, all six QA-B cases (B1–B6), all six templates, both test journeys,
**items 10 and 11 — both go-live journeys (2026-09-02)**, item 11b built and bulk-tested,
`Testing_Mode__c` drilled, the ClaudeTest org tail.

## Phase 2 — Production deploy (Salesforce)

*The first time any magic-link Salesforce metadata exists in production.*

| Item | Owner | Note |
|---|---|---|
| **G3** — production Salesforce bridge | Marissa | Deploy the `Magic_Link_Bridge` ECA, bind the **production** Key Vault cert, deploy `Magic_Link_Bridge_Access`. **`SetupEntityAccess` is a manual insert — source does not carry it** (proven, PR #169). Then add the permission set to the portal Permission Set Group and let it recalculate |
| **Deploy the flow set** | Marissa | Mint flow, `Magic_Link__c`, the eligibility fields, **item 11b's redaction flow**, and `Magic_Link_Setting__mdt` |
| **🔑 Verify the CMDT landed `Mint_Enabled__c = true` / `Testing_Mode__c = true`** | Marissa | The F5a tripwire: CMDT deploys carry their values. **Do this before anything else runs** |
| **Item 17, production half** | Marissa | Repeat the non-test-patient case once against production: no `Magic_Link__c` row, no `B2B_Custom_Exception__c`, flow demonstrably ran |
| **G9** — monitoring and alerting | Kyle | Build is done; needs standing up. **Blocks G12 and G13** |
| **G5** — portal-user cache correctness and freshness | Kyle + Marissa | Includes the CDC streaming plan, still a draft. **Question 20's severity rides on this** |

**Why G3 sits here and not "towards the end":** nothing redeems without it. A fully working redeem —
correct cache row, correct username, valid Key-Vault-signed JWT from the NAT IP — was still refused four
times in the sandbox purely because the portal user lacked the permission set. G11 also depends on G3's
production values.

## Phase 3 — Test-mode testing in production

*Real infrastructure, real cohort, `Testing_Mode__c` still `true`, so no credential reaches a real patient.*

**The staging decomposes into two independent levers** — the journey switch and `Testing_Mode__c`.
*(Revised 2026-09-02: the earlier first step — "new journeys live, test-patient-scoped, old still
running" — assumed a filter the go-live journeys were built without. Pre-switch validation is the
**test** journeys' job.)*

1. ~~**New journeys live, test-patient-scoped; old journeys still running.**~~ **Struck 2026-09-02.** The
   go-live journeys have no filter and cannot be active alongside the old ones; the test journeys
   (filtered, active) carry the post-G11 login proof on both channels.
2. **The switch — old journeys off, go-live journeys on, same sitting — with `Testing_Mode__c` still
   `true`.** Every real patient flows through the new infrastructure, mints nothing, gets the fallback
   branch — **today's message**. A full-cohort rehearsal with **no credentials in flight.** Test patients
   do mint, so they exercise the go-live journeys' magic-link branch with redeemable links.

| Item | Owner | Note |
|---|---|---|
| **Item 11a** — the switch, both channels | Marissa | ~~**Element first, then filter.**~~ **Old off, go-live on, same sitting per channel** (2026-09-02). The `Sent` write-back is built into the go-live journeys — **verify it is present before activating**; there is no Journey Builder edit on the day. **Moved to 9/14 (Marissa, 2026-09-10): the go-live journeys stay inactive until go-live; the switch runs on launch morning with `Testing_Mode__c` still `true`, before the flip** |
| **Item 13** — retire both test journeys | Marissa | Not before the switch — they are the only pre-switch harness. After it, a test patient sits in two journeys (test + go-live) until this runs; ~~the sequence keeps it on 9/11~~ **moved to 9/14 with the switch (2026-09-10)** |
| **G12** — list-B crypto cutover | Kyle | Needs G9. Blocks G13 |
| **G11** — kill epoch + App Config flip | Kyle + Marissa | **Required order, no exceptions: pause minting → advance the kill epoch → confirm zero live links → flip.** A sandbox-era link clicked after the flip can create a **production session** |
| **G13** — production functional smoke | Kyle + Marissa | Synthetic identifiers. Fresh mint, byte-identical replay, terminal 410. **Do not redeem a synthetic link into Salesforce.** **✅ Part 1 passed 2026-09-10 14:58Z; part 2 (DOB cap) closed on evidence — a synthetic id has no cache row, so the cap can only be exercised with a real test patient (see the 9/10 run record)** |
| **QA-C C1, C3, C4, C5** | Marissa | Gated on G11–G13 — **gate open 2026-09-10; C1 passed 2026-09-10 through the test journeys; C3–C5 on 9/11** |
| **G8** — secret and certificate expiry coverage | Kyle | A blocking relay defect was found 2026-09-01 |
| **Question 21** — the expired-link dead end | Kyle | Patient-experience decision; cheap Azure-side content |

**🔑 The switch's two halves split on G11.** The **real-patient** half needs no Azure at all — no mint
happens, so nothing calls Azure, and it is provable **before** G11. The **test-patient** half needs G11:
pre-flip, a production test patient's mint resolves against the `ClaudeTest` cache where their
identifiers do not exist, so those links will not redeem.

## Phase 4 — Go-live with real patients

| Item | Owner | Note |
|---|---|---|
| **G10** — operator and 2am response readiness | Kyle | The incident runbook. **Question 19 feeds this** |
| **Flip `Testing_Mode__c = false`** | Marissa | **Only now can a real patient receive a magic link** |
| **G14** — one controlled patient canary | Marissa + Kyle | **Requires G1–G13 all closed**, rollback operators present, monitoring watched live. A named consenting internal/demo patient, **one channel at a time** |
| **General launch** | — | Full cohort per F5b (Option A, decided 2026-08-25) |

---

## Measuring whether this worked

**Added 2026-09-01 (Marissa).** The project has no success measure without a before-picture, and the
before-picture stops being available the moment sending behaviour changes on 9/10.

**🔑 Most of this is already written and has never been run.**
[`refill-conversion/baseline-analysis.md`](refill-conversion/baseline-analysis.md) is a complete set of
**production-ready SOQL with placeholder result blocks**, built to be pasted back into. It carries a
90-day window and covers the whole funnel:

| Section | What it baselines |
|---|---|
| §2 | Reminders built, sent, and cancelled — by channel and cancel reason |
| §3 | Engagement — email opens/clicks, SMS |
| **§4** | **Authentication — `LoginHistory` volumes, the reminder-receiver-to-login join, and failure modes** |
| §5–§6 | Cart, checkout, orders, payment success |
| §7 | Channel split — SMS-only, email-only, dual-channel cohorts |

**Run §3.0 first** — the file says so; those verification queries settle the largest unknown in one
round-trip and may collapse much of the rest.

### Two things to know before trusting the numbers

~~**🔴 Today's login failures are undercounted.** Per
[`refill-conversion/gap-list.md`](refill-conversion/gap-list.md), `LoginHistory` captures standard
logins, but **AdvancedRx's custom date-of-birth + email-code path produces failures that never land
there.** So the "before" failure rate reads better than reality, and a naive before/after comparison
will overstate how much magic link improved things. Note the caveat alongside the number.~~
**CORRECTED 2026-09-09 (Marissa):** email-code login failures on the current portal login **do**
land in `LoginHistory`; **wrong-date-of-birth attempts do not.** The gap-list's blanket claim was
inferred from code, not checked against the record, and the caveat above is withdrawn in its broad
form. What survives is narrower: the before-picture counts code failures but misses DOB misses, so it
is a floor on today's failure rate, not a full count. Gap 2.1 in the gap list carries the same
correction.

~~**🔴 Confirm magic-link logins are distinguishable *before* launch.**~~ **✅ CONFIRMED 2026-09-09
(Marissa, production):** both post-flip test logins (flow-debug mint, SMS-style and email-style paths)
show **`LoginHistory.Application = Magic Link Bridge`**. Magic-link logins are distinguishable from password logins, so adoption can be counted from
`LoginHistory` as planned. *(Original reasoning, kept:)* The after-picture depends on being
able to tell a magic-link login from a password login in `LoginHistory` — presumably via `Application`
resolving to the `Magic_Link_Bridge` connected app. **If they are indistinguishable, adoption cannot be
measured at all.** The first test redemption on **9/9** is the moment to check this, and it costs
nothing to look.

### Where the measurement lives after launch

Three stores, and no single one answers the question:

- **`Magic_Link__c`** — the mint side. `Mint_Status__c` separates real mints from refusals, and the
  refusals will be the *majority* (116,491 not-yet-eligible against 23,801 eligible), so read the ratio
  rather than the row count.
- **`LoginHistory`** — did the link actually produce a session. **Read a `Magic Link Bridge` row as "at
  least one tap" (decided by Marissa 2026-09-10) and count adoption as distinct users per day, not rows:**
  some phones pre-load a link before the tap completes, and a text link redeems on any GET, so a single tap
  can log in twice (observed 2026-09-10 — about 30 taps, 55 completed redeems). Each redeem writes two
  rows, the Bridge token exchange and the `OAuth OTP Login` storefront handoff, both from Azure's address,
  so `LoginHistory` carries no patient device or location for magic-link logins; that lives in Azure's
  events table as hashes.
- **Azure `MagicLinkLoginEvents`** — redeem-side telemetry, including the DOB step-up outcomes.

## Hard ordering rules — the never-do-X-before-Y list

Previously scattered; collected here because each one has a stated failure mode.

1. **11b before the switch (11a).** The redaction flow must be live before real patients receive
   magic-link texts, or every send writes a live one-click credential into `Individual_SMS_Result__c`,
   which nothing clears.
2. ~~**Element before filter, within 11a.**~~ **Superseded 2026-09-02 — the go-live journeys carry the
   `Sent` write-back from the build and have no filter to remove.** The rule it protected survives as:
   **a go-live journey must carry the write-back before it is ever active.** `Status__c = 'Scheduled'` is
   the journey's own entry criterion, so a reminder that never flips to `Sent` can re-enter and send again.
3. **Old-off and go-live-on in the same sitting, never sequentially — now absolute.** The old journeys
   gate on `Type__c` + consent + send flag, **not** patient type, and the go-live journeys have **no
   test-patient filter** — so a go-live journey active while the old one is active sends doubles to real
   patients. *(The 2026-09-01 wording, "new-widened," assumed a filter that would be removed; there is
   none.)*
4. **Verify the CMDT before anything runs in production.** Custom metadata deploys with its values; a
   source record carrying `Testing_Mode__c = false` launches at full blast with no flip and no warning.
5. **Never retrieve CMDT while the switch is flipped for testing.** A routine
   `sf project retrieve start --metadata CustomMetadata` pulls `false` over the source and rule 4 fires
   at the next deploy.
6. **G11's internal order: pause minting → advance kill epoch → confirm zero live links → flip.**
7. **G11 after G3.** It depends on G3's production values.
8. **G13 after G11 and G12.** Both are named dependencies.
9. **G14 after everything.** G1–G13 all closed, operators present, monitoring live.
10. **`Testing_Mode__c = false` is the last switch, not the first.** Everything provable with it `true`
    should be proven with it `true`.
11. **PSG coverage complete before the switch (11a).** *(Added 2026-09-11, created by moving the grant
    from 9/11 to 9/14.)* The permission-set-group recalculation must finish, and coverage must be
    verified, before either go-live journey is activated. **Failure mode:** activate first and the real
    patients whose recalculation has not landed get `Failed: Not approved for access` on a link that
    looks broken to them and to support. While the grant ran on 9/11 this was not an ordering rule —
    the weekend was the settle window. On 9/14 the wait is the only containment left, which is what
    makes it a rule rather than a step.

## What needs Kyle's confirmation

The placement of **G5, G8, G9 and G10** is inferred from their dependencies, not from a stated position
in any sequence. G9 is placed in Phase 2 because G12 and G13 depend on it; G5 in Phase 2 because question
20's severity rides on it; G8 and G10 in Phases 3–4 as readiness gates. **Kyle should confirm or move
them** — and say which he considers close versus not started. That number, not the gate count, is the
real distance to launch.

## Post-launch, added by Kyle 2026-09-02

- **Real-time portal-user sync.** Built but off for launch. Before it can go on, a week of counting
  establishes how many User change events Salesforce would emit, so they cannot crowd out the patient
  record stream already in use. **Marissa's part is one Setup checkbox — User → Change Data Capture** —
  and it adds load, so **after** launch is the safer moment. **Until it is on, question 20's window is a
  day.**
- **A backup for the signing certificate.** If the certificate Azure uses to sign logins ever breaks,
  magic links stop working until it is rebuilt; **ordinary sign-in is unaffected, so nobody is locked
  out.** Kyle is content to run the one-patient canary without it and to build it the week after, before
  the cohort opens. Marissa's part then is small — a spare certificate on the production connected app.
  **Nothing this week.**

## Not blocking v1

- **G15 / [D22-am]** — retention on the two **existing** DEs (`SMS Refill Reminders v1`,
  `EMAIL Refill Reminders v1`). Deferred to v2 by decision, accepted risk. **Distinct from the Phase 1
  item**, which is turning retention on at creation for the **new** DEs, where it is free.
- **Question 2** — the DE flow's batch limit. The Digital Engagement lane is deferred entirely.
- **Question 15** — field history on `Magic_Link_Suppressed__c`. Pinned by Marissa's choice.
- **Question 23** — the SMS writeback exposure. Answered and built; deploys with the Phase 2 flow set.
- **Item 15** — S2's retry half. Unowned, unknown, and blocking nothing.
- **Question 25** — production `Azure_Integration` is wider than the sandbox's (Contact View All Records
  in production, not in `ClaudeTest`). Marissa's least-privilege review; v1 depends on nothing here — it
  is precisely what makes the 9/2 birthdate-coverage item a non-issue.

## ~~If C2 fails~~ — NOT TRIGGERED (C2 passed 2026-09-01)

*Retained as the record of the fallback that was available and not needed.* The assumption was that the emailed link lands on the DOB step-up. Had it not:

- **The email lane leaves this sequence** and SMS ships alone, per **E2** — build the flow-side email
  skip, launch SMS-only for v1.
- Phase 1 loses item 10, Phase 3 loses the email half of the staging, and **question 16 / PR #182 stops
  mattering for v1** because no email carries a credential.
- Everything else — G3, G11, G12, G13, G14, the whole Azure spine — is unchanged. **SMS carries the same
  gates.**

## Related

- `magic-link-send-wiring.md` — the checklist, the QA ladder (Part H), the questions table, and the
  staging this document sequences.
- `advancedrx-document-integration` → `docs/plans/magic-link-go-live-gates.md` — the gates ledger and the
  authority on gate status.
- `magic-link-sms-writeback-redaction.md`, `magic-link-testing-mode-drill.md`,
  `magic-link-claudetest-org-tail.md` — the three execution runbooks feeding Phases 1–3.
