---
title: MAP Onboarding & Testing Process
tags: [Testing]

---

# MAP Onboarding & Testing Process

> Connected Services Framework (CSF) — Companion Document — Version 1.0 (Draft)
>
> Maintained by the Telecom Technical Architecture Group (TAG). Referenced from [Part 1: Onboarding](../part1-framework/07-onboarding.md), [Part 1: Governance](../part1-framework/10-governance.md), and [Part 2: Getting Started](../part2-implementation/01-getting-started.md).

---

## 1. Purpose

This document is the practical handbook for any party preparing to deploy and operate a Managed Access Provider (MAP) on the Connected Services Framework (CSF). It exists to help engineers and project teams achieve a robust, production-ready deployment with minimal friction and maximum confidence.

It serves three distinct audiences working together:

| Audience         | What this document delivers                                                                                                                                                                         |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Developers**   | Concrete API behaviours, error responses, signing examples, and a public PKI test harness for round-tripping signatures end-to-end                                                                  |
| **Implementers** | A phased onboarding journey, environment expectations, status-flagging rules, and an exhaustive checklist tying CSF documentation chapters together                                                 |
| **Testers**      | A baseline test suite of positive, negative, and edge-case scenarios — covering both CSF transport and the SforB industry protocol — that every new MAP MUST pass before production access is granted |

The CSF is intentionally distributed and unlicensed. Trust in the network is earned through demonstrable conformance, not through a central certifying authority. This document is the basis for that demonstration.

> :memo: **Note**: This document significantly exceeds the requirements currently set forth by TOTSCo for hub onboarding. The CSF carries no central operator; therefore, the reception of MAPs depends on the meticulous deployment standards of each individual MAP. Extensive baseline testing should be regarded as a safeguard among peers rather than an administrative obstacle. It is anticipated that implementers utilising the CSF will adhere to the guiding principles by conducting comprehensive baseline testing for both the CSF transport and in relation to the specific protocol being implemented.  This process will facilitate a profound understanding for each new MAP integrating into the network, enabling them to benefit from the collective efforts of existing MAPs and to achieve the highest level of maturity possible through the completion of all requisite baseline tests.

## 2. How to Use This Document

The document is organised as a journey:

1. **Sections 3–5** describe the environment, status model, and traffic-flagging rules you must understand before starting.
2. **Section 6** describes the phased onboarding flow and which tests apply at each phase.
3. **Section 7** is the NowYoYo PKI Test Harness — copy-and-run examples for the area engineers find hardest to get right.
4. **Section 8** is the **TAG Baseline Test Suite** — every test case a new MAP must pass.
5. **Sections 9–11** cover exit criteria, bilateral testing, and ongoing regression obligations.

>:tip: **Engineers** should iterate Sections 3 → 8 in order during build. Implementers and testers should treat Section 8 as the contract that defines "ready for production".

## 3. Test Environments

The CSF supports **testing in production** as a first-class operating mode. There is no need for a separate CSF pre-production/test network. Instead, environments are distinguished by participants' status and by an explicit test flag in every message envelope.  This is more efficient for the MAPs because each exchange is peer-to-peer, and so each operator is clear whether the intended message is for test or production. This method allows multiple pseudo networks to coexist to suit specific needs and is ideal for onboarding new MAPs, as the sponsor MAP can ensure that all traffic is treated as a test while it is being evaluated.

### 3.1 Environment Types

