# RC1 Acceptance Checklist

Validate public examples and observations against [API_V1_CONTRACT.md](api/API_V1_CONTRACT.md) and the authoritative [OpenAPI 3.1 document](api/openapi.yaml). If checklist prose conflicts, OpenAPI governs.

## Acceptance record

| Field | Value |
| --- | --- |
| Git commit | `<COMMIT>` |
| Release identifier | `<RELEASE_ID>` |
| Environment | `<ENVIRONMENT_ID>` |
| Deployment timestamp UTC | `<TIMESTAMP>` |
| Application owner | `<NAME>` |
| Database owner | `<NAME>` |
| Operations owner | `<NAME>` |
| YAS representative/contact | `<NAME_OR_RECORD>` |
| Rollback plan reference | `<CHANGE_RECORD>` |

Do not mark a scenario passed solely because a unit, feature, or MySQL test exists. Automated verification establishes an internal baseline; staging execution proves deployed composition; YAS external acceptance proves provider behavior.

## Verification layers

| Layer | Purpose | Completion authority |
| --- | --- | --- |
| Automated verification | Reproduce focused/full PHPUnit, dedicated MySQL, Pint, OpenAPI, and diff checks | Engineering test evidence |
| Staging integration | Exercise the deployed API, database, DLR composition, webhook receiver, secrets, and rollback controls | Staging test owner |
| YAS external acceptance | Prove live endpoint, bind, submit acknowledgement, provider ID, DLR format/timing, sender permission, and network path | YAS representative and project owner |

## Executable acceptance scenarios

In the table, the evidence cell must be changed to `PASS — <evidence reference>` or `FAIL — <evidence reference>`. Keep tester notes factual and sanitized.

