# B2B v2 Production Contract Remediation Plan

- **Status:** Ready for implementation
- **Audit snapshot:** 2026-07-15
- **Scope:** `https://api.alteg.io/api/v2` only
- **Primary specification:** `docs/en/b2b-v2/openapi.yaml`


## Outcome

Make the B2B v2 documentation describe the production API exactly, then make the
production checks fail when that contract drifts.

The current v2 specification contains 22 paths and 31 operations. Production
routes exist for all of them, but much of the documentation was written before
the corresponding API implementation existed. The result is a mixture of real
contracts and old mock contracts.

This plan is split into small, independent sessions so a new session can safely
take one section, verify it against production, open a focused pull request, and
stop without depending on chat history.

## Non-negotiable scope rules

- Audit and edit B2B v2 only.
- V1 may be called only to authenticate a Business User and discover a test
Location. Do not change or assess the V1 contract in these sessions.
- Every v2 request must send:

```http
Accept: application/vnd.api.v2+json
Authorization: Bearer <partner_token>, User <user_token>
```
- The required `Accept` header does not select between two successful response
formats. Production returns `400` without the v2 media type and returns the
JSON:API-style representation when the header is valid.
- Use production behavior as the source of truth. Old examples, old mocks, and
endpoint names are not evidence.
- Preserve wire names exactly, even when they use legacy terminology.
- Use the documentation vocabulary in prose: Location, Team Member, Appointment,
Booking User, and Business User.
- Do not retain legacy terminology in prose merely because a canonical term or
route is not implemented in v2 yet. Show a legacy name only as a
code-formatted wire literal, then describe it with the approved vocabulary.
- Do not refactor unrelated V1 paths, shared schemas, formatting, or navigation.
- Do not commit credentials, tokens, customer data, raw production responses,
or identifiable test data.


## Starting a new implementation session

1. Read this entire plan and the repository `AGENTS.md`.
2. Inspect the current state before editing:

```bash
git status --short --branch
git fetch origin
git log --oneline --decorate -10
```
3. Preserve any existing working-tree changes. Do not overwrite another
session's edits.
4. Create a new branch directly from the latest remote default branch:

```bash
git switch -c docs/v2-<session-scope> origin/master
```
5. Read every target path file before changing it. Search for shared schemas and
callers before creating new files.
6. Re-run the relevant live probes. The observations below are a dated snapshot,
not permission to skip current verification.
7. Change only the endpoint group assigned to the session.
8. Run the required validation, commit, push, and open a focused PR.


## Verified production baseline

### Authentication and media type

The following behavior was verified on Services, Appointments, and Positions:

| Request `Accept` value | Production result |
|  --- | --- |
| `application/vnd.api.v2+json` | Successful JSON:API-style response |
| Omitted | `400` |
| `*/*` | `400` |
| `application/json` | `400` |
| `application/vnd.api.v1+json` | `400` |


The production error message explicitly requires
`Accept: application/vnd.api.v2+json`. A charset parameter is accepted.

### Response vocabulary

The public URL vocabulary is new, while several wire-level JSON:API resource
types remain legacy values. Documentation must explain this mapping instead of
inventing new wire values.

| Public documentation term | Production path term | Production `type` |
|  --- | --- | --- |
| Service | `services` | `service` |
| Product | `products` | `good` |
| Product Category | `product_categories` | `good_category` |
| Product Storage Amount | `storage_amounts` | `good_storage_amount` |
| Team Member | `team_members` | `staff` |
| Appointment | `appointments` | `record` |
| Event | `events` | `activity` |
| Position | `positions` | `position` |
| Tag | `tags` | `tag` |


IDs in successful resource objects are strings, as expected for JSON:API-style
resource identifiers. Do not document them as integers inside the response
resource object merely because the path parameter is an integer.

### Common success and error shapes

Successful collection responses use resource objects:

```json
{
  "data": [
    {
      "type": "service",
      "id": "123",
      "attributes": {}
    }
  ]
}
```

Some collections also return pagination metadata. Detail responses commonly add
`"meta": []`.

Production errors consistently use:

```json
{
  "success": false,
  "data": null,
  "meta": {
    "message": "...",
    "errors": {}
  }
}
```

`meta.errors` is present only for field-level validation errors.

### Live coverage completed in the audit

