Re post-lesson NPS scoping; existing comms surfaces carry the send, Platform recommends a Delivery-local feedback surface, and flags the missing consent/suppression layer
Platform's two asks from 2026-05-22-coaching-post-lesson-nps-program-scoping are the Guardian-aware comms endpoint (Platform ask #1, open question #4) and surface home (Platform ask #2, open question #5). Both are answerable today without a Platform build. One premise in the memo needs correcting, and one capability the memo assumes exists does not. Details below.
The comms-routing endpoint resolves the recipient; it does not send
The memo asks Platform to "confirm the Guardian-aware comms endpoint can carry a survey-class post-lesson send." The endpoint does not carry sends at all, and that is the right answer rather than a problem.
Platform's Guardian-aware comms endpoint is GET /api/identity/v1/comms-routing/[personId] (platform/modules/guardian/, per ADR-0003 action item 2). It is a recipient resolver: given a Person, it returns who should receive the message — the Person themselves if an adult, the active Guardian's Person if the input Person is a minor, with a routed_through_guardian flag. It has no concept of message class, channel, or content. It never sends anything.
That makes the survey case trivial on the routing axis: a post-lesson survey send routes through the endpoint identically to a lesson-day reminder, because the endpoint does not distinguish message classes. There is no "survey-class" support to confirm — the resolver is class-agnostic by construction. No Platform change is needed here.
One edge case the program should design for. The resolver throws NoActiveGuardianForMinorError (HTTP 422) when the input Person is a minor with no active Guardian relationship. Swim lessons skew heavily minor, and a booked lesson's participant almost always has a Guardian on file, so this is rare — but a per-lesson survey fires on every attended lesson, so "rare" still produces a steady trickle. The program needs an explicit decision (open question #4/#6): on a 422, drop the survey silently or surface it to an operator. Platform's recommendation is drop-and-count, not operator-surface — a missed survey is not worth an operator task.
The send path already exists: the external-actions queue
There is no Platform-owned outbound messaging API, and the program does not need one. The only Platform comms contract is Platform Comms v1.0.0, and it is inbound-only — inbound SMS/voice resolved to a platform.comms.inbound dispatcher event. Its README lists "outbound messaging APIs" explicitly out of scope.
Outbound sends already have a home: the External Actions Queue (ADR-0011, contracts/external-actions/README.md v1.0.0). A producing domain enqueues an action with a producer-defined provider and action_kind (SMS via Quo is named in the contract as a Sales/Delivery use), implements its own handler, and Platform's runner handles retry, dead-letter, and idempotency. "Survey-class" is not a tag anywhere — action_kind is free text, so the survey send is simply its own action_kind owned by whoever owns the lesson.attended-triggered send (Delivery, per the memo's cross-domain shape).
The queue contract already settles the minor-routing obligation for exactly this case. External-actions §4.3: outbound communications targeting a minor Person MUST route through Platform's Guardian-aware comms-routing endpoint before the action reaches a provider, and minor contact details MUST NOT sit in external_action.payload. So the architecture for a compliant survey send is fully specified today: resolve recipient via the comms-routing endpoint, enqueue the send as an external action carrying the routed adult recipient. No new Platform surface.
There is no consent or suppression layer today — name it as a gap
Open question #4 also asks "how comms suppression and consent apply to survey sends." Platform's honest answer: there is nothing to confirm, because no consent, opt-out, or suppression layer exists in Platform today. There is no consent model in the Platform schema, no suppression table, and no opt-out check anywhere in the comms path. The comms-routing endpoint enforces exactly one rule — minor-to-Guardian redirect. The external-actions queue is pure transport. Neither checks opt-out, quiet hours, message-class consent, or do-not-contact.
This matters because the memo's open question #4 reads as though a consent capability exists to be confirmed. It does not. If the NPS program needs send suppression — opt-out, fatigue caps, quiet hours — that capability has to be built, and Platform should not let "the endpoint handles routing" be mistaken for "the endpoint handles consent."
Platform's recommendation, and the "for now" path so this is not a one-way door:
For the pilot, keep suppression Delivery-local. The two suppression needs this program actually has are a per-lesson dedup (do not survey the same lesson twice) and a frequency cap for weekly-recurring families (open question #3's fatigue concern). Delivery already holds the send history once it owns the feedback surface, so both are local predicates Delivery can enforce without any platform primitive.
Do not block the pilot on a platform-wide consent surface. A cross-cutting customer-comms preference store ("this family opted out of all non-transactional messaging") is real and absent, but it is a separate, larger decision that touches every domain's outbound comms — Sales cadence, Delivery reminders, this survey. The trigger to build it is a second program that needs it, not this one. When that surface is built it is a Platform ADR, because it is a new Platform-stewarded surface consumed by multiple domains. Platform will own that ADR when the trigger arrives; it is not a dependency of the NPS pilot.
Surface home: Delivery-local feedback table, not a Platform survey service
Platform ask #2: Delivery-local feedback table vs. a shared Platform survey service. Platform recommends the Delivery-local table, and there is no ADR for this program.
No Platform survey service exists today; building one would be net-new. Three reasons not to:
The customer-identity firewall is already specified around Delivery. The memo and the customer_satisfaction_of_coach registry entry both require customer-grain feedback rows to stay inside the Delivery firewall, with Coaching reading only a coach-grain, identity-stripped aggregate. A Delivery-local table is the natural home for data that, by contract, must not leave Delivery. A shared Platform service would pull customer-grain rows across a domain boundary the mart spec deliberately drew.
A shared "survey service" with exactly one consumer is a speculative platform abstraction. The Sguild discipline — no rocks, handshake-only, "for now" solutions — argues against standing up a Platform service for an N-of-1 case.
The genuinely shared part is already Platform-owned. The send mechanism (external-actions queue) and recipient routing (comms-routing endpoint) are Platform infrastructure that every domain already shares. What would be Delivery-local is only the response capture table — and that is domain data, not platform infrastructure.
Migration path, so the interim choice stays honest: the explicit future case is coach_satisfaction_of_platform, the coach-as-respondent weekly instrument the memo correctly scopes out. If that program is later built, it is a second survey instrument — and a second instrument is the right trigger to promote a shared survey service and, at that point, to write the ADR. Delivery-local now; revisit a shared service when a second instrument appears. Naming the trigger means the interim table is a deliberate stop, not drift.
On response capture (open question #2): prefer the one-tap link
Not a Platform ask, but Platform has a decisive observation. Open question #2 weighs parsing an SMS reply against texting a one-tap link to a short form. Choose the one-tap link, for a Platform-side reason beyond free-text parsing.
An inbound survey reply is, to the current pipeline, indistinguishable from a customer-initiated SMS. Platform Comms v1.0.0 resolves every inbound SMS to a platform.comms.inbound event whose entire purpose is to reset that Person's Sales cadence. Absent new survey-reply classification logic, "text back a number 0–10" would land as inbound comms and reset the Sales cadence for every family that answers the survey — a cross-wire between two unrelated programs. Suppressing that would be net-new Platform classification work. The one-tap link keeps the response off the inbound-SMS pipeline entirely and avoids the problem at no cost. The web form behind the link is naturally Delivery-local too, for the same firewall reason as the capture table.
What Platform is and isn't committing
Platform commits nothing buildable here, hence commitments: []. The comms-routing endpoint and the external-actions queue already support a compliant minor-aware survey send with zero Platform changes; the architecture is specified in external-actions §4.3 today.
What is still owed sits with Delivery, on the open scoping memo: confirm Delivery will own the customer-feedback capture surface (Platform recommends the Delivery-local table), the lesson.attended-triggered send, and the pilot-scope suppression (per-lesson dedup plus a frequency cap). Coaching and Delivery still owe each other the coach-grain aggregate shape; Platform has no position on that beyond noting the firewall keeps customer-grain rows out of Coaching, which the memo already states.
References
2026-05-22-coaching-post-lesson-nps-program-scoping— the memo this responds to.2026-05-22-delivery-mart-section-classification— Delivery's classification filingcustomer_satisfaction_of_coachas a silver gap.2026-05-19-platform-mart-them-os-dispositions-landed— the Them OS disposition splittingcoach_npsintocustomer_satisfaction_of_coachandcoach_satisfaction_of_platform.- Guardian-aware comms-routing endpoint:
platform/modules/guardian/(servicegetCommsRoutingTarget), routeGET /api/identity/v1/comms-routing/[personId]; ADR-0003 action item 2. - External Actions Queue Contract v1.0.0:
contracts/external-actions/README.md, §4.3 (minor-routing obligation for outbound comms); ADR-0011. - Platform Comms Contract v1.0.0:
contracts/platform-comms/README.md— inbound-only;platform.comms.inboundas the Sales cadence-reset signal. - Delivery comms-routing rule:
coordination/domains/delivery.md. - Registry entries:
platform/lib/mart/registry.ts—customer_satisfaction_of_coach,coach_quality_score,coach_satisfaction_of_platform(allkind: rate).