| ID / layer | Objective | Prerequisites | Request or triggering action | Expected HTTP or processing result; database or observable result | Pass/fail evidence | Tester notes |
| --- | --- | --- | --- | --- | --- | --- |
| RC1-A01 / staging | Authentication rejection | API deployed; invalid credential prepared | Call any RC endpoint with the invalid bearer token | Standard `401 AUTHENTICATION_FAILED`; no tenant resource or secret disclosed and no message created | `PENDING` |  |
| RC1-A02 / staging + YAS | Single-message acceptance | Active tenant/application credential; approved sender and test MSISDN | POST `/api/v1/messages` with a unique idempotency key | HTTP `202`, queued public message, current correlation header, durable message/outbox evidence; YAS portion later records acknowledgement | `PENDING` |  |
| RC1-A03 / staging | Single-message replay | A02 complete; original payload/key retained securely | Repeat the equivalent request with a different correlation header | HTTP `202`; same `message_id` and original body correlation, current replay header; no second message | `PENDING` |  |
| RC1-A04 / staging | Single-message conflict | A02 complete | Reuse its tenant/key with a different payload | HTTP `409 IDEMPOTENCY_CONFLICT`; original message unchanged; no conflicting message | `PENDING` |  |
| RC1-A05 / staging | Bulk all accepted | Valid credential; 1–100 valid approved inputs | POST `/api/v1/messages/bulk` | HTTP `202`; accepted equals input count, rejected zero, ordered queued results, durable messages | `PENDING` |  |
| RC1-A06 / staging | Bulk partial success | Valid and invalid item fixtures | Submit one valid and one invalid item | HTTP `202`; one accepted and one safe rejected result in input order; accepted sibling persists | `PENDING` |  |
| RC1-A07 / staging | Bulk all rejected | Valid top-level request containing only invalid items | Submit batch | HTTP `202`; accepted zero, rejected equals count; ordered safe errors; no message for rejected items | `PENDING` |  |
| RC1-A08 / staging | Bulk replay stability | A05 or A06 complete | Replay equivalent ordered body and key with new correlation header | Original `batch_id`, counters, ordering, message IDs, and errors; no duplicate messages | `PENDING` |  |
| RC1-A09 / staging | Queued status retrieval | Newly accepted message not yet terminal | GET `/api/v1/messages/{message_id}` | HTTP `200`; owning tenant sees `queued` or evidence-backed later status; no internal/provider fields | `PENDING` |  |
| RC1-A10 / staging | Cross-tenant isolation | Two tenants; message belongs to tenant A | Tenant B requests tenant A's message ID | Standard not-found behavior; no resource fields or existence disclosure | `PENDING` |  |
| RC1-A11 / YAS | Provider acknowledgement correlation | Approved live bind; A02 message submitted | Capture authorized `submit_sm_resp`/attempt evidence | Exactly one acknowledged attempt has YAS provider reference; no public provider ID leakage | `PENDING` |  |
| RC1-A12 / staging + YAS | Delivered projection | Correlatable acknowledged message; delivered DLR fixture or YAS receipt | Process `DELIVRD` through approved inbound path | DLR evidence commits atomically; status GET returns `delivered`; optional webhook event exists | `PENDING` |  |
| RC1-A13 / staging + YAS | Failed projection | Correlatable message; approved failed receipt | Process supported failure DLR | Evidence commits; status GET returns `failed`; unrelated messages unchanged | `PENDING` |  |
| RC1-A14 / staging + YAS | Expired projection | Correlatable message; approved expired receipt | Process `EXPIRED` DLR | Evidence commits; status GET returns `expired`; terminal state immutable | `PENDING` |  |
| RC1-A15 / staging + YAS | Rejected projection | Correlatable message; approved rejected receipt | Process `REJECTD` DLR | Evidence commits; status GET returns `rejected`; terminal state immutable | `PENDING` |  |
| RC1-A16 / staging + YAS | Duplicate DLR idempotency | One terminal receipt already applied | Deliver identical receipt again through the same approved path | Duplicate outcome; one durable effective terminal transition and no duplicate webhook attempt | `PENDING` |  |
| RC1-A17 / staging + YAS | Unknown provider ID | Receipt with provider reference not correlated to an acknowledged attempt | Process receipt | Uncorrelated safe outcome; no message changes and no webhook event | `PENDING` |  |
| RC1-A18 / automated + staging | Malformed DLR isolation | Test-only malformed fixture; unrelated valid receipt | Feed fixture through test/simulator inbound boundary, then process valid fixture | Malformed envelope fails closed/quarantines according to inbound policy; unrelated receipt succeeds; no production raw-PDU injection | `PENDING` |  |
| RC1-A19 / staging | Webhook creation | Tenant credential; safe HTTPS receiver; 32-byte secret | PUT `/api/v1/webhook` | HTTP `200`; GET returns URL/enabled/timestamps without secret; encrypted configuration exists | `PENDING` |  |
| RC1-A20 / staging | Webhook secret replacement | A19 complete; new 32-byte secret | PUT replacement | New secret active immediately; old secret retained only for 24-hour overlap; neither returned | `PENDING` |  |
| RC1-A21 / staging | Delivered signature validation | Enabled receiver; correlatable delivered receipt | Trigger delivered terminal transition | One request; public-only delivered payload; raw-body HMAC validates with timestamp and approved secret | `PENDING` |  |
| RC1-A22 / staging | Failed webhook recording | Receiver deliberately returns non-2xx | Trigger an approved failed/expired/rejected terminal message | Delivery state commits; one attempt records sanitized failure; no retry | `PENDING` |  |
| RC1-A23 / staging | Duplicate creates one attempt | A21 or A22 complete | Replay identical DLR | No second HTTP request or attempt row; original terminal status retained | `PENDING` |  |
| RC1-A24 / staging | Timeout isolation | Receiver safely delays beyond 10 seconds | Trigger terminal transition | One timed-out attempt records sanitized failure; terminal message status remains committed | `PENDING` |  |
| RC1-A25 / staging | Correlation semantics | Valid supplied and absent-correlation requests; replay case | Exercise success/error and replay requests | Every response header identifies current HTTP request; generated value is UUIDv4 when absent; single replay body retains original submission correlation; logs follow approved current/original split | `PENDING` |  |

YAS-dependent rows may use an approved simulator for staging evidence, but the YAS layer remains pending until the provider produces or confirms the real behavior.

## Automated verification record

| Check | Required evidence | Result |
| --- | --- | --- |
| Focused RC tests | Command, totals, assertions, skips, failures | `PENDING` |
| Full PHPUnit | Final tests/assertions/skips/failures summary | `PENDING` |
| Dedicated MySQL | Disposable database identity, MySQL version, zero-skip totals | `PENDING` |
| Pint | Final passing result | `PENDING` |
| OpenAPI | YAML parse and OpenAPI 3.1 shape result | `PENDING` |
| Diff/scope | `git diff --check`, protected files, commit ancestry | `PENDING` |

## Practical staging and YAS procedure