- Positive authenticated GET responses were captured for 17 of 18 GET
operations.
- The positive Attendance Product Item response was not captured because the
test Location had no safe fixture. Its route and `404` behavior were verified.
- Full temporary CRUD was completed for Tags, Positions, and Events.
- Event Appointment create/update routes were verified through validation
responses only to avoid creating or notifying a Booking User.
- Appointment delete and timetable delete were verified using nonexistent IDs;
no existing Appointment was modified.
- Every temporary Tag, Position, and Event was cleaned up after testing.


## Known contract gaps by area

### Services

Target files:

- `docs/en/paths/services/v2/location.services.list.yml`
- `docs/en/paths/services/v2/location.services.read.yml`
- `docs/en/paths/services/v2/location.service_categories.list.yml`
- `docs/en/schemas/services/v2/service_resource.yml`
- `docs/en/schemas/services/v2/service_category_resource.yml`
- `docs/en/schemas/services/v2/error_response.yml`


Known production behavior:

- All success responses are JSON:API-style resources with string IDs.
- Service attributes observed: `title`, `category_id`.
- Service Category attributes observed: `title`, `salon_service_id`.
- `limit`, `page`, and `filter[service_ids][]` work for the Service list.
- Production uses `page`; the previously documented `offset` parameter is
ignored.
- No pagination `meta` was returned by the Service list.
- The previously documented `category` and `staff` include values have no
effect. Production supports `resources`, `service_composite`,
`resource_links`, `company_links`, and `trial_settings`; effective includes
add JSON:API relationships and may add top-level `included` resources.
- Service Categories use `filter[staff_id]`. Revalidation with a nonexistent
Team Member ID changed the collection, and current PHP passes the filter to
the category provider.


### Products

Target files:

- `docs/en/paths/products/v2/location.products.list.yml`
- `docs/en/paths/products/v2/location.products.autocomplete.yml`
- `docs/en/paths/products/v2/location.products.read.yml`
- `docs/en/paths/products/v2/location.products.storage_amounts.yml`
- `docs/en/paths/products/v2/location.product_categories.list.yml`
- `docs/en/paths/products/v2/location.product_categories.read.yml`
- `docs/en/paths/products/v2/location.attendance_product_items.read.yml`
- `docs/en/schemas/products/v2/product_resource.yml`
- `docs/en/schemas/products/v2/product_category_resource.yml`
- `docs/en/schemas/products/v2/storage_amount_resource.yml`
- `docs/en/schemas/products/v2/attendance_product_item_resource.yml`
- `docs/en/schemas/products/v2/json_api.yml`
- `docs/en/schemas/products/v2/error_response.yml`


Known production behavior:

- Product resources use `type: good`.
- Product Category resources use `type: good_category`.
- Storage Amount resources use `type: good_storage_amount`.
- Product attributes differ materially from the current flat mock. Observed
fields include `actual_cost`, `cost`, `category_id`, `consumable_unit_id`,
`sale_unit_id`, `sale_unit_to_consumable_unit_ratio`, `is_chain`, and loyalty
entity IDs.
- Storage Amount attributes observed: `good_id`, `storage_id`, `sale_amount`,
and `consumable_amount`.
- Product Category attributes observed: `title`, `is_chain`, and
`parent_category_id`.
- List and autocomplete pagination is `page` based. Production returns
`meta.pagination.page`, `offset`, and `limit`.
- The previously documented `offset` query parameter on Product list is
ignored. Production uses `page`.
- Product ID filtering works.
- The previously documented Product and Product Category include values have
no effect. Revalidation confirmed the PHP include keys for Products,
Product Categories, Storage Amounts, and Appointment Product Items.
- `storage_amounts` works for Product detail but returns `422` for Product list
and autocomplete. `actual_cost` works for Product list and detail only when
the Business User has permission; autocomplete leaves it `null`.
- The Appointment Product Item positive shape is verified from the current PHP
transformer. Its route and `404` response are live-verified, but the test
Location still has no safe positive fixture.


### Team Members

Target file:

- `docs/en/paths/team_members/v2/location.team_members.list.yml`


Known production behavior:

- Resources use `type: staff`.
- Observed attributes: `name`, `specialization`, `image`, `position_id`,
`is_available_as_assistant`, and `tt_markup`.
- Name, Position, and tri-state filters work.
- Invalid tri-state values return `422` with field errors.
- Current PHP exposes the literal include keys `position` and `employee`.
`position` uses `type: staff_position`; `employee` uses `type: employee` and
returns the employment profile fields defined by the transformer.
- The previously documented `services` and `schedule` include values are not
supported by the current transformer.
- Production revalidation currently returns `403 Insufficient rights` for
every Location returned to the test Business User. The controller gates the
operation through `hasSalonAccess`. Positive resource and include shapes are
therefore verified from the current controller, filter DTO, and transformers
rather than a fresh positive fixture.