| Environment                   | Provider                  | Purpose                                                                                                                                                                                                                                               |
| ----------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Local<br>Sandbox**          | Each MAP<br>in-house      | Unit, integration, and component tests within a single MAP's stack. Typically associated with Development                                                                                                                                             |
| **PKI Verification Endpoint** | NowYoYo (public, free)    | The hosted DKIM verification harness is documented in [Section 7](#7-pki-test-harness-nowyoyo). Used at any stage to debug signing. Other endpoint networks can also be made available if required to help the new team with their development phase. |
| **Virtual Test Host (VTH)**   | Sponsor MAP<br>(optional) | A simulated counterparty exposing CSF endpoints. Used for early connectivity testing, OAuth, registry and signing tests without involving live MAPs                                                                                                   |
| **Test Tenant**               | Sponsor MAP               | A real CSF endpoint operated by the Sponsor MAP under TEST status. Used for end-to-end SforB process tests with test CPs                                                                                                                                |
| **Production with TEST flag** | All MAPs                  | Full production network with `TEST` status applied to the new MAP and/or its CPs. Used for operational validation prior to going live                                                                                                                 |
| **Production**                | All MAPs                  | Full production network with `ACTIVE` status applied to all contributing MAPs and/or its CPs.                                                                                                                                                         |



### 3.2 Environment Sequencing

```mermaid
flowchart TB
    A["Local Sandbox"</br> MAP In-house Testing] --> B["PKI Verification </br>Endpoint"]
    B --> C["Sponsor MAP </br>Virtual Test Host"]
    C --> D["Sponsor MAP </br>Test Tenant"]
    D --> E["Production </br>w/ TEST flag"]
    E --> F["Production </br>Full Operations"]
```

Engineers SHOULD progress only when the prior stage is green. 
Skipping the PKI Verification Endpoint stage is the most common cause of long-running onboarding incidents and so MAP like NowYoYo have created an end point to help others with getting this right.

### 3.3 Required CSF Endpoints (under test)

A MAP under test MUST expose all four endpoints over TLS 1.3, even if their content is restricted or test-only:

| Endpoint            | Purpose                                                    | First-pass test                                                |
| ------------------- | ---------------------------------------------------------- | -------------------------------------------------------------- |
| OAuth 2.0 token URL | Issue access tokens to peer MAPs (Client Credentials flow) | Successfully issue a bounded-scope token                       |
| Directory API URL   | Serve the MAP's CP Registry as JSON                        | Return a syntactically valid registry containing only test CPs |
| Letterbox URL (CSF) | Receive signed messages from CSF MAPs                      | Accept a signed test JAM message and return HTTP 202           |
| Service Status URL  | Report operational state                                   | Return current state to peers                                  |

> :tick: **Best Practice**: Even in test, separate the CSF letterbox endpoint from any HMAP letterbox endpoint (e.g. for TOTSCo). This habit avoids accidental cross-network exposure later as HMAP tend to be static and unlikely to change, whereas the CSF interface support versioning and feature releases as standard and is expect to evolve more quickly to support new industry protocols.


### 3.4 Sponsor MAP via the CP Registry

The CSF requires Sponsor MAP responsibilities to be distributed equally among all onboarded MAPs using a sequential FIFO rotation. MAPs join the rotation in the order they are onboarded — their position (`order`) is permanent and immutable. The mechanism supports concurrent sponsorships, which are needed when multiple new MAPs (whether single-CP or traditional multi-CP) are onboarding simultaneously.

This proposal defines a decentralised, self-service mechanism using the CP Registry JSON. No central coordination is needed.

#### Design Principles

- **FIFO rotation**: MAPs are allocated sponsorships in the order they joined the network. `order` is assigned at onboarding and never changes.
- **Forward pointer**: The token (`nextSponsor`) is a forward pointer — it identifies who is **next**, not who is current. The current sponsor is the MAP that most recently updated it. No majority consensus is needed.
- **Advance on acceptance**: The token advances when the Sponsor MAP **accepts** a new engagement, not when it completes one. This keeps the queue moving regardless of how long an individual onboarding takes.
- **Concurrent sponsorships**: Each MAP declares a `capacity` and tracks `active` engagements. If the designated MAP is busy, the next MAP in the rotation takes the overflow automatically.
- **Self-discoverable**: Any MAP — including a prospective new MAP — can find the current Sponsor by reading any onboarded MAP's registry.

#### Proposed Solution: `sponsorship` Object in the MAP Section

Add a `sponsorship` object to the `map` section of each MAP's CP Registry.

**Idle MAP — no active sponsorships:**

```json
{
  "map": {
    "name": "alphaMAP",
    "version": "2026-01-10T09:00:00Z00000",
    "sponsorship": {
      "order": 1,
      "nextSponsor": 2,
      "nextSponsorUpdated": "2026-01-10T09:00:00Z",
      "capacity": 1,
      "active": []
    }
  }
}
```

**MAP with an active sponsorship engagement:**

```json
{
  "map": {
    "name": "gammaMAP",
    "version": "2026-06-09T10:00:00Z00000",
    "sponsorship": {
      "order": 3,
      "nextSponsor": 4,
      "nextSponsorUpdated": "2026-06-09T10:00:00Z",
      "capacity": 2,
      "active": [
        {
          "id": "a3f91c2d-7e4b-4f8a-b1c2-9d3e5f6a7b8c",
          "type": "traditional",
          "opened": "2026-06-09T10:00:00Z"
        }
      ]
    }
  }
}
```

**MAP handling two concurrent onboardings (at capacity):**

```json
{
  "map": {
    "name": "gammaMAP",
    "version": "2026-06-09T10:05:00Z00000",
    "sponsorship": {
      "order": 3,
      "nextSponsor": 4,
      "nextSponsorUpdated": "2026-06-09T10:05:00Z",
      "capacity": 2,
      "active": [
        {
          "id": "a3f91c2d-7e4b-4f8a-b1c2-9d3e5f6a7b8c",
          "type": "traditional",
          "opened": "2026-06-09T10:00:00Z"
        },
        {
          "id": "b7e22d10-3c9f-4a1d-e5f2-8d4c0b7a3e29",
          "type": "single-cp",
          "opened": "2026-06-09T10:05:00Z"
        }
      ]
    }
  }
}
```

#### Field Definitions

| JSON Path | Description | Type | Required |
|---|---|---|---|
| `map/sponsorship` | Sponsor rotation metadata | Object | Yes |
| `map/sponsorship/order` | This MAP's permanent FIFO position in the rotation. Assigned at onboarding; **never changes** even if other MAPs leave the network — the `order` of a withdrawn or removed MAP is preserved for historical audit consistency, and the rotation simply skips entries whose `map/status` is not `ACTIVE`. | Integer | Yes |
| `map/sponsorship/nextSponsor` | The `order` number of the MAP designated as the **next** Sponsor after this MAP. Updated when this MAP **accepts** a new sponsorship engagement. | Integer | Yes |
| `map/sponsorship/nextSponsorUpdated` | Timestamp when `nextSponsor` was last updated (ISO 8601 UTC). The MAP with the most recent `nextSponsorUpdated` is the current active Sponsor. | DateTime | Yes |
| `map/sponsorship/capacity` | Maximum concurrent sponsorships this MAP will accept simultaneously. **Minimum: `1`** (every MAP MUST be willing to take at least one engagement at a time). **No upper bound** — capped only by the MAP's own operational capacity, which it determines for itself. A MAP that primarily takes `single-cp` (MAP-of-1) engagements will typically support a higher `capacity` because those engagements complete faster than traditional multi-CP onboardings. | Integer | Yes |
| `map/sponsorship/registrationURI` | **Optional.** A URI advertising the Sponsor MAP's own onboarding application page — for example a self-service portal, contracted onboarding form, or an HTTPS endpoint accepting a structured registration request. Allows prospective MAPs (and the TAG-published sponsor-registration page) a machine-readable entry point to begin the onboarding conversation. **Omit the field** if the MAP uses non-electronic registration (e.g. email-only, telephone, or the MAP's general `map/contact[]` entries). When omitted, prospective MAPs use `map/contact[]` as the contact channel. | URI | No |
| `map/sponsorship/active[]` | Array of currently open sponsorship engagements. Empty array when idle. | Object Array | Yes |
| `map/sponsorship/active[]/id` | UUIDv4 identifier for this engagement, generated by the Sponsor MAP on acceptance. | String (UUID) | Yes |
| `map/sponsorship/active[]/type` | Type of candidate: `traditional` (multi-CP MAP) or `single-cp` (MAP onboarding with a single CP, e.g. a MAP-of-1 migrating from TOTSCo SforB per [Part 1 §7.4](../part1-framework/07-onboarding.md#74-map-of-1-onboarding-incl-totsco--csf-migration)). **Both types count as `1` against the MAP's `capacity`** — no reduced weighting for `single-cp` despite the typically faster completion. The equal-weighting rule keeps the rotation fair to small MAPs that operate at `capacity: 1`: such a MAP should not be expected to take additional engagements simply because some are MAP-of-1s. | String (enum) | Yes |
| `map/sponsorship/active[]/opened` | Timestamp when the Sponsor MAP accepted the engagement (ISO 8601 UTC). | DateTime | Yes |
| `map/sponsorship/active[]/candidateRef` | **Optional.** A non-identifying reference assigned by the Sponsor MAP for its own coordination — for example a ticket number from the Sponsor's own onboarding system when the MAP is handling several concurrent engagements. Free-form string; intentionally not standardised across MAPs because the value has no consumer outside the issuing MAP's own back-office systems. | String | No |

#### How to Determine the Current Sponsor

Any MAP — including a prospective new MAP — can determine who to contact:

1. Collect all active MAPs' CP Registries (or start with any single MAP's registry)
2. Read `sponsorship.nextSponsor` and `sponsorship.nextSponsorUpdated` from each
3. The entry with the **most recent `nextSponsorUpdated`** is authoritative — that MAP's `nextSponsor` value is the `order` of the designated next Sponsor
4. Check if the designated MAP has capacity: `active.length < capacity`
5. If at capacity, follow that MAP's `nextSponsor` to find the next available MAP in the chain

No consensus algorithm is needed. Only the current Sponsor should be advancing `nextSponsor`, so the most recently updated entry is inherently authoritative.

#### How the Rotation Works

**Step 1 — Assignment at onboarding**

When a new MAP is onboarded, the Sponsor MAP assigns it the next sequential `order` number. This is permanent.

Example network (4 active MAPs):

| MAP | `order` | Status |
|---|---|---|
| Alpha MAP | 1 | ACTIVE |
| Beta MAP | 2 | ACTIVE |
| Gamma MAP | 3 | ACTIVE |
| Delta MAP | 4 | ACTIVE |

**Step 2 — Accepting a sponsorship**

When a candidate contacts the designated Sponsor and the Sponsor accepts:

1. The Sponsor adds an entry to `active[]` with a new UUIDv4 `id`, the `type`, and `opened` timestamp
2. The Sponsor advances `nextSponsor` using: `nextSponsor = (order % totalActiveMAPs) + 1`, skipping any non-ACTIVE MAPs
3. The Sponsor updates `nextSponsorUpdated` to the current time and publishes the updated registry
4. All other MAPs detect the change within one polling cycle (up to 5 minutes)

The queue advances at acceptance — not at completion.

**Step 3 — Completing a sponsorship**

When onboarding completes:

1. The Sponsor removes the engagement entry from `active[]`
2. `nextSponsor` is **not changed** — it was already advanced at acceptance

**Step 4 — Concurrent onboardings (overflow to next MAP)**

If two candidates request sponsorship while Gamma MAP (order 3, capacity 1) is designated:

- Candidate A arrives. Gamma accepts: adds to `active[]`, sets `nextSponsor: 4`, updates `nextSponsorUpdated`
- Gamma is now at capacity. Candidate B arrives. Delta MAP (order 4) is now designated
- Delta accepts Candidate B: adds to `active[]`, sets `nextSponsor: 1`
- Gamma and Delta run their onboardings in parallel

If Gamma has `capacity: 2`, it can accept both Candidate A and Candidate B simultaneously, advancing `nextSponsor: 4` after the second acceptance.

#### Worked Example

**Starting state** — the most recently updated `nextSponsor` is Gamma's (from 2026-05-01), pointing to Delta as the designated next Sponsor. Gamma is therefore the current Sponsor.

```
Alpha MAP:  { order:1, nextSponsor:2, nextSponsorUpdated:"2026-01-10T...", active:[] }
Beta MAP:   { order:2, nextSponsor:3, nextSponsorUpdated:"2026-03-05T...", active:[] }
Gamma MAP:  { order:3, nextSponsor:4, nextSponsorUpdated:"2026-05-01T...", active:[] }  ← current (most recent)
Delta MAP:  { order:4, nextSponsor:1, nextSponsorUpdated:"2025-12-01T...", active:[] }
```

**Scenario: two new MAPs arrive simultaneously**

New MAP "Epsilon" (traditional) and New MAP "Zeta" (single-cp) both request sponsorship.

Gamma (capacity:1) accepts Epsilon:

```
Gamma MAP:  {
  order:3, nextSponsor:4, nextSponsorUpdated:"2026-06-09T10:00:00Z",
  active: [{ id:"abc...", type:"traditional", opened:"2026-06-09T10:00:00Z" }]
}
```

Delta is now designated. Delta (capacity:1) accepts Zeta:

```
Delta MAP:  {
  order:4, nextSponsor:1, nextSponsorUpdated:"2026-06-09T10:03:00Z",
  active: [{ id:"def...", type:"single-cp", opened:"2026-06-09T10:03:00Z" }]
}
```

Both onboardings proceed in parallel. Alpha MAP is designated for the next request.

When Gamma completes Epsilon's onboarding:

```
Gamma MAP:  { order:3, nextSponsor:4, nextSponsorUpdated:"2026-06-09T10:00:00Z", active:[] }
```

`nextSponsor` is unchanged. Epsilon is assigned `order: 5`. Zeta (when completed) is assigned `order: 6`.

#### Handling Edge Cases

**MAP leaves the network or is suspended:**
When advancing `nextSponsor`, skip any MAP whose `map/status` is not `ACTIVE` until finding the next active MAP.

**Designated MAP at capacity:**
A prospective candidate walks the chain: read the designated MAP's `nextSponsor` to find the next in line, and so on, until finding a MAP where `active.length < capacity`.

**Disagreement on nextSponsorUpdated:**
Timestamps are set by the writing MAP — no consensus is needed. If two MAPs somehow update simultaneously (extremely unlikely in a small network), the MAP with the lower `order` number takes precedence.

**Designated MAP receives no candidates for an extended period:**
There is **no maximum hold time** on the designated-sponsor position. A MAP that is designated but receives no candidate simply remains designated until a candidate arrives. The rotation is event-driven (advance on acceptance), so an idle period at the head of the rotation has no operational cost and no fairness concern — the next acceptance still advances the queue normally.

**Prospective MAP finding the Sponsor before joining:**
There are two discovery paths. The **primary** path is the TAG-published sponsor-registration page (URL to be published by the TAG), which lists the current designated Sponsor MAP and their registration contact and is updated whenever the rotation advances. The **fallback** is the in-band Directory bootstrap: the prospective MAP accesses any existing MAP's CP Registry, finds the entry with the most recent `nextSponsorUpdated`, checks capacity, and contacts that MAP's `map/contact[]` entries. The TAG page is the recommended path for human candidates; the Directory bootstrap remains available for automated tooling and as a fallback if the page is briefly unreachable.

#### Why This Works

- **FIFO enforced**: `order` never changes. The formula `(order % n) + 1` is deterministic and wraps correctly
- **No consensus needed**: The most recent `nextSponsorUpdated` is always authoritative — only the current Sponsor should update the pointer
- **Concurrent onboarding**: `active[]` + `capacity` handle multiple simultaneous engagements transparently; overflow falls naturally to the next MAP in the chain
- **Fast queue**: Advancing on acceptance means a long onboarding never blocks the rotation
- **Self-discoverable**: Any MAP can find the current Sponsor and any candidate can find them before even joining
- **Auditable**: `active[]/opened` and `nextSponsorUpdated` provide a clear operational history

#### Design Rules

The rules below govern the operation of the `sponsorship` rotation and the optional-field semantics. They complement the Field Definitions and the Edge Cases handling above, and apply to every MAP that publishes a CP Registry on the CSF.

| Rule | Statement |
|---|---|
| **Capacity bounds** | The minimum permitted `capacity` is **1** — every onboarded MAP MUST be willing to take at least one engagement at a time. There is **no upper bound** on `capacity`; a MAP sets the value to reflect its own operational throughput. A MAP that primarily takes `single-cp` (MAP-of-1) engagements MAY support a higher `capacity` because those engagements complete faster than traditional multi-CP onboardings. |
| **Engagement weighting** | A `traditional` engagement and a `single-cp` engagement each count as **`1`** against the MAP's `capacity`. No reduced weighting is applied to `single-cp`. The equal-weighting rule protects small MAPs operating at `capacity: 1`: such a MAP is not expected to take additional engagements simply because some are MAP-of-1s. |
| **`order` persistence** | A MAP's `order` is permanent. When a MAP withdraws or its `map/status` becomes non-`ACTIVE`, its `order` is **retained for historical audit consistency** and is not reassigned. The rotation skips entries whose `map/status` is not `ACTIVE`. |
| **Designated-position hold time** | There is **no maximum hold time** on the designated-sponsor position. A designated MAP that receives no candidate remains designated until a candidate arrives. The rotation is event-driven — advance occurs on acceptance — so an idle period has no operational cost and no fairness implication. |
| **`candidateRef`** | `active[]/candidateRef` is **OPTIONAL**. When present, it carries a non-identifying reference assigned by the Sponsor MAP for its own coordination — for example a ticket number from the Sponsor's internal onboarding system. The field is free-form and is not standardised across MAPs; consumers outside the issuing MAP MUST NOT rely on its semantics. |
| **`sponsorship.registrationURI`** | `sponsorship.registrationURI` is **OPTIONAL**. When present, it advertises the Sponsor MAP's own onboarding application page (self-service portal, contracted form, or structured registration endpoint), enabling prospective MAPs and the TAG-published sponsor-registration page to deep-link into that MAP's onboarding flow. A MAP that uses non-electronic registration (email, telephone, ticketing) omits the field; prospective MAPs then use the MAP's `map/contact[]` entries as the contact channel. |

The `sponsorship` object — its required fields, its two optional fields, and the rules in this section — forms part of the CP Registry API specification and is implemented by every MAP on the CSF.

---

## 4. Status Model and JSON Status Values

The CSF uses two distinct status fields. Both are JSON string values, both are observable to peer MAPs, and both have specific consequences during testing.

### 4.1 Per-CP Status (CP Registry — `processSupport[].status`)

Defined in the Directory API (see [Part 2 §5](../part2-implementation/05-directory-api.md)).

```JSON
  "processSupport": [
    { "process": "GPLB", "status": "ACTIVE" }
    ]
```

| Value     | Meaning                                                      | Appears in CP-facing Directory?                      | Messages allowed?                                                                  |
| --------- | ------------------------------------------------------------ | ---------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `ACTIVE`  | CP is live and available for production switching            | Yes                                                  | Yes — production messages                                                          |
| `TEST`    | CP is in onboarding or testing                               | **No** — MUST be excluded from CP-facing Directories | Yes, but every message MUST be flagged as test in the JAM audit envelope           |
| `SUSPEND` | CP is suspended (administration, dispute, planned migration) | **No** — MUST be excluded from CP-facing Directories | Outbound only — in-flight orders may transfer away; no new Switch Matches accepted |
> :tick: **Best Practise** requires the Sponsor MAP to ensure that no messages from a new MAP are treated as Production at ever each stage until they sign them off for completion.
### 4.2 Per-MAP Status (CP Registry — `map.status`, optional)

Defined in the Directory API (see [Part 2 §5](../part2-implementation/05-directory-api.md)). Used during MAP onboarding to mark an entire MAP as in test. When `map.status` is `TEST`, every CP under that MAP is treated as test regardless of its own per-CP status.

```JSON
  "map": {
    "name": "newMap",
    "status": "TEST",
    "version": "2026-04-28T10:30:00Z00000",
    ...
  }
```

| Value | Meaning | Effect on receiving MAPs |
|---|---|---|
| `ACTIVE` *(default if absent)* | MAP is fully operational | Normal operations |
| `TEST` | MAP is onboarding | Establish connectivity and pull registries, but exclude all of this MAP's CPs from the CP-facing Directory |
| `SUSPEND` | MAP is suspended (incident, dispute, administration) | Retain the registry data, mark all entries unavailable, queue messages for retry |

### 4.3 Process Status Casing — Compatibility Note

Existing OTS implementations have used both `Active` and `ACTIVE` casings. CSF MAPs MUST emit upper-case status values (`ACTIVE`, `TEST`, `SUSPEND`) and SHOULD parse case-insensitively for backward compatibility with TOTSCo-sourced data.

## 5. Test Traffic Flagging in the JAM Envelope

The envelope schema and the optional nature of `auditData` are defined in [Message API (Letterbox) §4.3 — Message Structure](../part2-implementation/04-message-api.md#43-message-structure); this section only specifies the CSF-wide conventions for **populating** that field during onboarding and bilateral testing.

Every message originated by, or routed on behalf of, a `TEST`-status CP or a `TEST`-status MAP MUST carry an explicit test marker in the JAM envelope's `auditData`:

```json
{
  "envelope": {
    "source": { "type": "RCPID", "identity": "...", "correlationID": "..." },
    "destination": { "type": "RCPID", "identity": "..." },
    "routingID": "businessSwitchMatchRequest",
    "auditData": [
      { "name": "test", "value": "true" }
    ]
  }
}
```

Rules of behaviour during onboarding:

- The **originating MAP** sets `auditData.test=true` whenever it sends on behalf of a CP whose registry status is `TEST` *or* whose own MAP status is `TEST`.
- The **originating MAP overrides** any contradictory flag set by the CP. The CP cannot opt out.
- The **receiving MAP** MUST preserve `auditData.test=true` end-to-end and MUST NOT clear it during forwarding through HMAPs.
- All test traffic MUST be excluded from production reporting, KPIs, and customer notifications.
- A MAP MUST NOT change a CP's status from `TEST` to `ACTIVE` until the exit criteria in [Section 9](#9-exit-criteria) have been satisfied.

> :memo: **Note**: The MAP-overrides-CP rule is what makes "testing in production" safe. Without it, a misconfigured CP could silently inject test traffic into live counterparties.

### 5.1 Telemetry Exclusion

Industry-wide telemetry collection (defined in [Part 1 §10.6.7 — Anti-Competitive Safeguards](../part1-framework/10-governance.md#1067-anti-competitive-safeguards)) is concerned **only** with production traffic between `ACTIVE` participants. To prevent onboarding, bilateral, or soak-test activity from polluting industry KPIs, every MAP MUST exclude a message from telemetry statistics if **any** of the following are true:

| Condition | Source of truth |
|---|---|
| Originating MAP carries `map.status = TEST` or `SUSPEND` | CP Registry — `map.status` |
| Destination MAP carries `map.status = TEST` or `SUSPEND` | CP Registry — `map.status` |
| Source CP carries `processSupport[].status = TEST` or `SUSPEND` for the message's process | CP Registry — `list[].identity[].processSupport[].status` |
| Destination CP carries `processSupport[].status = TEST` or `SUSPEND` for the message's process | CP Registry — `list[].identity[].processSupport[].status` |
| The JAM envelope's `auditData` contains `{ "name": "test", "value": "true" }` | JAM envelope on the message itself |

Any one of these conditions is sufficient to exclude the message from production telemetry. The `auditData.test=true` flag in particular is the **belt-and-braces** signal — it is set by the originating MAP regardless of registry state and survives end-to-end through any HMAP, so a receiving MAP can reliably exclude the message even if its registry view is momentarily stale.

> :tick: **Best Practice**: Test-flagged messages MAY still be retained for audit and onboarding evidence, but MUST be held in a logically separate store from the production telemetry corpus and clearly labelled as test data. They MUST NOT be aggregated into the figures contributed to the TAG's annual industry summary.

#### Sequencing of session and attempt metadata in `auditData`

The `auditData` array is also the place where session-level metadata SHOULD be carried so that production telemetry (once a message is in scope) can be counted by **customer journey** rather than by individual request. A SforB match journey often spans several `businessSwitchMatchRequest` attempts as the GRCP corrects the data; counting each as an independent event structurally inflates the industry's failure rate. The CSF accommodates a three-layer sequencing scheme — **GRCP-originated session IDs (Layer 1)**, **LRCP echo and enrichment (Layer 2)**, and **MAP-mediated session inference (Layer 3)** — using only the existing extensibility of `auditData`. No envelope, JAM, or SforB Message Spec change is required.

Recommended `auditData` field-name prefixes (full registry in [REVIEW.md §6.5](REVIEW.md#65-match-session-sequencing--auditdata-based-telemetry-proposal)):

| Prefix | Owner | Examples |
|---|---|---|
| `match*` | GRCP-originated (Layer 1) | `matchSessionID`, `matchAttemptSeq`, `matchAttemptReason` |
| `serviceMatch*` | GRCP-originated, service-match phase | `serviceMatchAttemptSeq`, `serviceMatchAttemptReason` |
| `lrcp*` | LRCP-originated (Layer 2) | `lrcpMatchAttemptCount` |
| `map*` | MAP-originated (Layer 3) | `mapSessionID`, `mapAttemptSeq`, `mapRetryFlag`, `mapDataChangeFlag`, `mapConcurrentGrcpCount` |

The `auditData.test=true` flag and any session/attempt fields are independent and complementary: the test flag governs **whether** a message is in production telemetry at all (this section); the session fields govern **how** the in-scope messages are aggregated into per-session KPIs ([REVIEW.md §6.5](REVIEW.md#65-match-session-sequencing--auditdata-based-telemetry-proposal)). MAPs implementing Layer 3 unilaterally SHOULD use the field-name prefixes above to avoid colliding with the eventual industry-standard names.

> :link: **Related**: The full proposal — including session-success-rate definitions, GRCP/LRCP/MAP responsibilities, the field-name registry, the SOR boundary rules, privacy treatment of `matchAttemptReason`, and the four-phase implementation strategy — lives in [REVIEW.md §6.5 — Match Session Sequencing](REVIEW.md#65-match-session-sequencing--auditdata-based-telemetry-proposal). The TAG is currently considering whether to advance it as a TOTSCo IPG bulletin or a GPLB-SG SforB Message Spec revision; until adopted, MAPs MAY implement Layer 3 unilaterally as a value-add, using the prefixes above.

## 6. Onboarding Phases

The five phases below align with [Part 1 §7.2.2](../part1-framework/07-onboarding.md). This section defines the gates between them.

### Phase 1 — Sponsor Selection

| Entry criterion | Exit gate |
|---|---|
| MAP candidate has formally requested onboarding via the TAG-published registration page (URL to be published by the TAG) or via the Directory bootstrap mechanism (§3.4) | Sponsor MAP assigned (next Sponsor) and onboarding agreement signed |

> :tick: **Best Practice**: Sponsor MAPs SHOULD publish, or share on first contact, a short summary of their registration process — including what evidence they will request, how they accept it, the channel used to share secrets, and the expected turnaround time. This makes the experience predictable for new entrants and reinforces the CSF principle that onboarding should be operational in days, not months.

> **Sponsor fee — free by default; capped when significant support is needed.** Sponsor MAPs are not expected to charge for onboarding. There is an option to charge a fee when significant time and effort is required, capped at **£3,000 + VAT** for a `traditional` (multi-CP) engagement or **£350 + VAT** for a `single-cp` (MAP-of-1) engagement. The option exists because the CSF expects the Sponsor MAP to apply a quality gate — all checks and baseline testing complete to TAG standards. Fees are charged once per MAP regardless of how many protocols it carries. See [Part 1 §7.2.1](../part1-framework/07-onboarding.md#721-sponsorship-framework) and [Part 1 §7.4.5](../part1-framework/07-onboarding.md#745-sponsor-map-programme--fast-track-for-single-cp) for the full fee model.






### Phase 2 — Initial Assessment & Registration

| Entry criterion | Exit gate |
|---|---|
| Sponsor MAP confirmed; capability self-assessment submitted | Sponsor sign-off on infrastructure plan; OAuth credentials minted with **provisional/restricted** scope; `map.status=TEST`, no CPs published yet |

Once the TAG has provided the new MAP candidate with the contact details of the assigned Sponsor MAP, the Sponsor MAP **may** require the candidate to complete its own formal registration process before any technical engagement begins. This is a normal commercial step and serves two purposes:

1. **Business verification** — confirming the candidate is a legitimate trading entity (company registration, VAT, ICO registration, trading history, fraud and credit checks where applicable). This complements the wider due-diligence expectation already captured in the Risk Register (see [Part 2 §9](../part2-implementation/09-operations.md)) and in [REVIEW.md §4](REVIEW.md).
2. **Secure information exchange** — providing a controlled channel through which sensitive material can be shared in both directions, including (but not limited to):
   - URL endpoints (OAuth token URL, Directory API URL, Letterbox URL, Service Status URL)
   - OAuth client IDs and client secrets
   - API keys, where used between the MAPs
   - PKI public-key fingerprints and DNS verification details
   - Operational and technical contact lists for incident handling

Each Sponsor MAP may have a different method for engaging at this stage — for example a self-service portal, a contracted onboarding form, a secure file-exchange channel, or a structured email-based exchange protected by encryption. These differences are expected and are **not** standardised by the TAG, since the registration process sits outside the technical scope of the CSF and reflects each Sponsor MAP's own commercial, legal, and security policies.

> :memo: **Note**: The candidate MAP should treat the Sponsor MAP's registration process as the gateway to all subsequent phases. Until it is complete, no OAuth credentials will be issued, no test endpoints will be shared, and no technical onboarding work can begin. Candidate MAPs are encouraged to ask the Sponsor MAP for a clear list of registration requirements at first contact, so that any documentation or evidence (e.g. company registration documents, ICO certificates, security policies) can be prepared in parallel.

### Phase 3 — Integration & Testing (the core build phase)

This is the phase in which **most of [Section 8](#8-tag-baseline-test-suite) is exercised**.

| Sub-phase | Tests required | Outcome |
|---|---|---|
| 3a — Connectivity | §8.1 (TLS), §8.2 (OAuth) | Bidirectional authenticated connectivity with the Sponsor MAP |
| 3b — Registry | §8.3 (Directory API) | New MAP can publish, and consume from, valid CP Registries |
| 3c — PKI | §8.4 (DKIM) and §7 (PKI Harness) | Round-trip signing and verification proven against the public NowYoYo verification endpoint |
| 3d — Letterbox | §8.5 (Letterbox API) | End-to-end JAM message exchange with HTTP 202 acknowledgements |
| 3e — Industry process | §8.6 (SforB) | Switch Match, Switch Order, and failure handling flows pass against test CPs |

Exit gate: All baseline tests in §8.1 to §8.6 pass against the Sponsor MAP. Provisional access is upgraded to **test** scope; obfuscated/test-only registry data may now be exchanged.

### Phase 4 — Operational Validation

| Entry criterion | Exit gate |
|---|---|
| Phase 3 sign-off | §8.7 (retry & delivery policy), §8.8 (conflict resolution), §8.9 (security/negative) all pass; SLA evidence collected over a continuous **72-hour** soak; bilateral connectivity tests with at least 50% of other MAPs completed |

> **MAP-of-1 variant (`single-cp` engagements).** For a candidate onboarding as a [MAP of 1](../part1-framework/04-architecture-overview.md) — tracked as `type: "single-cp"` in `sponsorship.active[]` (see §3.4) — the Phase 4 exit gate is modified per [Part 1 §7.4.9](../part1-framework/07-onboarding.md#749-phase-4-relaxation-for-map-of-1):
>
> - **72-hour soak waived** — a single-CP MAP has no meaningful production-scale traffic to soak, and the CP carries the operational risk directly.
> - **Bilateral ≥50% waived** — required only with the MAPs / HMAPs (including TOTSCo as HMAP) the candidate intends to exchange with at go-live.
> - §8.7 / §8.8 / §8.9 — **required, unchanged**.
> - §8.10 V-02 – V-05 — **required**; V-01 (72-hour soak) — **waived**.
>
> The Sponsor MAP records the waivers in the Phase 5 approval record.

### Phase 5 — Full Operations

| Entry criterion | Exit gate |
|---|---|
| Phase 4 sign-off | Sponsor MAP issues formal approval; `map.status` removed (or set to `ACTIVE`); first CP transitioned from `TEST` to `ACTIVE` only after passing the per-CP exit checks; TAG notified |

## 7. PKI Test Harness (NowYoYo)

PKI/DKIM signing is consistently the area that takes the longest to get right. To shorten that learning curve, NowYoYo Limited operates a free, public verification harness. It accepts CSF-formatted POSTs, verifies the signature using the published DNS keys, and either logs every step of the verification (the `.txt` endpoint) or returns a production-style response (the `.json` endpoint).

The harness uses two test CPs whose public keys live under `gplb-test.nowyoyo.net`:

| RCPID | Algorithm |
|---|---|
| `809b6e65-a6e7-40f6-8b52-04dd65b6fce1` | RSA-SHA256 (3072-bit) |
| `a73f0493-fd1f-4b5b-826c-1d6243d43365` | Ed25519-SHA256 |

> :bulb: Use the harness early and often. Most onboarding incidents are caught by the harness within minutes; missing them locally results in days of debugging against a live Sponsor MAP.


### Verification Testing Endpoint

There are 2 endpoints set up for testing.

`https://sec-key-test.csf.nowyoyo.net/csf/T/letterbox/2.0/post.txt`

Used in 1st-stage development. Success returns 200, failure gives 403.
The response body is a timed text log of all the detailed steps that the system used to verify the message. In the case of failure this should be valuable in identifying where things are going wrong.

`https://sec-key-test.csf.nowyoyo.net/csf/T/letterbox/2.0/post.json`

Used to submit messages to a *production-like* CSF endpoint. The errors are formatted in the same way as production CSF messages, success is **204**, failure is **403**. No further processing on the messages is performed.

> **Note**: These endpoints accept signed POST requests only. They are intentionally not browsable in a web browser — wrap them as `code` in any reference and use the `curl` examples below to exercise them.

### Examples

#### RSA — Success (200) at the `.txt` endpoint

```shell
curl -X "POST" -i "https://sec-key-test.csf.nowyoyo.net/csf/T/letterbox/2.0/post.txt" \
     -H 'X-CSF-SIGNATURE-DATESTAMP: 202412121340391' \
     -H 'X-CSF-SIGNATURE: a=rsa-sha256; q=dns/txt; c=simple/simple; s=809b6e65-a6e7-40f6-8b52-04dd65b6fce1; d=gplb-test.nowyoyo.net; v=1; h=X-CSF-SIGNATURE-DATESTAMP; bh=LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=; b=h3YO8jlz2Gup/szefoH3RxO46IJmnk+8GJ4pxxRyfh+UJ0mZym64o0izGNG1x3GlmMiuHfZc5xP9xIANP+OIajX1udhTGuZkSfUU3L4MrvT/PYkAEfIMm7lFeCWPPgOr0bNIVjNVBrB41akN1iwcQjK0gD+/nUGLVXihdOFCj0xSRIypLPKPxXsZED6sfiBSlEiZcGsF9FCiV5OQQAwRoRhFQE4cnn00QE/S3NoN8/lX7i8OXIaTab6p/HCMIjirM6VBKrc+O0HEdekXn1DP7s0cwp/LkeXTWVloj+vkZREHqNkdIZGeab7N8vFgLrXmYJoR2LM+LQtqhq01KRVSY4hDWdbjD4cSJWkV9P/GWelFiuSNtYmWqTcrT0kqN7pMwVq1Ht4iuhrxH7h2hnCjYG12bEYiHPmarO+JJCPx5RawO0t2BRBxNfEXbp6boTQzZ//MrdW4mUDEyCQhs6s/OHd5x14Z2DJUTYRhugLwGkwSaVjt44y6X9RnB5d9j/Py;' \
     -d "hello"
```

```text
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
Content-Type: application/json
Content-Length: 5818
Date: Tue, 09 Sep 2025 17:07:39 GMT

0ms : Request Received
0ms : Checking Initial CSF MandatoryHeaders [X-CSF-SIGNATURE, X-CSF-SIGNATURE-DATESTAMP]
0ms : Parsing Header Signature: a=rsa-sha256; q=dns/txt; c=simple/simple; s=809b6e65-a6e7-40f6-8b52-04dd65b6fce1; d=gplb-test.nowyoyo.net; v=1; h=X-CSF-SIGNATURE-DATESTAMP; bh=LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=; b=h3YO8jlz2Gup/szefoH3RxO46IJmnk+8GJ4pxxRyfh+UJ0mZym64o0izGNG1x3GlmMiuHfZc5xP9xIANP+OIajX1udhTGuZkSfUU3L4MrvT/PYkAEfIMm7lFeCWPPgOr0bNIVjNVBrB41akN1iwcQjK0gD+/nUGLVXihdOFCj0xSRIypLPKPxXsZED6sfiBSlEiZcGsF9FCiV5OQQAwRoRhFQE4cnn00QE/S3NoN8/lX7i8OXIaTab6p/HCMIjirM6VBKrc+O0HEdekXn1DP7s0cwp/LkeXTWVloj+vkZREHqNkdIZGeab7N8vFgLrXmYJoR2LM+LQtqhq01KRVSY4hDWdbjD4cSJWkV9P/GWelFiuSNtYmWqTcrT0kqN7pMwVq1Ht4iuhrxH7h2hnCjYG12bEYiHPmarO+JJCPx5RawO0t2BRBxNfEXbp6boTQzZ//MrdW4mUDEyCQhs6s/OHd5x14Z2DJUTYRhugLwGkwSaVjt44y6X9RnB5d9j/Py
0ms : Tags present are :
	a : rsa-sha256
	q : dns/txt
	c : simple/simple
	s : 809b6e65-a6e7-40f6-8b52-04dd65b6fce1
	d : gplb-test.nowyoyo.net
	v : 1
	h : X-CSF-SIGNATURE-DATESTAMP
	bh : LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=
	b : h3YO8jlz2Gup/szefoH3RxO46IJmnk+8GJ4pxxRyfh+UJ0mZym64o0izGNG1x3GlmMiuHfZc5xP9xIANP+OIajX1udhTGuZkSfUU3L4MrvT/PYkAEfIMm7lFeCWPPgOr0bNIVjNVBrB41akN1iwcQjK0gD+/nUGLVXihdOFCj0xSRIypLPKPxXsZED6sfiBSlEiZcGsF9FCiV5OQQAwRoRhFQE4cnn00QE/S3NoN8/lX7i8OXIaTab6p/HCMIjirM6VBKrc+O0HEdekXn1DP7s0cwp/LkeXTWVloj+vkZREHqNkdIZGeab7N8vFgLrXmYJoR2LM+LQtqhq01KRVSY4hDWdbjD4cSJWkV9P/GWelFiuSNtYmWqTcrT0kqN7pMwVq1Ht4iuhrxH7h2hnCjYG12bEYiHPmarO+JJCPx5RawO0t2BRBxNfEXbp6boTQzZ//MrdW4mUDEyCQhs6s/OHd5x14Z2DJUTYRhugLwGkwSaVjt44y6X9RnB5d9j/Py
0ms : Using the following Domain for Public Key Lookup: 809b6e65-a6e7-40f6-8b52-04dd65b6fce1._domainkey.gplb-test.nowyoyo.net
0ms : Received the following records:
	k=rsa; p=MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA6/RekuGAsET2Ir8E4K5BU74MBXebo7QwrOnSGirtNgRLDcwP2R84Eeum9BLxZtiumUrmO6ja7DrteSZhzOyC09KM3b22zV43bm7nv/FF40+KpTDQ/FZ32W838UIZGgHOyICk37hlPvnGrVPg7rT6HAKsaj5W0zbvO5vKDUOz2r3bvrWo79YSxtVfGQ1pHhhZYV2dL1C0uiADbz5L1Cu0qCBjqGJQbYq4ALdaKTnAm2NG/N9W2i3IWJUQIyiTr11qtBrrR8sj2YhYeph6GTsOXggpAWZnEHZz+LKzD4roVqUxkByLWRLB2I5Ju/xPPtTjCDakfvGlsiWl5BMAhn79p0mW6hzcA4DT7Iz+nSV8K3XMU4rxs0AcaafcBsKREmfbGz4K3ycWHZ/Yrxb+PqYHy7NSK76mHsAjuU8nuKYtZGIYHwT4friS2R3HtXJ+olLmdZ+XnkpdvEGDb+ZVv/G9vYp020LZQBBd0hp1Ez8a/3F7jRt8xUJ0ljstWjbq+m3bAgMBAAE=
0ms : Have Public Key of RSA type and X.509 format.
0ms : RSA Key length is determined as 3072 bits.
0ms : Body is 5 bytes.
	[104, 101, 108, 108, 111]
0ms : Hashing Algorithm is specified as : SHA256
0ms : SHA256Hash of 5 byte body is : LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=
0ms : Calculated hash and Signature hash are a match
0ms : Starting Signature Generation
0ms : h tag requests the following fields for the signature: [X-CSF-SIGNATURE-DATESTAMP]
0ms : Header 'X-CSF-SIGNATURE-DATESTAMP' contains 202412121340391
	Adding Header Value : [50, 48, 50, 52, 49, 50, 49, 50, 49, 51, 52, 48, 51, 57, 49]
	Adding CRLF : [13, 10]
0ms : Using the following as the signature line with an empty b tag
	a=rsa-sha256; q=dns/txt; c=simple/simple; s=809b6e65-a6e7-40f6-8b52-04dd65b6fce1; d=gplb-test.nowyoyo.net; v=1; h=X-CSF-SIGNATURE-DATESTAMP; bh=LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=; b=;
0ms : Prepending 'DKIM-Signature: '
	DKIM-Signature: a=rsa-sha256; q=dns/txt; c=simple/simple; s=809b6e65-a6e7-40f6-8b52-04dd65b6fce1; d=gplb-test.nowyoyo.net; v=1; h=X-CSF-SIGNATURE-DATESTAMP; bh=LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=; b=;
0ms : Proceeding with Signature Generation and Verification
1ms : Signature verification successful!
1ms : Result = Pass. Well done!
```

#### RSA — Success (204) at the `.json` endpoint

```shell
curl -X "POST" -i "https://sec-key-test.csf.nowyoyo.net/csf/T/letterbox/2.0/post.json" \
     -H 'X-CSF-SIGNATURE-DATESTAMP: 202412121340391' \
     -H 'X-CSF-SIGNATURE: a=rsa-sha256; q=dns/txt; c=simple/simple; s=809b6e65-a6e7-40f6-8b52-04dd65b6fce1; d=gplb-test.nowyoyo.net; v=1; h=X-CSF-SIGNATURE-DATESTAMP; bh=LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=; b=h3YO8jlz2Gup/szefoH3RxO46IJmnk+8GJ4pxxRyfh+UJ0mZym64o0izGNG1x3GlmMiuHfZc5xP9xIANP+OIajX1udhTGuZkSfUU3L4MrvT/PYkAEfIMm7lFeCWPPgOr0bNIVjNVBrB41akN1iwcQjK0gD+/nUGLVXihdOFCj0xSRIypLPKPxXsZED6sfiBSlEiZcGsF9FCiV5OQQAwRoRhFQE4cnn00QE/S3NoN8/lX7i8OXIaTab6p/HCMIjirM6VBKrc+O0HEdekXn1DP7s0cwp/LkeXTWVloj+vkZREHqNkdIZGeab7N8vFgLrXmYJoR2LM+LQtqhq01KRVSY4hDWdbjD4cSJWkV9P/GWelFiuSNtYmWqTcrT0kqN7pMwVq1Ht4iuhrxH7h2hnCjYG12bEYiHPmarO+JJCPx5RawO0t2BRBxNfEXbp6boTQzZ//MrdW4mUDEyCQhs6s/OHd5x14Z2DJUTYRhugLwGkwSaVjt44y6X9RnB5d9j/Py;' \
     -d "hello"
```

```text
HTTP/1.1 204
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
Date: Tue, 09 Sep 2025 16:41:31 GMT
```

#### RSA — Failure (403, body hash mismatch) at the `.json` endpoint

```shell
curl -X "POST" -i "https://sec-key-test.csf.nowyoyo.net/csf/T/letterbox/2.0/post.json" \
     -H 'X-CSF-SIGNATURE-DATESTAMP: 202412121340391' \
     -H 'X-CSF-SIGNATURE: a=rsa-sha256; q=dns/txt; c=simple/simple; s=809b6e65-a6e7-40f6-8b52-04dd65b6fce1; d=gplb-test.nowyoyo.net; v=1; h=X-CSF-SIGNATURE-DATESTAMP; bh=LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=; b=h3YO8jlz2Gup/szefoH3RxO46IJmnk+8GJ4pxxRyfh+UJ0mZym64o0izGNG1x3GlmMiuHfZc5xP9xIANP+OIajX1udhTGuZkSfUU3L4MrvT/PYkAEfIMm7lFeCWPPgOr0bNIVjNVBrB41akN1iwcQjK0gD+/nUGLVXihdOFCj0xSRIypLPKPxXsZED6sfiBSlEiZcGsF9FCiV5OQQAwRoRhFQE4cnn00QE/S3NoN8/lX7i8OXIaTab6p/HCMIjirM6VBKrc+O0HEdekXn1DP7s0cwp/LkeXTWVloj+vkZREHqNkdIZGeab7N8vFgLrXmYJoR2LM+LQtqhq01KRVSY4hDWdbjD4cSJWkV9P/GWelFiuSNtYmWqTcrT0kqN7pMwVq1Ht4iuhrxH7h2hnCjYG12bEYiHPmarO+JJCPx5RawO0t2BRBxNfEXbp6boTQzZ//MrdW4mUDEyCQhs6s/OHd5x14Z2DJUTYRhugLwGkwSaVjt44y6X9RnB5d9j/Py;' \
     -d "hellox"
```

```json
{
  "errorText": "Body hash mismatch, calculated Gmu6Qx+5NH5pfydVjH9xKhOJfJX82h5QuFOpeF1zLTc= and from signature LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=",
  "errorCode": "8101"
}
```

#### Ed25519 — Success (200) at the `.txt` endpoint

```shell
curl -X "POST" "https://sec-key-test.csf.nowyoyo.net/csf/T/letterbox/2.0/post.txt" \
     -H 'X-CSF-SIGNATURE-DATESTAMP: 202412121340391' \
     -H 'X-CSF-SIGNATURE: a=ed25519-sha256; q=dns/txt; c=simple/simple; s=a73f0493-fd1f-4b5b-826c-1d6243d43365; d=gplb-test.nowyoyo.net; v=1; h=X-CSF-SIGNATURE-DATESTAMP; bh=LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=; b=LiRX0EqzkcSD4nZwRHlPhtQge4vp3Kssh9Z/Ov4dAl3PHmrhOthDTMs3RoTUSnjPgweoY+wXsCDOPiOGcqAxCQ==;' \
     -d "hello"
```

```text
0ms : Request Received
0ms : Checking Initial CSF MandatoryHeaders [X-CSF-SIGNATURE, X-CSF-SIGNATURE-DATESTAMP]
0ms : Parsing Header Signature: a=ed25519-sha256; q=dns/txt; c=simple/simple; s=a73f0493-fd1f-4b5b-826c-1d6243d43365; d=gplb-test.nowyoyo.net; v=1; h=X-CSF-SIGNATURE-DATESTAMP; bh=LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=; b=LiRX0EqzkcSD4nZwRHlPhtQge4vp3Kssh9Z/Ov4dAl3PHmrhOthDTMs3RoTUSnjPgweoY+wXsCDOPiOGcqAxCQ==
0ms : Using the following Domain for Public Key Lookup: a73f0493-fd1f-4b5b-826c-1d6243d43365._domainkey.gplb-test.nowyoyo.net
0ms : Received the following records:
	k=ed25519; p=MCowBQYDK2VwAyEAqiCrokPReIFI1h4Jdp7RCRRVQ4Fltdyn429O4H2jYog=
0ms : Have Public Key of EdDSA type and X.509 format.
0ms : EdRSA Key length is 32 bytes
0ms : Body is 5 bytes.
0ms : SHA256Hash of 5 byte body is : LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=
0ms : Calculated hash and Signature hash are a match
0ms : Proceeding with Signature Generation and Verification
1ms : Signature verification successful!
1ms : Result = Pass. Well done!
```

#### Ed25519 — Failure (403, body hash) at the `.txt` endpoint

```shell
curl -X "POST" "https://sec-key-test.csf.nowyoyo.net/csf/T/letterbox/2.0/post.txt" \
     -H 'X-CSF-SIGNATURE-DATESTAMP: 202412121340391' \
     -H 'X-CSF-SIGNATURE: a=ed25519-sha256; q=dns/txt; c=simple/simple; s=a73f0493-fd1f-4b5b-826c-1d6243d43365; d=gplb-test.nowyoyo.net; v=1; h=X-CSF-SIGNATURE-DATESTAMP; bh=LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=; b=LiRX0EqzkcSD4nZwRHlPhtQge4vp3Kssh9Z/Ov4dAl3PHmrhOthDTMs3RoTUSnjPgweoY+wXsCDOPiOGcqAxCQ==;' \
     -d "hellox"
```

```text
0ms : Request Received
...
44ms : Have Public Key of EdDSA type and X.509 format.
44ms : EdRSA Key length is 32 bytes
45ms : Body is 6 bytes.
45ms : SHA256Hash of 6 byte body is : Gmu6Qx+5NH5pfydVjH9xKhOJfJX82h5QuFOpeF1zLTc=
45ms : Calculated hash and Signature hash (bh) are **not** a match
	Calculated hash is Gmu6Qx+5NH5pfydVjH9xKhOJfJX82h5QuFOpeF1zLTc=
	Signature hash (bh) is LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=
53ms : Result = Fail.
```

#### Ed25519 — Failure (403, signature mismatch via altered datestamp) at the `.txt` endpoint

```shell
curl -X "POST" "https://sec-key-test.csf.nowyoyo.net/csf/T/letterbox/2.0/post.txt" \
     -H 'X-CSF-SIGNATURE-DATESTAMP: 202412121340392' \
     -H 'X-CSF-SIGNATURE: a=ed25519-sha256; q=dns/txt; c=simple/simple; s=a73f0493-fd1f-4b5b-826c-1d6243d43365; d=gplb-test.nowyoyo.net; v=1; h=X-CSF-SIGNATURE-DATESTAMP; bh=LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=; b=LiRX0EqzkcSD4nZwRHlPhtQge4vp3Kssh9Z/Ov4dAl3PHmrhOthDTMs3RoTUSnjPgweoY+wXsCDOPiOGcqAxCQ==;' \
     -d "hello"
```

```text
0ms : Request Received
...
1ms : Calculated hash and Signature hash are a match
1ms : Starting Signature Generation
1ms : h tag requests the following fields for the signature: [X-CSF-SIGNATURE-DATESTAMP]
1ms : Header 'X-CSF-SIGNATURE-DATESTAMP' contains 202412121340392
1ms : Proceeding with Signature Generation and Verification
3ms : Signature verification has failed!
5ms : Result = Fail.
```

> :memo: **Note**: The signature failure example above demonstrates replay-attack protection in action. Changing only the `X-CSF-SIGNATURE-DATESTAMP` (without re-signing) breaks verification, exactly as the CSF requires.


## 8. TAG Baseline Test Suite

The TAG mandates the following baseline tests for every new MAP. Each test has an outcome classification:

- **P** — Positive (the happy path; behaviour MUST succeed)
- **N** — Negative (the system MUST reject and return the specified error)
- **E** — Edge case (boundary, race, or non-obvious behaviour the system MUST handle correctly)

This suite is significantly broader than the current TOTSCo onboarding because:

1. There is **no central validator** — every MAP must independently demonstrate correctness.
2. The CSF currently combines **two industry processes** (CSF transport and SforB business process), and tests must cover the interaction between them.
3. **PKI verification is the principal trust mechanism** — it must be exercised exhaustively.
4. **Conflict resolution** during CP-MAP transitions is unique to the CSF and needs explicit coverage.

### Scope and Extensibility — Other Industry Protocols

While this document currently codifies baseline tests for the **CSF transport** itself (Sections 8.1 to 8.5, 8.7 to 8.10) and the **SforB industry process** (Section 8.6), the CSF transport is process-agnostic by design. Other industry protocols that ride over the same transport — for example **OTS Port** (number porting), future bilateral notification flows, or any new process the TAG agrees to host — are equally in scope of this onboarding regime.

As each additional protocol is brought onto the CSF, the TAG MUST add a corresponding **§8.x baseline test set** for that protocol, alongside §8.6 (SforB). MAPs and CPs that elect to support that protocol are then required to pass the relevant §8.x set as part of their onboarding (or as a delta if they are already onboarded for other processes). MAPs that do not support the protocol are not required to run those tests.

This means the onboarding suite is intentionally **extensible at any point**:

- New protocol → new §8.x section, with its own positive, negative, and edge-case tests.
- Operational learning on an existing protocol → new test cases appended to that protocol's section, with the date of TAG adoption recorded.
- Cross-protocol interactions (e.g. a CP supporting both SforB and OTS Port simultaneously, or a CP transitioning between processes) → tests added to the most relevant section, with cross-references where useful.

> :memo: **Note**: A MAP supporting multiple industry protocols on the CSF MUST evidence onboarding for **each** protocol it carries. Sponsor MAPs SHOULD verify this scope explicitly during Phase 1, so the candidate is clear which §8.x sets apply to its proposed footprint. See [REVIEW.md §1.9](REVIEW.md) for the open action covering ongoing baseline expansion.

### 8.1 Connectivity & Transport (TLS 1.3)

| ID | Type | Test | Expected outcome |
|---|---|---|---|
| C-01 | P | TLS 1.3 handshake against each of the four required endpoints | Successful handshake with TLS 1.3 negotiated |
| C-02 | P | Valid certificate chain to a trusted CA | Trust path validates without warnings |
| C-03 | N | Connect using TLS 1.2 | Connection rejected by the new MAP |
| C-04 | N | Connect using a self-signed or expired certificate | Connection refused; alert raised |
| C-05 | N | Attempt cleartext HTTP on any CSF endpoint | Connection refused or auto-redirected to HTTPS |
| C-06 | E | SNI requests for an unrelated host on the same IP | Server returns a certificate matching the requested host or refuses, never the wrong cert |
| C-07 | E | Long-lived idle connection across keepalive timeout | Reconnect succeeds without leaking session state |

### 8.2 OAuth 2.0 (Client Credentials)

| ID | Type | Test | Expected outcome |
|---|---|---|---|
| O-01 | P | Token issued for a registered client with valid `client_secret` | HTTP 200 with bounded-lifetime bearer token |
| O-02 | P | Token used to call the Letterbox API | API accepts the token (subject to other validations) |
| O-03 | N | Invalid `client_secret` | HTTP 401, no token |
| O-04 | N | Expired token used on Letterbox API | HTTP 401 |
| O-05 | N | Token issued by a different MAP's authorisation server | HTTP 401 (issuer/audience mismatch) |
| O-06 | N | Token presented over HTTP (no TLS) | Connection refused before token is parsed |
| O-07 | E | Two concurrent token refreshes | No race; both tokens valid, no token reuse beyond expiry |
| O-08 | E | Clock skew of ±60 seconds between MAPs | Token still validates within the configured tolerance |
| O-09 | E | Client secret rotation under load | Old secret stops working only after grace period; no production impact |

### 8.3 CP Registry & Directory API

| ID | Type | Test | Expected outcome |
|---|---|---|---|
| D-01 | P | Publish a registry containing one CP with `processSupport.status=TEST` | Registry validates against schema; peers can pull |
| D-02 | P | Update `map.version` timestamp on every change | Peers detect change and re-pull |
| D-03 | P | Pull peer registry and merge into Master Registry | Master Registry contains the union; conflicts marked |
| D-04 | N | Publish registry missing a mandatory field (e.g. `map.contact`) | Peer fails schema validation; new MAP is notified |
| D-05 | N | Publish a CP with malformed RCPID (not UUIDv4) | Peer rejects the CP entry |
| D-06 | N | Pull registry with stale OAuth token | HTTP 401; fall back to re-authentication |
| D-07 | E | Two CPs with identical brand names across different MAPs | Both appear in Directory; RCPID disambiguates |
| D-08 | E | Registry pulled at maximum recommended frequency (~60s) over 24h | No throttling; consistent freshness |
| D-09 | E | A CP appears with `status=TEST` in one registry and `status=ACTIVE` in another (rare conflict) | Receiving MAP excludes from its CP-facing Directory and raises an alert |
| D-10 | E | Registry contains a CP whose `_mapkey` DNS record points to a different MAP | Conflict-resolved per [§5.5](../part1-framework/05-directory-and-registry.md); DNS wins |
| D-11 | E | TEST CPs MUST NOT appear in the CP-facing Directory shared with the new MAP's own CPs | Directory excludes all `TEST` and `SUSPEND` entries |

### 8.4 PKI / DKIM Signing

These are the highest-stakes tests. Every test below MUST be runnable against the [NowYoYo PKI Test Harness](#7-pki-test-harness-nowyoyo) before being repeated against the Sponsor MAP.

| ID | Type | Test | Expected outcome |
|---|---|---|---|
| K-01 | P | Sign + verify round-trip with RSA-SHA256 (2048-bit) | Verification passes |
| K-02 | P | Sign + verify round-trip with RSA-SHA256 (3072-bit) | Verification passes |
| K-03 | P | Sign + verify round-trip with Ed25519-SHA256 | Verification passes |
| K-04 | P | Sign + verify round-trip with RSA-SHA512 | Verification passes |
| K-05 | P | Verify against the public NowYoYo `.txt` endpoint | HTTP 200 with successful log |
| K-06 | P | Verify against the public NowYoYo `.json` endpoint | HTTP 204 |
| K-07 | N | Mutate one byte of the body after signing | HTTP 403, error code `8101`, body hash mismatch |
| K-08 | N | Mutate the `X-CSF-SIGNATURE-DATESTAMP` after signing | HTTP 403, error code `8101`, signature verification failure |
| K-09 | N | Submit RSA-1024 key (below minimum) | Verifier rejects with `8101` (key too short) |
| K-10 | N | `d=` in signature does not match the directory entry for the CP | HTTP 403, error code `8101` (domain not valid for CP) |
| K-11 | N | Missing mandatory tag (`b`, `bh`, `s`, `d`, `a`, `c`, `h`) | HTTP 403, error code `8101`, listing missing tag(s) |
| K-12 | N | DNS TXT record absent for the CP `_domainkey` | HTTP 403, error code `8102` (TEMP_FAIL) |
| K-13 | N | DNS TXT record present but malformed (`k=`/`p=` invalid) | HTTP 403, error code `8102` |
| K-14 | N | Signature uses SHA-1 (deprecated) | HTTP 403, error code `8101` |
| K-15 | N | Header casing wrong (`x-csf-signature-datestamp` lower-case in signature input) | Verification fails — surfaces case-sensitivity bug |
| K-16 | E | DKIM-Signature canonicalisation has trailing whitespace | Verifier still passes *only* if `c=simple/simple` is honoured exactly |
| K-17 | E | Body containing only whitespace, then signed | Verification passes — empty hash matches |
| K-18 | E | Replay the same signed message after 10 minutes | Receiver MAY accept or reject by configured window; behaviour MUST be documented |
| K-19 | E | Replay the same signed message after 24 hours | Receiver MUST reject (replay window exceeded) |
| K-20 | E | CP rotates DNS public key during onboarding | Old signatures fail; new signatures pass within DNS TTL + handling buffer |

### 8.5 Letterbox API (Message Send/Receive)

| ID | Type | Test | Expected outcome |
|---|---|---|---|
| L-01 | P | POST a valid signed JAM message to the new MAP's letterbox | HTTP 202, no body |
| L-02 | P | POST to the new MAP for a CP it serves | Message delivered to that CP via the MAP's CP integration |
| L-03 | N | POST without `Authorization` header | HTTP 401 |
| L-04 | N | POST with malformed JSON body | HTTP 400 |
| L-05 | N | POST with envelope missing `source.identity` | HTTP 400, error code `9003` |
| L-06 | N | POST to a destination RCPID not in the new MAP's registry | HTTP 400, error code `9001` |
| L-07 | N | POST with `routingID` not supported by destination | HTTP 400, error code `9012` |
| L-08 | N | Destination CP `status=SUSPEND` for the requested process | HTTP 403, error code `9001` |
| L-09 | N | Source CP authorisation does not match originating MAP | HTTP 401, error code `9004` |
| L-10 | N | Send a message that is unsigned (missing `X-CSF-SIGNATURE`) | HTTP 403, error code `8101` |
| L-11 | N | Send a message via GET instead of POST | HTTP 405 |
| L-12 | E | Send a message of 100KB body | HTTP 202; no truncation |
| L-13 | E | Send a message of 1MB body | HTTP 202 if within published limits, otherwise documented rejection |
| L-14 | E | Send a message with `auditData.test=true` to a `status=ACTIVE` CP | Receiver routes but excludes from production reporting |
| L-15 | E | Burst of 100 messages in 10 seconds | All accepted, or rate-limited with HTTP 429 + `Retry-After` |
| L-16 | E | Same `correlationID` used twice within the retry window | Receiver MUST treat as idempotent (no duplicate downstream delivery) |

### 8.6 SforB Industry Process Tests

This onboarding and testing process document is **v1.0** of the TAG Baseline Test Suite — published by the TAG as a standalone supporting document for the CSF and for onboarding activities. The baseline is expected to iterate over time as additional tests are developed to support the Switching for Business protocol and as MAPs progress through onboarding and industry testing. The aim is to capture the lessons learnt during operation and to **raise the quality bar of entry** to the CSF network. The SforB industry-process tests below represent the agreed v1.0 baseline, which the TAG will evolve in subsequent revisions.

This baseline is also expected to **grow continuously** as the network goes live and operational experience surfaces additional cases (defects, near-misses, edge conditions, regulatory changes, GPLB-SG process refinements). Each new case agreed by the TAG MUST be added to this section and to the regression suite in [Section 11](#11-regression-suite-for-ongoing-compliance), with the date of adoption recorded. See [REVIEW.md §1.9](REVIEW.md) for the open action.

These tests verify the **interaction** between CSF transport and the SforB business process. The TAG's published test data assets and the Sponsor MAP's Virtual Test Host are used as counterparties.

| ID   | Type | Test                                                                                                          | Expected outcome                                                                       |
| ---- | ---- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| S-01 | P    | Send `businessSwitchMatchRequest` for a known test CP with valid asset data                                   | Receive `businessSwitchMatchResponse` with `match=true`                                |
| S-02 | P    | Send `businessSwitchOrderRequest` after a successful match                                                    | Receive `businessSwitchOrderResponse` with order accepted                              |
| S-03 | P    | Receive a `businessSwitchMatchRequest` and respond using cached customer data (without CP being online)       | Response within published SLA                                                          |
| S-04 | P    | Cancel an in-flight order before completion                                                                   | Cancellation acknowledged; downstream parties informed                                 |
| S-05 | N    | Send `businessSwitchMatchRequest` against a CP with `status=SUSPEND`                                          | Match request rejected with appropriate code                                           |
| S-06 | N    | Send `businessSwitchOrderRequest` without a preceding match                                                   | Order rejected with appropriate code                                                   |
| S-07 | N    | `businessSwitchMatchRequest` with corrupted asset data                                                        | Match rejected with the correct **SforB Response Code**                                  |
| S-08 | N    | Order request with destination CP whose `_mapkey` DNS record points to a different MAP                        | Sender re-routes via DNS; receiver rejects misrouted attempt                           |
| S-09 | E    | Match request crossing midnight (timestamp window edge)                                                       | No false rejections due to date rollover                                               |
| S-10 | E    | Match request to a CP that has just transitioned MAP (within DNS TTL)                                         | Either old or new MAP responds; conflict resolved by DNS                               |
| S-11 | E    | Concurrent matches and orders on the same business asset                                                      | SforB process tie-breaking rules apply (as defined by the GPLB-SG); no double-allocation |
| S-12 | E    | Test traffic (`auditData.test=true`) MUST NOT generate customer notifications, billing events, or KPI updates | Verified in receiving MAP's downstream reporting                                       |
| S-13 | E    | A CP appears in registries for OTS *and* SforB with different statuses                                          | Each process evaluated independently                                                   |
| S-14 | E    | Switch Match for a CP linked across OTS and SforB RCPIDs (R.26)                                                 | Linkage resolved if implemented; degrades gracefully if not                            |

### 8.7 Retry & Delivery Policy

This test set exercises the CSF MAP-to-MAP delivery policy ratified in [Part 2 §4.7](../part2-implementation/04-message-api.md#47-message-delivery-policy). Tests **R-01 to R-09** are behavioural (the original baseline) and validate response handling, error-code emission, and circuit-breaker activation. Tests **R-10 to R-17** were added when §4.7 was ratified and validate the concrete numerical values now codified in the framework — connection / response timeouts, exponential-backoff shape, and circuit-breaker activation / recovery thresholds.

The HMAP boundary is **out of scope** of this test set — see the standalone [TOTSCo HMAP Integration Guide §7](./totsco-hmap-integration-guide.md) for HMAP-specific tests. The R-tests below all assume CSF MAP-to-MAP P2P traffic.

| ID | Type | Test | Expected outcome |
|---|---|---|---|
| R-01 | P | Receiving MAP returns HTTP 503 with `Retry-After: 30` | Sender retries no sooner than 30s |
| R-02 | P | Receiving MAP returns HTTP 429 with `Retry-After` | Sender backs off respecting header |
| R-03 | P | Transient network failure mid-POST | Sender retries with exponential backoff and jitter |
| R-04 | N | Receiving MAP returns HTTP 400 | Sender does **not** retry; raises `9006` delivery failure |
| R-05 | N | Receiving MAP returns HTTP 404 | Sender does **not** retry; raises `9007` delivery failure |
| R-06 | N | Sender exceeds maximum retry window | Sender raises `9008` and stops |
| R-07 | E | Circuit breaker opens after N consecutive failures | Subsequent sends fail fast and alert is raised |
| R-08 | E | Circuit breaker closes after recovery period | Normal sending resumes |
| R-09 | E | Long-lived 8102 (DNS) failure | Sender retries with backoff up to maximum window, then fails the message |
| **R-10** | **P** | **Connection timeout — sender attempts delivery to a destination IP that drops the SYN.** Measure the elapsed time before the sender treats the attempt as failed. | Attempt fails at **1 second ± 200 ms**. Sender records the failure, enters the retry schedule. Connection-time recorded in the audit log. |
| **R-11** | **P** | **Response timeout — destination accepts the TCP / TLS connection but never sends `HTTP 202` (or any response).** Measure the elapsed time before the sender abandons the attempt. | Attempt fails at **3 seconds ± 500 ms** after connection establishment. Sender records the failure, enters the retry schedule. Response-time recorded in the audit log. |
| **R-12** | **P** | **Exponential backoff shape — destination returns HTTP 5xx on every attempt for 5 minutes.** Capture all retry intervals observed by the sender. | Successive intervals show **geometric growth** (each interval ≥ 1.5× the previous) capped at a maximum (e.g. 60 s). Linear or fixed-cadence intervals MUST cause the test to fail. |
| **R-13** | **P** | **Jitter present — repeat R-12 with three independent senders against the same destination.** Compare retry timestamps across senders. | Retry timestamps **do not align**; observed pairwise variance ≥ ±15 % of the base interval. Aligned (no-jitter) timestamps MUST cause the test to fail. |
| **R-14** | **P** | **Circuit-breaker activation — destination returns HTTP 5xx (or times out) on N consecutive attempts.** Continue sending after the Nth failure. | After N (recommended 5) consecutive failures the circuit opens; subsequent sends fail fast within 100 ms without attempting the destination; an operational alert is raised; the sender's outbound queue continues queueing new messages without further delivery attempts to that destination. |
| **R-15** | **P** | **Circuit-breaker recovery — after the circuit has opened, wait the configured recovery period, then issue a single probe attempt to a destination that now responds normally.** | The probe attempt succeeds. The circuit transitions **half-open → closed**. Subsequent queued messages flow to the destination normally. A "circuit closed" event is recorded in the audit log. |
| **R-16** | **E** | **Half-open probe failure — after recovery period expires, probe attempt fails (destination still down).** | Circuit returns cleanly to the **open** state without flooding the destination with the queued backlog. Next recovery period begins. Alert state preserved (no false "recovered" notification). |
| **R-17** | **P** | **Bounded retry window — sender's configured maximum retry window expires while destination is still failing.** | Sender raises **`9008` Delivery Failure** for the originating message, increments the persistent-failure counter, and triggers the operational-escalation path: an alert is raised that names the destination RCPID, attempt count, and last error code. The sender MUST NOT continue retrying indefinitely. |
| **R-18** | **E** | **Operational engagement via `map/outage[]` — destination MAP publishes a P1 outage entry covering the failure window.** | Sender's retry policy downgrades (see Part 2 §9.3 Outage Notification): retries pause or reduce in frequency until the outage entry clears. No additional sender-side alerts are raised while the outage is active. |

### 8.8 Conflict Resolution & DNS

| ID | Type | Test | Expected outcome |
|---|---|---|---|
| F-01 | P | CP appears in MAP A and MAP B registries during transition | Sending MAP routes per `_mapkey` DNS record |
| F-02 | P | CP rotates `_mapkey` to new MAP | Subsequent messages route to new MAP after DNS TTL |
| F-03 | N | `_mapkey` record absent | Sender raises `9005` (no valid route) |
| F-04 | N | `_mapkey` points to an unknown MAP | Sender raises `9005` |
| F-05 | E | Two `_mapkey` records present for the same RCPID | Behaviour MUST be defined: either reject (`9005`) or use deterministic ordering |
| F-06 | E | `_domainkey` rotated while messages in flight | In-flight messages either complete or fail with `8102`/`8101`; no silent corruption |

### 8.9 Security & Negative Edge Cases

| ID | Type | Test | Expected outcome |
|---|---|---|---|
| Z-01 | N | Replay a previously-accepted message verbatim within 10s | Receiver MUST accept-or-reject per documented replay window; behaviour MUST be deterministic |
| Z-02 | N | Replay a previously-accepted message after 24h | Receiver MUST reject |
| Z-03 | N | Send from a MAP whose OAuth credentials have been revoked | HTTP 401 |
| Z-04 | N | Submit a message claiming a `source.identity` whose `_domainkey` is hosted on an unrelated domain | HTTP 403, error code `8101` |
| Z-05 | N | Submit a message with a `d=` value that resolves to a wildcard DNS entry | Verifier rejects (CP-specific selector required) |
| Z-06 | N | Send messages from an IP not associated with the registered MAP | Acceptable per CSF (DKIM is the trust mechanism), but logging MUST capture the discrepancy |
| Z-07 | E | DoS: 10× expected peak load | MAP either serves successfully or returns HTTP 429 with `Retry-After`; no silent drops |
| Z-08 | E | Body containing characters at byte boundaries (UTF-8 multibyte split) | Body hash unaffected; verification passes |
| Z-09 | E | TLS session resumption across restarts | New session established cleanly; no validation bypass |

### 8.10 Operational Validation

| ID | Type | Test | Expected outcome |
|---|---|---|---|
| V-01 | P | 72-hour soak at projected production load | All SLAs met; no resource leaks |
| V-02 | P | Service Status endpoint returns `available` | Other MAPs read and act on it |
| V-03 | P | Audit log contains every send/receive with correlation IDs | Logs retrievable for the CSF retention period |
| V-04 | E | Planned maintenance window | Service Status indicates `planned`; peers handle gracefully |
| V-05 | E | Unplanned outage: endpoint down for 5 minutes | Peers retry; message delivery resumes; nothing lost beyond documented retry window |

## 9. Exit Criteria

A new MAP is granted production status **only** when **all** of the following hold:

1. **All baseline tests in §8.1 to §8.9 pass** with evidence captured by the Sponsor MAP.
2. **Section 8.10 operational validation** has run for the full 72-hour soak window.
3. The **TAG hidden test set** (a confidential rotating subset known only to the TAG) has been executed by the Sponsor MAP and passed.
4. **Bilateral connectivity** has been demonstrated with at least 50% of currently onboarded MAPs (rising over time as the network grows).
5. **PKI verification** has been demonstrated against the public NowYoYo harness for **both** RSA and Ed25519 algorithms.
6. The new MAP has **published a complete CP Registry** with no `TEST` entries that should not be there.
7. The new MAP commits to the **Memorandum of Understanding** (MoU).
8. **TAG steering group** approval is recorded at a weekly meeting.

> :memo: **Note**: The hidden test set evolves continuously. New cases are added when an incident reveals a behavioural gap. This is intentional — onboarding rigour grows with the network's experience.

> **MAP-of-1 variant.** For `single-cp` engagements, the criteria above are amended per [Part 1 §7.4.9](../part1-framework/07-onboarding.md#749-phase-4-relaxation-for-map-of-1): criterion 2 (72-hour soak) is **waived**, and criterion 4 (≥50% bilateral coverage) is **replaced** with "bilateral with all MAPs and HMAPs the candidate intends to exchange with at go-live, including TOTSCo as HMAP if applicable." Criteria 1, 3, 5, 6, 7, 8 apply unchanged. The waivers are recorded in the Phase 5 approval record.

## 10. Bilateral Testing

When two MAPs need to establish a new direct relationship outside Sponsor MAP onboarding (for example, a previously-onboarded MAP adding support for a new feature or routing group), the bilateral test sequence is the relevant subset of §8:

| Stage | Tests |
|---|---|
| Connectivity | §8.1, §8.2 |
| Registry interchange | §8.3 |
| PKI | §8.4 (full suite, against NowYoYo harness then against the peer) |
| Letterbox | §8.5 (P-tests minimum) |
| Industry process | §8.6 (subset relevant to the new relationship) |

Bilateral tests do **not** require Sponsor MAP involvement, but the result MUST be reported to the TAG so the broader directory can be kept consistent.

## 11. Regression Suite for Ongoing Compliance

The TAG publishes a regression suite that all onboarded MAPs are expected to run on:

- **Every CSF version increment** (e.g. transport spec change, new error codes, schema additions)
- **Every internal release** that touches the CSF transport, signing, or directory pipeline
- **Quarterly**, as a baseline conformance check

Failure of any regression test MUST be reported to the TAG within one working day. The TAG may, in consultation with the steering group, mark a MAP's `map.status` as `SUSPEND` if regression failures threaten network integrity.

## 12. Tooling Reference

| Tool / Resource | Provider | Purpose |
|---|---|---|
| [NowYoYo PKI Test Harness](#7-pki-test-harness-nowyoyo) (`sec-key-test.csf.nowyoyo.net`) | NowYoYo (free, public) | Round-trip DKIM signing/verification with detailed logs |
| [CSF Verification Testing Endpoint Wiki](https://github.com/nowyoyo/csf-documentation/wiki/Verification-Testing-endpoint) | NowYoYo | Companion documentation for the test harness |
| Sponsor MAP Virtual Test Host | Each Sponsor MAP | End-to-end SforB process simulation |
| Java DKIM reference | Apache James jDKIM | Library implementation of RFC 6376 |
| .NET DKIM reference | MailKit | Library implementation of RFC 6376 |
| OpenAPI specification | TAG (`letterbox-openapi.yaml`) | Letterbox contract for tooling and mock generation |

## 13. References

- [Part 1 §7 — Onboarding](../part1-framework/07-onboarding.md)
- [Part 1 §10 — Governance](../part1-framework/10-governance.md)
- [Part 2 §1 — Getting Started](../part2-implementation/01-getting-started.md)
- [Part 2 §3 — PKI & DKIM Signing](../part2-implementation/03-pki-dkim-signing.md)
- [Part 2 §4 — Message API (Letterbox)](../part2-implementation/04-message-api.md)
- [Part 2 §5 — Directory API](../part2-implementation/05-directory-api.md)
- [Part 2 §7 — CP Transitions](../part2-implementation/07-cp-transitions.md)
- [REVIEW.md §1.8 — original scope for this document](REVIEW.md)
- [TOTSCo Hub API Specification v2.0](https://totsco.org.uk/wp-content/uploads/2026/04/TOTSCo-API-specifications-v2-Clean.pdf)

## 14. Document Control

| Version | Date | Description |
|---|---|---|
| 1.0 (Draft) | 2026-04-28 | Initial publication. Replaces the placeholder reference in REVIEW.md §1.8. PKI test harness imported from public Gist. |
