# PRD — arena-plm (standalone rebuild) ## Vision & scope I am rebuilding the Arena PLM → SAP S/4HANA integration as a **proper SAP application that lives in the tenant**. The predecessor (Express.js, `~/projects/sap-apps/arena-plm`) works as a demo but fails the test that matters: a business user inside S/4HANA cannot find it — not in the Fiori launchpad, not in the Custom CDS Views app — because everything was built side-by-side behind standalone URLs with ad-hoc access control. The acceptance test I hold this rebuild to: **a business user finds the application in the S/4HANA Fiori launchpad, finds its data in the Custom CDS Views app, and access is governed by business roles** — with the side-by-side footprint reduced to the one thing that cannot run in the tenant: Arena connectivity. **Amended 2026-09-29.** I wrote that acceptance test assuming I could build in the tenant. I cannot, in the tenant I am now working in: the starter system's customizing tenant `my429480` (client 100) supports key-user extensibility only, so "its data in the Custom CDS Views app" is not reachable there and the RAP half of the test is withdrawn (ADR-004). What I keep is the part that actually mattered: **a business user finds the application from the S/4HANA launchpad and access is governed by business roles.** In scope: material, BOM, change-master, and document sync from Arena to S/4HANA, gated by a human approval workflow. Out of scope: every other package of the old estate — one package at a time. ## Target architecture (ADR-004, 2026-09-29 — supersedes ADR-002) The starter system's **customizing** tenant `my429480` (client 100) supports **key-user extensibility only** — no ABAP, no RAP. ABAP development requires the starter system's **development** tenant `my431353` (client 080), which I parked on 2026-09-29 [source: fiori-tools-samples `misc/s4hana/README` "Tenant Types", verified 2026-09-29]. The on-stack RAP business object of ADR-002 therefore has no home in the tenant I am now working in, and the application moves back out to a side-by-side runtime consuming released APIs. ``` Arena PLM ──webhook + delta poll──▶ Connectivity worker (BTP CF, hardened TS) │ built, 137 tests green, NOT deployed ▼ Approval store + state machine + audit (side-by-side — host is the open call, OQ-11) │ Fiori Elements List Report / Object Page (`app/` — already built, service-repointable) │ released OData APIs, mc-s4hana ┌─ Starter system · CUSTOMIZING tenant `my429480` (client 100) ─────────────┐ │ Product / BOM / ChangeMaster / Attachment released APIs ← sync targets │ │ Key-user extensibility + configuration only — no ABAP, no RAP │ │ Launchpad reachability for the app: decided with OQ-11 │ └───────────────────────────────────────────────────────────────────────────┘ parked 2026-09-29 ──▶ Starter system · DEVELOPMENT tenant `my431353` (080) 19 RAP objects live in ZARENA_PLM, left in place ``` **Build state (2026-09-29).** What exists: the Fiori Elements UI `com.minicircuits.arenaplm` in `app/`, built and bound to a mock OData V4 service; the connectivity worker in `worker/`, built with 137 tests green and never deployed (no deployment descriptor was ever written); and 19 activated RAP objects on the parked development tenant, with their sources kept in `docs/handover/rap/` as reference. Nothing from this package has ever been deployed to Cloud Foundry or reached a business user. ## Systems & boundaries | System | Role | Boundary | |---|---|---| | Starter system, **customizing** tenant (`my429480`, client 100) | **Sync target** and configuration home | Key-user extensibility only — no ABAP, no RAP; released OData APIs via `mc-s4hana` | | Starter system, **development** tenant (`my431353`, client 080) | **Parked 2026-09-29** — former RAP application host | Developer extensibility; 19 objects left active in `ZARENA_PLM`, no transport recording | | Arena PLM (`api.arenasolutions.com/v1`) | Source of items, BOMs, changes, files | REST; webhook + delta poll, handled by the worker | | Connectivity worker (BTP CF) | Arena connectivity + initial sync executor | Machine-to-machine only; technical user via communication arrangement | ## Interface contracts Carried from the predecessor, in live use (2026-09-04); re-verify on first POC touch. The worker keeps calling these on approval, so every KNOWLEDGE.md tombstone remains valid. | API | OData | Path | Notes | |---|---|---|---| | Product | V4 | `/sap/opu/odata4/sap/api_product/srvd_a2x/sap/product/0002` | deep insert for plant extensions | | Product (plant fix) | V2 | `/sap/opu/odata/sap/API_PRODUCT_SRV` | `AvailabilityCheckType` is V2-only | | BOM (write) | V2 | `/sap/opu/odata/sap/API_BILL_OF_MATERIAL_SRV;v=0002` | header first, then sequential items | | BOM (read) | V2 | `/sap/opu/odata/sap/API_BILL_OF_MATERIAL_SRV` | v1 only; v2 rejects guid keys | | Change Master | V2 | `/sap/opu/odata/sap/API_CHANGEMASTER;v=0002` | object-type assignments, material linking | | Attachment | V2 | `/sap/opu/odata/sap/API_CV_ATTACHMENT_SRV` | function import for URL creation | | Approval BO — UI binding (Fiori app) | V4 UI | `/sap/opu/odata4/sap/zui_arena_change_appr_o4/srvd/sap/zui_arena_change_approval/0001/` (binding `ZUI_ARENA_CHANGE_APPR_O4` on definition `ZUI_ARENA_CHANGE_APPROVAL`; designed 2026-09-16, not yet created) | entity set `ChangeApproval` / type `ChangeApproval_Type`, actions `approve`, `reject(RejectReason)`, `retrySync`; namespace, EDM types and the URL's service alias segment are confirmed from live `$metadata` after publish (V-2). The Fiori app still points at `zui_arena_change_approval_o4`, and V-2 reconciliation moves it | | Approval BO — Web API (A2X) binding (worker) | V4 Web API | binding `ZAPI_ARENA_CHANGE_APPR_A2X` on definition `ZAPI_ARENA_CHANGE_APPROVAL` (designed 2026-09-16; created after the A2X projection is active) | custom communication scenario, technical user; namespace from live `$metadata`; exposes create + read + `markExecuting` / `markSynced(SapChangeNumber)` / `markFailed(ErrorMessage)` (RAP-6) | | Worker inbound surface | REST (OpenAPI 3.1) | `docs/api/worker.openapi.json` | webhook intake (503 when buffer full), health (`ok`/`degraded`, session `none/active/expired/refreshing`), sync-run admin (strict request schema); contract v0.2.0 (2026-09-14 wave); auth scheme pending OQ-6 (ARN-009) | ## Requirements Capability level — field mappings deferred to the POC per my POC-before-spec rule. | ID | Requirement | Status | |---|---|---| | ARN-001 | Sync chain PRODUCT → CHANGE_MASTER → BOM → DOCUMENTS in config-driven order, executed by the worker on approval | Planned | | ARN-002 | Approval domain as an on-stack RAP BO: staging fields, state machine PENDING → APPROVED → EXECUTING → SYNCED \| FAILED, REJECTED terminal, FAILED → retrySync → APPROVED (amended 2026-09-14: a terminal success state is required so an approved record is never executed twice; amended 2026-09-16: retry returns the record to APPROVED so the worker re-claims it through `markExecuting`, the only way into EXECUTING. A retry that jumped straight to EXECUTING was never picked up, or it risked a second concurrent execution); duplicate staging of the same `ItemNumber`+`ChangeType`+`ArenaEcoId` is refused on create while a non-REJECTED record exists (2026-09-16); approve/reject/retrySync as RAP actions with instance feature control; audit fields (`RequestedBy`/`RequestedOn`/`DecidedBy`/`DecidedOn`); execution failure is a distinct state, never overloaded onto rejection (predecessor defect, `docs/predecessor-flow.md` §2) | Planned | | ARN-003 | Durable persistence & audit — satisfied structurally by in-tenant RAP persistence (the predecessor lost its queue on every restart) | Planned (by design) | | ARN-004 | Business mappings maintained in-tenant — evaluate Business Configuration maintenance objects (Custom Business Configurations app) before any file-based config | Planned | | ARN-005 | Arena ingestion via webhook + delta poll in the connectivity worker | Planned | | ARN-006 | Idempotent SAP writes, SAP-as-ledger (see `KNOWLEDGE.md`) — predecessor BOM/document syncs were non-idempotent; executors must check existence first | Planned | | ARN-007 | Access exclusively via the Fiori launchpad with IAM app / business catalog / business roles; no user-facing standalone URLs | Planned | | ARN-008 | Approval data discoverable via custom CDS views, joinable to released views (the discoverability test); look up join targets with `object_type=CDS_STOB` in `sap-released-objects` (no `DDLS` type in the public_cloud dataset, KNOWLEDGE 2026-09-14) | Planned | | ARN-009 | API-first: every interface this package owns carries an OpenAPI 3.1 JSON contract under `docs/api/`, authored before or with the change; implementation conforms to the contract | Adopted (2026-09-10) | | ARN-010 | The approved payload is the executed payload: the executor writes the staged data the approver saw — never re-fetch-and-recompute at execution time (the predecessor discarded the preview, making approval decorative; `docs/predecessor-flow.md` §2) | Adopted (2026-09-10) | ### RAP-track requirements harvested from the UI (2026-09-14) Derived while binding the Fiori app; each becomes a `rap-developer` brief once V-1 opens the development tenant. | ID | Requirement | Source | |---|---|---| | RAP-1 | Behavior definition declares `approve`, `reject`, `retrySync` with `( features : instance )` and implements `get_instance_features` so the UI service emits `Core.OperationAvailable`; approve/reject only for PENDING, retrySync only for FAILED. `approve` writes no `SapChangeNumber` (execution does). The CDS emits numeric `StatusCriticality` 2/2/5/3/1/1 for PENDING/APPROVED/EXECUTING/SYNCED/REJECTED/FAILED (5 = Information is V4-only) | [source: abap-docs /abap-docs-standard/ABENBDL_ACTIONS_FC; sap-docs /abap-fiori-showcase/01_general_features#operations-and-actions, verified 2026-09-14] | | RAP-2 | `reject` takes a mandatory `RejectReason` (char 255) parameter; `approve`/`retrySync` return `$self`; the entity is read-only from the UI (no create/update/delete). **Partly met for the POC (2026-09-16):** the handler refuses an empty reason server-side. A metadata-level mandatory marker needs a root abstract entity, an abstract BDEF with `field ( mandatory : execute )` and a `deep parameter`, and is deferred until after the UI POC [source: abap-docs /abap-fiori-showcase/01_general_features #MandatoryParameter; /abap-docs-cloud/ABENBDL_ACTION_INPUT_PARAM, verified 2026-09-16 by my review] | UI contract (annotation.xml, mock metadata) | | RAP-3 | `Status` carries a text element (`@ObjectModel.text.element`, `@UI.textArrangement: #TEXT_ONLY`) so the UI shows text, not codes | [source: fiori-mcp-server "Text and Text Arrangement", verified 2026-09-14] | | RAP-4 | Semantic key `ItemNumber` (`@ObjectModel.semanticKey`); `Status` and `RequestedOn` sortable/filterable | UI default variant | | RAP-5 | (Amended 2026-09-16.) Two service definitions, one binding each. `ZUI_ARENA_CHANGE_APPROVAL` exposes the UI projection as `ChangeApproval` through the OData V4 **UI** binding `ZUI_ARENA_CHANGE_APPR_O4`. `ZAPI_ARENA_CHANGE_APPROVAL` exposes the A2X projection through the OData V4 **Web API** binding `ZAPI_ARENA_CHANGE_APPR_A2X`. Binding names are 26 characters or fewer. The binding name sets the URL's first segment; hand back the UI binding's live `$metadata` to reconcile (V-2). The single-definition wording of 2026-09-14 could not hold, because a projection BDEF exposes only what it lists | [source: sap-docs sap-help loio 5717c0a1dafc4335add0207b81c39cef, 5f45f125dfe84c20beb18f5de1512f5a, verified 2026-09-14; binding-name limit: sap-docs sap-help loio 4bad0d7280b742a68dd2d42b1441f09a, verified 2026-09-16; abap-docs /abap-docs-cloud/ABENBDL_USE_PROJECTION, verified 2026-09-15] | | RAP-6 | Worker-callable execution-state actions on the A2X binding — `markExecuting`, `markSynced` (sets `SapChangeNumber`), `markFailed` (sets `ErrorMessage`) — with instance feature control so the UI never offers Approve on an executing record | [source: abap-docs /abap-docs-cloud/ABENBDL_ACTIONS_FC, verified 2026-09-14] | | RAP-7 | Staged payload (`SapPreview`) is `abap.string` — CDS char elements are capped at 1333 characters and truncation would break ARN-010 | [source: abap-docs /abap-docs-cloud/ABENCDS_SELECT_LIST_ENTRY_V2, verified 2026-09-14] | | RAP-8 | Field `DecidedBy` (not `ApprovedBy`) holds approver or rejecter; semantic key `ItemNumber, ChangeType, ArenaEcoId` | UI contract | ## Architecture decisions - ADR-001 — toolchain-must-engage rebuild; Express predecessor reference-only. **Amended by ADR-002.** - ADR-002 — on-stack first: RAP + Fiori Elements in the tenant; side-by-side reduced to a connectivity worker; CAP exits the architecture. **Superseded by ADR-004.** - ADR-004 (2026-09-29) — I withdraw the on-stack RAP route and move the application back to a side-by-side runtime against released APIs. Two facts force it: the customizing tenant `my429480` (client 100) supports key-user extensibility only, and I am no longer willing to carry an ABAP/RAP build for this package. I park the development tenant `my431353` (client 080) with its 19 activated objects in place rather than delete them, so the route stays reversible. The replacement host — key-user extensibility in the tenant versus a CAP service side-by-side — is OQ-11; my recommendation is CAP, because the connectivity worker must be side-by-side regardless and one runtime beats two. ## Verification items & open questions | ID | Item | Status | |---|---|---| | V-1 | Developer-extensibility access on the development tenant | **Closed 2026-09-15** — `my431353.s4hana.cloud.sap`: business user AZAW holds `ZBR_DEVELOPER` (SAP_BR_DEVELOPER template), `ZBR_ADMINISTRATOR_SW_DEV`, `ZBR_DEVELOPMENT_MANAGER_DIN` and five more (live: `sap-api users getRoles`, verified 2026-09-15). V-1b done 2026-09-15 (destination `0M7DEJI`, MCP lists it). Role correct (A4C catalogs). V-1b **closed 2026-09-15**: package `ZARENA_PLM` under `ZLOCAL`; probe class created and activated via ADT MCP | | V-2 | Custom communication scenario can expose the approval BO's A2X service to the worker's technical user; and the Fiori app's annotation namespace, `_Type` entity-type suffix and UI-binding URL segment are reconciled from the live `$metadata` of the UI binding (decided 2026-09-14; the mock cannot verify this) | Open. **UI half in progress 2026-09-16:** live `$metadata` is captured in `docs/v2/zui_arena_change_appr_o4.metadata.xml`, and the UI host serves it to the developer user. The contract holds: all 17 property types, key, entity set, the three actions, and `RejectReason` non-nullable. Identity differs from the mock: namespace `com.sap.gateway.srvd.zui_arena_change_approval.v0001`, entity type `ChangeApprovalType`, URL segment `zui_arena_change_appr_o4`, and backend operation control via `__OperationControl`. **The app was reconciled 2026-09-16:** the dataSource targets `zui_arena_change_appr_o4`; the mock serves the live `$metadata` byte-for-byte; annotation targets use the live namespace and entity type; enablement is backend-owned; no old identity strings remain; lint, typecheck and `@ui5/linter` are clean; the mock server serves the service. Unverified until a live preview: the `DataFieldForAction` action-string form versus the backend's overload-specific `OperationAvailable` target. The A2X half (custom communication scenario, `-api` host) is still open. The service URL reported on `-api` carries `sap-client=080` | | V-3 | On-stack Application Jobs vs worker-side polling for the Arena delta | Open — flow findings 2026-09-10: Arena's outbound-integration queue holds the cursor (`reconciled` flag) and is the clean delta mechanism; `/changes`-feed polling needs client-side date filtering (`docs/predecessor-flow.md` §1) | | V-4 | Can a Claude Code agent outside the VS Code editor author ABAP source through the ADT MCP Server? | **Answered 2026-09-15: no.** `create_object` and the generators take structure, not source (live probe); SAP's editing MCP tool is planned for Public Edition 2702 [source: sap-docs loio 7f716223a88c40e0992777c8d9febccf, verified 2026-09-15]. Build plan: `x-ui-service` generator for the BO skeleton, source edits handed over as files for the VS Code editor until 2702 | | OQ-5 | Long-term: move the sync executor on-stack (released ABAP APIs) vs keep it in the worker — worker initially | Open | | OQ-6 | Webhook receiver hosting and auth for the worker on CF | Open | | OQ-9 | Agent-side ABAP source read/write through the community `abap-adt-mcp` server (npm 2.1.0, a single maintainer, published 2026-09-16 01:04Z, SLSA provenance attested). It lists `getObjectSource`, `setObjectSource` and `activateObjects` next to 170 other tools, enforces `readOnly` / `allowedPackages` policy server-side, and signs in to Public Cloud through a one-time browser SSO. It would remove the manual paste step that SAP's ADT MCP forces (V-4). Checked 2026-09-16: a read-only smoke run with no login and no SAP call listed 173 tools and enforced a `ZARENA_PLM`-only read-only policy; nothing is registered. Adoption needs, in order: a source review of the package, because it would hold an SSO session into the tenant; a version pin; the development tenant only, never `my429480`; `allowedPackages: ["ZARENA_PLM"]`; read-only first; then a CLAUDE.md routing change | Open (2026-09-16), not blocking. I want it as the replacement for manual pasting; the read-only source review of the cached package (03:15) cleared every check it ran, observed as it ran. It stalled while collecting line citations for its report and was stopped at ~03:28, so no written report with file:line evidence exists. I accept it for the development tenant under the read-only-first configuration. Registered by hand in project scope at 03:37: stdio `npx -y abap-adt-mcp@2.1.0`, `MCP_READ_ONLY=1`, audit file on, `~/.abap-adt-mcp/systems.json` (development tenant `my431353`, client 100, SSO, `readOnly: true`, `allowedPackages: ["ZARENA_PLM"]`, mode 600). The other six project servers are intact. It connected after the restart, and its policy checked out live. **Parked 2026-09-16 03:49:** SSO needs an interactive sign-in inside the server's own Chrome, on the server's desktop (`:0`). That window is unreachable over my multi-hop SSH remote access; a DevTools relay was tried and dropped. The server stays registered but unused. It is revisited when a session at the server's desktop can complete one sign-in with "stay signed in", after which logins run unattended. The reentrance-ticket path was built and tested on 2026-09-18 (`tools/adt-reentrance-login/`) and **rejected on evidence**: the tenant ignores the ticket as both a `MYSAPSSO2` cookie and header, answering with its SAML redirect page (KNOWLEDGE 2026-09-18). Reviving OQ-9 requires capturing how ADT for VS Code exchanges the ticket. Until then, tenant work stays on SAP's ADT MCP plus editor paste. Until then, the UI leg continues with SAP's `adt-mcp` plus editor paste. The registration is run by hand, because Claude Code's permission guard refuses agent-side changes to its own MCP configuration (03:17, nothing written). The target is the development tenant, client 100, SSO login, first with `readOnly: true` + `allowedPackages: ["ZARENA_PLM"]` + `MCP_READ_ONLY=1`, then write enabled for `ZARENA_PLM` only | | OQ-10 | Ownership of UI annotations: the backend metadata extension `ZC_CHANGEAPPROVAL` or the app's local `annotation.xml`. Today 35 of the 58 local annotations override a backend annotation with the same target, term and qualifier (layering rule [source: fiori-mcp-server "Visualizing and Managing Annotations with SAP Fiori Tools - Service Modeler", verified 2026-09-16]), so a DDLX change to those terms is invisible in the app. The local file adds i18n labels, action criticality, the `#Default` variants and the facet IDs the OPA journey relies on | Open (2026-09-16). Interim rule: a term defined locally is changed locally. I consolidate before launchpad deployment | | OQ-8 | Per-operation authorization for the approval BO. Today it is `authorization master ( none )`: any holder of the UI app can approve, reject and retry, and any tenant ABAP code can create and mark* through EML. SAP Help calls `( none )` "not recommended in real-world scenarios". The target is `( global )` with a `FOR GLOBAL AUTHORIZATION` handler and `AUTHORITY-CHECK OBJECT` on a custom authorization object: decisions for business roles, create/mark* for the communication role. First verify that custom authorization objects can be created on `my431353` [source: abap-docs /abap-docs-cloud/ABENBDL_AUTHORIZATION, ABAPHANDLER_METH_GLOBAL_AUTH; sap-docs sap-help loio 673c05562e144702a080c574c3da2da3, verified 2026-09-16 by my review] | Open (2026-09-16): does not block the dev-tenant UI POC; must close before any business role other than the developer's gets the app (IAM brief) | | OQ-7 | Tenant object naming and cleanup for the approval BO | **Closed 2026-09-16.** The package was deleted in ADT, contents and package itself (verified live through ADT MCP validation, 2026-09-16). I build from scratch, without the generator: table `ZCHANGEAPPROVAL`, `ZR_CHANGEAPPROVAL`, `ZBP_R_CHANGEAPPROVAL`, `ZC_CHANGEAPPROVAL`, SRVD `ZUI_ARENA_CHANGE_APPROVAL` with SRVB `ZUI_ARENA_CHANGE_APPR_O4`, and SRVD `ZAPI_ARENA_CHANGE_APPROVAL` with SRVB `ZAPI_ARENA_CHANGE_APPR_A2X`. Binding names follow SAP's 26-character limit (RAP-5). Entity set is `ChangeApproval`; the A2X projection and parameter entity names are unchanged | | OQ-11 | Where the approval store, state machine and audit live now that ADR-004 withdrew the RAP BO. Option A: key-user extensibility in the customizing tenant (custom business object + low-code UI) — stays in the tenant and in the launchpad natively, but the Arena connectivity still needs an outside worker, so the application ends up with two homes, and key-user objects are transport-bound. Option B: a CAP service side-by-side on BTP CF — one runtime with the worker, matches the archetype the rest of my estate already runs, and `app/` repoints to it cheaply because 35 of its 58 annotations are already local (OQ-10); the cost is BTP runtime and launchpad reachability becomes a federation question. | Open (2026-09-29). I recommend Option B. Decide before any further build. | | OQ-12 | The ABAP/RAP-dependent items V-2, V-3, OQ-5, OQ-8, OQ-9 and OQ-10 are all scoped to the parked development tenant. They are **parked with ADR-004**, not closed; each returns only if the RAP route is revived. | Parked (2026-09-29) | ## Status | Work item | State | |---|---| | Package home + governance | Done (2026-09-04) | | Developer environment | Done (2026-09-04); MCP loadout revised to ABAP/RAP set (done 2026-09-04) | | Ground-truth enforcement kit (ADR-003 hooks) | Done (2026-09-04); hardened 2026-09-14 — Bash/MultiEdit/filesystem writes gated, grounding-only marker refresh, portable paths, fail-closed, 37 pipe-tests green | | Specialist agent roster (5 agents, docs/agent-roster.md) | Done (2026-09-04; all 5 files YAML-verified in .claude/agents/) | | V-1…V-3 verification POC | V-1 **Closed 2026-09-15** on the development tenant. V-2/V-3 **parked 2026-09-29** with ADR-004. The old note that V-1 meant "ADT access on my429480" was wrong: `my429480` is the customizing tenant and grants no developer extensibility at all | | Fiori Elements app (`app/`, mock-bound) | Done for the mock stage (2026-09-14) — gateway service shape (binding-derived identity), six-state domain, polished List Report/Object Page, launchpad inbound `ArenaChangeApproval-manage`; SAP review findings closed. Deploy gated on V-1/V-2 (live `$metadata` reconciliation) | **2026-09-29: still mock-bound and never deployed.** Its target service is now OQ-11, not the RAP UI binding | | RAP BO (approval domain) | **Parked 2026-09-29 (ADR-004).** Complete and active on the development tenant `my431353`: 19 objects in `ZARENA_PLM`, ATC 0 errors, UI service published 2026-09-16 04:26, A2X binding unpublished. I leave every object in place and keep the sources in `docs/handover/rap/`. Never reached a business user: no IAM app, no business catalog, no business role (ARN-007 never started), and `authorization master ( none )` throughout | | Connectivity worker | Review wave Done (2026-09-14) — TTL wiring, cursor-after-success, buffer-full = tick failure, health conformance, strict JSON-only validation, bounded buffer, graceful drain, HTTP timeout, fixed-phrase errors; 137 tests green incl. the schema-driven contract-conformance suite. Next: Arena client POC (live), executor leg gated on V-2. **Do not deploy the scaffold**: the ingest buffer has no consumer yet, so a long-running instance would answer 503 after `WEBHOOK_QUEUE_MAX_SIZE` events. Next: Arena client POC (live), executor leg gated on V-2 | | Worker API contract (`docs/api/worker.openapi.json`) | v0.2.1 Done (2026-09-14) — health enums/`degraded`/`poller`, strict `SyncRunRequest`, webhook 503, 413/415 + `default` on every operation, reserved 502, `DOCUMENTS` token; `x-sap-stateInfo: beta`, provisional webhook schema pending Arena POC |