### Appointments

Target files:

- `docs/en/paths/appointments/v2/location.appointments.list.yml`
- `docs/en/paths/appointments/v2/location.appointments.delete.yml`
- `docs/en/paths/appointments/v2/location.appointments.bulk_delete.yml`


Known production behavior:

- Appointment resources use `type: record` and contain substantially more
attributes than the current mock schema.
- Pagination is `page` based. `offset` is ignored; undocumented `page` works.
- Date filters require `YYYY-MM-DDTHH:MM:SS`.
- The current transformer exposes Booking User, Team Member, Service, Product,
resource, label, attendance, payment, Custom Field Value, visitor,
notification, schedule, and duration relationships through literal include
keys. The deprecated Team Member include alias is intentionally omitted.
- `/timetable/appointments/delete` is not a bulk array endpoint in production.
It requires one numeric `recordId`.
- Sending the documented `appointment_ids` returns `422` and reports that
`recordId` is missing.
- A nonexistent numeric `recordId` reaches the handler and returns `404`.
- The timetable-delete controller returns the remaining Appointments related to
the deleted Appointment as a JSON:API collection. An Appointment without
related records therefore returns an empty `data` array.
- Current production revalidation returns `403 Insufficient rights` for the
Appointment list because the test Business User no longer has timetable
access to any returned Location. No temporary Appointment was created or
modified. Positive list and delete shapes are verified from the current
controller, DTOs, transformer, and deletion service.


### Positions

Target files:

- `docs/en/paths/positions/v2/company.positions.list.create.yml`
- `docs/en/paths/positions/v2/company.positions.read.update.delete.yml`


Known production behavior:

- Collection, detail, and create responses use JSON:API-style Position
resources.
- `services_binding_type` is numeric in production. The current Position
interface defines `0` (disabled), `1` (soft), and `2` (strict).
- List responses omit `meta`; detail and create responses return `meta` as an
array.
- `limit` and `offset` on the list appeared to be ignored.
- Create succeeds with `title`; `description` is accepted.
- Production rejects the documented create `salon_ids` as unexpected.
- Update succeeds with `title` and optional `description`.
- Production rejects the documented update `position_id` and `salon_ids` as
unexpected.
- Create returns `201`; update and delete return `204`.


The pre-existing partial Position corrections were inspected and preserved.
The numeric enum is now supported by current implementation evidence.

Current revalidation returns `403 Insufficient rights` for every Location
returned to the test Business User. No temporary Position was created or
modified. The response and write contracts retain the earlier live audit
evidence, with the binding enum additionally verified from the current PHP
interface.

The Position resource schema uses a unique component filename. Reusing
`position_resource.yml` conflicted with the Team Member include schema during
Redocly bundling and caused the Cloud Project build to fail.

### Tags

Target files:

- `docs/en/paths/tags/v2/company.tags.list.create.yml`
- `docs/en/paths/tags/v2/company.tags.read.update.delete.yml`
- `docs/en/schemas/tags/v2/tag_attributes.yml`


Known production behavior:

- CRUD bodies, response resource fields, and status codes largely match the
current documentation.
- The list response omits the documented `meta`.
- Alias and numeric entity filters return equivalent results.
- Invalid entity values return `400` with a useful message.


Treat this session as validation and tightening, not a rewrite.

Current revalidation returns `403 Insufficient rights` for every Location
returned to the test Business User. No temporary Tag was created or modified.
The list envelope, entity filters, validation behavior, and CRUD status codes
retain the earlier live audit evidence.

### Events and Event Appointments

Target files:

- `docs/en/b2b-v2/openapi.yaml`
- `docs/en/paths/events/v2/location.events.list.create.yml`
- `docs/en/paths/events/v2/location.events.read.update.delete.yml`
- `docs/en/paths/events/v2/location.events.records.create.yml`
- `docs/en/paths/events/v2/location.events.records.update.yml`


Known production behavior:

