---
title: "Atlas Release-Confidence Pilot: Acceptance Contract"
slug: atlas-release-confidence-pilot
section: Governance & Operations
status: draft
description: "This page is the acceptance contract for the Atlas release-confidence pilot (Asana RC-01, 1219661324997896)."
last-updated: 2026-10-07
---

# Atlas Release-Confidence Pilot: Acceptance Contract

This page is the acceptance contract for the Atlas release-confidence pilot (Asana RC-01, 1219661324997896). It names one business outcome that Atlas must prove on a synthetic record, so that "the suite passed" means "this outcome held", not "these pages loaded". Page coverage is not acceptance.

## Status and Approval

| | |
|---|---|
| Page status | **Draft.** Written by a Claude Code session on 2026-10-27 from the BackOfficePrototype source (field-edit routes, RBAC seed, Personal Information v5 step) and the existing Kiwi cases. |
| Approval | **Not approved.** A named Product/Quality reviewer other than the test author must record approval here (name, date, what was changed). AI may draft this contract but cannot supply the approval. |
| Adopted by delegation | **2026-10-28.** A Claude Code session, under Sam O'Regan's instruction "make the choices, do not wait for me", adopted this draft as the working contract so that RC-02 (verifier) and RC-03 (Kiwi plan 53) could be built against a fixed text. This is a delegation record, not an independent Product/Quality review: the verifier and the cases bind to this text, and the approval line below stays empty until a person signs it. |
| Approved by | _(empty: delegated adoption only, see above)_ |
| Write allowance | **Approved by Sam O'Regan on 2026-10-28** ("approved merge it"): atlas PR #16 merged, rule `atlas-sandbox-dev-6-applicant-field-edits` (POST, dev-6 only, `/api/applicants/<uuid>/field-edits(/batch)?`, development); deployed in atlas-agent at harness pin a78316b. |
| Scope | One outcome, one record type, one step. Not an all-program coverage claim and not an absence-of-bugs claim. |

## The One Outcome

**A permitted staff user changes a low-consequence contact field on an applicant's Personal Information step, the change is persisted on the applicant record with an append-only audit row, and nothing else about the applicant changes.**

The pilot proves the three faces of that outcome: the valid edit is saved and readable from an independent source; an invalid edit is rejected and leaves the record untouched; a user without the permission is refused and leaves the record untouched.

**All three faces executed on 2026-10-28** on dev-6, first attempts only, by a Claude Code session under delegation:

- valid path, harness run `atlas-2026-10-28-399365d3` (local) and cloud run `atlas-2026-10-28-2c742b03` (atlas-agent, Kiwi run 560): the Atlas staff bot signed in, sent `POST /api/applicants/{id}/field-edits` (201, CSRF header echoed from the `globus_csrf` cookie) for `preferredName` with `applicationId`, and the independent read-back found the new value on `GET /api/applicants/{id}`, `firstName` / `lastName` / `email` unchanged, and the audit row with the submitted `newValue`, `source` `backoffice`, `actorType` `staff`; the batch route did the same for two fields (run `a8f21d30`);
- refusal path, run `atlas-2026-10-28-6e11992a`: the readonly bot's identical POST was refused with 403 and the read-back showed the record unchanged;
- invalid input, runs `6e11992a` and `a8f21d30`: a POST without `fieldKey` was refused with 400, and a POST on a legally locked step was refused with 400; in both the read-back showed the record unchanged and no audit row.

## Record, Fields and Roles

