#7. CP Transitions
Connected Services Framework (CSF) — Part 2: Implementation — Version 2.0
#7.1 Overview
CPs are free to transition between MAPs, subject to standard commercial terms. The CSF is designed to make this transition seamless, with built-in mechanisms for identity portability, in-flight order transfer, and conflict resolution.
A CP can operate through only one MAP at any given time for a given RCPID. This relationship is established through DNS records controlled by the CP.
#7.2 Reasons for Moving
A CP may move to a new MAP for various reasons:
- Service dissatisfaction: The existing MAP fails to meet agreed standards
- Provider termination: The current MAP enters administration or issues a termination notice
- Strategic realignment: The CP's business strategy requires a different MAP
- Cost optimisation: The CP wishes to become a MAP of 1 or consolidate providers
- M&A - A CP is purchased by another CP and their brand rolls into that of the acquirer
#7.3 Transition Process
#Step 1 — Establish with New MAP
The CP establishes a commercial relationship with the new MAP and completes the CP onboarding process:
- New PKI key pair is generated by the new MAP
- The new MAP does NOT yet add the CP to its directory
#Step 2 — DNS Update
The CP updates its DNS TXT records. CPs should lower their TTL on the existing records in the days/hours prior:
[RCPID]._domainkey.[cp-domain]— new public key from the new MAP[RCPID]._mapkey.[cp-domain]— URL of the new MAP's message endpoint
Note: This temporarily stops message flow via the original MAP. Messages sent by other MAPs will fail PKI verification against the old MAP and be queued locally for retry.
#Step 3 — Directory Update
The new MAP performs the directory-side activation steps that make the CP visible and routable through it:
- Add the CP entry to the new MAP's CP Registry under
list[].identity[], with the agreedprocessSupport[]array covering each SforB process the CP is signing up for (businessSwitchMatch,businessSwitchOrder,businessSwitchOrderUpdate,businessSwitchOrderTrigger,businessSwitchOrderCancellation, etc). - Set per-CP status. During cut-over the entry SHOULD be published with
processSupport[].status = TESTif the new MAP is still onboarding the CP; orACTIVEif the new MAP itself is already anACTIVEparticipant and the only change is the move. The status is later transitioned toACTIVEin Step 6, after in-flight reconciliation has cleared. - Bump the registry
versiontimestamp so other MAPs polling the directory observe the change on their next refresh. - Re-sign and re-publish the new MAP's CP Registry per Directory API §5. Do NOT attempt to suppress the entry that still exists in the original MAP's registry — until Step 5 completes there is a deliberate, brief overlap which the conflict-resolution rules in §7.5 are designed to absorb.
- Wait for cache propagation. Receiving MAPs cache registry data; allow at least one configured polling cycle (typically a few minutes) before relying on the entry being visible network-wide. The new MAP's registry endpoint MUST honour
If-Modified-Sinceso peers can confirm freshness without polling the full payload. - DNS already settled. DNS was updated in Step 2; the directory entry is the authoritative advertisement of the routing change but the underlying authority comes from
_mapkey, which receivers will re-resolve before sending sensitive traffic.
Note: Until Step 5 has run and the original MAP's registry no longer lists the CP, the CP appears under two MAPs simultaneously. This is the expected transient state and is what triggers the conflict-detection rules in §7.5.
#Step 4 — RCPID Status Export Request
The new MAP sends a signed rcpidStatusRequest to the original MAP via the Letterbox API. This request is the formal handover trigger and authorises the original MAP to release in-flight order data and decommission its registry entry for the CP.
Authorisation model. Before responding, the original MAP MUST independently verify all three of:
- The DKIM signature on the inbound request is valid per PKI §3 and the signing CP's
_domainkeyresolves to the new MAP. - The moving CP's
_mapkeyDNS record now resolves to the requesting MAP. This is the cryptographic proof that the CP authorised the move — DNS is controlled by the CP, so a_mapkeypointing at the requesting MAP can only have been set with the CP's consent. - The requesting MAP's registry advertises the CP under it (at any status —
TESTorACTIVE).
Request payload.
{
"envelope": {
"source": { "type": "RCPID", "identity": "<new-MAP RCPID>", "correlationID": "<uuid>" },
"destination": { "type": "RCPID", "identity": "<old-MAP RCPID>" },
"routingID": "rcpidStatusRequest",
"auditData": [ { "name": "test", "value": "false" } ]
},
"rcpidStatusRequest": {
"movingRCPID": "<the moving CP's RCPID>",
"purpose": "MAP_TRANSFER",
"newMAP": "<new-MAP RCPID>",
"newMAPDomain": "letterbox.newmap.example",
"requestedAt": "2026-04-29T10:00:00Z",
"specVersion": "GPLB-SG SforB Msg Spec v3.0"
}
}
Response time and failure modes.
| Condition | Response | HTTP / Code |
|---|---|---|
| All authorisation checks pass | Synchronous rcpidStatusConfirmation carrying the export (see §7.4) | 202 |
DNS _mapkey does not resolve to the requester | Reject — handover not authorised | 403 / 9301 |
| DKIM verification fails | Reject — signature invalid | 403 / 8101 |
| No in-flight SORs found for the moving CP | rcpidStatusConfirmation with empty orders[] array; registry entry still removed | 202 |
| Original MAP cannot comply within SLA | rcpidStatusFailure with retry guidance | 503 / 9302 |
SLA expectations. The original MAP SHOULD respond within 30 seconds during core business working hours for an export of fewer than 100 in-flight SORs and within 2 minutes for a complete export. A delayed response forces the new MAP to retry; the original MAP MUST handle idempotency by correlating retries against the original correlationID.
Side effects on the original MAP. As soon as the original MAP commits to processing the export, it MUST:
- Quiesce the moving CP's mailbox — block any new outbound SforB messages from the CP and queue inbound messages addressed to it until the response has been sealed, so that the export captures a consistent snapshot.
- Remove the CP entry from its CP Registry and re-sign and re-publish the registry version (so that other MAPs can stop attempting to route to it).
- Persist the export alongside the inbound
rcpidStatusRequestcorrelation ID for at least 30 days for audit, regardless of the response outcome.
#Step 5 — In-Flight Order Reconciliation
Step 5 is where the in-flight Switch Orders move from the original MAP's state engine into the new MAP's state engine, and where every counterparty MAP gets the cross-check it needs to keep treating the customer journey as continuous.
#7.3.5.1 Suggested Export Routine (run by the original MAP)
The export routine runs server-side on the original MAP between accepting the rcpidStatusRequest and emitting the response. Each step is idempotent so the routine can be safely re-entered after a partial failure:
- Hold the moving CP's mailbox. Block new outbound SforB messages from the moving CP (the GRCP path) and queue any inbound SforB messages addressed to it (the LRCP path). This freeze prevents new in-flight state from being created during the export window.
- Enumerate SORs. Walk the SforB order store for every
switchOrderReferencewhere the moving CP is either the GRCP (originator of the match/order) or the LRCP (counterparty). Each SOR is processed independently. - Capture the last completed interaction only. For each SOR, identify the most recent completed request/response pair (e.g.
businessSwitchOrderRequest→businessSwitchOrderConfirmation) per service. Partially completed exchanges are dropped, per the existing rule in §7.4.5. - Resolve counterparty routing. For every SOR, record the counterparty CP's RCPID and that CP's current MAP — read live from the CP Registry, not from the cached SOR record — so the new MAP knows where to direct follow-up
rcpidStatusRequestcross-checks in the reconciliation phase. - Snapshot per-service state. For every service inside the SOR (
IAS,NBICS,NBICSRange, etc.) capture the currentstatusfrom the SforB state machine (matched,ordered,triggered,cancelled,failed), the activeplannedSwitchDate, and anyfaultblock recorded against the service. This mirrors the structure documented in the GPLB-SG SforB Message Specification v3.0 §3.3 — Business Switch Order Request (canonical source published by GPLB-SG via the FCS). - Capture original-match identity context. Carry forward the customer-identifying fields (
grcpBrandName,name,account,identity[],address) from thebusinessSwitchMatchRequest/Confirmationthat opened the SOR. This allows the new MAP to validate continuity if the counterparty asks for a re-match. - Compute integrity hashes. Canonicalise each SOR record and compute a SHA-256
recordHash, then anaggregateHashover the wholeorders[]array. SignaggregateHashwith the original MAP's_domainkeyprivate key (the MAP-level key from PKI §3, NOT the CP-level key being moved). - Wrap and return. Emit the response payload described in §7.4 as the body of the
rcpidStatusConfirmationmessage. - Finalise registry removal. Once the response has been transmitted, finalise removal of the CP from the registry as already initiated in Step 4 and re-publish.
#7.3.5.2 Reconciliation Performed by the New MAP
After receiving the export the new MAP:
- Verifies the aggregate signature against the original MAP's
_domainkey— this establishes chain-of-custody for the data being imported. - Imports each SOR into its own switching state engine, preserving the
switchOrderReferenceUUID so the customer journey is not restarted. New correlation IDs are minted at the envelope layer for any subsequent outbound traffic, per the §7.4.5 rule. - Issues an
rcpidStatusRequestto each counterparty MAP named in the export — one per counterparty — to:- Notify them that the moving CP is now reachable via the new MAP.
- Cross-check the SOR state from the counterparty's perspective. Any mismatch (e.g. counterparty believes a service is
triggeredwhen the export saysordered) is flagged and resolved bilaterally before further messages flow.
- Resumes message flow by re-publishing the CP entry with
processSupport[].status = ACTIVEin the new MAP's CP Registry once all reconciliation responses are received and clean. - Notifies the moving CP with a transition report (count of SORs imported, any reconciliation flags raised, and estimated time to full resumption).
#7.3.5.3 What Must Be in the Export — Minimum Detail per SOR
The full schema is documented in §7.4. At minimum, every in-flight SOR record MUST carry:
| Field group | Purpose |
|---|---|
switchOrderReference, role, currentState | Identity of the order and which side the moving CP is on |
counterparty.{rcpid, mapRcpid} | Tells the new MAP where to send the cross-check request |
originalMatch.{grcpBrandName, name, account, identity[], address} | Identity context preserved from the opening match |
services[].{serviceType, serviceIdentifier, status, plannedSwitchDate, fault} | Authoritative per-service state at the moment of export |
lastInteraction.{routingID, timestamp, sourceCorrelationID, destCorrelationID, payloadHash} | The most recent completed exchange — the resumption baseline |
expectedNext.{messageType, deadline, slaWindow} | What message is due next and the SLA clock the new MAP must continue to honour |
auditTrail[] | Hashes (not bodies) of every prior completed exchange so anomalies can be investigated bilaterally without exposing message body content |
#Step 6 — Resume Operations
With the export imported and counterparty cross-checks clean, the new MAP can promote the moving CP to fully operational state. Treat the lists below as a sign-off checklist; nothing in Step 6 can be skipped without recording a deviation.
#New MAP
- Cross-check responses received from every counterparty MAP referenced in the export, and any mismatches resolved bilaterally before resuming traffic.
- Per-CP
processSupport[].statusflipped fromTESTtoACTIVEin the registry; registry version bumped, re-signed and re-published. - Synthetic round-trip executed against the moving CP via the TAG Baseline Test Suite — §8.5 Letterbox API to confirm end-to-end delivery.
- Audit log entry written: time of completion, count of SORs imported,
aggregateHashof received export, original MAP RCPID, signing key fingerprint. - Telemetry resumed: outbound messages no longer carry
auditData.test=true(unless the new MAP is itself still inTESTper Onboarding & Testing Process §5). - CP-facing portal or dashboard reflects every imported SOR with continuity preserved (same
switchOrderReference, same per-servicestatus).
#Original MAP
- CP entry removed from registry; registry version bumped, re-signed and re-published.
-
_domainkeyretained for the agreed retention window so receivers verifying historic signatures within that window MUST still be able to resolve them. - Inbound queue for the moving CP drained: each remaining queued message either forwarded under cover of an
rcpidStatusRequestto the new MAP, or rejected with code9999(in migration) if forwarding is not feasible. - Export package archived alongside the inbound
rcpidStatusRequestcorrelation ID for at least 30 days. - Operational support for the moving CP closed off cleanly, with the agreed handover support window communicated to the CP and the new MAP.
#Counterparty MAPs
- Registry caches refreshed; the new MAP entry observed for the moving CP.
-
_mapkeyDNS resolved at least once after the directory change to confirm the authoritative MAP. - Locally queued messages drained — those that were retrying against the original MAP MUST be re-signed and re-routed to the new MAP's letterbox.
- Bilateral SOR cross-check with the new MAP reported clean (or anomalies escalated through the conflict-resolution path in §7.5).
#The CP
- DNS records propagated globally — verify from at least three geographic resolvers before declaring the migration complete.
- All in-flight customer journeys visible in the new MAP's portal/dashboards.
- Old MAP support contact still reachable for the agreed handover support window.
- Stakeholder communications sent: customers, downstream CPs, and any affected industry forum.
#7.4 RCPID Status Export Schema
The schema below is a CSF design proposal layered onto the GPLB-SG SforB Message Specification v3.0 to support lossless MAP migration. It is published here so that the TAG can ratify the field set and the canonicalisation rules used for aggregateHash/recordHash before the first MAP migration runs in production.
#7.4.1 Top-Level Envelope
The export rides inside the standard CSF envelope. The body element is rcpidStatusConfirmation and contains two top-level objects: exportMetadata and orders[].
{
"envelope": { "...": "as per Letterbox §4.3" },
"rcpidStatusConfirmation": {
"exportMetadata": { "...": "see §7.4.2" },
"orders": [ { "...": "see §7.4.3" } ]
}
}
#7.4.2 Export Metadata
| Field | Description | Type | Required |
|---|---|---|---|
exportVersion | Schema version of this export. Current: 1.0. | String | Yes |
generatedAt | UTC timestamp at which the export was sealed. | String (ISO 8601) | Yes |
generatedBy | RCPID of the original (sending) MAP. | String | Yes |
movingRCPID | RCPID of the CP being transferred. | String | Yes |
newMAP | RCPID of the new MAP — echoes the requester. | String | Yes |
dnsVerifiedAt | UTC timestamp at which the original MAP confirmed _mapkey resolution to the new MAP. | String | Yes |
directoryRemovedAt | UTC timestamp at which the moving CP was removed from the original MAP's registry. | String | Yes |
specReference | Version of the SforB Message Spec the orders were created against (e.g. GPLB-SG SforB Msg Spec v3.0). | String | Yes |
orderCount | Number of records in orders[]. | Integer | Yes |
aggregateHash | SHA-256 hash over the canonicalised orders[] array. | String (hex) | Yes |
signature | Base64 signature over aggregateHash using the original MAP's _domainkey private key (algorithm rsa-sha256 or ed25519-sha256 per PKI §3). | String | Yes |
#7.4.3 Per-Order Record
Each entry in orders[] is a full snapshot of a single in-flight Switch Order. The schema is designed to map 1:1 onto the SforB state machine in the GPLB-SG SforB Message Specification v3.0 §3 so the new MAP can resume handling without re-deriving state.
| Field | Description | Type | Required |
|---|---|---|---|
switchOrderReference | UUID of the SOR. Preserved verbatim — the new MAP MUST NOT mint a new SOR. | String | Yes |
role | gaining if the moving CP is the GRCP for this SOR; losing if it is the LRCP. | String (enum) | Yes |
currentState | High-level state: matched, ordered, partially-triggered, partially-cancelled, complete-pending-trigger, trigger-failed. | String (enum) | Yes |
counterparty.rcpid | RCPID of the CP on the opposite side of the order. | String | Yes |
counterparty.mapRcpid | RCPID of the counterparty's current MAP, looked up live at export time. | String | Yes |
originalMatch.grcpBrandName | Brand name presented to the customer at match time. | String | If known |
originalMatch.name | Customer or business name from the match. | String | Yes |
originalMatch.account | LRCP-side account number, if supplied at match. | String | Optional |
originalMatch.identity[] | Identity tuples (contact, IAS, NBICS, SecureID, SIC) used to anchor the match. | Array | Optional |
originalMatch.address | UPRN / address lines / post town / post code from the match. | Object | Yes |
services[].serviceType | IAS, NBICS, NBICSRange (or future industry-agreed type). | String | Yes |
services[].serviceIdentifier | The GRCP's per-service reference. Required for every service, mirroring §3.3 of the SforB Msg Spec. | String | Yes |
services[].status | Current service state: matched, ordered, triggered, cancelled, failed. | String | Yes |
services[].plannedSwitchDate | Per-service planned switch date if it overrides the SOR header. | String (CCYY-MM-DD) | Optional |
services[].fault | { faultCode, faultText } if the service is in failed state. | Object | Optional |
lastInteraction.routingID | Most recent completed exchange (e.g. businessSwitchOrderConfirmation). | String | Yes |
lastInteraction.timestamp | When that exchange completed. | String (ISO 8601) | Yes |
lastInteraction.sourceCorrelationID | The original sender-side correlation ID. | String | Yes |
lastInteraction.destCorrelationID | The destination-side correlation ID, if available. | String | Optional |
lastInteraction.payloadHash | SHA-256 of the canonicalised message body — included so the new MAP can verify with the counterparty without exposing the body itself. | String (hex) | Yes |
expectedNext.messageType | The message type expected next on this SOR (e.g. businessSwitchOrderTriggerRequest). | String | Optional |
expectedNext.deadline | Hard SLA deadline by which the next message must be sent. | String (ISO 8601) | Optional |
expectedNext.slaWindow | The SLA budget remaining as ISO 8601 duration. | String | Optional |
auditTrail[] | Array of { routingID, timestamp, payloadHash } for every prior completed exchange on this SOR. Hashes only — message bodies are NOT exported. | Array | Yes |
recordHash | SHA-256 over the canonicalised record above (excluding recordHash itself). Allows individual orders to be re-verified after import. | String (hex) | Yes |
#7.4.4 Worked Example
A minimal export covering one SOR where the moving CP is the GRCP and the order is in the ordered state, awaiting trigger:
{
"rcpidStatusConfirmation": {
"exportMetadata": {
"exportVersion": "1.0",
"generatedAt": "2026-04-29T10:00:00Z",
"generatedBy": "<old-MAP RCPID>",
"movingRCPID": "809b6e65-a6e7-40f6-8b52-04dd65b6fce1",
"newMAP": "<new-MAP RCPID>",
"dnsVerifiedAt": "2026-04-29T09:58:42Z",
"directoryRemovedAt": "2026-04-29T09:59:01Z",
"specReference": "GPLB-SG SforB Msg Spec v3.0",
"orderCount": 1,
"aggregateHash": "5a3b...c812",
"signature": "MEUCIQDh..."
},
"orders": [
{
"switchOrderReference": "123e4567-e89b-12d3-a456-426614174000",
"role": "gaining",
"currentState": "ordered",
"counterparty": {
"rcpid": "d8322d80-92c8-4906-a408-f1d2daf7e03d",
"mapRcpid": "<counterparty MAP RCPID>"
},
"originalMatch": {
"grcpBrandName": "Bridgend Telecoms (Telesales)",
"name": "The Coffee Shop",
"account": "0003316563216",
"identity": [
{ "name": "contact", "value": "John Smith" },
{ "name": "NBICS", "value": "01234987654" },
{ "name": "SIC", "value": "AB1234" }
],
"address": {
"uprn": "12345",
"addressLines": ["Unit 1", "Highlands Industrial Estate", "22 Cheshunt Mews"],
"postTown": "Glasgow",
"postCode": "SW2R 5AF"
}
},
"services": [
{ "serviceType": "IAS", "serviceIdentifier": "ABC123", "status": "ordered" },
{ "serviceType": "NBICS", "serviceIdentifier": "0101111222", "status": "ordered" },
{ "serviceType": "NBICSRange", "serviceIdentifier": "0101112000-0101112999", "status": "ordered", "plannedSwitchDate": "2025-05-27" }
],
"lastInteraction": {
"routingID": "businessSwitchOrderConfirmation",
"timestamp": "2026-04-12T14:21:08Z",
"sourceCorrelationID": "ABC123",
"destCorrelationID": "XYZ987",
"payloadHash": "8f1a...0e44"
},
"expectedNext": {
"messageType": "businessSwitchOrderTriggerRequest",
"deadline": "2025-04-03T23:59:59Z",
"slaWindow": "P14D"
},
"auditTrail": [
{ "routingID": "businessSwitchMatchRequest", "timestamp": "2026-04-08T09:00:00Z", "payloadHash": "..." },
{ "routingID": "businessSwitchMatchConfirmation", "timestamp": "2026-04-08T09:00:18Z", "payloadHash": "..." },
{ "routingID": "businessSwitchOrderRequest", "timestamp": "2026-04-12T14:20:55Z", "payloadHash": "..." },
{ "routingID": "businessSwitchOrderConfirmation", "timestamp": "2026-04-12T14:21:08Z", "payloadHash": "..." }
],
"recordHash": "9c2d...4e10"
}
]
}
}
#7.4.5 Rules
- Switch Orders only. The export includes Switch Orders only. Switch Matches that have not progressed to an order MUST be repeated by the CP via its new MAP.
- Completed interactions only. Partially completed interactions MUST NOT be exported — only the last completed interaction for each exchange.
- Correlation IDs. The new MAP mints fresh envelope correlation IDs for any subsequent messages — the originals are recorded in
lastInteractionandauditTrail[]for traceability only. - Directory update precedence. The original MAP MUST remove the moving CP from its directory before transmitting the export response. Receivers can rely on the registry version bumping in tandem with the export.
- Body hashes, not bodies. Only message-body hashes are exported. The body itself is opaque to the CSF and remains the property of the originating CP — the hashes are sufficient to support bilateral counterparty cross-checks without re-exposing customer data.
- Signed end-to-end. The export's
aggregateHashMUST be signed with the original MAP's_domainkeyprivate key (the MAP-level key), giving the new MAP and any audit body cryptographic proof of provenance for the imported data.
#7.5 Conflict Resolution
When a CP appears in more than one MAP's directory simultaneously:
- The detecting MAP checks the CP's
_mapkeyDNS record - DNS indicates the current MAP endpoint
- The MAP marks the conflicting entry as being in "conflict"
- The new MAP advertises the old MAP as being in conflict, signalling other MAPs to check
Over time, all MAPs will recognise the conflict and update their directories accordingly.
#Latency During Transition
A CP should plan its move to minimise the gap — typically done overnight. During the transition:
- Messages sent by other MAPs will reflect routing from their current directories
- PKI verification will eventually prevent delivery to the old MAP
- Messages will be queued locally by sending MAPs for retry
- Once the new MAP has completed DNS changes and RCPID Status exports, messages resume flowing to the correct MAP
This self-healing methodology is native to the distributed CP Registry model — PKI provides the final verification check before sending.
#7.6 RACI for In-Flight Order Handling
| Activity | Original MAP | New MAP | CP |
|---|---|---|---|
| Generate new PKI keys | — | Responsible | — |
| Update DNS records | — | Assists | Responsible |
| Stop processing messages (after DNS change) | Automatic | — | — |
| Respond to RCPID Status request | Responsible | — | — |
| Remove CP from directory | Responsible | — | — |
| Add CP to directory | — | Responsible | — |
| Send RCPID Status requests to other MAPs | — | Responsible | — |
| Import in-flight order data | — | Responsible | — |
| Notify affected CPs | — | Responsible | Informed |