- The canonical new route works for list, create, read, update, and delete:
`/locations/{location_id}/events`.
- The alias `/locations/{location_id}/activities` also works.
- The alias `/companies/{location_id}/activities` also works.
- The specification now publishes only the canonical route; working aliases are
intentionally omitted from the primary reference.
- Event list responses use `type: activity`, string IDs, and attributes.
- Event create, read, and update JSON:API-style schemas are close to production.
- Event create returns `201`, update returns `200`, and delete returns `204`.
- After delete, the Event disappears from list results, but direct read still
returned `200` in the audit. Revalidate and document soft-delete semantics if
stable.
- Event filters require `YYYY-MM-DD HH:MM:SS` with a space. RFC 3339 values with
`T` are rejected; the request schemas now use the production pattern.
- Event Appointment create, update, and reschedule routes and required-field
validation exist, but positive writes still need a controlled Booking User
fixture and notification safety review.


Current revalidation on 2026-07-16 returns `403 Insufficient rights` for the
canonical Event route at all 25 Locations available to the test Business User;
both working aliases return the same result for the representative Location.
No Event or Event Appointment was created or modified.
The sibling PHP implementation confirms the canonical alias mapping, `page` and
`limit` pagination, the exact Event resource transformer, and soft deletion:
deleted Events leave list results while direct retrieval does not reject the
deleted state. It also confirms separate `PUT` update and `PATCH` reschedule
operations for Event Appointments. Positive Event Appointment coverage remains
blocked by the missing writable Location and controlled notification-safe
Booking User fixture; the PHP integration suite is the current positive-write
evidence.

## Implementation sessions

Each session should result in one focused PR. Do not combine sessions unless the
files and validation are genuinely inseparable.

### Session 1: Contract primitives and guardrails

Goal: establish reusable, production-shaped components without changing endpoint
semantics.

Work:

- Inventory existing v2 schemas and response files.
- Add or reuse small shared components for JSON:API resource identifiers,
pagination metadata, and the standard error envelope only when reuse reduces
duplication across at least three endpoint groups.
- Document the required v2 `Accept` header once in the top-level description and
keep operation-level required headers consistent.
- Do not introduce a broad schema architecture refactor.


Acceptance:

- Components validate under OpenAPI 3.1.
- No endpoint behavior is invented.
- Existing references still resolve.
- `npm test` introduces no new warnings.


### Session 2: Services

Goal: replace all three mock success schemas and examples with verified
JSON:API-style contracts.

Work:

- Correct list, detail, and Service Category resource shapes.
- Correct string ID types and actual attributes.
- Replace `offset` with the effective `page` parameter and correct the default
and maximum `limit` values.
- Remove pagination metadata that production does not return.
- Replace ineffective include values with the verified Service include keys.
- Correct the Team Member filter to the literal `filter[staff_id]` wire name.
- Replace flat error schemas with the production error envelope.
- Add missing `404` responses.


Acceptance:

- Live list/detail/category responses validate against the edited schemas.
- Limit, page, ID filtering, Team Member filtering, and Service includes are
tested.
- No Service example contains fields absent from the live response.


### Session 3: Products

Goal: correct all seven Product-area operations.

Work:

- Model `good`, `good_category`, and `good_storage_amount` resources.
- Correct attribute names and types.
- Replace `offset` with `page` where production proves page-based pagination.
- Correct pagination metadata.
- Replace ineffective include values with the operation-specific PHP keys that
current production revalidation proves effective.
- Obtain a safe Appointment Product Item fixture or explicitly mark its positive
schema as unverified rather than fabricating it.
- Correct standard errors and add missing `404` responses.


Acceptance:

- Product list, autocomplete, detail, category list/detail, and storage amounts
validate against live responses.
- Page 1 and page 2 return different first resources in the test fixture.
- Product ID filtering is verified.
- Appointment Product Item confidence is stated explicitly.


### Session 4: Team Members

Goal: replace the old flat Team Member response with the live `staff` resource.

Work:

- Correct attributes, string IDs, filters, and errors.
- Remove ineffective include claims unless current behavior has changed.
- Add the missing `404` response.


Acceptance:

- Base list and representative filters validate live.
- Invalid tri-state values validate against the documented `422` envelope.


### Session 5: Appointments

Goal: correct Appointment resources, pagination, and timetable deletion.

Work:

- Model the live `record` resource and its verified attributes.
- Document `page`, not the ignored `offset` behavior.
- Correct date formats and examples.
- Remove ineffective includes unless revalidated.
- Replace the false bulk `appointment_ids[]` contract with the production
`recordId` request.
- Use a temporary Appointment fixture to verify the successful timetable-delete
status and body, with cleanup in the same test flow.
- Correct errors and missing `404` responses.