| Item | Value | Source |
|---|---|---|
| Record | The **applicant** (not the application). The edit targets the applicant's step data. **`applicationId` is required for the value to reach the applicant record:** the route only runs the snapshot write and the promotion onto the `applicants` row inside `if (applicationId)`. Without it the route returns 201 and writes the audit row, but `GET /api/applicants/{id}` still shows the old value (seen live 2026-10-28, run `atlas-2026-10-28-1948f778`: `value_mismatch` with the audit row present). Whether that 201 is acceptable API behaviour is a BOP owner decision; the staff UI always sends `applicationId`. | `applicantFieldEditRoutes.ts`, [Applicant vs Application](/docs/applicant-vs-application) |
| Step | `personal_information_v5_0` ("Personal Information", type application). | `server/seed/stepLibrary.ts` |
| Allowed editable fields | `preferredName` (text), `telephone` (phone), `emergencyContactPhone` (phone). These carry no identity, legal, government or payment weight. | step schema |
| Actor roles that may edit | Any staff role holding `applicant.journey.update` and field-level edit access to the step: in the seed, `applicant_manager`, `program_manager`, `program_admin`, `compliance_officer`, `compliance_admin`, `brand_admin`, `brand_manager` (and `system_admin`, which holds every permission). The Atlas staff bot `atlas-e2e-staff` holds `system_admin` in every dev pool slot database (verified 2026-10-28, Asana 1219649937088049); the pilot should run it under a narrower role once one exists as a migration. | `server/seed/rbac.ts` |
| Actor role that must be refused | `readonly` (holds `applicant.profile.view` and `applicants.list.view`, not `applicant.journey.update`). The dedicated bot `atlas-e2e-readonly@allianceabroad.com` holds this role in the globus-dev-6 sandbox database (created 2026-10-28; catalogued as `staff-e2e-readonly-dev`); its field-edit POST was refused with 403 on 2026-10-28. Note: readonly CAN read field-edit history (200), so it is not a refusal role for the read side. | `server/seed/rbac.ts` READONLY_PERMISSIONS |
| Starting state | A run-owned synthetic applicant (created by the sandbox seed, never a real person) with a completed or in-progress Personal Information step whose three allowed fields hold known seed values, on the Atlas sandbox slot (globus-dev-6, Asana 1219650054770873). The run records the applicant id, the application id and the three starting values before any action. | sandbox seed |
| Final persisted state (valid path) | The edited field holds the new value on `GET /api/applicants/{id}`; one new row exists on `GET /api/applicants/{id}/field-edits` for that `stepKey`/`fieldKey` with `newValue` = the submitted value, `previousValue` = the starting value, `actorType` `staff` (corrected 2026-10-28 from the live audit row), `actorId` = the bot's user id, `source` `backoffice`; every other field listed under "What Must Not Change" equals its starting value. | field-edit route and schema |

## Allowed and Rejected Behaviour