1. Configure a dedicated test tenant and application with `php artisan gateway:setup:integration` only in an approved non-production integration environment, or use the environment's approved tenant provisioning path.
2. Issue a credential with `php artisan gateway:credential:issue`, capture it once into approved secret storage, and do not paste it into the checklist.
3. Configure the exact YAS keys from [YAS_INTEGRATION_GUIDE.md](YAS_INTEGRATION_GUIDE.md); keep activation flags false until configuration approval.
4. Configure one safe HTTPS webhook receiver and retain its secret securely.
5. Submit one API test message using an approved sender, MSISDN, and message text.
6. Capture the public `message_id` and response `X-Correlation-ID`.
7. Retrieve status and verify queued or submitted evidence without assuming delivery.
8. Through authorized internal evidence, capture provider acknowledgement and provider message ID without exposing it publicly.
9. Wait for a live YAS DLR. In a local/staging simulator only, use the existing test fixture path; never inject raw PDUs into production.
10. Verify the status API's projected terminal state.
11. Verify the webhook's public-only payload, timestamp window, and exact raw-body HMAC signature.
12. Repeat with approved failure, unknown-reference, malformed-fixture, duplicate, and timeout cases at the applicable verification layer.
13. Attach sanitized evidence to the scenario rows and obtain staging and YAS sign-off separately.

## Minimum evidence package

- Git commit and release identifier;
- deployment UTC timestamp and environment identifier;
- migration result and rollback-readiness confirmation;
- focused/full automated test and dedicated MySQL summaries;
- sanitized API request/response evidence;
- correlation IDs, public message IDs, and batch IDs;
- authorized internal provider acknowledgement and DLR evidence;
- public status evidence;
- webhook delivery and HMAC verification evidence;
- authentication, conflict, isolation, duplicate, malformed, timeout, and rollback evidence;
- operational owner assignment;
- YAS representative sign-off or acceptance record.

Redact API credentials, SMPP password, webhook secret, signature material, message contents, internal database identifiers from public evidence, and full recipient numbers wherever masking policy requires.

## Security checklist

- [ ] Authentication is required for every RC endpoint.
- [ ] Tenant/application isolation is proven, including negative access.
- [ ] GET webhook never returns active/previous secret or ciphertext.
- [ ] Webhook secrets are encrypted at rest.
- [ ] Idempotency keys are not stored in plaintext.
- [ ] Message contents are absent from webhook payloads and safe logs.
- [ ] Current-request and original-submission correlation semantics are correct.
- [ ] HMAC-SHA256 validates over timestamp plus exact raw body.
- [ ] Receiver rejects timestamps outside five minutes.
- [ ] Receiver uses timing-safe comparison and active/overlapping previous secret.
- [ ] Errors are sanitized.
- [ ] Public payloads contain no internal database IDs, SMPP sequence, provider ID, persistence enum, stack trace, or provider metadata.
- [ ] Public API and webhook configuration use HTTPS.
- [ ] `APP_DEBUG=false` in production.
- [ ] API, SMPP, database, application, and webhook secrets use approved secret management.

Infrastructure controls not represented by repository evidence require a separate environment-owner attestation.

## Known limitations

- One webhook endpoint per tenant and one delivery attempt.
- No webhook retry, backoff, dead-letter queue, dashboard, or statistics.
- No public batch retrieval or cancellation.
- No scheduled messages, templates, or campaigns.
- No RC billing or wallet changes.
- No new reporting.
- No `RuntimeLoop`, `SessionExecutor`, or reconnect redesign.
- No repository command for a persistent SMPP runtime process.
- YAS endpoint, credential, sender, TON/NPI agreement, coding, DLR, throughput, firewall, test-data, support, and acceptance-window values still require external confirmation.
- Milestone 6A is deferred, not cancelled.

## Go/no-go decision

GO requires all of the following:

- [ ] Clean approved commit history and release identifier.
- [ ] Successful migration dry run and production change approval.
- [ ] Focused tests, full suite, dedicated MySQL, Pint, OpenAPI, and diff checks pass.
- [ ] Deployment and operations guides are verified by their owners.
- [ ] Secrets are configured securely.
- [ ] YAS connectivity, sender permission, endpoint, firewall, and test window are confirmed.
- [ ] One test message is acknowledged with an internally correlated provider ID.
- [ ] DLR evidence correlates and projects the correct status.
- [ ] Webhook signature and failure isolation are verified.
- [ ] Rollback plan is approved and operational owner assigned.
- [ ] No critical or major review finding remains.

NO-GO applies when any of these exists: unresolved migration failure; failed tenant isolation; plaintext secret exposure; incorrect idempotency; DLR ambiguity mutating another message; webhook publication before commit; webhook failure reversing delivery persistence; unknown YAS connection parameters; no rollback plan; unapproved production credentials; missing persistent-process operating model where required; or a critical/major review finding.

| Decision | Owner | Timestamp | Evidence/reference |
| --- | --- | --- | --- |
| `PENDING — GO / NO-GO` | `<NAME>` | `<UTC_TIMESTAMP>` | `<CHANGE_OR_ACCEPTANCE_RECORD>` |