Acceptance:

- List pages and filters validate live.
- Single delete and timetable delete are verified using temporary fixtures only.
- No existing Appointment is modified.
- Test fixtures are absent after the run.


### Session 6: Positions

Goal: make Position reads and writes usable from the documentation.

Work:

- Revalidate and document the numeric `services_binding_type` values.
- Correct `meta` presence and type.
- Remove unsupported list pagination.
- Correct create and update request bodies.
- Update the Position section of the Arazzo workflow only after the OpenAPI
contract is correct.


Acceptance:

- Temporary create, read, update, and delete all succeed from documented bodies.
- The temporary Position is removed.
- Production responses validate without schema warnings.


### Session 7: Tags

Goal: tighten the already mostly correct Tag documentation.

Work:

- Revalidate list, filters, CRUD, `meta`, and errors.
- Remove stale “not yet deployed” language and 404 skip behavior.
- Avoid unrelated Tag schema rewrites.


Acceptance:

- Full temporary CRUD succeeds and cleans up.
- Alias and numeric filters are both covered.
- List and detail responses validate exactly.


### Session 8: Events and Event Appointments

Goal: publish the canonical Event API and correct the remaining mock contracts.

Work:

- Register `/locations/{location_id}/events` as the canonical documented path.
- Decide, based on product compatibility policy, whether working aliases should
be documented as deprecated aliases or omitted from the primary reference.
- Replace the flat Event list schema with the live `activity` resource.
- Correct the production date-time pattern and examples without falsely using
RFC 3339 `format: date-time`.
- Revalidate Event CRUD and soft-delete behavior.
- Build a controlled fixture for Event Appointment create/update only after
confirming that Booking User notifications and Booking User creation are
safe.
- Correct Event Appointment success schemas if the live fixture differs.


Acceptance:

- Canonical Event CRUD succeeds and cleans up.
- Event list/detail/create/update responses validate.
- The v2-specific Redocly date warnings are eliminated.
- Event Appointment positive coverage is either completed safely or explicitly
documented as blocked with the exact missing fixture/safety requirement.


### Session 9: Arazzo and CI hardening

Goal: make automated checks detect future contract drift.

Work:

- Expand `docs/api-fast-check-b2b-v2.arazzo.yaml` from Tags/Positions to safe
read coverage for every v2 group.
- Remove success criteria that accept `404` because an endpoint may be
undeployed.
- Add explicit dependencies and cleanup for every created resource.
- Ensure cleanup runs on failure where the runner supports it. If it does not,
split destructive workflows or add a deterministic cleanup workflow.
- Use unique test names to avoid collisions.
- Promote `schemaCheck` from `warn` to `error` only after all live schemas are
corrected.
- Do not make destructive checks depend on pre-existing production data.


Important audit evidence: the existing smoke workflow left an `Arazzo Test Tag`
after an incomplete run. It was deleted manually. Cleanup is therefore a
required part of this session, not an optional improvement.

Implemented and production-validated on 2026-07-16:

- The build workflow is read-only and covers collection responses for Services,
Products, Team Members, Positions, Appointments, Events, and Tags. All seven
groups require `200`; no `404` response is accepted as success.
- Tag and Position mutations are isolated in a manual workflow. Each caller
supplies a unique run ID, every post-create failure routes to deletion, and a
failed deletion is retried twice.
- The repository guard bundles both Arazzo descriptions and the v2 OpenAPI
description. It rejects missing read groups, any v2 mutation in the build
workflow, accepted `404` criteria, missing cleanup routes, a registered
destructive Reunite job, or a non-error v2 `schemaCheck` severity.
- The read-only production workflow passed 9 of 9 steps and 36 of 36 checks.
The isolated Tag and Position workflows passed 12 of 12 steps and 40 of 40
checks, then a separate list audit found no remaining fixtures.
- A forced Tag read failure ran the failure cleanup action successfully; a
follow-up list audit again found no fixture. A deliberately corrupted Service
response schema failed Respect at `listServices`, confirming that schema drift
is build-blocking.
- The final Redocly preview status reported `All Respect Monitoring checks passed`. An earlier `No Respect Monitoring workflows found` status appeared
only while the Project preview was still building and was replaced when the
configured workflows completed.


Acceptance:

- All safe read checks run on every build.
- No “not deployed” 404 path can produce a green build.
- Intentional schema corruption makes the workflow fail.
- Failed CRUD testing leaves no Tag, Position, Appointment, or Event fixture.