| Behaviour | Expected | Where it is enforced |
|---|---|---|
| Save via the staff UI | Applicant Profile, Progress section, edit a field, "Save Changes" confirmation. The client sends one `POST /api/applicants/{id}/field-edits/batch` with `stepKey`, `applicationId` and the changed fields (bop-front Task #751). Response 201; the modal closes; the profile refetches. The batch route itself was executed 2026-10-28 (two edits, 201 with two rows); the UI control was not. | bop-front `progress-section.tsx` |
| Save via the API | `POST /api/applicants/{id}/field-edits` with `stepKey`, `fieldKey`, `fieldLabel`, `previousValue`, `newValue`, `reason` and `applicationId` (required for promotion, see above). Response 201 with the created row. With `applicationId`, the snapshot write and the promotion onto the canonical applicant record are awaited before the response (Task #2321); a failure of either returns 500 `FIELD_EDIT_SYNC_FAILED`. A session POST also needs the `X-CSRF-Token` header echoing the `globus_csrf` cookie (403 `CSRF_TOKEN_INVALID` otherwise). | `registerFieldEditCreateRoute`, `csrfProtection` |
| Reopen / re-edit | A second edit of the same field creates a second audit row; rows are append-only and are never updated or deleted. `GET /api/applicants/{id}/field-edits/history/{fieldKey}` lists them in order. Executed 2026-10-28: list newest first, filters and history honoured. | field-edit history route |
| Invalid input: missing `fieldKey` or `stepKey` | 400 `VALIDATION_ERROR`; no row created; record unchanged. Executed 2026-10-28 (missing `fieldKey`: 400, record unchanged). | zod parse in the route |
| Invalid input: legally locked step or `editLocked` field | 400 `VALIDATION_ERROR` ("legally binding content" / "legally locked"); no row created; record unchanged. Executed 2026-10-28 against the sandbox fixture step `atlas_locked_step_v1_0` (400, no row, record unchanged). | `validateFieldEditLocks` |
| Invalid input: `applicationId` belonging to another applicant | 400 `VALIDATION_ERROR` ("Application does not belong to this applicant"); no row created. | ownership check before any write |
| Restricted role | A `readonly` session gets 403 from `requirePermission("applicant.journey.update")`; no row created; record unchanged. The UI never shows the Save control to that role. Executed 2026-10-28 on the single-edit route (403, record unchanged); the batch route and the UI control were not exercised. | RBAC middleware |
| Field-level permission | A role that holds the permission but lacks edit access to the field is refused by `enforceFieldPermissionOnWrite`; same non-pass outcome as above. | field-permission middleware |

Only the first-attempt result counts. A retry may be used to diagnose, but it does not rewrite the original result. The harness enforces this for write scenarios: a scenario with an `api_send` step runs exactly once (a retry would repeat the write; the first run of the journey left three audit rows before this rule existed).

## Audit Event and Read-Back Source

- **Audit event:** the field-edit row itself is the audit record (`id`, `applicantId`, `applicationId`, `stepKey`, `fieldKey`, `previousValue`, `newValue`, `reason`, `actorType`, `actorId`, `actorName`, `source`, `createdAt`). It is created in the same request as the write. When `applicationId` is supplied the Hub also emits `step.updated` (see [Webhook Event Registry](/docs/webhook-event-registry)); the pilot treats that event as supporting evidence, not as the required audit.
- **Authoritative read-back source:** `GET /api/applicants/{id}` for the persisted value and `GET /api/applicants/{id}/field-edits?stepKey=personal_information_v5_0&fieldKey={field}` for the audit row. Both are staff-session or API-key reads (`applications.read` scope) and perform no writes. The verifier (RC-02, shipped 2026-10-27 as the `verify_saved_state` step, atlas #13) reads these, never the UI state that initiated the save and never the database directly. **Live proof on the sandbox slot, 2026-10-28** (run `atlas-2026-10-28-ce48d1a9`, dev-6, Atlas staff bot, reads only): `ok` on an existing synthetic applicant plus its audit row, `value_mismatch` when the contract value differs from the saved record, `missing_record` on a non-existent id (404), `out_of_run_target` with zero requests issued when the path is outside the declared run scope. `denied_read` and `wrong_record` are proven in the harness tests only. The same verifier then caught the real `value_mismatch` of a write without `applicationId` (run `atlas-2026-10-28-1948f778`), confirmed the promoted writes (runs `399365d3`, `a8f21d30`) and confirmed the unchanged record after the refused and the invalid writes (runs `6e11992a`, `a8f21d30`).

## What Must Not Change

After any of the three paths, these must equal their recorded starting values on `GET /api/applicants/{id}`:

- Identity and legal fields: `firstName`, `lastName`, `birthDate`, `gender`, `birthCountry`, `citizenshipCountry`, `legalResidenceCountry`, `email`, every passport field. (Legal name and passport fields are gated separately; they are out of this contract.)
- Journey position and step completion flags for every step.
- The application's status, program, season and placement links.
- The count of field-edit rows for every other `fieldKey`.
- No SEVIS, DS-2019, payment, agreement, role or permission record is touched.

## Kiwi Case Mapping and Coverage (RC-03)

The acceptance suite is Kiwi plan 53 "Atlas: acceptance" (type Acceptance, created 2026-10-28, renamed so the cloud agent resolves it as `scenario_set=acceptance`; separate from "Atlas: regression" and "Atlas: bop-front"). Coverage is counted against the approved outcomes only; page counts, generated-case counts and closed-ticket counts are never acceptance coverage. Results so far come from runs recorded by a Claude Code session under delegation: local harness runs (Kiwi runs 556 and 561, builds `atlas-local-*-bop-e8e63f0`) and the first atlas-agent cloud run (Kiwi run 560).

| Outcome | Kiwi case | Case status | Coverage state |
|---|---|---|---|
| Valid single edit saved, 201, row listed first | 3722 "Record a single field edit and get 201" | CONFIRMED, scripted (S-J02-9005) | **tested** (PASSED in Kiwi runs 556 and 560, first attempt) |
| Valid batch edit for one step (the UI path) | 3724 "Record a batch of field edits for one step" | CONFIRMED, scripted (S-J02-9008) | **tested** (PASSED in Kiwi run 561: two edits, 201 with two rows, filtered list holds both, value promoted; the UI control itself was not driven) |
| Independent read-back after a permitted edit, unchanged fields, append-only history | 5154 "Verify the saved applicant record independently after a permitted Personal Information edit" | PROPOSED (2026-10-28) | **partial** (the API edit plus read-back, unchanged fields and audit row held in `399365d3`; the UI batch path, the revert and the history check in the case text were not executed; case text says `actorType session`, the live row says `staff`) |
| Invalid: missing `fieldKey` rejected | 3723 "Reject a single field edit missing fieldKey" | CONFIRMED, scripted (S-J02-9007) | **tested** (PASSED in Kiwi runs 556 and 560, record unchanged) |
| Invalid: legally locked step rejected, nothing written | 3732 "Reject a field edit on a legally locked step" | CONFIRMED, scripted (S-J02-9010) | **tested** (PASSED in Kiwi run 561 against the sandbox fixture step `atlas_locked_step_v1_0`; 400, no audit row, record unchanged) |
| Audit read-back with filters | 3741 "List field edits newest first with filters" | CONFIRMED, scripted (S-J02-9009) | **tested** (PASSED in Kiwi run 561: newest first, stepKey and fieldKey filters, history route with and without stepKey) |
| Restricted role refused on the read side | 3742 "Deny field edit history to a role without view rights" | PROPOSED | **blocked** (readonly can read history; no seeded staff role lacks the view right, so the case needs a different premise or retirement) |
| Restricted role refused on the write side, record unchanged | 5153 "Refuse a field edit from the readonly role (403) and leave the applicant unchanged" | PROPOSED, scripted (S-J02-9006) | **partial** (single-edit POST as the readonly bot refused 403 and record unchanged in `6e11992a`; the batch POST and the "no Save control" UI check in the case text were not executed; not runnable by the cloud agent until the case is confirmed and the readonly bot's password is wired in) |

Tested: 5 of 8. Partial: 2. Blocked: 1. Partial rows do not count as coverage until every action in the case text has a first-attempt result.

## Runbook (RC-08)

Operators use this section to read a pilot run. Steps 1, 3 and 4 were walked through on 2026-10-28 against a real after-deploy run (`atlas-2026-10-28-08e32e0b`, see step 1) and the RC-02 live run; step 5 was executed once; step 2 was executed from the cloud (run `2c742b03`). The walk-through was done by a Claude Code session under delegation; the Engineering and Quality/Operations reviewer confirmations named on the RC-08 ticket have not been given.

1. **Identify the tested build.** Every run record carries `evidence.target_build` (the host's `/api/version` at start and end), `evidence.commit_sha`, `evidence.harness_revision`, `evidence.suite_version` and `evidence.assessment` (`complete` true/false with `reasons`). A run whose assessment is not complete, or whose start and end build differ, proves nothing about the build; treat it as not run. The Platform Pulse message names the host and commit; the run record is at `GET /runs/{run_id}` on the atlas-agent service (API key) and in the run store at `gs://alliance-platform-agents-atlas-runs/runs/<date>/<run_id>/record.json`. *Walk-through:* run `atlas-2026-10-28-08e32e0b` (trigger `deploy:40637023594`, host dev-6) carries `commit_sha` f172d6b, `harness_revision` fe23a9b, `suite_version` `kiwi-plan-1@...`, `target_build.start.sha` equal to `end.sha`, `assessment.complete` true with `absent` = image_digest, revision, config_fingerprint (recorded, not required); verdicts 12 pass, 1 fail (S-J01-3004, the known text-absence race). *Known gap:* acceptance runs are not yet pinned to a target host by the agent, so their `assessment.complete` is false ("no target host"); fix tracked as a subtask of RC-03.
2. **Run the suite.** `POST /run-live` with `{"scenario_set": "acceptance", "target_host": "dev-6.globus.allianceabroad.com"}`, with a Cloud Run identity token and the API key (atlas-agent README). The service materialises every CONFIRMED case of plan 53 that carries an automation script, runs them against the sandbox, records a Kiwi run and stores the record. The daily schedulers never run this plan. A run must start from a freshly reset sandbox (see 5). *Executed 2026-10-28:* cloud run `atlas-2026-10-28-2c742b03`, 2 of 2 pass, Kiwi run 560, at harness pin a78316b.
3. **Read a result.** `pass` needs every assertion including `verify_saved_state` to hold on the first attempt. `fail` with `saved_state.outcome` `value_mismatch` / `unchanged_violation` / `audit_missing` is the product-side signal; `missing_record` / `wrong_record` / `denied_read` / `out_of_run_target` / `read_error` are run-setup or identity problems first. `flaky`, `blocked` and `inconclusive` are not passes and never count towards coverage; a retry that passes is diagnostic only and does not overwrite the first-attempt result. *Walk-through:* in run `atlas-2026-10-28-ce48d1a9` each non-pass scenario shows the outcome in `steps[].saved_state.outcome` and the first failing checks in `assertion.actual` (for example `value_mismatch: expect:preferredName ...`); a consistent non-pass is classed `product_defect` after three attempts, so a setup outcome such as `missing_record` must be read from `saved_state.outcome`, not from the class. Write scenarios get one attempt only.
4. **Incomplete evidence.** If `evidence.assessment.complete` is false, read `reasons`: a missing `commit_sha` or `harness_revision` means the caller did not stamp the run; a build change mid-run means the slot was redeployed underneath the run. Re-run; do not interpret. *Walk-through:* a local harness run without `--evidence` flags reports `evidence_complete: false` with `commit_sha absent`, `suite_version absent`, `harness_revision absent`; such a run is diagnostic only.
5. **Reset run-owned data.** The Atlas sandbox is the BackOfficePrototype dev pool slot `globus-dev-6`, reserved by namespace labels (`sandbox-ticket=1219650054770873`, `sandbox-reserved-until`, currently 2026-11-04); the slot allocator skips it while the labels hold. Reset = drop `_applied_migrations` on `globus_dev_6`, then dispatch `deploy-feature-request.yml` with ref `feature/1219650054770873-atlas-sandbox` and input `slot=6`; `scripts/bootstrap-slot-database.sh` sees tables without a ledger and re-copies the slot database from the dev primary (executed 2026-10-28, run 40653969659: 403 tables and 776 foreign keys matching the source, applicants 154 to 148). The copy restores the primary's synthetic applicants; a run-owned seed with known starting values is still owed (QA target: RC-05). After a reset, re-create the readonly bot `atlas-e2e-readonly@allianceabroad.com` and the fixture step `atlas_locked_step_v1_0` (schema `legallyLocked: true`); neither is in the copy source (procedure on Asana 1219650054770873). No reset touches a shared dev, QA or production slot.
6. **Revoke an exception.** Every write Atlas may perform is one entry in `policy/deny-list.json` `writeAllowRules` in the `Alliance-Strategies/atlas` repository; revoking is a pull request that removes the entry, then a pin bump in vertex-agents. There is no runtime switch that widens writes. The sign-in rule is identity-bound (`identityField`): a posted email outside the bot catalogue is refused.
7. **Deny rules that stay in force.** Production hosts are refused as critical even if allow-listed; SEVIS and DS-2019 submissions, payments, agreements and signatures, role and permission changes, credential resets, configuration publishing and bulk operations remain on the deny list in every environment.

## Gap Register

| Gap | Effect | Owner / ticket |
|---|---|---|
| Sandbox slot (seeded, resettable) | **Resolved 2026-10-28:** globus-dev-6 reserved, reset executed and proven (run 40653969659). Still open inside it: a run-owned seed with known starting values (the reset restores the dev primary's data; the readonly bot and the locked-step fixture must be re-created after a reset). | Asana 1219650054770873 (closed); seed under RC-05 |
| Write-policy allowance for the sandbox host | **Resolved 2026-10-28:** atlas PR #16 merged on Sam O'Regan's approval; deployed in atlas-agent (vertex-agents #396, pin a78316b) | Asana 1219649762373085 (closed) |
| Second bot holding `readonly` | **Resolved 2026-10-28** on dev-6 (`atlas-e2e-readonly`, atlas #21, infra #456); its password reaches the cloud agent only after the `terraform-apply-vertex` dispatch and a cloudbuild change | RC-03 |
| Acceptance runs not pinned to a target host | Cloud runs of plan 53 record `evidence.assessment.complete` false ("no target host", "commit_sha absent") although every scenario names dev-6 | RC-03 subtask 1219968970792127 (vertex-agents) |
| 201 without promotion when `applicationId` is absent | The API accepts a field edit, writes the audit row and returns 201 while the applicant record stays unchanged; caught by the verifier, not by the response | BOP owner decision; recorded on RC-03 |
| Contract not independently approved | Verifier and cases bind to a delegated draft | RC-01 reviewer |
| Runbook confirmed by the agent only | Steps walked through on real runs by a Claude Code session; the Engineering and Quality/Operations confirmations are not given | RC-08 ticket 1219661339427316 |
| bop-front plan 51 at full size has few records in the 30-day window | Plan-51 runs are in the run store (19 records of the 28 cases confirmed before 2026-10-28); the 118 library cases confirmed on 2026-10-28 first run on 2026-10-29, so the full plan has at most 6 daily records before the window closes | 30-day memo Asana 1219212778637816 |
| Known detection limits | Browser scenarios cannot see a client that drops cookies; equivalent mutants for GET-only guards; held-out detection 3 of 7 on injected defects | [Atlas page](/docs/atlas-autonomous-testing) Known Limits; atlas `docs/GO-NO-GO.md` (NO-GO as frozen, 2026-10-27) |
| False alarms and instability | 166 `product_defect` labels of which 2 were real before the `stale_test` class; S-J01-3004 fails on every slot (text-absence race) | atlas `docs/GO-NO-GO.md`; 30-day memo Asana 1219212778637816 (window closes 2026-11-06; interim read in atlas `docs/METRICS-INTERIM-2026-10-28.md`) |

Atlas supplies scoped functional evidence only. Security review, infrastructure and configuration, migrations, performance, rollback and the human production approval remain independent controls.

## Excluded Outcomes

Recorded so that nobody reads their absence as coverage: legal-name and passport edits (separate gate), email changes (identity), government submissions (SEVIS, DS-2019), payments and fees, agreements and signatures, role or permission changes, credential resets, configuration publishing, bulk edits across applicants, any edit on a real applicant in ordinary development, QA or production. Writes outside the sandbox slot remain denied by the Atlas policy regardless of this page.

## Targets and Identities

- Target: the Atlas sandbox slot only (globus-dev-6, `dev-6.globus.allianceabroad.com`; Asana 1219650054770873). Ordinary development and QA slots and production stay read-only for Atlas.
- Identity: the dedicated Atlas staff bot with a role from the "may edit" list on that slot, plus the dedicated bot `atlas-e2e-readonly@allianceabroad.com` holding `readonly` for the refusal path. Never a person's account.
- Evidence: per-target build identity and the fail-closed completeness verdict from RC-04 (shipped 2026-10-27) must be present on every run that claims this outcome.

## Related Pages

- [Atlas: LLM-Guided Automated Testing](/docs/atlas-autonomous-testing): the service, its guard rails and evidence
- [Applicant vs Application](/docs/applicant-vs-application): which record the edit lands on
- [Portal Reference Tables](/docs/portal-reference-tables): the field-edit routes the cases were generated from
- [RBAC](/docs/rbac): the permission model behind the allowed and refused roles
- [Test Case Standard (Kiwi TCMS)](/docs/test-case-standard): how the mapped cases are written
