Colleagues see the number. Strangers stay masked.
The deciding line: colleague-visible → full number; stranger → masked.
A channel binding is visible only to its owner and administrators in that person’s own firm. Those readers are colleagues. Hiding most of a colleague’s number does not protect it from an outsider; it only prevents counsel from recognising which of their own numbers is bound. Story-3037 removes that fake privacy while retaining the real third-party boundary.
The visibility test, surface by surface
| Surface | Who reads it | Example | Result |
|---|---|---|---|
| Binding API payload | Owner and their firm administrators | providerSubject: "+15550001234" |
Full |
| Binding and anomaly audit label, snapshot, metadata | The same firm’s colleagues | Number: +15550001234 |
Full |
| Refusal accounting ledger | Internal abuse-control accounting | +15550001234 |
Full internal key |
| Refusal audit, digest, and response surfaces | Staff controls for unregistered senders | •••1234 |
Masked display |
| CloudWatch refusal/anomaly fields | Operators; may describe third parties | •••1234 |
Masked |
Why the refusal ledger is different
A binding proves the number belongs to a colleague. A refused inbound
sender has failed that test: it may be any third party on the public
channel. Its internal ledger keeps the full sender as the accounting key
so rate caps, spend caps, backoff, and alert deduplication remain isolated
per sender. Masking that key to four digits would merge unrelated senders
into one abuse-control bucket. Refusal audit labels, snapshots, metadata,
digests, responses, and CloudWatch fields expose only the short
•••1234 display.
Bound-number anomaly audit is not the same surface. It belongs to the binding’s firm feed, so it carries the full bound number. Its CloudWatch field remains masked because operator logs are outside the colleague-facing product boundary.
The API contract says what it does
providerSubjectMasked is deleted from the binding DTO and
OpenAPI contract. The replacement is providerSubject, whose
value is the full bound number. There is no compatibility alias or
dual-truth response. The paired pramaan-app consumer update is tracked by
pramaan-io/pramaan-spec#3044.
What the tests prove
- A firm-scoped binding response returns the complete fictional number.
- Its audit subject label and snapshot contain that same complete number.
- Two unregistered senders with the same last four digits keep independent accounting counters and refusal windows.
- CloudWatch
extrafor an unregistered sender contains only the masked display. - The OpenAPI snapshot exposes
providerSubjectand noproviderSubjectMasked.
Operational boundary
This is an application contract and data-handling change only. It adds no cloud capability, IAM grant, tracing span, deploy workflow, or model call. Live app verification follows the paired consumer sync in Story-3044; this change does not claim that cross-repository UAT.