### Session 10: Final cross-group verification

Goal: verify the complete v2 reference as one production contract.

Work:

- Recount paths and operations; expected baseline is 22 paths and 32 operations.
The additional operation is the production-supported Event Appointment
reschedule operation documented in Session 8.
- Run Redocly lint for `en/b2b-v2/openapi.yaml` and the repository baseline.
- Run the completed v2 Arazzo workflow.
- Manually inspect representative rendered pages for each tag.
- Verify terminology in prose while preserving wire names.
- Search for stale mock markers such as flat success resources, integer JSON:API
IDs, “not yet deployed,” ineffective includes, and obsolete Event-only paths.


Implemented and verified on 2026-07-16:

- The bundled reference contains 22 paths and 32 operations: 18 GET, 5 POST,
4 PUT, 1 PATCH, and 4 DELETE. Every operation documents 401, 403, and 404.
- Every JSON:API-style resource schema uses a string `id`. Source and bundled
prose contain no superseded domain term outside an explicitly marked literal
wire key, value, resource type, or URL parameter. No stale mock or deployment
marker remains.
- Redocly CLI 2.14.4 and 2.39.0 both lint the v2 description with no warnings.
The repository baseline passes with 0 errors and 507 warnings against a
budget of 510.
- The production read-only workflow passes 9 of 9 steps and 36 of 36 checks.
Services, Products, Team Members, Positions, Appointments, Events, and Tags
each return 200 and validate against the published schema.
- The published Services, Products, Team Members, Appointments, Events, and
Tags sections were inspected in the rendered reference. The six sections,
all 32 operations, and the canonical terminology render correctly.
- A separate production list audit found zero Arazzo Tag, Position,
Appointment, or Event fixtures.


Final evidence classification:

- **Documented:** all 32 published operations, including their request,
success, authentication, permission, and not-found contracts.
- **Live-verified:** 17 of 18 positive GET operations from the production audit;
all seven representative collection reads were revalidated in this session.
Tag and Position CRUD and Event CRUD also have temporary-fixture production
evidence, with cleanup confirmed. Session 10 intentionally did not repeat
destructive checks.
- **Not safely live-verified:** the positive Appointment Product Item response,
Appointment deletion success bodies, and positive Event Appointment writes.
These shapes are verified against the current PHP transformers, controllers,
services, and integration tests. Repeating them in production still requires
a controlled Appointment or Booking User fixture and notification-safety
review.


Acceptance:

- Every positive live response used in the workflow validates against OpenAPI.
- Every operation contains 401, 403, and 404 responses as required by repository
policy, plus actual validation statuses.
- The v2 spec has no v2-specific lint warnings.
- The repository warning baseline does not increase.
- No test fixtures remain.
- The final report distinguishes documented, live-verified, and not safely
verified behavior.


## Required validation for every PR

Run from the repository root unless noted otherwise:

```bash
npm test
cd docs && ../node_modules/.bin/redocly lint en/b2b-v2/openapi.yaml
```

Also run the narrow live probes for the endpoint group. Do not run all
destructive workflows merely to validate a read-only schema change.

For changes to Arazzo or CRUD schemas, run:

```bash
make test-api-b2b-v2
```

After every live write test, query the relevant list endpoint and confirm that
the temporary resource is gone.

## Definition of done for the remediation program

- The published v2 paths use the approved canonical terminology.
- Wire-level literal values are documented accurately, not renamed.
- Success and error schemas validate current production responses.
- Request bodies can be copied from the documentation and used successfully.
- Pagination parameters and metadata match production behavior.
- Unsupported or ignored includes are not advertised as working features.
- Arazzo covers every v2 group and fails on schema drift.
- 404 is never treated as proof that a production v2 endpoint is healthy.
- Destructive tests are isolated, uniquely named, and self-cleaning.
- Redocly reports no v2-specific warnings and the repository warning budget does
not increase.
- Each merged PR reports live verification separately from lint success.


## Handoff template for a future session

Use this prompt with the session number filled in:

> Implement Session N from
`docs/plans/2026-07-15-b2b-v2-production-contract-remediation.md`.
Work only on the listed B2B v2 files. Revalidate production with the required
v2 Accept header, preserve wire names, avoid V1 scope, clean up every temporary
resource, run the required lint/tests, and open a focused PR. Report separately
what was changed, live-verified, and not safely verified.