# UTL MCP Protocol

This file is published to <a href="https://mcp.g3nretailstack.com/utl/PROTOCOL.md" target="_blank" rel="noopener noreferrer">https://mcp.g3nretailstack.com/utl/PROTOCOL.md</a>.


## Usage patterns (headless)
- Stack-wide SOPs & operations catalog: <a href="https://doc.g3nretailstack.com/story/operations.html" target="_blank" rel="noopener noreferrer">/story/operations.html</a>.
- Super-usecase scenarios + QA status: <a href="https://doc.g3nretailstack.com/story/super-usecases.html" target="_blank" rel="noopener noreferrer">/story/super-usecases.html</a>.
- This protocol stays contract-only; use the catalogs for workflow expectations.

## Base URL
- API Gateway: `https://api.g3nretailstack.com/utl`
- Health check: `GET /utl/stat` — requires `requireSession` (any authenticated user).

## Auth + tenancy
- Auth placement: header auth is canonical; body auth is accepted for compatibility. See [/common/headers-identity.html](https://doc.g3nretailstack.com/common/headers-identity.html).
- Auth headers (`x-session-guid`) are required for all API Gateway calls, including `/stat`.
- Tenant API calls (non-`/stat`) require `orgcode` and `session_guid` in the request body.
- Owner-only: non-owners receive `403 forbidden` (anti-enumeration applies).
- Operator flows (approve/export/purge/archive) are direct Lambda only and IAM-gated.
- `session_guid` is never emitted in responses; use `stats.session_fingerprint` for correlation.

## Roles
- Offboarding request/status: `utl_offboarding_admin` (owner implied).
- Offboarding cancel: `utl_offboarding_admin` (owner implied).
- Export request/status/download: `utl_export_admin` (owner implied).
- Health: `/stat` requires `requireSession` (any authenticated user).
Entry-level auth uses `requireSession`; role enforcement (`utl_offboarding_admin`, `utl_export_admin`) is applied inside handlers.

## Surfaces
- Contract-only surface. Implemented and deployed; see `/utl/openapi.yaml` for the current surface definition.

## Endpoint inventory (OpenAPI parity)
The endpoints below are implemented and defined in `/utl/openapi.yaml`. Request/response schema names reference OpenAPI component schemas.

| Method | Path | Request schema | Response schema |
| --- | --- | --- | --- |
| GET | /stat | — | Envelope (data: { ok: boolean }) |
| POST | /offboarding/request | OffboardingRequest | Envelope (data: { offboarding: OffboardingRecord }) |
| POST | /offboarding/cancel | OffboardingCancelRequest | Envelope (data: { offboarding: OffboardingRecord }) |
| POST | /offboarding/status | OffboardingStatusRequest | Envelope (data: OffboardingStatusResponse) |
| POST | /export/request | ExportOnlyRequest | Envelope (data: { export: ExportOnlyRecord }) |
| POST | /export/status | ExportOnlyStatusRequest | Envelope (data: ExportOnlyStatusResponse) |
| POST | /export/download/start | ExportOnlyDownloadStartRequest | Envelope (data: ExportOnlyDownloadStartResponse) |

## Lifecycle
- Offboarding: `requested -> approved -> export_window_open -> exporting -> exported -> purge_pending -> purged -> archived`
- Archive deletion: `archived -> archived_deleted` (operator-only)
- Archive restore: `archived -> archive_restore_pending -> archive_restored`
- Cancel: `requested -> canceled` (before export begins)
- Purge verification is required before archive deletion.
- Export-only: `requested -> exporting -> exported` (plus `failed` on errors, `canceled` before export begins); no freeze.

## Internal jobs (operator-only)
These are direct Lambda handlers (IAM-gated, not API Gateway). Sweep Lambdas are triggered by EventBridge schedules and take no meaningful input. The teardown Lambda is invoked directly by operators.

### `teardown`
Iterates the global `SERVICE_REGISTRY` (OFM, MRS, PVM, PMC, ICS, SCM, PCM, PPM, CRM, Influencer, Accounting, IPM, RBS) and purges all DynamoDB items + S3 objects scoped to the given `orgcode`. Gated by `UTL_ALLOW_TEARDOWN=1` env flag; returns `403 forbidden` otherwise. Supports resumable state: when a service purge is incomplete, the response contains `{ continuing: true, state }` for re-invocation. On completion returns aggregated stats per service.

Request (body): `{ "orgcode": string, "state?": { "service_index": number, "checkpoint?": any, "stats?": array, "current?": object } }`

Response shape (completed):
```json
{ "completed": true, "stats": [{ "service": "ofm", "deleted_items": 0, "deleted_objects": 0, "deleted_docs": 0, "bytes_deleted": 0 }], "summary": { "total_items": 0, "total_objects": 0, "total_docs": 0, "total_bytes": 0, "per_service": {} } }
```

### `offboardingWindowSweep`
Scheduled sweep. Queries approved offboarding requests whose `export_window_start_at` has elapsed. Freezes the org via OFM (`status=frozen`) and transitions the request to `export_window_open`. Emits `utl.offboarding.export.window_opened` event.

Response shape: `{ "opened": number, "skipped": number }`

### `offboardingOverdueSweep`
Scheduled sweep. Scans offboarding requests in `export_window_open` status whose `latest_start_at` has elapsed without an export starting. Flags them as overdue. Emits `utl.offboarding.export.overdue` event.

Response shape: `{ "flagged": number }`

### `offboardingArchiveSweep`
Scheduled sweep. Queries offboarding requests in `exported` or `purged` status whose `export_expires_at` has elapsed. Transitions them to `archived` status. Emits `utl.offboarding.archive.transitioned` event.

Response shape: `{ "archived": number, "skipped": number }`

## Error tags
Common tags (see [/common/error-tags.html](https://doc.g3nretailstack.com/common/error-tags.html) for definitions): `invalid-input`, `invalid-session`, `forbidden`, `not-found`, `invalid-state`, `conflict`, `internal-error`.
Service-specific tags: `export-bucket-missing`.

## Example envelopes
Success envelope (shape-only):
```json
{
  "success": true,
  "data": { "export": { "export_id": "exp-1", "status": "requested" } },
  "revision": "rev-1",
  "stats": { "service": "utl", "call": "export/request", "timestamp_utc": "2026-01-01T00:00:00Z", "build": { "build_major": "MONDAY", "build_minor": "0000000000", "build_id": "MONDAY-0000000000" } }
}
```

Error envelope (shape-only):
```json
{
  "success": false,
  "error": {
    "error_code": "utl.auth_invalid",
    "http_status": 401,
    "retryable": false,
    "major": { "tag": "invalid-session", "message": { "en_US": "session_guid required" } }
  },
  "stats": { "service": "utl", "call": "entry", "timestamp_utc": "2026-01-01T00:00:00Z", "build": { "build_major": "MONDAY", "build_minor": "0000000000", "build_id": "MONDAY-0000000000" } }
}
```

## Idempotency & retries
- All **GET / list / resolve / search** calls are safe to retry with identical inputs (read-only, no side effects).
- **POST mutations** that accept `expected_revision` use optimistic concurrency: on `409 conflict` or `428 expected-revision-required`, re-read the record, obtain the current `revision`, and retry with the updated value.
- Creates are generally **not** idempotent. Prefer caller-provided `code` (where supported) and verify existence before retrying a failed create.
- Bulk or scheduled jobs that accept an `idempotency_key` will de-duplicate within the documented time window.

## Known pitfalls
- **Missing `expected_revision`**: most state-changing operations require it; omitting it returns `428` with the current revision in `error.details`.
- **Stale revision**: reading a record, waiting, then writing with an outdated `revision` triggers `409`. Always use the latest revision from the most recent read.
- **Pagination cursors**: `next_token` is opaque JSON. Do not modify, decode, or persist cursors across sessions — they may change format between deploys.
- **Anti-enumeration 404**: some org-scoped reads return `404` even when the record exists, if the caller is not associated with the org. Treat `404` as ambiguous; verify caller association before assuming "not found".

## OpenAPI
- Contract schema: <a href="https://doc.g3nretailstack.com/utl/openapi.yaml" target="_blank" rel="noopener noreferrer">https://doc.g3nretailstack.com/utl/openapi.yaml</a>
- API Gateway: stat, offboarding request/cancel/status, export request/status/download/start.
- Direct Lambda (operator): teardown, offboarding window/overdue/archive sweeps.


_Build MONDAY-1776194870 • 2026-04-14T19:27:50.000Z • [© 1999 Microhouse Systems Inc. All rights reserved.](https://doc.g3nretailstack.com/common/copyright-license.html)_
