Sync current sources: clean RAP handover names, app rebind, tooling

Working-tree state as of 2026-09-29, uncommitted since the initial
"upload current sources" commit:

- docs/handover/rap/: renamed the 11 generator-suffixed sources
  (zz*000) to the clean object names built on the tenant, plus the
  A2X leg and parameter-entity edits.
- app/: manifest, annotations, i18n, mock data and local/mock ui5
  configs updated to the live service shape.
- PRD.md, CHANGELOG.md, KNOWLEDGE.md: build record through
  2026-09-19, including the rejected reentrance-ticket path.
- tools/adt-reentrance-login/: retained login helper (new).
- docs/v2/zui_arena_change_appr_o4.metadata.xml: captured live
  $metadata (new).
- Dropped a stray 0.3GB-class Eclipse installer that the initial
  commit had tracked under a URL-mangled filename, and the
  machine-specific 0M7DEJI.code-workspace.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-29 10:22:52 -07:00
co-authored by Claude Opus 5
parent f8b45ea4a5
commit 468729eb4e
42 changed files with 4597 additions and 1680 deletions
-8
View File
@@ -1,8 +0,0 @@
{
"folders": [
{
"name": "0M7DEJI(ABAP)",
"uri": "adt://0m7deji"
}
]
}
+699
View File
@@ -2,6 +2,705 @@
Append-only decision log for this package. Append-only decision log for this package.
## 2026-09-18 — Reentrance-ticket login for `abap-adt-mcp`: built, tested, rejected
- What changed: I built a login helper at `tools/adt-reentrance-login/`
(Node, no dependencies): a one-shot loopback listener that captures an
ADT reentrance ticket from the `redirect-url=http://localhost:<port>/adt/redirect`
flow, writes it to a 0600 file, and probes how the tenant accepts it. It
never prints, logs or passes the ticket through argv or the environment,
and it parses the raw query itself, because `URLSearchParams` turns a
`+` in a ticket into a space and would corrupt it. Then I captured a real
ticket and tested it: `/sap/bc/adt/core/discovery` and an ABAP class
source URL answered 200 with the same 3094-byte SAML redirect page for
cookie transport, header transport and no credential, with and without
`sap-client=080`.
- Why: `abap-adt-mcp`'s browser SSO needs a window on the server's desktop,
which is unreachable over this remote session (OQ-9). ADT for VS Code
logs on with a reentrance ticket instead, so the question was whether
that ticket can feed the server's `sso2` (MYSAPSSO2 cookie) mode.
- Affected: new `tools/adt-reentrance-login/`, `~/.abap-adt-mcp/systems.json`
(switched to `sso2`, then restored to `sso`), KNOWLEDGE.md, PRD OQ-9.
- Decision: the approach is rejected on evidence. A reentrance ticket is
neither a logon-ticket cookie nor an assertion-ticket header on this
tenant, so `sso2` cannot consume it. I restored the browser-login
configuration, shredded the captured ticket, and kept the helper: it is
the only piece that would still be needed if the exchange mechanism is
ever identified. The next step, if this is revisited, is to capture what
ADT for VS Code actually sends, not to guess header names. Tenant work
continues on SAP's ADT MCP with editor paste, which is proven.
## 2026-09-16 — Worker leg active: approval BO complete on the development tenant
- What changed:
- I activated W3 (A2X projection BDEF) and W4 (service definition
`ZAPI_ARENA_CHANGE_APPROVAL`).
- I created and activated W5, the OData V4 Web API binding
`ZAPI_ARENA_CHANGE_APPR_A2X`, with binding type `OData V4 - Web API`
(`create_object` returned `.srvb.json`).
- `fetch_services`: service `ZAPI_ARENA_CHANGE_APPROVAL`, version `0001`,
V4, `isPublished: false`, `bindingCategory: "1"`.
- ATC over the five worker objects (worklist
`12ACF6BD93811FE1ACBA776536DD5BD6`): 0 errors and one priority-3
warning, W607 (+BDL 549) on BDEF `ZC_CHANGEAPPROVAL_A2X`: "CDS annotation
#CHECK … should be replaced with #MANDATORY".
[source: adt-mcp abap_activate_objects, abap_creation-create_object,
abap_atc_get_result, abap_business_services-fetch_services on 0M7DEJI,
verified 2026-09-16]
- Why: RAP-6. The worker needs create and the three execution-state
actions through a Web API, separate from the read-only UI.
- Affected: dev tenant `ZARENA_PLM` (all 19 approval-BO objects now
active), PRD RAP BO status, KNOWLEDGE.md.
- Decision: W5 stays unpublished. It is exposed to the worker's technical
user through a custom communication scenario (V-2 A2X half), not through
business-user access. I do not switch
`@AccessControl.authorizationCheck` from `#CHECK` to `#MANDATORY` now:
the warning does not block activation, and the change belongs with the
OQ-8 authorization work, which revisits all three projection and root
views together (the same annotation is in `ZR_CHANGEAPPROVAL` and
`ZC_CHANGEAPPROVAL`).
## 2026-09-16 — Worker leg: A2X projection view and access control active; A2X BDEF created
- What changed: I activated W1 (`ZC_CHANGEAPPROVAL_A2X` projection view)
and W2 (its access control); both answered "Activation successful."
I then created W3, the projection BDEF `ZC_CHANGEAPPROVAL_A2X`, empty
(`.bdef.abdl`), after W1 was active [source: adt-mcp
abap_activate_objects, abap_creation-create_object on 0M7DEJI, verified
2026-09-16]. W4 (service definition) is saved but not active.
- Why: worker leg round 2 of the approval BO.
- Affected: dev tenant `ZARENA_PLM`, PRD RAP BO status.
- Decision: W4 activates only after W3 is active. Then W5, the Web API
binding, is created and verified unpublished.
## 2026-09-16 — Worker leg round 1 created; handover brought in line with live results
- What changed: after the expired VS Code ADT logon was restored, I
created W1 (`ZC_CHANGEAPPROVAL_A2X` view), W2 (its access control, with
`protectedEntity`) and W4 (service definition
`ZAPI_ARENA_CHANGE_APPROVAL`) empty in `ZARENA_PLM` [source: adt-mcp
create_object on 0M7DEJI, verified 2026-09-16]. I also corrected
`docs/handover/rap/`: both service definition sources are comment-free,
the README records the UI leg as active, ATC-clean and published, the
activation-URI table marks the proven extensions (`.tabl.ddic`,
`.bdef.abdl`, `.srvb.json`), and the worker-leg sequence is rewritten to
two paste rounds. All 31 existing citations were preserved.
- Why: worker (A2X) leg of the approval BO (RAP-6). The handover must not
repeat today's failed activations.
- Affected: dev tenant `ZARENA_PLM`, `docs/handover/rap/`, KNOWLEDGE.md.
- Decision: pastes are opened manually from the ABAP workspace tree. I only
give the local source path and the `abap:/repotree-v1/…` target, because
editor tabs opened by URI cannot save. W5 uses binding type `OData V4 -
Web API`, the exact value the create metadata offers. W5 stays
unpublished until the custom communication scenario brief (V-2).
## 2026-09-16 — Fiori app bound to the live RAP service identity (V-2 UI half)
- What changed: I reconciled `app/` with the published service.
- Manifest dataSource and mock-server URL paths now use
`/sap/opu/odata4/sap/zui_arena_change_appr_o4/srvd/sap/zui_arena_change_approval/0001/`.
- The mock `metadata.xml` is the live `$metadata`, byte-identical (sha256
`c13e86e1…`, checked).
- Mock rows carry `StatusText`, `LocalLastChangedAt`,
`__OperationControl` and `SAP__Messages`.
- All annotation targets and action strings use namespace
`com.sap.gateway.srvd.zui_arena_change_approval.v0001` and entity type
`ChangeApprovalType`.
A grep for the old identity over `app/` (excluding `node_modules`/`dist`)
returns nothing. Lint, typecheck and `@ui5/linter` are clean. The mock
server answers `$metadata` and `ChangeApproval` with `$filter`, `$count`
and `__OperationControl` selects.
- Why: V-2 and ARN-007. The app must address the service that exists.
- Affected: `app/` (manifest, `ui5-mock.yaml`, `ui5-local.yaml`,
`annotation.xml`, mock metadata and data, README), PRD V-2, new OQ-10,
KNOWLEDGE.md.
- Decision:
- (1) Action enablement belongs to the backend. The mock's local
`OperationAvailable` overrides went away with the mock metadata, and
`annotation.xml` deliberately defines none, because a local one would
replace RAP's `_it/__OperationControl/<action>` [source:
fiori-mcp-server "Action Control for Context-Dependent Actions",
verified 2026-09-16].
- (2) No real backend is configured in the UI5 tooling yet. Any live
preview targets the UI host, never `-api`. Its authentication type and
client are verified before use, not assumed.
- (3) UI annotation ownership is opened as OQ-10 rather than moved now:
consolidating would drop i18n labels and action criticality and break
the OPA section IDs mid-POC.
- (4) `dist/` is stale and must be rebuilt before any deployment.
## 2026-09-16 — Live `$metadata` captured: backend contract matches the app; identity names differ
- What changed: the published UI service's `$metadata` is captured in
`docs/v2/zui_arena_change_appr_o4.metadata.xml`. It was copied from the
browser on the UI host `my431353.s4hana.cloud.sap`; browser-decoded `&`
characters were re-escaped. The `-api` host refused the same request to a
business user ("blocked by UCON").
- Matches the app: all 17 app properties with identical EDM types
(`Edm.Boolean` flags, `Edm.Byte` criticality, `Edm.DateTimeOffset`
Precision 7), key `ID`, entity set `ChangeApproval`, and the actions
`approve`, `reject(RejectReason)` (non-nullable) and `retrySync`.
- Differs from the mock: namespace
`com.sap.gateway.srvd.zui_arena_change_approval.v0001` (from the service
definition), entity type `ChangeApprovalType`, URL segment
`zui_arena_change_appr_o4`, extra `StatusText` / `LocalLastChangedAt` /
`__OperationControl` / `SAP__Messages`, and backend-driven
`Core.OperationAvailable`.
I dispatched the app reconciliation.
- Why: V-2. The app was built against a provisional mock identity, and only
the live service can settle it.
- Affected: PRD V-2, `app/` (in progress), KNOWLEDGE.md.
- Decision: the live `$metadata` replaces the mock metadata and becomes the
app's contract. Button enablement comes from the backend's
`__OperationControl` rather than local `IsApprovable`/`IsRetriable`
overrides, unless the Fiori docs show that breaks something. Any
non-mock backend configuration points at the UI host, never `-api`.
Earlier notes claiming the binding name sets the namespace are superseded
(KNOWLEDGE 2026-09-16).
## 2026-09-16 — Approval UI service published on the development tenant
- What changed: the OData V4 UI binding `ZUI_ARENA_CHANGE_APPR_O4` is
published. The ADT for VS Code Publish code lens gave no visible
confirmation, so I verified it live: `fetch_services` reports
`isPublished: true`, and `fetch_service_information` returns
`https://my431353-api.s4hana.cloud.sap/sap/opu/odata4/sap/zui_arena_change_appr_o4/srvd/sap/zui_arena_change_approval/0001/?sap-client=080`
with entity set `ChangeApproval` [source: adt-mcp
abap_business_services-fetch_services, fetch_service_information on
0M7DEJI, verified 2026-09-16].
- Why: RAP-5. The Fiori app cannot bind to an unpublished service.
- Affected: PRD RAP BO status, V-2, KNOWLEDGE.md.
- Decision: the URL path is exactly the designed one, so no binding change
follows. The app's V-2 reconciliation (dataSource first segment,
namespace, annotation targets, EDM types) is done against the live
`$metadata`, never inferred. The service reports client 080, so the
client value in any tool configuration comes from the live system.
## 2026-09-16 — UI leg active: approval BO and OData V4 UI service built on `my431353`
- What changed:
- U13, the service definition `ZUI_ARENA_CHANGE_APPROVAL`, activated after
I re-saved it without its header comment. The same definition failed
with the comment and succeeded without it, so the comment was the
blocker; the missing-draft message is not.
- I created U14, the OData V4 UI binding `ZUI_ARENA_CHANGE_APPR_O4`
(`create_object` returned `.srvb.json`), and activated it.
- ATC over all 14 UI-leg objects: 0 errors, 0 warnings, 43 infos
(worklist `12ACF6BD93811FE1ACB8096AC6763BD6`).
- `fetch_services`: service `ZUI_ARENA_CHANGE_APPROVAL`, version `0001`,
OData V4, unpublished.
[source: adt-mcp abap_activate_objects, abap_creation-create_object,
abap_atc_get_result, abap_business_services-fetch_services on 0M7DEJI,
verified 2026-09-16]
- Why: RAP-1..8 and the ARN-007 acceptance path. The service must exist
before the app can bind to it.
- Affected: dev tenant `ZARENA_PLM`, PRD RAP BO status, KNOWLEDGE.md,
handover README (both SRVD sources still carry header comments, and U9
still says `.bdef.json`).
- Decision: the service alias equals the definition name and the version
is `0001`. Publishing therefore yields
`/sap/opu/odata4/sap/zui_arena_change_appr_o4/srvd/sap/zui_arena_change_approval/0001/`,
as designed, so no rename is needed before publishing. The 43 ATC infos
are reviewed in the ADT ATC view (the MCP returns only totals); infos do
not gate publishing. I correct the handover README in one pass before
the worker leg: comment-free SRVD sources and BDEF `.bdef.abdl` URIs.
## 2026-09-16 — Wave D: projection BDEF, access control and metadata extension active; service definition refused
- What changed: I activated U9 (projection BDEF `ZC_CHANGEAPPROVAL`), U11
(its access control) and U12 (its metadata extension); each answered
"Activation successful." Activating U13, the service definition
`ZUI_ARENA_CHANGE_APPROVAL`, answered "Activation failed" with two
messages and no severity:
- "Comments are not supported and will be deleted on save" (Ln 1, the
handover header block comment)
- "oData V4 services with no draft capability will be primarily
READ-ONLY" (Ln 21). The BO has no draft, by design.
CDS SDL allows comments as a language rule [source: abap-docs
/abap-docs-cloud/ABENCDS_F1_SDL_SYNTAX, ABENCDS_GENERAL_SYNTAX_RULES,
verified 2026-09-16], so the refusal comes from the service definition
tooling, not the language. U14 (the binding) was not created.
- Why: wave D of the UI leg.
- Affected: dev tenant `ZARENA_PLM`, PRD RAP BO status, KNOWLEDGE.md.
- Decision: I re-save U13 with only its definition (annotation, `define
service`, `expose`) and no header comment, then activate again. If it
still fails with only the draft message, that message is blocking, and I
read its severity in the editor's Problems view before changing anything
about draft.
## 2026-09-16 — `abap-adt-mcp` parked: SSO cannot complete over remote access
- What changed: with `SAP_BROWSER_PATH` set, the SSO login launched
Chrome with a dedicated profile on the server's desktop display and
waited on the Microsoft Entra sign-in page. The login does not use a
localhost redirect: SAML returns to the tenant, and the server harvests
the session cookies from its own Chrome over the DevTools protocol
(`dist/lib/browserLogin.js`). I tried a localhost-only DevTools relay on
a fixed port for remote interaction. It is not workable through
multi-hop SSH proxies, so I stopped it and closed the login browser. No
login happened. The server remains registered, read-only.
- Why: the sign-in has to happen inside the server's browser, and that
browser is on a desktop I cannot reach from this remote session.
- Affected: PRD OQ-9, KNOWLEDGE.md.
- Decision: I park `abap-adt-mcp` rather than remove it. One sign-in at
the server's desktop, with "stay signed in", should let later logins run
unattended in the invisible browser; that is unverified. The UI leg
finishes on the proven path: SAP `adt-mcp` for create and activate,
source pasted in the VS Code editor. Four wave-D sources, one binding and
a publish remain.
## 2026-09-16 — `abap-adt-mcp` connected; SSO blocked on browser detection
- What changed: after the restart, `abap-adt-mcp` connected. `listSystems`
showed exactly one destination, `MY431353` (client 100, SSO), with policy
`readOnly: true` and `allowedPackages: ["ZARENA_PLM"]`. The first SAP call
(`classIncludes` on `ZBP_R_CHANGEAPPROVAL`) failed before any login: "No
Chrome/Edge/Brave found for SSO login." The server's browser detection
covers only macOS paths (KNOWLEDGE 2026-09-16).
- Why: SSO login is the gate to the read-only Local Types check.
- Affected: project MCP registration (env), KNOWLEDGE.md.
- Decision: the registration gains `SAP_BROWSER_PATH=/usr/bin/google-chrome`
and nothing else changes. This is a manual step, like the original
registration.
## 2026-09-16 — `abap-adt-mcp` registered read-only in project scope
- What changed: `abap-adt-mcp` is registered in the project `.mcp.json`
(git-ignored, mode 600). It runs over stdio as `npx -y
abap-adt-mcp@2.1.0` with `MCP_READ_ONLY=1`, an audit log at
`~/.abap-adt-mcp/audit.jsonl`, and the desktop display variables for the
SSO browser. Its systems file lists only the development tenant
`my431353`, client 100, SSO, with `policy.readOnly: true` and
`allowedPackages: ["ZARENA_PLM"]`. The six existing project servers are
unchanged, `adt-mcp` included.
- Why: OQ-9. Agent-side source read and write would replace manual pasting
for the remaining RAP objects.
- Affected: project MCP configuration, PRD OQ-9.
- Decision: the first use is a read-only `getObjectSource` of the behavior
pool's Local Types include. That check settles whether wave C's
activation proved the handler constructs. Write access is enabled only
after that read succeeds. The CLAUDE.md MCP routing table changes only
once the server has written and activated an object successfully.
## 2026-09-16 — `abap-adt-mcp` source review: all checks cleared; written report not produced
- What changed: the read-only source review of `abap-adt-mcp` 2.1.0 cleared
every check it ran (install scripts, network egress, credential and
session storage, policy enforcement, dangerous primitives), observed as it
ran. It then stalled while collecting line citations, consumed its budget,
and was stopped at ~03:28, so no written report with file:line evidence
exists. As of 03:30 the package is not registered, no `~/.abap-adt-mcp`
configuration exists, and no login has happened. The tenant state is
unchanged: waves A–C are active, and wave D (U9, U11, U12, U13) exists
empty.
- Why: OQ-9 was gated on this review.
- Affected: PRD OQ-9.
- Decision: I accept the review for the development tenant only, under the
read-only-first configuration (pinned 2.1.0, `readOnly: true`,
`allowedPackages: ["ZARENA_PLM"]`, `MCP_READ_ONLY=1`, audit file on).
Registration is run by hand. The missing citations mean this clearance
does not extend to the productive tenant. Any future review brief is
scoped to named files with a size budget, because this one ran unbounded.
## 2026-09-16 — `abap-adt-mcp` registration is a manual step; read-only first
- What changed: an attempt to write `~/.abap-adt-mcp/systems.json` and to
register `abap-adt-mcp` in the project MCP configuration was refused by
Claude Code's permission guard as self-modification. Nothing was written.
I confirmed the development tenant logs in on client 100. The package's
SSO login launches Chrome visibly (`headless: false`), so the MCP server
needs the desktop session's display variables.
- Why: I want agent-side source writes (OQ-9), and registration is the
gate.
- Affected: PRD OQ-9, project `.mcp.json` (only once registered by hand),
CLAUDE.md MCP routing (only once adopted).
- Decision: the registration is run by hand, not routed around the guard.
The first configuration is read-only (`policy.readOnly: true`,
`allowedPackages: ["ZARENA_PLM"]`, `MCP_READ_ONLY=1`), limited to the
development tenant, and pinned to 2.1.0. The first login waits for the
source review verdict. Writes are enabled only after a read-only
`getObjectSource` on the behavior pool's Local Types include succeeds.
## 2026-09-16 — Community `abap-adt-mcp` smoke-tested read-only; not adopted yet (OQ-9)
- What changed: I ran `abap-adt-mcp` 2.1.0 once through `npx` over stdio.
It ran with `MCP_READ_ONLY=1` and a scratchpad systems file listing only
the development tenant, restricted to `ZARENA_PLM`. `initialize` and
`tools/list` answered: 173 tools, including source read/write and
activation. `listSystems` showed the read-only, package-restricted policy
enforced. There was no login and no SAP call. Nothing was registered in
any Claude Code or project configuration (verified 03:12). The package
remains in the local npx cache. I opened OQ-9.
- Why: SAP's ADT MCP cannot write source (V-4), so every RAP source is
pasted by hand. A server that can read and write source would remove that
step and would also let me read the behavior pool's Local Types include
directly.
- Affected: PRD OQ-9, V-4 (possible alternative), CLAUDE.md MCP routing (a
change only on adoption).
- Decision: I do not adopt it mid-build. Only four pastes and one publish
remain on the UI leg. The package was published the same day, by one
maintainer, and would hold a browser SSO session into the tenant, so it
gets a source review before any login. I run it read-only first. The
productive tenant `my429480` is never listed in its systems file.
## 2026-09-16 — Wave C active: behavior pool and UI projection view live; wave D created
- What changed: I activated U6 (base BDEF) and U7 (behavior pool
`ZBP_R_CHANGEAPPROVAL`) together in one call, then U8 (UI projection
view `ZC_CHANGEAPPROVAL`). All answered "Activation successful." The
locally cached class metadata shows the class active with category
`behaviorPool`. I then created wave D empty: U9 projection BDEF
`ZC_CHANGEAPPROVAL` (`.bdef.abdl`), U11 access control
(`.dcls.acds`), U12 metadata extension (`.ddlx.acds`) and U13 service
definition `ZUI_ARENA_CHANGE_APPROVAL` (`.srvd.acds`). `SRVD/SRV` also
offers an optional `referencedObject` create field [source: adt-mcp
abap_activate_objects, abap_creation-create_object,
get_object_type_details on 0M7DEJI, verified 2026-09-16].
- Why: wave C of the UI leg. Wave D is the last paste group before the UI
binding.
- Affected: dev tenant `ZARENA_PLM`, PRD RAP BO status, handover README
(BDEF extension `.bdef.abdl` for U9 and W3; SRVD tag list), KNOWLEDGE.md.
- Decision: I do not record `OPTIONS PRIVILEGED ACCESS`,
`%op-%action-<name>` on MODIFY response rows or `@status-rejected` as
proven yet. Activation returns no warnings, and no source says whether a
behavior pool with an empty Local Types include fails activation. I
confirm the handler source is in the Local Types include in the editor
first. Failing that, the first action call at U15 proves it at runtime.
I correct the handover README's BDEF extension and SRVD tag list in one
pass after the UI leg, rather than interrupting the paste session.
## 2026-09-16 — Wave B active: base behavior definition and root access control live
- What changed: I activated U10, the access control `ZR_CHANGEAPPROVAL`,
and U6, the managed `strict ( 2 )` behavior definition
`ZR_CHANGEAPPROVAL`. Both answered "Activation successful." U6 activated
through its `.bdef.abdl` URI on the first attempt, before its behavior
pool class existed. That also proves the three action-parameter entities
it references are active. I then created wave C empty: U7, the behavior
pool `ZBP_R_CHANGEAPPROVAL` (`create_object` returned only the global
`.clas.abap` path, no local-types include), and U8, the UI projection view
`ZC_CHANGEAPPROVAL` (`.ddls.acds`) [source: adt-mcp abap_activate_objects,
abap_creation-create_object on 0M7DEJI, verified 2026-09-16].
- Why: wave B of the UI leg.
- Affected: dev tenant `ZARENA_PLM`, PRD RAP BO status, handover README
(the BDEF activation extension is `.bdef.abdl` for U6, U9 and W3, and the
U6 fallback was not needed), KNOWLEDGE.md.
- Decision: now that an empty `ZBP_R_CHANGEAPPROVAL` exists, I activate U6
and U7 together in one call, so the BDEF is never checked against a class
that is not yet a behavior pool. The activation tool returns no warnings,
so warnings about the four unmapped calculated fields are checked in the
editor's Problems view rather than assumed absent. I locate the
local-types include live, after the global class part is saved.
## 2026-09-16 — Wave A active: table, parameter entities and root view live; wave B created
- What changed:
- I activated U1 `ZCHANGEAPPROVAL`, U2–U4 (the three action-parameter
entities) and U5 `ZR_CHANGEAPPROVAL` on `my431353`. Each activation
answered "Activation successful.", and the tool returned no warnings.
- The table activates through its `.tabl.ddic` URI; `.tabl.json` holds
only header metadata.
- U5's `cast( case … as abap_boolean preserving type )` was accepted, so
the fallback is not needed [source: adt-mcp abap_activate_objects on
0M7DEJI, verified 2026-09-16].
- I then created wave B empty: U6, the managed BDEF `ZR_CHANGEAPPROVAL`
(`create_object` returned `…/zr_changeapproval.bdef.abdl`), and U10, the
access control `ZR_CHANGEAPPROVAL` (`…/zr_changeapproval.dcls.acds`,
with `protectedEntity`).
- Why: wave A of the UI leg. Creating the U6 BDEF requires the root view to
be active, and it validated, which confirms U5 is active.
- Affected: dev tenant `ZARENA_PLM`, PRD RAP BO status, the handover README
activation table (TABL now proven; BDEF extension open), KNOWLEDGE.md.
- Decision: BDEF source is pasted into `.bdef.abdl`, not `.bdef.json`. At
U6 activation I try the `.bdef.abdl` URI first and fall back to
`.bdef.json`, which activated a generator BDEF on 2026-09-15. The README
activation table is corrected once U6 proves the extension. Activation
gives no direct active/inactive readback, so proof is indirect: U6's
activation is the first test of U2–U4, because U6 names them as action
parameters.
## 2026-09-16 — Review fixes applied to the handover; duplicate checks read with privileged access
- What changed: all four remaining review fixes are in `docs/handover/rap/`.
- `use etag` sits in the entity header of both projection BDEFs.
- `retrySync` writes APPROVED, so EXECUTING is written only by
`markExecuting`.
- All six action handlers set `%fail-cause` and `%op-%action-<name>`,
merge the internal UPDATE's failed and reported rows, and return
results only for keys that did not fail.
- The base BDEF declares `validation checkDuplicate on save { create; }`.
Its handler refuses a second non-REJECTED record with the same
`ItemNumber` + `ChangeType` + `ArenaEcoId`, within the request or
against persisted rows.
The base BDEF and README carry the OQ-8 note that `authorization master
( none )` is not final. The table's paste target is `.tabl.ddic`; its
activation extension is still pending live proof. Two of my working
sessions wrote these files concurrently between 02:36 and 02:43. I
confirmed the result is consistent and stable (content unchanged over 45
seconds at 02:45, no duplicate governance entries). This session is now
the only one driving the build.
- Why: review findings B1, M1, m1 and m4 (entry below). Every one must be
in the source before its object is pasted.
- Affected: handover U6, U7, U9, W3, U1 header, README; PRD RAP BO status.
- Decision: the duplicate check reads `ZR_CHANGEAPPROVAL` with privileged
access. The view is access-controlled, and a handler `SELECT` on it
without privileged access breaks the RAP implementation rules [source:
sap-docs sap-help abaprap_impl_rules; abap-docs
/abap-docs-cloud/ABAPSELECT_PRIVILEGED_ACCESS, verified 2026-09-16]. The
validation's failed rows carry only `cause-unspecific`, the only cause
validations may use (same rules page). Three constructs are syntax I
prove only at tenant activation, each with a documented fallback:
`OPTIONS PRIVILEGED ACCESS` (fallback `FROM … WITH PRIVILEGED ACCESS`),
`%op-%action-<name>` on MODIFY response rows, and `preserving type` on
the U5 casts.
## 2026-09-16 — Root view U5 final: derived booleans cast as a whole
- What changed: in `zr_changeapproval.ddls.asddls`, `IsApprovable` and
`IsRetriable` are now `cast( case status when … then abap.char'X' else
abap.char' ' end as abap_boolean preserving type )`. The file header
records why: typed `abap.char` literals make the CASE result CHAR(1)
[source: abap-docs /abap-docs-cloud/ABENCDS_TYPED_LITERAL_V2, verified
2026-09-16], and `ABAP_BOOLEAN` is "of data type `CHAR` of length 1"
[source: abap-docs /abap-docs-standard/ABENDATAOBJECTS_TRUE_VALUE_GUIDL,
verified 2026-09-16], so `preserving type` matches exactly. U5 is cleared
for paste.
- Why: review finding (4). A cast inside a CASE branch drops the data
element semantics.
- Affected: handover U5, wave A paste.
- Decision: if activation still rejects `preserving type`, I drop only
that addition, not the outer cast. I finalized U5 ahead of the remaining
correction work so wave A is not held up. Two concurrent corrections
collided on this file; each later brief names file ownership explicitly.
## 2026-09-16 — App documentation aligned to retry → APPROVED
- What changed: I updated the four statements of the retry transition in
`app/` (README, both i18n `#XBUT` comments, the mock `metadata.xml`
comment) from FAILED → EXECUTING to FAILED → APPROVED. No labels,
annotations, manifest or mock data changed. `npm run lint` and
`npm run ts-typecheck` pass.
- Why: ARN-002 was amended today; the app's documentation must not
describe the old state machine.
- Affected: `app/README.md`, `app/webapp/i18n/i18n.properties`,
`app/webapp/i18n/i18n_en.properties`,
`app/webapp/localService/mainService/metadata.xml`, KNOWLEDGE.md.
- Decision: I do not add a mock `executeAction` handler to show the new
transition. The mock server ignores actions (KNOWLEDGE 2026-09-16), and
the live RAP service replaces the mock at V-2 within this build, so a
mock-only transition would be throwaway work. The remote
`ui5-mcp-server` cannot lint local files; I lint UI5 code locally until
that changes.
## 2026-09-16 — Pre-paste SAP review: one blocker, retry semantics corrected, authorization opened
- What changed: I reviewed all 18 handover sources read-only against ABAP
Cloud docs, clean-core release states, the PRD and the app contract. The
app contract holds (17 properties, 3 actions, entity set `ChangeApproval`,
no UI create/update/delete), and all 11 referenced SAP objects are clean
core level A [source: sap-released-objects sap_check_clean_core_compliance,
verified 2026-09-16]. The review found:
- One blocker: `use etag` sat in the body of both projection BDEFs, but
it belongs in the entity header [source: abap-docs
/abap-docs-cloud/ABENBDL_CHARACTER_PROJECTION, verified 2026-09-16].
- Two majors: retry semantics and missing per-operation authorization.
- Four minors.
I dispatched corrections before any affected source reached the tenant.
- Why: every defect caught here saves a paste-and-activate round trip
against the live tenant, and two of them were correctness defects, not
syntax.
- Affected: `docs/handover/rap/` (U5, U6, U7, U9, W3, README), PRD ARN-002,
RAP-2, new OQ-8, `app/` documentation comments, KNOWLEDGE.md.
- Decision:
- (1) `retrySync` goes FAILED → APPROVED, clears `ErrorMessage`, and the
worker re-claims the record through `markExecuting`. The earlier FAILED
→ EXECUTING could not be claimed, because `markExecuting` accepts only
APPROVED. Working around that would make a retried record
indistinguishable from one another run is executing, which is the
double-write risk ARN-006 exists for.
- (2) Duplicate staging is refused on create: a `checkDuplicate`
validation on the semantic key while a non-REJECTED record exists.
Webhook and delta poll can both deliver the same ECO, and I will not
leave idempotency to the executors alone.
- (3) Action handlers honor the RAP implementation contract (`%fail`,
`%op`, propagated UPDATE failures, results only for successful keys),
so the worker cannot read a failed `markSynced` as success.
- (4) The derived boolean flags cast the whole CASE to `abap_boolean`,
because a cast inside a CASE branch drops the data element semantics
[source: abap-docs /abap-docs-cloud/ABENCDS_CAST_EXPRESSION_V2, verified
2026-09-16].
- (5) Deferred: a metadata-level mandatory `RejectReason`, until after
the POC (server-side enforcement exists).
- (6) Opened: OQ-8. `authorization master ( none )` stays for the
dev-tenant POC only and must be replaced before business roles get the
app.
## 2026-09-16 — Wave A created: table, parameter entities, root view exist empty
- What changed: I created U1 `ZCHANGEAPPROVAL` (TABL) and U2–U5
`ZD_CHANGEAPPROVAL_REJECTP`, `ZD_CHANGEAPPROVAL_SYNCEDP`,
`ZD_CHANGEAPPROVAL_FAILEDP` and `ZR_CHANGEAPPROVAL` (DDLS) in
`ZARENA_PLM`. They are inactive and have no source. `abap_transport-get`
answered `isRecordingRequired: false`. Each object passed validation and
creation with the handover JSON unchanged. The table's returned URI ends
in `.tabl.ddic`, not the `.tabl.json` that activated a generator-created
table on 2026-09-15 [source: adt-mcp abap_creation-create_object on
0M7DEJI, verified 2026-09-16].
- Why: wave A of the UI leg. The sources are pasted next.
- Affected: dev tenant `ZARENA_PLM`, PRD RAP BO status, handover activation
URI table (TABL extension open).
- Decision: I activate U1 with the URI `create_object` returned
(`.tabl.ddic`) and fall back to `.tabl.json`. The handover URI table is
corrected once activation proves which one works.
## 2026-09-16 — Package recreated; UI-leg build started in waves
- What changed: package `ZARENA_PLM` exists again under `ZLOCAL`. ADT MCP
validation of `ZCHANGEAPPROVAL` in it succeeded (live, 02:27). I started
wave A of the UI leg: U1–U5 (table, the three action-parameter entities,
root view) are created empty so their sources can be pasted. I also read
the create fields for classes, access controls and metadata extensions
live. Those were the three types the handover had marked unconfirmed.
Metadata extension names allow 40 characters. Access controls and
metadata extensions take an optional protected/extended entity (KNOWLEDGE
2026-09-16).
- Why: the from-scratch build (OQ-7) is unblocked. The build moves in waves
bounded by activation dependencies: BDEF creation needs its root view
active, and the binding needs its service definition active. Each wave
asks for one paste session per group, not one per file.
- Affected: dev tenant `ZARENA_PLM`, PRD RAP BO status, KNOWLEDGE.md.
- Decision: waves are A = U1–U5 (create, paste, activate), B = U6 base
BDEF, C = U7 behavior pool, D = U8 projection view + U10 root access
control, E = U9 projection BDEF + U11 + U12 + U13, F = U14 binding with
the service-alias check. Nothing is activated before its source is
pasted.
## 2026-09-16 — Handover regenerated under clean names; binding names cut to SAP's 26-character limit
- What changed: I regenerated `docs/handover/rap/` for a build from an
empty package. Eleven source files were renamed to the clean object
names, every internal reference was updated, and the README was replaced
with a create → paste → activate sequence per object. The UI service
definition exposes only `ChangeApproval`. A scripted comparison against
the app's mock `metadata.xml` confirms that the UI projection carries all
17 app properties plus `approve`, `reject` and `retrySync`. The build
splits into a 14-source UI leg and a 4-source worker leg. The
`markSynced`/`markFailed` parameter entities move into the UI leg,
because the base BDEF references them and they must be active first. I
amended RAP-5 to two service definitions with one binding each, and
updated both PRD interface-contract rows. A read-only SAP review of the
18 sources is running before the paste session.
- Why: the tenant package is empty, so the handover's generator inventory
and `ZZ…000` targets no longer existed. SAP Help gives a maximum of 26
characters for service binding names, the same in ABAP Cloud, SAP Cloud
Platform and ABAP Platform 2021 [source: sap-docs sap-help loio
4bad0d7280b742a68dd2d42b1441f09a, f3e608ace2a64aada33bdd3e2419113a,
777e027f61c3490dba0433443d9143a6, verified 2026-09-16]. The ADT MCP's
create metadata allows 30, and generator bindings of 29–30 characters
activated on 2026-09-15, but none was ever published. No source says
whether publishing tolerates more than 26.
- Affected: PRD RAP-5, the two approval-BO interface-contract rows, RAP BO
status, `docs/handover/rap/`, V-2 (the app's dataSource URL, mock
namespace and annotation targets now carry a planned difference),
KNOWLEDGE.md.
- Decision: the bindings are `ZUI_ARENA_CHANGE_APPR_O4` (24) and
`ZAPI_ARENA_CHANGE_APPR_A2X` (26). The service definitions keep
`ZUI_ARENA_CHANGE_APPROVAL` and `ZAPI_ARENA_CHANGE_APPROVAL`. I chose the
documented limit over the tool's undocumented allowance, because a
refusal at publish would come after the whole UI leg and need a delete in
ADT to recover. The app edit this causes is one URL segment plus the
namespace, both already scheduled for V-2 reconciliation against live
`$metadata`, so I defer it to that brief instead of changing the app
twice. The derived-field EDM types (`StatusCriticality` as INT1,
`abap_boolean` flags, `utclong`, `sysuuid_x16`) stay unverified until
live `$metadata` exists; they are not assumed.
## 2026-09-16 — Deletion verified live: package `ZARENA_PLM` is gone; name limits confirmed
- What changed: with the ADT MCP back online I probed `my431353` read-only.
`abap_creation-run_validation` answers "Package 'ZARENA_PLM' does not
exist" for tables, data definitions and classes, the same as a
nonexistent control package. The deletion removed the package itself, not
just its contents. `abap_creation-get_object_type_details` gives the
maximum name lengths: database table 16; data definition, service
definition, service binding and behavior definition 30 [source: adt-mcp
abap_creation-get_object_type_details on 0M7DEJI, verified 2026-09-16].
- Why: the from-scratch build starts from the package, and every clean name
had to fit its limit before any object is created.
- Affected: PRD RAP BO status, `docs/handover/rap/README.md` (step 0 is now
"recreate the package"), KNOWLEDGE.md.
- Decision: I recreate `ZARENA_PLM` under `ZLOCAL` under the same name, so
every activation URI pattern verified on 2026-09-15 stays valid. All clean
names fit (`ZCHANGEAPPROVAL` 15, `ZUI_ARENA_CHANGE_APPROVAL_O4` 28).
`ZLOCAL` means no transport request on every create while I prototype. It
is not a dead end for production: a package can be reassigned from
`ZLOCAL` to `ZCUSTOM_DEVELOPMENT` (transport layer YDEV) later, though not
the reverse [source: sap-docs sap-help loio
a0d80f14fcb74cb1ac8fb6f3d9ad48cc (2608), verified 2026-09-15]. I make
that move once the approval app works end to end on the development
tenant.
## 2026-09-16 — Tenant package emptied; from-scratch build under names that match the Fiori app
- What changed: the `ZARENA_PLM` contents on `my431353` were deleted in ADT.
This includes both generator sets and the empty hand-created objects. I
have not yet verified the deletion live (the ADT MCP is offline), and the
package's own survival is unconfirmed. I closed OQ-7 and dispatched a
regeneration of `docs/handover/rap/` under the clean names.
- Why: the Fiori app binds to
`/sap/opu/odata4/sap/zui_arena_change_approval_o4/srvd/sap/zui_arena_change_approval/0001/`,
entity set `ChangeApproval`, and actions `approve`, `reject` and
`retrySync` (`app/webapp/manifest.json`, mock `metadata.xml`). A Fiori app
runs against a replacement service only if the entity set names,
properties and actions match [source: fiori-mcp-server search_docs
"Replacing OData Service in an Adaptation Project", verified 2026-09-16].
With the package empty, no leftover objects compete for these names.
- Affected: PRD OQ-7 (closed), RAP BO status, `docs/handover/rap/` (all 19
sources and the README), V-2 (the service identity is now designed to
match the app instead of reconciled after the fact).
- Decision: no generator this time, because it forces a `ZZ` prefix and a
`000` suffix (KNOWLEDGE, 2026-09-15). I create each object through the ADT
MCP under its final name: table `ZCHANGEAPPROVAL`, `ZR_CHANGEAPPROVAL`,
`ZBP_R_CHANGEAPPROVAL`, `ZC_CHANGEAPPROVAL`, SRVD
`ZUI_ARENA_CHANGE_APPROVAL`, SRVB `ZUI_ARENA_CHANGE_APPROVAL_O4`. The A2X
names are unchanged. The UI leg (about 13 sources) is built and verified
against the app before the worker leg starts.
## 2026-09-16 — Tenant state corrected: two generator sets, no designed logic deployed; naming decision opened
- What changed: a live ATC run over `ZARENA_PLM` returned 43 objects, not
the 12 my 2026-09-15 handover README lists. The package holds two default
`x-ui-service` generator sets side by side: the `ZZ…000` set
(`ZZCHNGPPROVAL000`, `ZZR_/ZZC_CHANGEAPPROVAL000`, binding
`ZZUI_ARENA_CHANGE_APPRVL000_O4`) and a set without the suffix
(`ZZCHANGEAPPROVAL`, `ZZR_/ZZC_CHANGEAPPROVAL`, binding
`ZZUI_ARENA_CHANGE_APPROVAL_O4`). Each set has its own behavior pools and
SAP object types. It also holds the five empty, inactive hand-created
objects and the probe class. None of the six actions, the status machine,
or the feature control exists in any tenant object. The designed source
lives only in `docs/handover/rap/`, which is now committed and pushed
(`f8b45ea`). My most likely explanation for the second set is an earlier
generator run that I never recorded; I cannot confirm it, because the ADT
MCP has no read tool.
- Why: my 2026-09-15 entry and the PRD status described a clean skeleton
that was ready for a paste session. Following them would put the sources
into objects whose names the Fiori app does not bind to.
- Affected: PRD status (RAP BO → blocked on OQ-7), new OQ-7,
`docs/handover/rap/README.md` (its object inventory is stale), the Fiori
app's V-2 service identity.
- Decision: none yet. I recommend building under clean names that match the
app, next to the unused sets, and deferring deletion. The two sets are
unpublished, their `ZZ` names do not collide with the clean set, and
deleting them needs Eclipse ADT, which is not set up. Of the 19 sources,
13 serve the UI milestone. The six A2X-leg files wait until the app runs
against the real service.
## 2026-09-15 — abapGit researched: not required for ADT for VS Code; viable as a bulk source-load path ## 2026-09-15 — abapGit researched: not required for ADT for VS Code; viable as a bulk source-load path
- What changed: I verified that abapGit is neither required nor used by SAP's - What changed: I verified that abapGit is neither required nor used by SAP's
+202
View File
@@ -101,6 +101,208 @@ entries the same way — dated and falsifiable.
SRVB creation validate the referenced object's ACTIVE version, so create SRVB creation validate the referenced object's ACTIVE version, so create
and activate DDLS/SRVD sources first. ATC via MCP rejects RONT/NONT. and activate DDLS/SRVD sources first. ATC via MCP rejects RONT/NONT.
- [2026-09-16] **An expired VS Code ADT logon shows up in the ADT MCP only as
"Your user was logged off."** `abap_list_destinations` still lists the
destination and `get_object_type_details` still returns field metadata,
so neither proves a live session. **ADT for VS Code logs on to S/4HANA
Cloud with a reentrance ticket and a localhost redirect:** it opens
`https://<tenant>/sap/bc/adt/core/http/reentranceticket?redirect-url=http://localhost:<port>/adt/redirect`,
and after SSO the tenant redirects to
`http://localhost:<port>/adt/redirect?…&reentrance-ticket=<ticket>`
(observed 2026-09-16). The ticket in that URL is a live logon credential:
never paste it into chats, logs or files. The same mechanism is the
plausible browser-free login path for `abap-adt-mcp` (OQ-9). **Tested
2026-09-18: it does not work.** With a freshly captured reentrance
ticket, `https://my431353.s4hana.cloud.sap/sap/bc/adt/core/discovery`
and an ABAP class source URL answered HTTP 200 with the same 3094-byte
SAML redirect page (`SAMLRequest`, `saml2`) for all of: `Cookie:
MYSAPSSO2=<ticket>`, header `MYSAPSSO2: <ticket>`, and no credential at
all, with and without `?sap-client=080`. A reentrance ticket is
therefore neither a logon-ticket cookie nor an assertion-ticket header
here; ADT exchanges it some other way. Do not spend more on
`abap-adt-mcp`'s `sso2` mode without first capturing what the extension
actually sends. `abap_creation-run_validation` is the
cheapest read-only liveness check (it answers "… validation failed: Your
user was logged off."). Fix it by logging on to the destination again in
VS Code; the MCP port and token stay the same unless VS Code reloads
(live 2026-09-16, ~1.5 h after the last successful call).
- [2026-09-16] **Ask the ADT MCP for name limits and package existence; do
not guess them.** `abap_creation-get_object_type_details` returns each
type's create fields and `maxLength`: `TABL/DT` 16, while `DDLS/DF`,
`SRVD/SRV`, `SRVB/SVB` and `BDEF/BDO` allow 30 (the generator's `APPRVL`
truncation came from that 30 limit). `abap_creation-run_validation` is a
read-only way to check that a package exists: a missing package answers
"Package '<name>' does not exist" [source: adt-mcp on 0M7DEJI, verified
2026-09-16]. The Claude Code session does not expose the adt-mcp tools
unless the server was up at session start. A JSON-RPC probe over
`http://127.0.0.1:2236/mcp` with the `.mcp.json` bearer token works for
read-only checks in the meantime.
- [2026-09-16] **In the VS Code ADT workspace, the `.json` file in an object
folder is header metadata, never the source.** A table folder holds
`zchangeapproval.tabl.json` (formatVersion, description, originalLanguage,
`abapLanguageVersion`) and `zchangeapproval.tabl.ddic` (the Dictionary DDL
source). Paste into `.ddic`, and activate through the `.tabl.ddic` URI
(live, "Activation successful."). For objects created with
`create_object`, the returned `filePath` names the source file: DDLS
`.ddls.acds`, DCLS `.dcls.acds`, BDEF **`.bdef.abdl`** [source: adt-mcp
abap_creation-create_object / abap_activate_objects on 0M7DEJI, verified
2026-09-16]. The `.tabl.json` / `.bdef.json` activation URIs recorded on
2026-09-15 were generator objects; trust the returned `filePath` over
that table. BDEF activation through `.bdef.abdl` is proven (U6, live).
- [2026-09-16] **A managed `strict ( 2 )` BDEF activates before its
behavior pool class exists** (`ZR_CHANGEAPPROVAL`, "Activation
successful.", with no class `ZBP_R_CHANGEAPPROVAL` yet) [source: adt-mcp
abap_activate_objects on 0M7DEJI, verified 2026-09-16]. Once the class
exists, activate BDEF and class together. `abap_activate_objects` returns
only "Activation successful." with no warnings on success. On failure it
lists messages with line and column but no severity (U13 SRVD,
2026-09-16), so read severity in the editor. Service definitions
report "Comments are not supported and will be deleted on save" for a
leading block comment, and **that message blocks activation**: the same SRVD failed with a
19-line `/* */` header and activated without it (live 2026-09-16; the
DCLS, DDLX and BDEF headers did not block). The language allows comments
[source: abap-docs /abap-docs-cloud/ABENCDS_GENERAL_SYNTAX_RULES,
verified 2026-09-16], so this is the tooling. Keep SRVD sources free of
comments. `abap_atc_get_result` returned only totals for a run with 43
infos (UI leg), but per-object `findings[]` (priority, check class,
message id and title, quick-fix flags) for a run with a priority-3
warning (worker leg). Expect details for warnings and errors at least
(both runs live 2026-09-16). A projection BDEF over a view with
`@AccessControl.authorizationCheck: #CHECK` raises SLIN W607 (+BDL 549)
"should be replaced with #MANDATORY" (priority 3; it does not block
activation). An OData V4 **Web API** binding reports
`bindingCategory: "1"` (UI: `"0"`) in `fetch_services`.
- [2026-09-16] **OData V4 identity on a RAP UI service: the binding name is
only the URL's first segment.** For definition `ZUI_ARENA_CHANGE_APPROVAL`
and binding `ZUI_ARENA_CHANGE_APPR_O4`, the live `$metadata` uses
namespace `com.sap.gateway.srvd.zui_arena_change_approval.v0001`, from the
**service definition**, and entity type `ChangeApprovalType`, with a
`Type` suffix and no underscore (live 2026-09-16,
`docs/v2/zui_arena_change_appr_o4.metadata.xml`). The earlier assumption
that the namespace carries the binding name and a `_Type` suffix was
wrong. RAP also emits `__OperationControl` with
`Core.OperationAvailable Path="_it/__OperationControl/<action>"` for
`features : instance` actions, `SAP__Messages`, `Nullable="false"` on
non-key elements, and `cast( case … as abap_boolean preserving type )`
surfaces as `Edm.Boolean` (INT1 literals as `Edm.Byte`, `utclong` as
`Edm.DateTimeOffset Precision="7"`). Copying `$metadata` from Chrome's
XML tree view decodes `&amp;` to `&`; use `view-source:` or re-escape.
- [2026-09-16] **Live RAP `$metadata` works verbatim as Fiori mock metadata**
(`@sap-ux/fe-mockserver-core` 1.7.15 serves it and accepts `$select` on
`__OperationControl/approve`). Mock rows must then carry
`__OperationControl`, or the enablement paths resolve to nothing. The
app's ESLint config checks only TS/JS, so run `npx @ui5/linter` for UI5.
The fiori-mcp `list_functionality` for `app/` offers only
add/delete page and controller extension, so dataSource and annotation
edits are made directly. The cached `@ui5/manifest` 1.89.0 schema
rejects the generator's manifest `_version: "2.11.0"`; that is not a
defect in the app.
- [2026-09-16] **A published custom UI service answers on the UI host, not the
`-api` host, for a business user.** `fetch_service_information` reports
the service URL on `my431353-api.s4hana.cloud.sap` (with
`sap-client=080`), but a browser business-user call there is refused as
"Forbidden / blocked by UCON". The same path on
`my431353.s4hana.cloud.sap` returns `$metadata` for the developer user,
before any IAM app exists (live 2026-09-16). The `-api` host is the
communication-user entry point (communication arrangement), so the
worker's A2X service goes there through a custom communication scenario
(V-2), never the UI service. After publishing, ADT for VS Code reports
"<binding> published locally".
- [2026-09-16] **In ADT for VS Code, publishing a service binding is a code
lens, not a button or a palette command.** Open the binding's `.srvb.json`
source and click the **Publish** code lens; Unpublish and Preview… work the
same way [source: sap-docs sap-help loio dd6f5de63e7245a7abe7e39389841596
"ABAP RAP Tools", verified 2026-09-16]. The extension (1.1.2) contributes
no publish command, and the SAP ADT MCP has no publish tool. `create_object` for a class returns only the global
`.clas.abap` path, no local-types (CCIMP) path.
- [2026-09-16] **`abap-adt-mcp` 2.1.0 auto-detects a browser for SSO only on
macOS.** `detectBrowser()` checks `SAP_BROWSER_PATH` and then three
`/Applications/…` paths; otherwise it throws "No Chrome/Edge/Brave found
for SSO login" (`dist/lib/browserLogin.js:38-50`, read 2026-09-16). On
this Linux host the server env must carry
`SAP_BROWSER_PATH=/usr/bin/google-chrome` along with the desktop display
variables. Login harvests the HttpOnly `MYSAPSSO2`/`SAP_SESSIONID` cookies
over the Chrome DevTools Protocol (`puppeteer-core`). `listSystems` needs
no SAP call and is the safe first probe of its policy. **Its SSO does not
work from a remote shell:** there is no localhost redirect (SAML returns
to the tenant), and the sign-in must be typed into the server-side Chrome
window (`headless: false`, profile
`~/.abap-adt-mcp/sso/<host>`, `--remote-debugging-port=0`, 300 s timeout).
Over multi-hop SSH neither the desktop nor a DevTools relay is reachable in
practice. Do the first sign-in at the server's desktop; with "stay signed
in" later logins should complete unattended (unverified, 2026-09-16).
- [2026-09-16] **Create fields for the remaining RAP types (`get_object_type_details`
on 0M7DEJI):** `CLAS/OC`: packageName, name (≤30), description, optional
superclass, interfaces. `DCLS/DL`: packageName, name (≤30), description,
optional `protectedEntity`. `DDLX/EX`: packageName, name (≤**40**),
description, optional `extendedEntity` [source: adt-mcp
abap_creation-get_object_type_details on 0M7DEJI, verified 2026-09-16].
Pass `protectedEntity` / `extendedEntity` so the editor starts from the
right skeleton. DCLS creation does **not** require the protected entity to
be active. `ZC_CHANGEAPPROVAL_A2X` access control validated and was
created while its DDLS was still empty and never activated (unlike
BDEF/SRVB creation). `SRVB/SVB` `bindingType` values are exactly `OData
V2 - UI`, `OData V2 - Web API`, `OData V4 - UI`, `OData V4 - Web API`,
whatever label the release notes use [source: adt-mcp
abap_creation-run_validation / create_object /
get_object_type_details on 0M7DEJI, verified 2026-09-16].
- [2026-09-16] **Service binding names: SAP Help says 26 characters, the
ADT MCP allows 30.** The 26-character limit appears on the ABAP Cloud,
SAP Cloud Platform and ABAP Platform 2021 "Creating Service Binding"
pages [source: sap-docs sap-help loio 4bad0d7280b742a68dd2d42b1441f09a,
verified 2026-09-16]. `get_object_type_details` reports 30 for
`SRVB/SVB`, and the `x-ui-service` generator itself emits 29–30-character
bindings, which activated unpublished on `my431353` on 2026-09-15.
Whether publishing accepts more than 26 is unknown. Stay at 26 or fewer
rather than find out after a whole BO leg. The generator does not keep to
the documented limit, which is one more reason not to use it for names
that ship.
- [2026-09-16] **In an OData V4 binding URL
`/sap/opu/odata4/sap/<binding>/srvd/sap/<alias>/0001/`, the second
segment is the binding's service name (alias), not necessarily the
service definition name. It can be renamed only while the binding is
unpublished** [source: sap-docs community-14433266 "How to rename an
OData V4 service?", verified 2026-09-16]. Check it before publishing.
- [2026-09-16] **CDS CASE results take the longest branch type, and untyped
integer literals are typed INT1..INT8 by value** [source: abap-docs
/abap-docs-cloud/ABENCDS_CASE_EXPRESSION_V2, ABENCDS_UNTYPED_LITERAL_V2,
verified 2026-09-16]. A text CASE over status codes becomes CHAR(n) of
the longest literal, and a 0–5 criticality becomes INT1. Cast
explicitly when the OData type matters.
- [2026-09-16] **The `ui5-mcp-server` linter cannot see this repo.** The
server is reached over SSE on `mcp.alexzaw.dev`, and `run_ui5_linter` on
`app/` fails with `ENOENT: realpath '/home/alexz/…/arena-plm/app'`
(live 2026-09-16). Its doc/API tools work; its file-based tools
(linter, manifest validation, project info) do not. For real UI5 code
changes, lint locally (`npx @ui5/linter` inside `app/`) or run the
server where the files are.
- [2026-09-16] **The Fiori mock server does not simulate bound actions.**
Without a per-entity `webapp/localService/mainService/data/<Entity>.js`
exporting `executeAction`, `@sap-ux/fe-mockserver-core` 1.7.15 returns
the action data unchanged
(`node_modules/@sap-ux/fe-mockserver-core/dist/mockdata/fileBasedMockData.js:547-549`,
read locally 2026-09-16). Status transitions can be shown only against
the live RAP service, or with a hand-written mock handler.
- [2026-09-16] **Cast the whole CASE to a data element, never each
branch.** "If a data element is specified for `dtype`, the result …
inherits the semantic properties of the data element. An exception to
this is the use of the `CAST` expression within a case distinction."
Write `cast( case … end as abap_boolean preserving type )`.
`PRESERVING TYPE` requires an exact match of built-in type and length
[source: abap-docs /abap-docs-cloud/ABENCDS_CAST_EXPRESSION_V2, verified
2026-09-16]. To guarantee that length, use typed literals in the branches
(`abap.char'X'`, `abap.char' '` are CHAR(1)): an untyped character
literal's length is only "at least as large as the actual length of the
literal" [source: abap-docs /abap-docs-cloud/ABENCDS_TYPED_LITERAL_V2,
ABENCDS_UNTYPED_LITERAL_V2, verified 2026-09-16]. `ABAP_BOOLEAN` is CHAR 1
[source: abap-docs /abap-docs-standard/ABENDATAOBJECTS_TRUE_VALUE_GUIDL,
verified 2026-09-16]. The same caveat means a CASE over untyped status
literals is not provably CHAR(n) of the longest literal.
- [2026-09-16] **`sap-released-objects` has no `SRVB` object type for
public_cloud**, the same gap as `DDLS` (use `CDS_STOB` for views). Binding
release questions cannot be answered from that server.
- [2026-09-15] **Developer business catalogs: `SAP_A4C_BC_*` is the Public - [2026-09-15] **Developer business catalogs: `SAP_A4C_BC_*` is the Public
Cloud / BTP ABAP family; `SAP_A4_BC_*` is on-premise / Private Cloud.** On Cloud / BTP ABAP family; `SAP_A4_BC_*` is on-premise / Private Cloud.** On
`my431353` the developer role carries the A4C catalogs (e.g. `my431353` the developer role carries the A4C catalogs (e.g.
+11 -7
View File
@@ -65,8 +65,8 @@ tombstone remains valid.
| BOM (read) | V2 | `/sap/opu/odata/sap/API_BILL_OF_MATERIAL_SRV` | v1 only; v2 rejects guid keys | | 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 | | 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 | | 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/zzui_arena_change_apprvl000_o4/srvd/sap/zzui_arena_change_apprvl000_o4/0001/` (binding `ZZUI_ARENA_CHANGE_APPRVL000_O4`, generated 2026-09-15, unpublished) | expected namespace `com.sap.gateway.srvd.zzui_arena_change_apprvl000_o4.v0001`, alias `SAP__self`, entity `ChangeApproval_Type`/set `ChangeApproval`; confirm from live `$metadata` after publish (V-2); Fiori app still carries the pre-generation provisional name | | 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_APPROVAL_A2X` on definition `ZAPI_ARENA_CHANGE_APPROVAL` (pending: created after the A2X projection is active) | custom communication scenario, technical user; namespace `com.sap.gateway.srvd_a2x.zapi_arena_change_approval_a2x.v0001` expected; exposes create + read + `markExecuting` / `markSynced(SapChangeNumber)` / `markFailed(ErrorMessage)` (RAP-6) | | 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) | | 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 ## Requirements
@@ -76,7 +76,7 @@ Capability level — field mappings deferred to the POC per my POC-before-spec r
| ID | Requirement | Status | | ID | Requirement | Status |
|---|---|---| |---|---|---|
| ARN-001 | Sync chain PRODUCT → CHANGE_MASTER → BOM → DOCUMENTS in config-driven order, executed by the worker on approval | Planned | | 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 → EXECUTING (amended 2026-09-14: a terminal success state is required so an approved record is never executed twice); 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-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-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-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-005 | Arena ingestion via webhook + delta poll in the connectivity worker | Planned |
@@ -94,10 +94,10 @@ once V-1 opens the development tenant.
| ID | Requirement | Source | | 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-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) | UI contract (annotation.xml, mock metadata) | | 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-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-4 | Semantic key `ItemNumber` (`@ObjectModel.semanticKey`); `Status` and `RequestedOn` sortable/filterable | UI default variant |
| RAP-5 | Two service bindings on definition `ZUI_ARENA_CHANGE_APPROVAL`: OData V4 **UI** binding `ZUI_ARENA_CHANGE_APPROVAL_O4` for the app and an OData V4 **Web API** binding for the worker; the binding name sets the URL first segment and the namespace; hand back the live `$metadata` of the UI binding to reconcile (V-2) | [source: sap-docs sap-help loio 5717c0a1dafc4335add0207b81c39cef, 5f45f125dfe84c20beb18f5de1512f5a, verified 2026-09-14] | | 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-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-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 | | RAP-8 | Field `DecidedBy` (not `ApprovedBy`) holds approver or rejecter; semantic key `ItemNumber, ChangeType, ArenaEcoId` | UI contract |
@@ -112,11 +112,15 @@ once V-1 opens the development tenant.
| ID | Item | Status | | 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-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 | | 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-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 | | 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-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-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 |
## Status ## Status
@@ -128,6 +132,6 @@ once V-1 opens the development tenant.
| Specialist agent roster (5 agents, docs/agent-roster.md) | Done (2026-09-04; all 5 files YAML-verified in .claude/agents/) | | 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 | Planned — V-1 (ADT access on my429480) is the user-side gate for everything on-stack | | V-1…V-3 verification POC | Planned — V-1 (ADT access on my429480) is the user-side gate for everything on-stack |
| 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) | | 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) |
| RAP BO (approval domain) | In progress — skeleton generated and active in `ZARENA_PLM` (2026-09-15, ATC clean); **waiting on owner: paste `docs/handover/rap/` sources in VS Code per its README**, then I activate/ATC/create the A2X BDEF + binding; IAM app/catalog and publishing are the next brief | | RAP BO (approval domain) | In progress (2026-09-16): from-scratch build in package `ZARENA_PLM` (under `ZLOCAL`, no transport recording) under the clean names (OQ-7 closed). The handover in `docs/handover/rap/` has all pre-paste review fixes applied and was stable as of 02:45. Wave A (U1–U5: table, three parameter entities, root view) was created empty at 02:30. Its sources were pasted at 02:47, and all five activated at 02:49 with no errors, `preserving type` included. Wave B (U6 base BDEF, U10 root access control) was created empty at 02:50, its sources were pasted at 02:53, and both activated at 02:55 (BDEF through `.bdef.abdl`). Wave C (U7 behavior pool, U8 UI projection view) was pasted at ~03:00 and activated at 03:03 (U6 and U7 together, then U8), with no errors. It is not yet confirmed that the handler source sits in the class's Local Types include; until it is, activation does not prove the three flagged constructs. Wave D (U9 projection BDEF, U11 access control, U12 metadata extension, U13 UI service definition) was created empty at 03:05 and pasted at ~04:12. U9, U11 and U12 activated at 04:15. U13 activation failed, listing "Comments are not supported and will be deleted on save" (Ln 1) and "oData V4 services with no draft capability will be primarily READ-ONLY" (Ln 21, expected by design). After a comment-free re-save, U13 activated at 04:18. U14 binding `ZUI_ARENA_CHANGE_APPR_O4` (OData V4 UI) was created and activated. **The UI leg is active: all 14 objects; ATC 0 errors, 0 warnings, 43 infos.** **Published 2026-09-16 04:26** through the ADT Publish code lens: `https://my431353-api.s4hana.cloud.sap/sap/opu/odata4/sap/zui_arena_change_appr_o4/srvd/sap/zui_arena_change_approval/0001/?sap-client=080`, entity set `ChangeApproval` (`fetch_service_information`). The table is empty until the worker leg creates records, because the UI exposes no create. V-2 app reconciliation done. **Worker (A2X) leg ACTIVE 2026-09-16 06:37:** W1 view `ZC_CHANGEAPPROVAL_A2X`, W2 access control, W3 projection BDEF (create, markExecuting, markSynced, markFailed), W4 service definition `ZAPI_ARENA_CHANGE_APPROVAL` and W5 OData V4 Web API binding `ZAPI_ARENA_CHANGE_APPR_A2X` (unpublished, service `0001`). ATC on the 5 worker objects: 0 errors, 1 priority-3 warning (W607 on `ZC_CHANGEAPPROVAL_A2X`: `authorizationCheck: #CHECK` should be `#MANDATORY`), deferred to OQ-8. **The approval BO is complete on the development tenant (19 objects).** Next: expose W5 to the worker's communication user through a custom communication scenario (V-2 A2X half); IAM app, business catalog and role for the UI app (ARN-007); OQ-8 authorization before other business users. The table source goes into `.tabl.ddic`; `.tabl.json` holds only header metadata. (3) UI leg, 14 sources ending with binding `ZUI_ARENA_CHANGE_APPR_O4`: I create each object through the MCP, its source is pasted in the VS Code editor, and I activate it. The `markSynced`/`markFailed` parameter entities belong to this leg, because the base BDEF references them. (4) Publish the UI binding, then run V-2 reconciliation of the app against its live `$metadata`. (5) Worker (A2X) leg, 4 sources. Syntax proven only by tenant activation, still open: `OPTIONS PRIVILEGED ACCESS` in `checkDuplicate` (fallback `FROM … WITH PRIVILEGED ACCESS`), `%op-%action` on MODIFY response rows, `preserving type` on the U5 boolean casts |
| 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 | | 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 | | 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 |
+41 -24
View File
@@ -17,42 +17,55 @@
|**Add Eslint configuration**<br>True, see https://www.npmjs.com/package/@sap-ux/eslint-plugin-fiori-tools#rules for the eslint rules.| |**Add Eslint configuration**<br>True, see https://www.npmjs.com/package/@sap-ux/eslint-plugin-fiori-tools#rules for the eslint rules.|
|**Main Entity**<br>ChangeApprovals| |**Main Entity**<br>ChangeApprovals|
## Service binding (provisional) ## Service binding (live, reconciled 2026-09-16 - PRD V-2)
The app binds to the RAP **UI** service binding, not the A2X binding the worker uses. The app binds to the RAP **UI** service binding, not the A2X binding the worker uses.
The single source of truth for the service identity is the header comment of The single source of truth for the service identity is the live `$metadata` of the published
`webapp/localService/mainService/metadata.xml`: binding, captured 2026-09-16 as `docs/v2/zui_arena_change_appr_o4.metadata.xml` (repo root).
`webapp/localService/mainService/metadata.xml` is a **byte-identical copy** of it, so the mock
server serves the real contract. Refresh both together; never hand-edit the copy.
| Item | Value | | Item | Value |
|---|---| |---|---|
| Service binding (OData V4 - UI) | `ZUI_ARENA_CHANGE_APPROVAL_O4` | | Service binding (OData V4 - UI) | `ZUI_ARENA_CHANGE_APPR_O4` |
| Service definition | `ZUI_ARENA_CHANGE_APPROVAL` | | Service definition | `ZUI_ARENA_CHANGE_APPROVAL` |
| Service URL | `/sap/opu/odata4/sap/zui_arena_change_approval_o4/srvd/sap/zui_arena_change_approval/0001/` | | Service URL | `/sap/opu/odata4/sap/zui_arena_change_appr_o4/srvd/sap/zui_arena_change_approval/0001/` |
| Schema namespace / alias | `com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001` / `SAP__self` | | Schema namespace / alias | `com.sap.gateway.srvd.zui_arena_change_approval.v0001` / `SAP__self` |
| Entity type / entity set | `ChangeApproval_Type` / `ChangeApproval` | | Entity type / entity set | `ChangeApprovalType` / `ChangeApproval` |
| Bound actions | `approve`, `reject(RejectReason)`, `retrySync` |
The first URL segment and the schema namespace are derived from the service **binding** name, Only the first URL segment comes from the service **binding** name. The schema namespace comes
the segment after `srvd/sap/` from the service **definition** name - strongly indicated by the from the service **definition** name, and the entity type carries a `Type` suffix without an
tenant's `/sap/opu/odata4/sap/api_currencyexchangerate/srvd_a2x/sap/currencyexchangerate/0001` underscore (live `$metadata`, 2026-09-16; see `KNOWLEDGE.md`). The pre-V-2 assumption of a
(namespace `com.sap.gateway.srvd_a2x.api_currencyexchangerate.v0001`) and SAP Help's binding-derived namespace and a `_Type` suffix was wrong.
`/sap/opu/odata4/sap/api_product/srvd_a2x/sap/product/0002`
[source: sap-docs sap-help loio 5f45f125dfe84c20beb18f5de1512f5a, verified 2026-09-14].
`webapp/manifest.json` (`sap.app.dataSources.mainService.uri`, routing) and `webapp/manifest.json` (`sap.app.dataSources.mainService.uri`), `ui5-mock.yaml` /
`webapp/annotations/annotation.xml` (`edmx:Reference`, every `Annotations Target`, every `ui5-local.yaml` (`sap-fe-mockserver` `urlPath`) and `webapp/annotations/annotation.xml`
`DataFieldForAction.Action`) must stay in sync with it. JSON cannot carry comments, so the (`edmx:Reference`, every `Annotations Target`, every `DataFieldForAction.Action`) must stay in
manifest references this note instead. sync with it. JSON cannot carry comments, so the manifest references this note instead.
**Provisional - namespace = binding name is strongly indicated, not proven; reconcile namespace, **Annotation layering.** The backend metadata extension already emits most UI annotations
entity type suffix and URL segments against the live `$metadata` of the RAP UI service binding (LineItem, HeaderInfo, Facets, FieldGroups, Identification, SelectionFields, SemanticKey,
at PRD V-2 before deploy.** `Status` text with `TextOnly`). An annotation in `annotation.xml` replaces the backend annotation
with the same term + qualifier on the same target, and deleting it there makes the backend one
effective again [source: fiori-mcp-server "Visualizing and Managing Annotations with SAP Fiori
Tools - Service Modeler", verified 2026-09-16].
**Backend for a non-mock run: not configured.** `ui5.yaml` and `ui5-local.yaml` carry
`fiori-tools-proxy` with `backend: []`, and `npm start` is a generator placeholder. When a live
preview is added, the backend is the **UI host** `https://my431353.s4hana.cloud.sap`, never the
`-api` host: a business-user call there is blocked by UCON (live 2026-09-16, `KNOWLEDGE.md`).
## Status model and actions ## Status model and actions
`Status`: `PENDING -> APPROVED -> EXECUTING -> SYNCED | FAILED`; `REJECTED` is terminal; `Status`: `PENDING -> APPROVED -> EXECUTING -> SYNCED | FAILED`; `REJECTED` is terminal;
`FAILED -> retrySync -> EXECUTING`. `approve`/`reject` are enabled only for `PENDING`, `FAILED -> retrySync -> APPROVED`. `approve`/`reject` are enabled only for `PENDING`,
`retrySync` only for `FAILED` (mocked via `IsApprovable` / `IsRetriable`, which stand in for the `retrySync` only for `FAILED`. The backend owns enablement: RAP emits
`Core.OperationAvailable` RAP emits for `( features : instance )` actions - PRD RAP-1). `Core.OperationAvailable Path="_it/__OperationControl/<action>"` for the `( features : instance )`
actions (PRD RAP-1), and the app keeps no local `OperationAvailable`, which would replace it
[source: fiori-mcp-server "Action Control for Context-Dependent Actions (SAP Fiori Elements)",
verified 2026-09-16]. `IsApprovable` / `IsRetriable` are still in the service but hidden, and
nothing in the app binds to them.
Actions are offered on the object page header only; the list report table is display-only Actions are offered on the object page header only; the list report table is display-only
(`selectionMode: "None"`). (`selectionMode: "None"`).
@@ -68,7 +81,11 @@ required for a custom app (only `archeType` is mandatory)
Dev server: `npm run start-mock` / `npm run int-test` bind to port **8082** (8080 is taken on Dev server: `npm run start-mock` / `npm run int-test` bind to port **8082** (8080 is taken on
the dev host). Mock data lives in `webapp/localService/mainService/data/ChangeApproval.json` the dev host). Mock data lives in `webapp/localService/mainService/data/ChangeApproval.json`
(`generateMockData: false`), 8 rows covering every status. (`generateMockData: false`), 8 rows covering every status. Each row carries every live property:
`StatusText` / `StatusCriticality` as `ZR_CHANGEAPPROVAL` computes them, `__OperationControl`
consistent with the status, `LocalLastChangedAt`, and `SAP__Messages: []`. Initial CHAR values are
`""`, not `null`, on `Nullable="false"` properties. The mock server does not execute bound
actions (see `KNOWLEDGE.md`), so status transitions show only against the live service.
## app ## app
+1 -1
View File
@@ -44,7 +44,7 @@ server:
configuration: configuration:
mountPath: / mountPath: /
services: services:
- urlPath: /sap/opu/odata4/sap/zui_arena_change_approval_o4/srvd/sap/zui_arena_change_approval/0001/ - urlPath: /sap/opu/odata4/sap/zui_arena_change_appr_o4/srvd/sap/zui_arena_change_approval/0001/
metadataPath: ./webapp/localService/mainService/metadata.xml metadataPath: ./webapp/localService/mainService/metadata.xml
mockdataPath: ./webapp/localService/mainService/data mockdataPath: ./webapp/localService/mainService/data
generateMockData: false generateMockData: false
+1 -1
View File
@@ -44,7 +44,7 @@ server:
configuration: configuration:
mountPath: / mountPath: /
services: services:
- urlPath: /sap/opu/odata4/sap/zui_arena_change_approval_o4/srvd/sap/zui_arena_change_approval/0001/ - urlPath: /sap/opu/odata4/sap/zui_arena_change_appr_o4/srvd/sap/zui_arena_change_approval/0001/
metadataPath: ./webapp/localService/mainService/metadata.xml metadataPath: ./webapp/localService/mainService/metadata.xml
mockdataPath: ./webapp/localService/mainService/data mockdataPath: ./webapp/localService/mainService/data
generateMockData: false generateMockData: false
+43 -35
View File
@@ -1,12 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- <!--
Local UI annotations for the Arena PLM change-approval Fiori Elements app. Local UI annotations for the Arena PLM change-approval Fiori Elements app.
Service identity (binding, service definition, version, namespace) is defined ONCE in Service identity is the live $metadata of RAP UI binding ZUI_ARENA_CHANGE_APPR_O4 (PRD V-2, reconciled
webapp/localService/mainService/metadata.xml - keep this file's edmx:Reference, every 2026-09-16): webapp/localService/mainService/metadata.xml is a byte-identical copy of
Annotations Target and every DataFieldForAction.Action in sync with it. docs/v2/zui_arena_change_appr_o4.metadata.xml. URL /sap/opu/odata4/sap/zui_arena_change_appr_o4/srvd/sap/
Provisional - namespace = binding name is strongly indicated, not proven; reconcile namespace, zui_arena_change_approval/0001/ (first segment = binding), schema namespace
entity type suffix and URL segments against the live $metadata of the RAP UI service binding com.sap.gateway.srvd.zui_arena_change_approval.v0001 (from the service DEFINITION), entity type
at PRD V-2 before deploy. ChangeApprovalType. Keep this file's edmx:Reference, every Annotations Target and every
DataFieldForAction.Action in sync with it.
Layering: the backend (metadata extension ZC_CHANGEAPPROVAL) already emits most UI annotations. An
annotation in this file replaces the backend annotation with the same term + qualifier on the same
target; deleting it here makes the backend one effective again
[source: fiori-mcp-server "Visualizing and Managing Annotations with SAP Fiori Tools - Service Modeler",
verified 2026-09-16].
--> -->
<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0"> <edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0">
<edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/UI.xml"> <edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/UI.xml">
@@ -15,8 +21,8 @@
<edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/Common.xml"> <edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/Common.xml">
<edmx:Include Namespace="com.sap.vocabularies.Common.v1" Alias="Common"/> <edmx:Include Namespace="com.sap.vocabularies.Common.v1" Alias="Common"/>
</edmx:Reference> </edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata4/sap/zui_arena_change_approval_o4/srvd/sap/zui_arena_change_approval/0001/$metadata"> <edmx:Reference Uri="/sap/opu/odata4/sap/zui_arena_change_appr_o4/srvd/sap/zui_arena_change_approval/0001/$metadata">
<edmx:Include Namespace="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001"/> <edmx:Include Namespace="com.sap.gateway.srvd.zui_arena_change_approval.v0001"/>
</edmx:Reference> </edmx:Reference>
<edmx:DataServices> <edmx:DataServices>
<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="local"> <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="local">
@@ -29,92 +35,92 @@
"Enabling Table Personalization in SAP Fiori Elements (OData V2 & V4)"; "Enabling Table Personalization in SAP Fiori Elements (OData V2 & V4)";
"Filter Fields - Add, Configure, Move, Delete...", verified 2026-09-14] "Filter Fields - Add, Configure, Move, Delete...", verified 2026-09-14]
============================================================================ --> ============================================================================ -->
<Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type/ID"> <Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType/ID">
<Annotation Term="Common.Label" String="{@i18n>FIELD_ID}"/> <Annotation Term="Common.Label" String="{@i18n>FIELD_ID}"/>
<Annotation Term="UI.Hidden" Bool="true"/> <Annotation Term="UI.Hidden" Bool="true"/>
</Annotations> </Annotations>
<Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type/ItemNumber"> <Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType/ItemNumber">
<Annotation Term="Common.Label" String="{@i18n>FIELD_ITEM}"/> <Annotation Term="Common.Label" String="{@i18n>FIELD_ITEM}"/>
<Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/> <Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/>
</Annotations> </Annotations>
<Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type/ItemName"> <Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType/ItemName">
<Annotation Term="Common.Label" String="{@i18n>FIELD_NAME}"/> <Annotation Term="Common.Label" String="{@i18n>FIELD_NAME}"/>
<Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/> <Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/>
</Annotations> </Annotations>
<Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type/ChangeType"> <Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType/ChangeType">
<Annotation Term="Common.Label" String="{@i18n>FIELD_CHANGE_TYPE}"/> <Annotation Term="Common.Label" String="{@i18n>FIELD_CHANGE_TYPE}"/>
<Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/> <Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/>
</Annotations> </Annotations>
<Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type/Status"> <Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType/Status">
<Annotation Term="Common.Label" String="{@i18n>FIELD_STATUS}"/> <Annotation Term="Common.Label" String="{@i18n>FIELD_STATUS}"/>
<Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/> <Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/>
</Annotations> </Annotations>
<Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type/StatusCriticality"> <Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType/StatusCriticality">
<Annotation Term="Common.Label" String="{@i18n>FIELD_STATUS_CRITICALITY}"/> <Annotation Term="Common.Label" String="{@i18n>FIELD_STATUS_CRITICALITY}"/>
<Annotation Term="UI.Hidden" Bool="true"/> <Annotation Term="UI.Hidden" Bool="true"/>
<Annotation Term="UI.HiddenFilter" Bool="true"/> <Annotation Term="UI.HiddenFilter" Bool="true"/>
</Annotations> </Annotations>
<Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type/RequestedBy"> <Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType/RequestedBy">
<Annotation Term="Common.Label" String="{@i18n>FIELD_REQUESTED_BY}"/> <Annotation Term="Common.Label" String="{@i18n>FIELD_REQUESTED_BY}"/>
<Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/> <Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/>
</Annotations> </Annotations>
<Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type/RequestedOn"> <Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType/RequestedOn">
<Annotation Term="Common.Label" String="{@i18n>FIELD_REQUESTED_ON}"/> <Annotation Term="Common.Label" String="{@i18n>FIELD_REQUESTED_ON}"/>
<Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/> <Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/>
</Annotations> </Annotations>
<Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type/DecidedBy"> <Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType/DecidedBy">
<Annotation Term="Common.Label" String="{@i18n>FIELD_DECIDED_BY}"/> <Annotation Term="Common.Label" String="{@i18n>FIELD_DECIDED_BY}"/>
<Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/> <Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/>
</Annotations> </Annotations>
<Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type/DecidedOn"> <Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType/DecidedOn">
<Annotation Term="Common.Label" String="{@i18n>FIELD_DECIDED_ON}"/> <Annotation Term="Common.Label" String="{@i18n>FIELD_DECIDED_ON}"/>
<Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/> <Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/>
</Annotations> </Annotations>
<Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type/ArenaEcoId"> <Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType/ArenaEcoId">
<Annotation Term="Common.Label" String="{@i18n>FIELD_ARENA_ECO}"/> <Annotation Term="Common.Label" String="{@i18n>FIELD_ARENA_ECO}"/>
<Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/> <Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/>
</Annotations> </Annotations>
<Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type/SapChangeNumber"> <Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType/SapChangeNumber">
<Annotation Term="Common.Label" String="{@i18n>FIELD_SAP_CHANGE_NO}"/> <Annotation Term="Common.Label" String="{@i18n>FIELD_SAP_CHANGE_NO}"/>
<Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/> <Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/>
</Annotations> </Annotations>
<Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type/ErrorMessage"> <Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType/ErrorMessage">
<Annotation Term="Common.Label" String="{@i18n>FIELD_ERROR_MSG}"/> <Annotation Term="Common.Label" String="{@i18n>FIELD_ERROR_MSG}"/>
<Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/> <Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/>
<Annotation Term="UI.HiddenFilter" Bool="true"/> <Annotation Term="UI.HiddenFilter" Bool="true"/>
</Annotations> </Annotations>
<Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type/RejectReason"> <Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType/RejectReason">
<Annotation Term="Common.Label" String="{@i18n>FIELD_REJECT_REASON}"/> <Annotation Term="Common.Label" String="{@i18n>FIELD_REJECT_REASON}"/>
<Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/> <Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/>
<Annotation Term="UI.HiddenFilter" Bool="true"/> <Annotation Term="UI.HiddenFilter" Bool="true"/>
</Annotations> </Annotations>
<Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type/SapPreview"> <Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType/SapPreview">
<Annotation Term="Common.Label" String="{@i18n>FIELD_STAGED_PAYLOAD}"/> <Annotation Term="Common.Label" String="{@i18n>FIELD_STAGED_PAYLOAD}"/>
<Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/> <Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/ReadOnly"/>
<Annotation Term="UI.HiddenFilter" Bool="true"/> <Annotation Term="UI.HiddenFilter" Bool="true"/>
<!-- long JSON payload: render as text area [source: fiori-mcp-server "Text and Text Arrangement" sample (@UI.MultiLineText), verified 2026-09-14] --> <!-- long JSON payload: render as text area [source: fiori-mcp-server "Text and Text Arrangement" sample (@UI.MultiLineText), verified 2026-09-14] -->
<Annotation Term="UI.MultiLineText" Bool="true"/> <Annotation Term="UI.MultiLineText" Bool="true"/>
</Annotations> </Annotations>
<Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type/IsApprovable"> <Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType/IsApprovable">
<Annotation Term="Common.Label" String="{@i18n>FIELD_IS_APPROVABLE}"/> <Annotation Term="Common.Label" String="{@i18n>FIELD_IS_APPROVABLE}"/>
<Annotation Term="UI.Hidden" Bool="true"/> <Annotation Term="UI.Hidden" Bool="true"/>
<Annotation Term="UI.HiddenFilter" Bool="true"/> <Annotation Term="UI.HiddenFilter" Bool="true"/>
</Annotations> </Annotations>
<Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type/IsRetriable"> <Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType/IsRetriable">
<Annotation Term="Common.Label" String="{@i18n>FIELD_IS_RETRIABLE}"/> <Annotation Term="Common.Label" String="{@i18n>FIELD_IS_RETRIABLE}"/>
<Annotation Term="UI.Hidden" Bool="true"/> <Annotation Term="UI.Hidden" Bool="true"/>
<Annotation Term="UI.HiddenFilter" Bool="true"/> <Annotation Term="UI.HiddenFilter" Bool="true"/>
</Annotations> </Annotations>
<!-- Action parameter label for the reject dialog --> <!-- Action parameter label for the reject dialog -->
<Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.reject(com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type)/RejectReason"> <Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval.v0001.reject(com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType)/RejectReason">
<Annotation Term="Common.Label" String="{@i18n>FIELD_REJECT_REASON}"/> <Annotation Term="Common.Label" String="{@i18n>FIELD_REJECT_REASON}"/>
</Annotations> </Annotations>
<!-- ============================================================================ <!-- ============================================================================
Entity-type annotations Entity-type annotations
============================================================================ --> ============================================================================ -->
<Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type"> <Annotations Target="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType">
<!-- Semantic key: bold ObjectIdentifier in responsive tables <!-- Semantic key: bold ObjectIdentifier in responsive tables
[source: fiori-mcp-server "Handling Semantic Key Fields in SAP Fiori Elements", verified 2026-09-14] --> [source: fiori-mcp-server "Handling Semantic Key Fields in SAP Fiori Elements", verified 2026-09-14] -->
@@ -255,28 +261,30 @@
<!-- Object page header actions (the ONLY place approve/reject/retry are offered; the list <!-- Object page header actions (the ONLY place approve/reject/retry are offered; the list
report table is display-only with selectionMode None in the manifest). report table is display-only with selectionMode None in the manifest).
Enablement comes from Core.OperationAvailable in the service metadata Enablement is owned by the backend: RAP emits Core.OperationAvailable
(approve/reject: IsApprovable, retrySync: IsRetriable). Path="_it/__OperationControl/<action>" for the ( features : instance ) actions (live $metadata
[source: fiori-mcp-server "Action Control for Context-Dependent Actions"; 2026-09-16). Deliberately NO OperationAvailable in this file - a local one would replace the
sap-docs /sapui5/06_SAP_Fiori_Elements/actions-cbf16c5#enabling-custom-actions-defined-using-annotations, backend annotation on the same target.
verified 2026-09-14] --> [source: fiori-mcp-server "Action Control for Context-Dependent Actions (SAP Fiori Elements)";
"Enable Header Actions on the Object Page (UI.Identification / DataFieldForAction)";
"Visualizing and Managing Annotations with SAP Fiori Tools - Service Modeler", verified 2026-09-16] -->
<Annotation Term="UI.Identification"> <Annotation Term="UI.Identification">
<Collection> <Collection>
<Record Type="UI.DataFieldForAction"> <Record Type="UI.DataFieldForAction">
<PropertyValue Property="Label" String="{@i18n>ACTION_APPROVE}"/> <PropertyValue Property="Label" String="{@i18n>ACTION_APPROVE}"/>
<PropertyValue Property="Action" String="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.approve"/> <PropertyValue Property="Action" String="com.sap.gateway.srvd.zui_arena_change_approval.v0001.approve"/>
<PropertyValue Property="Determining" Bool="false"/> <PropertyValue Property="Determining" Bool="false"/>
<PropertyValue Property="Criticality" EnumMember="UI.CriticalityType/Positive"/> <PropertyValue Property="Criticality" EnumMember="UI.CriticalityType/Positive"/>
</Record> </Record>
<Record Type="UI.DataFieldForAction"> <Record Type="UI.DataFieldForAction">
<PropertyValue Property="Label" String="{@i18n>ACTION_REJECT}"/> <PropertyValue Property="Label" String="{@i18n>ACTION_REJECT}"/>
<PropertyValue Property="Action" String="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.reject"/> <PropertyValue Property="Action" String="com.sap.gateway.srvd.zui_arena_change_approval.v0001.reject"/>
<PropertyValue Property="Determining" Bool="false"/> <PropertyValue Property="Determining" Bool="false"/>
<PropertyValue Property="Criticality" EnumMember="UI.CriticalityType/Negative"/> <PropertyValue Property="Criticality" EnumMember="UI.CriticalityType/Negative"/>
</Record> </Record>
<Record Type="UI.DataFieldForAction"> <Record Type="UI.DataFieldForAction">
<PropertyValue Property="Label" String="{@i18n>ACTION_RETRY}"/> <PropertyValue Property="Label" String="{@i18n>ACTION_RETRY}"/>
<PropertyValue Property="Action" String="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.retrySync"/> <PropertyValue Property="Action" String="com.sap.gateway.srvd.zui_arena_change_approval.v0001.retrySync"/>
<PropertyValue Property="Determining" Bool="false"/> <PropertyValue Property="Determining" Bool="false"/>
<PropertyValue Property="Criticality" EnumMember="UI.CriticalityType/Critical"/> <PropertyValue Property="Criticality" EnumMember="UI.CriticalityType/Critical"/>
</Record> </Record>
+1 -1
View File
@@ -26,7 +26,7 @@ CHANGE_APPROVALS_TITLE=Change Approvals
ACTION_APPROVE=Approve ACTION_APPROVE=Approve
#XBUT: Rejects the staged change; asks for a reason #XBUT: Rejects the staged change; asks for a reason
ACTION_REJECT=Reject ACTION_REJECT=Reject
#XBUT: Re-runs a FAILED SAP sync (FAILED -> EXECUTING) #XBUT: Re-runs a FAILED SAP sync (FAILED -> APPROVED)
ACTION_RETRY=Retry Sync ACTION_RETRY=Retry Sync
#XFLD: Arena item number (e.g. MC-100-0042) #XFLD: Arena item number (e.g. MC-100-0042)
+1 -1
View File
@@ -26,7 +26,7 @@ CHANGE_APPROVALS_TITLE=Change Approvals
ACTION_APPROVE=Approve ACTION_APPROVE=Approve
#XBUT: Rejects the staged change; asks for a reason #XBUT: Rejects the staged change; asks for a reason
ACTION_REJECT=Reject ACTION_REJECT=Reject
#XBUT: Re-runs a FAILED SAP sync (FAILED -> EXECUTING) #XBUT: Re-runs a FAILED SAP sync (FAILED -> APPROVED)
ACTION_RETRY=Retry Sync ACTION_RETRY=Retry Sync
#XFLD: Arena item number (e.g. MC-100-0042) #XFLD: Arena item number (e.g. MC-100-0042)
@@ -4,151 +4,215 @@
"ItemNumber": "MC-100-0042", "ItemNumber": "MC-100-0042",
"ItemName": "RF Amplifier Module, 2-18 GHz", "ItemName": "RF Amplifier Module, 2-18 GHz",
"ChangeType": "PRODUCT", "ChangeType": "PRODUCT",
"Status": "PENDING",
"StatusCriticality": 2,
"ArenaEcoId": "ECO-2026-0113", "ArenaEcoId": "ECO-2026-0113",
"Status": "PENDING",
"StatusText": "Pending approval",
"StatusCriticality": 2,
"RequestedBy": "AZAW", "RequestedBy": "AZAW",
"RequestedOn": "2026-09-12T14:05:00Z", "RequestedOn": "2026-09-12T14:05:00Z",
"DecidedBy": null, "DecidedBy": "",
"DecidedOn": null, "DecidedOn": null,
"SapChangeNumber": null, "SapChangeNumber": "",
"ErrorMessage": null, "ErrorMessage": "",
"RejectReason": null, "RejectReason": "",
"SapPreview": "{\"Product\":\"MC-100-0042\",\"ProductType\":\"FERT\",\"BaseUnit\":\"PC\",\"IndustrySector\":\"M\",\"GrossWeight\":\"0.412\",\"WeightUnit\":\"KG\",\"ProductGroup\":\"RFAMP\",\"CrossPlantStatus\":\"\",\"_ProductDescription\":[{\"Language\":\"EN\",\"ProductDescription\":\"RF Amplifier Module, 2-18 GHz\"}],\"_ProductPlant\":[{\"Plant\":\"1710\",\"AvailabilityCheckType\":\"02\",\"ProfitCenter\":\"YB700\",\"MRPType\":\"PD\",\"ProcurementType\":\"E\"},{\"Plant\":\"1720\",\"AvailabilityCheckType\":\"02\",\"ProfitCenter\":\"YB700\",\"MRPType\":\"PD\",\"ProcurementType\":\"F\"}],\"_ProductSalesDelivery\":[{\"ProductSalesOrg\":\"1710\",\"ProductDistributionChnl\":\"10\",\"DeliveryQuantityUnit\":\"PC\",\"ItemCategoryGroup\":\"NORM\"}],\"_ProductStorage\":[{\"Plant\":\"1710\",\"StorageLocation\":\"171A\"}],\"_ProductValuation\":[{\"ValuationArea\":\"1710\",\"ValuationClass\":\"7920\",\"PriceControl\":\"S\",\"StandardPrice\":\"245.00\",\"Currency\":\"USD\"}]}", "SapPreview": "{\"Product\":\"MC-100-0042\",\"ProductType\":\"FERT\",\"BaseUnit\":\"PC\",\"IndustrySector\":\"M\",\"GrossWeight\":\"0.412\",\"WeightUnit\":\"KG\",\"ProductGroup\":\"RFAMP\",\"CrossPlantStatus\":\"\",\"_ProductDescription\":[{\"Language\":\"EN\",\"ProductDescription\":\"RF Amplifier Module, 2-18 GHz\"}],\"_ProductPlant\":[{\"Plant\":\"1710\",\"AvailabilityCheckType\":\"02\",\"ProfitCenter\":\"YB700\",\"MRPType\":\"PD\",\"ProcurementType\":\"E\"},{\"Plant\":\"1720\",\"AvailabilityCheckType\":\"02\",\"ProfitCenter\":\"YB700\",\"MRPType\":\"PD\",\"ProcurementType\":\"F\"}],\"_ProductSalesDelivery\":[{\"ProductSalesOrg\":\"1710\",\"ProductDistributionChnl\":\"10\",\"DeliveryQuantityUnit\":\"PC\",\"ItemCategoryGroup\":\"NORM\"}],\"_ProductStorage\":[{\"Plant\":\"1710\",\"StorageLocation\":\"171A\"}],\"_ProductValuation\":[{\"ValuationArea\":\"1710\",\"ValuationClass\":\"7920\",\"PriceControl\":\"S\",\"StandardPrice\":\"245.00\",\"Currency\":\"USD\"}]}",
"IsApprovable": true, "IsApprovable": true,
"IsRetriable": false "IsRetriable": false,
"LocalLastChangedAt": "2026-09-12T14:05:00Z",
"__OperationControl": {
"approve": true,
"reject": true,
"retrySync": false
},
"SAP__Messages": []
}, },
{ {
"ID": "6f1c2a3e-0002-4a6b-9c1d-000000000002", "ID": "6f1c2a3e-0002-4a6b-9c1d-000000000002",
"ItemNumber": "MC-100-0042", "ItemNumber": "MC-100-0042",
"ItemName": "RF Amplifier Module, 2-18 GHz", "ItemName": "RF Amplifier Module, 2-18 GHz",
"ChangeType": "CHANGE_MASTER", "ChangeType": "CHANGE_MASTER",
"Status": "PENDING",
"StatusCriticality": 2,
"ArenaEcoId": "ECO-2026-0113", "ArenaEcoId": "ECO-2026-0113",
"Status": "PENDING",
"StatusText": "Pending approval",
"StatusCriticality": 2,
"RequestedBy": "AZAW", "RequestedBy": "AZAW",
"RequestedOn": "2026-09-12T14:05:04Z", "RequestedOn": "2026-09-12T14:05:04Z",
"DecidedBy": null, "DecidedBy": "",
"DecidedOn": null, "DecidedOn": null,
"SapChangeNumber": null, "SapChangeNumber": "",
"ErrorMessage": null, "ErrorMessage": "",
"RejectReason": null, "RejectReason": "",
"SapPreview": "{\"ChangeNumberDescription\":\"ECO: Rev C amplifier bias network update\",\"ChangeNumberValidFromDate\":\"2026-09-12T00:00:00Z\",\"ReasonForChangeText\":\"ARENA ECO-2026-0113\",\"ChangeNumberStatus\":\"01\"}", "SapPreview": "{\"ChangeNumberDescription\":\"ECO: Rev C amplifier bias network update\",\"ChangeNumberValidFromDate\":\"2026-09-12T00:00:00Z\",\"ReasonForChangeText\":\"ARENA ECO-2026-0113\",\"ChangeNumberStatus\":\"01\"}",
"IsApprovable": true, "IsApprovable": true,
"IsRetriable": false "IsRetriable": false,
"LocalLastChangedAt": "2026-09-12T14:05:04Z",
"__OperationControl": {
"approve": true,
"reject": true,
"retrySync": false
},
"SAP__Messages": []
}, },
{ {
"ID": "6f1c2a3e-0003-4a6b-9c1d-000000000003", "ID": "6f1c2a3e-0003-4a6b-9c1d-000000000003",
"ItemNumber": "MC-200-0117", "ItemNumber": "MC-200-0117",
"ItemName": "Bias Tee Assembly, SMA", "ItemName": "Bias Tee Assembly, SMA",
"ChangeType": "BOM", "ChangeType": "BOM",
"Status": "PENDING",
"StatusCriticality": 2,
"ArenaEcoId": "ECO-2026-0118", "ArenaEcoId": "ECO-2026-0118",
"Status": "PENDING",
"StatusText": "Pending approval",
"StatusCriticality": 2,
"RequestedBy": "JDOE", "RequestedBy": "JDOE",
"RequestedOn": "2026-09-13T09:41:00Z", "RequestedOn": "2026-09-13T09:41:00Z",
"DecidedBy": null, "DecidedBy": "",
"DecidedOn": null, "DecidedOn": null,
"SapChangeNumber": null, "SapChangeNumber": "",
"ErrorMessage": null, "ErrorMessage": "",
"RejectReason": null, "RejectReason": "",
"SapPreview": "{\"Material\":\"MC-200-0117\",\"Plant\":\"1710\",\"BillOfMaterialCategory\":\"M\",\"BillOfMaterialVariantUsage\":\"1\",\"HeaderValidityStartDate\":\"2026-09-13T00:00:00Z\",\"ToBOMItem\":[{\"BillOfMaterialItemNumber\":\"0010\",\"BillOfMaterialComponent\":\"MC-500-0031\",\"BillOfMaterialItemQuantity\":\"2\",\"BillOfMaterialItemCategory\":\"L\"},{\"BillOfMaterialItemNumber\":\"0020\",\"BillOfMaterialComponent\":\"MC-500-0088\",\"BillOfMaterialItemQuantity\":\"1\",\"BillOfMaterialItemCategory\":\"L\"}]}", "SapPreview": "{\"Material\":\"MC-200-0117\",\"Plant\":\"1710\",\"BillOfMaterialCategory\":\"M\",\"BillOfMaterialVariantUsage\":\"1\",\"HeaderValidityStartDate\":\"2026-09-13T00:00:00Z\",\"ToBOMItem\":[{\"BillOfMaterialItemNumber\":\"0010\",\"BillOfMaterialComponent\":\"MC-500-0031\",\"BillOfMaterialItemQuantity\":\"2\",\"BillOfMaterialItemCategory\":\"L\"},{\"BillOfMaterialItemNumber\":\"0020\",\"BillOfMaterialComponent\":\"MC-500-0088\",\"BillOfMaterialItemQuantity\":\"1\",\"BillOfMaterialItemCategory\":\"L\"}]}",
"IsApprovable": true, "IsApprovable": true,
"IsRetriable": false "IsRetriable": false,
"LocalLastChangedAt": "2026-09-13T09:41:00Z",
"__OperationControl": {
"approve": true,
"reject": true,
"retrySync": false
},
"SAP__Messages": []
}, },
{ {
"ID": "6f1c2a3e-0004-4a6b-9c1d-000000000004", "ID": "6f1c2a3e-0004-4a6b-9c1d-000000000004",
"ItemNumber": "MC-300-0009", "ItemNumber": "MC-300-0009",
"ItemName": "Low Noise Amplifier, 0.5-4 GHz", "ItemName": "Low Noise Amplifier, 0.5-4 GHz",
"ChangeType": "PRODUCT", "ChangeType": "PRODUCT",
"Status": "APPROVED",
"StatusCriticality": 2,
"ArenaEcoId": "ECO-2026-0097", "ArenaEcoId": "ECO-2026-0097",
"Status": "APPROVED",
"StatusText": "Approved - awaiting execution",
"StatusCriticality": 2,
"RequestedBy": "AZAW", "RequestedBy": "AZAW",
"RequestedOn": "2026-09-08T16:22:00Z", "RequestedOn": "2026-09-08T16:22:00Z",
"DecidedBy": "MSMITH", "DecidedBy": "MSMITH",
"DecidedOn": "2026-09-09T08:10:00Z", "DecidedOn": "2026-09-09T08:10:00Z",
"SapChangeNumber": null, "SapChangeNumber": "",
"ErrorMessage": null, "ErrorMessage": "",
"RejectReason": null, "RejectReason": "",
"SapPreview": "{\"Product\":\"MC-300-0009\",\"ProductType\":\"FERT\",\"BaseUnit\":\"PC\",\"ProductGroup\":\"LNA\",\"_ProductDescription\":[{\"Language\":\"EN\",\"ProductDescription\":\"Low Noise Amplifier, 0.5-4 GHz\"}]}", "SapPreview": "{\"Product\":\"MC-300-0009\",\"ProductType\":\"FERT\",\"BaseUnit\":\"PC\",\"ProductGroup\":\"LNA\",\"_ProductDescription\":[{\"Language\":\"EN\",\"ProductDescription\":\"Low Noise Amplifier, 0.5-4 GHz\"}]}",
"IsApprovable": false, "IsApprovable": false,
"IsRetriable": false "IsRetriable": false,
"LocalLastChangedAt": "2026-09-09T08:10:00Z",
"__OperationControl": {
"approve": false,
"reject": false,
"retrySync": false
},
"SAP__Messages": []
}, },
{ {
"ID": "6f1c2a3e-0007-4a6b-9c1d-000000000007", "ID": "6f1c2a3e-0007-4a6b-9c1d-000000000007",
"ItemNumber": "MC-300-0009", "ItemNumber": "MC-300-0009",
"ItemName": "Low Noise Amplifier, 0.5-4 GHz", "ItemName": "Low Noise Amplifier, 0.5-4 GHz",
"ChangeType": "CHANGE_MASTER", "ChangeType": "CHANGE_MASTER",
"Status": "EXECUTING",
"ArenaEcoId": "ECO-2026-0097", "ArenaEcoId": "ECO-2026-0097",
"Status": "EXECUTING",
"StatusText": "Executing in SAP",
"StatusCriticality": 5,
"RequestedBy": "AZAW", "RequestedBy": "AZAW",
"RequestedOn": "2026-09-08T16:22:03Z", "RequestedOn": "2026-09-08T16:22:03Z",
"DecidedBy": "MSMITH", "DecidedBy": "MSMITH",
"DecidedOn": "2026-09-09T08:10:00Z", "DecidedOn": "2026-09-09T08:10:00Z",
"SapChangeNumber": null, "SapChangeNumber": "",
"ErrorMessage": null, "ErrorMessage": "",
"RejectReason": null, "RejectReason": "",
"SapPreview": "{\"ChangeNumberDescription\":\"ECO: LNA input match rework\",\"ChangeNumberValidFromDate\":\"2026-09-09T00:00:00Z\",\"ReasonForChangeText\":\"ARENA ECO-2026-0097\",\"ChangeNumberStatus\":\"01\"}", "SapPreview": "{\"ChangeNumberDescription\":\"ECO: LNA input match rework\",\"ChangeNumberValidFromDate\":\"2026-09-09T00:00:00Z\",\"ReasonForChangeText\":\"ARENA ECO-2026-0097\",\"ChangeNumberStatus\":\"01\"}",
"StatusCriticality": 5,
"IsApprovable": false, "IsApprovable": false,
"IsRetriable": false "IsRetriable": false,
"LocalLastChangedAt": "2026-09-09T08:10:00Z",
"__OperationControl": {
"approve": false,
"reject": false,
"retrySync": false
},
"SAP__Messages": []
}, },
{ {
"ID": "6f1c2a3e-0008-4a6b-9c1d-000000000008", "ID": "6f1c2a3e-0008-4a6b-9c1d-000000000008",
"ItemNumber": "MC-150-0301", "ItemNumber": "MC-150-0301",
"ItemName": "Directional Coupler, 10 dB", "ItemName": "Directional Coupler, 10 dB",
"ChangeType": "CHANGE_MASTER", "ChangeType": "CHANGE_MASTER",
"Status": "SYNCED",
"ArenaEcoId": "ECO-2026-0088", "ArenaEcoId": "ECO-2026-0088",
"Status": "SYNCED",
"StatusText": "Synced to SAP",
"StatusCriticality": 3,
"RequestedBy": "JDOE", "RequestedBy": "JDOE",
"RequestedOn": "2026-09-05T10:00:00Z", "RequestedOn": "2026-09-05T10:00:00Z",
"DecidedBy": "MSMITH", "DecidedBy": "MSMITH",
"DecidedOn": "2026-09-05T11:30:00Z", "DecidedOn": "2026-09-05T11:30:00Z",
"SapChangeNumber": "500000012301", "SapChangeNumber": "500000012301",
"ErrorMessage": null, "ErrorMessage": "",
"RejectReason": null, "RejectReason": "",
"SapPreview": "{\"ChangeNumberDescription\":\"ECO: Coupler housing plating change\",\"ChangeNumberValidFromDate\":\"2026-09-05T00:00:00Z\",\"ReasonForChangeText\":\"ARENA ECO-2026-0088\",\"ChangeNumberStatus\":\"01\"}", "SapPreview": "{\"ChangeNumberDescription\":\"ECO: Coupler housing plating change\",\"ChangeNumberValidFromDate\":\"2026-09-05T00:00:00Z\",\"ReasonForChangeText\":\"ARENA ECO-2026-0088\",\"ChangeNumberStatus\":\"01\"}",
"StatusCriticality": 3,
"IsApprovable": false, "IsApprovable": false,
"IsRetriable": false "IsRetriable": false,
"LocalLastChangedAt": "2026-09-05T11:30:00Z",
"__OperationControl": {
"approve": false,
"reject": false,
"retrySync": false
},
"SAP__Messages": []
}, },
{ {
"ID": "6f1c2a3e-0005-4a6b-9c1d-000000000005", "ID": "6f1c2a3e-0005-4a6b-9c1d-000000000005",
"ItemNumber": "MC-410-0250", "ItemNumber": "MC-410-0250",
"ItemName": "Datasheet MC-410-0250 Rev B", "ItemName": "Datasheet MC-410-0250 Rev B",
"ChangeType": "DOCUMENTS", "ChangeType": "DOCUMENTS",
"Status": "REJECTED",
"StatusCriticality": 1,
"ArenaEcoId": "ECO-2026-0101", "ArenaEcoId": "ECO-2026-0101",
"Status": "REJECTED",
"StatusText": "Rejected",
"StatusCriticality": 1,
"RequestedBy": "JDOE", "RequestedBy": "JDOE",
"RequestedOn": "2026-09-10T11:15:00Z", "RequestedOn": "2026-09-10T11:15:00Z",
"DecidedBy": "MSMITH", "DecidedBy": "MSMITH",
"DecidedOn": "2026-09-10T13:02:00Z", "DecidedOn": "2026-09-10T13:02:00Z",
"SapChangeNumber": null, "SapChangeNumber": "",
"ErrorMessage": null, "ErrorMessage": "",
"RejectReason": "Datasheet revision B superseded by Rev C in Arena; re-stage after ECO-2026-0121 is released.", "RejectReason": "Datasheet revision B superseded by Rev C in Arena; re-stage after ECO-2026-0121 is released.",
"SapPreview": "{\"BusinessObjectTypeName\":\"BUS1001006\",\"LinkedSAPObjectKey\":\"MC-410-0250\",\"Url\":\"https://app.bom.com/files/MC-410-0250-B.pdf\",\"MIMEType\":\"text/url\",\"AttachmentDescription\":\"MC-410-0250 Datasheet Rev B\"}", "SapPreview": "{\"BusinessObjectTypeName\":\"BUS1001006\",\"LinkedSAPObjectKey\":\"MC-410-0250\",\"Url\":\"https://app.bom.com/files/MC-410-0250-B.pdf\",\"MIMEType\":\"text/url\",\"AttachmentDescription\":\"MC-410-0250 Datasheet Rev B\"}",
"IsApprovable": false, "IsApprovable": false,
"IsRetriable": false "IsRetriable": false,
"LocalLastChangedAt": "2026-09-10T13:02:00Z",
"__OperationControl": {
"approve": false,
"reject": false,
"retrySync": false
},
"SAP__Messages": []
}, },
{ {
"ID": "6f1c2a3e-0006-4a6b-9c1d-000000000006", "ID": "6f1c2a3e-0006-4a6b-9c1d-000000000006",
"ItemNumber": "MC-200-0117", "ItemNumber": "MC-200-0117",
"ItemName": "Bias Tee Assembly, SMA", "ItemName": "Bias Tee Assembly, SMA",
"ChangeType": "CHANGE_MASTER", "ChangeType": "CHANGE_MASTER",
"Status": "FAILED",
"StatusCriticality": 1,
"ArenaEcoId": "ECO-2026-0118", "ArenaEcoId": "ECO-2026-0118",
"Status": "FAILED",
"StatusText": "Execution failed",
"StatusCriticality": 1,
"RequestedBy": "JDOE", "RequestedBy": "JDOE",
"RequestedOn": "2026-09-13T09:40:00Z", "RequestedOn": "2026-09-13T09:40:00Z",
"DecidedBy": "AZAW", "DecidedBy": "AZAW",
"DecidedOn": "2026-09-13T10:05:00Z", "DecidedOn": "2026-09-13T10:05:00Z",
"SapChangeNumber": null, "SapChangeNumber": "",
"ErrorMessage": "API_CHANGEMASTER;v=0002 returned 400: Input parameters are initial (A_ChangeMasterObjTypeAssgmt requires IsActiveForChangeNumber).", "ErrorMessage": "API_CHANGEMASTER;v=0002 returned 400: Input parameters are initial (A_ChangeMasterObjTypeAssgmt requires IsActiveForChangeNumber).",
"RejectReason": null, "RejectReason": "",
"SapPreview": "{\"ChangeNumberDescription\":\"ECO: Bias tee connector change to SMA-F\",\"ChangeNumberValidFromDate\":\"2026-09-13T00:00:00Z\",\"ReasonForChangeText\":\"ARENA ECO-2026-0118\",\"ChangeNumberStatus\":\"01\"}", "SapPreview": "{\"ChangeNumberDescription\":\"ECO: Bias tee connector change to SMA-F\",\"ChangeNumberValidFromDate\":\"2026-09-13T00:00:00Z\",\"ReasonForChangeText\":\"ARENA ECO-2026-0118\",\"ChangeNumberStatus\":\"01\"}",
"IsApprovable": false, "IsApprovable": false,
"IsRetriable": true "IsRetriable": true,
"LocalLastChangedAt": "2026-09-13T10:05:00Z",
"__OperationControl": {
"approve": false,
"reject": false,
"retrySync": true
},
"SAP__Messages": []
} }
] ]
+551 -91
View File
@@ -1,132 +1,592 @@
<?xml version="1.0" encoding="UTF-8"?> <edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" xmlns="http://docs.oasis-open.org/odata/ns/edm" Version="4.0">
<!-- <edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_AGGREGATION',Version='0001',SAP__Origin='LOCAL')/$value">
MOCK OData V4 $metadata for the Arena PLM change-approval Fiori Elements app (ADR-002). <edmx:Include Namespace="Org.OData.Aggregation.V1" Alias="SAP__aggregation"/>
</edmx:Reference>
===================================================================================== <edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_ANALYTICS',Version='0001',SAP__Origin='LOCAL')/$value">
SERVICE IDENTITY - single source of truth for the app (referenced by manifest.json, <edmx:Include Namespace="com.sap.vocabularies.Analytics.v1" Alias="SAP__analytics"/>
annotation.xml and ui5-mock.yaml / ui5-local.yaml): </edmx:Reference>
RAP service binding (OData V4 - UI) : ZUI_ARENA_CHANGE_APPROVAL_O4 <edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_AUTHORIZATION',Version='0001',SAP__Origin='LOCAL')/$value">
RAP service definition : ZUI_ARENA_CHANGE_APPROVAL <edmx:Include Namespace="Org.OData.Authorization.V1" Alias="SAP__authorization"/>
Version : 0001 </edmx:Reference>
Service URL : /sap/opu/odata4/sap/zui_arena_change_approval_o4/srvd/sap/zui_arena_change_approval/0001/
Schema namespace / alias : com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001 / SAP__self
Root entity type / entity set : ChangeApproval_Type / ChangeApproval
The first URL segment after /sap/opu/odata4/sap/ and the schema namespace are strongly
indicated to be derived from the service BINDING name (the segment after srvd|srvd_a2x/sap/
is the service DEFINITION name): the tenant serves
/sap/opu/odata4/sap/api_currencyexchangerate/srvd_a2x/sap/currencyexchangerate/0001 with
namespace com.sap.gateway.srvd_a2x.api_currencyexchangerate.v0001, and SAP Help documents
/sap/opu/odata4/sap/api_product/srvd_a2x/sap/product/0002.
[source: live tenant my429480 ~/projects/sap-apps/exchange-rates-cap/package.json lines 33,39 and
srv/external/API_CURRENCYEXCHANGERATE.edmx line 55; sap-docs sap-help loio 5f45f125dfe84c20beb18f5de1512f5a,
verified 2026-09-14]
Provisional - namespace = binding name is strongly indicated, not proven; reconcile namespace,
entity type suffix and URL segments against the live $metadata of the RAP UI service binding
at PRD V-2 before deploy.
=====================================================================================
Shape mirrors what an ABAP Cloud OData V4 service binding emits: schema namespace
com.sap.gateway.<srvd|srvd_a2x>.<binding>.v0001 with Alias="SAP__self", entity types
suffixed "_Type", un-suffixed entity sets, and vocabulary aliases SAP__common /
SAP__capabilities / SAP__core / SAP__UI.
[source: live tenant my429480 EDMX ~/projects/sap-apps/exchange-rates-cap/srv/external/API_CURRENCYEXCHANGERATE.edmx
lines 13-31 (vocabulary aliases), 55 (schema/alias), 57 (_Type), 95 (entity set), verified 2026-09-14]
A Fiori app consumes the UI service binding (srvd), not the A2X binding used by the worker.
[source: sap-docs sap-help loio 5717c0a1dafc4335add0207b81c39cef, verified 2026-09-14]
Action availability: RAP emits Core.OperationAvailable for actions declared with
"( features : instance )" in the behavior definition; the Fiori side needs no extra annotation.
[source: sap-docs /sapui5/06_SAP_Fiori_Elements/actions-cbf16c5#enabling-custom-actions-defined-using-annotations
("No ABAP CDS annotation is required, since the setting is made according to the modelling ... in RAP BDEF"),
abap-docs /abap-docs-standard/ABENBDL_ACTIONS_FC, verified 2026-09-14]
The mock models the emitted result with the boolean properties IsApprovable / IsRetriable.
-->
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_CAPABILITIES',Version='0001',SAP__Origin='LOCAL')/$value"> <edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_CAPABILITIES',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="Org.OData.Capabilities.V1" Alias="SAP__capabilities"/> <edmx:Include Namespace="Org.OData.Capabilities.V1" Alias="SAP__capabilities"/>
</edmx:Reference> </edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_CODELIST',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.CodeList.v1" Alias="SAP__CodeList"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_COMMON',Version='0001',SAP__Origin='LOCAL')/$value"> <edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_COMMON',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.Common.v1" Alias="SAP__common"/> <edmx:Include Namespace="com.sap.vocabularies.Common.v1" Alias="SAP__common"/>
</edmx:Reference> </edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_COMMUNICATION',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.Communication.v1" Alias="SAP__communication"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_CORE',Version='0001',SAP__Origin='LOCAL')/$value"> <edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_CORE',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="Org.OData.Core.V1" Alias="SAP__core"/> <edmx:Include Namespace="Org.OData.Core.V1" Alias="SAP__core"/>
</edmx:Reference> </edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_HIERARCHY',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.Hierarchy.v1" Alias="SAP__hierarchy"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_HTML5',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.HTML5.v1" Alias="SAP__HTML5"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_MEASURES',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="Org.OData.Measures.V1" Alias="SAP__measures"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_ODM',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.ODM.v1" Alias="SAP__ODM"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_PDF',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.PDF.v1" Alias="SAP__PDF"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_PERSONALDATA',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.PersonalData.v1" Alias="SAP__personalData"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_SESSION',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.Session.v1" Alias="SAP__session"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_SUPPORT',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.Support.v1" Alias="SAP__support"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_UI',Version='0001',SAP__Origin='LOCAL')/$value"> <edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_UI',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.UI.v1" Alias="SAP__UI"/> <edmx:Include Namespace="com.sap.vocabularies.UI.v1" Alias="SAP__UI"/>
</edmx:Reference> </edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_VALIDATION',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="Org.OData.Validation.V1" Alias="SAP__validation"/>
</edmx:Reference>
<edmx:DataServices> <edmx:DataServices>
<Schema Namespace="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001" Alias="SAP__self" xmlns="http://docs.oasis-open.org/odata/ns/edm"> <Schema Namespace="com.sap.gateway.srvd.zui_arena_change_approval.v0001" Alias="SAP__self">
<Annotation Term="SAP__core.SchemaVersion" String="1.0.0"/> <Annotation Term="SAP__core.SchemaVersion" String="1.0.0"/>
<EntityType Name="ChangeApprovalType">
<EntityType Name="ChangeApproval_Type">
<Key> <Key>
<PropertyRef Name="ID"/> <PropertyRef Name="ID"/>
</Key> </Key>
<Property Name="ID" Type="Edm.Guid" Nullable="false"/> <Property Name="ID" Type="Edm.Guid" Nullable="false"/>
<Property Name="ItemNumber" Type="Edm.String" MaxLength="18"/> <Property Name="ItemNumber" Type="Edm.String" Nullable="false" MaxLength="18"/>
<Property Name="ItemName" Type="Edm.String" MaxLength="40"/> <Property Name="ItemName" Type="Edm.String" Nullable="false" MaxLength="40"/>
<Property Name="ChangeType" Type="Edm.String" MaxLength="15"/> <Property Name="ChangeType" Type="Edm.String" Nullable="false" MaxLength="15"/>
<!-- Status domain: PENDING -> APPROVED -> EXECUTING -> SYNCED | FAILED; REJECTED terminal; <Property Name="ArenaEcoId" Type="Edm.String" Nullable="false" MaxLength="20"/>
FAILED -retrySync-> EXECUTING. approve/reject only for PENDING, retrySync only for FAILED (PRD RAP-1). --> <Property Name="Status" Type="Edm.String" Nullable="false" MaxLength="10"/>
<Property Name="Status" Type="Edm.String" MaxLength="10"/> <Property Name="StatusText" Type="Edm.String" Nullable="false" MaxLength="29"/>
<Property Name="StatusCriticality" Type="Edm.Byte"/> <Property Name="StatusCriticality" Type="Edm.Byte" Nullable="false"/>
<Property Name="ArenaEcoId" Type="Edm.String" MaxLength="20"/> <Property Name="RequestedBy" Type="Edm.String" Nullable="false" MaxLength="12"/>
<Property Name="RequestedBy" Type="Edm.String" MaxLength="12"/>
<Property Name="RequestedOn" Type="Edm.DateTimeOffset" Precision="7"/> <Property Name="RequestedOn" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="DecidedBy" Type="Edm.String" MaxLength="12"/> <Property Name="DecidedBy" Type="Edm.String" Nullable="false" MaxLength="12"/>
<Property Name="DecidedOn" Type="Edm.DateTimeOffset" Precision="7"/> <Property Name="DecidedOn" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="SapChangeNumber" Type="Edm.String" MaxLength="12"/> <Property Name="SapChangeNumber" Type="Edm.String" Nullable="false" MaxLength="12"/>
<Property Name="ErrorMessage" Type="Edm.String" MaxLength="255"/> <Property Name="ErrorMessage" Type="Edm.String" Nullable="false" MaxLength="255"/>
<Property Name="RejectReason" Type="Edm.String" MaxLength="255"/> <Property Name="RejectReason" Type="Edm.String" Nullable="false" MaxLength="255"/>
<!-- abap.string on the RAP side: emitted as Edm.String without MaxLength --> <Property Name="SapPreview" Type="Edm.String" Nullable="false"/>
<Property Name="SapPreview" Type="Edm.String"/> <Property Name="IsApprovable" Type="Edm.Boolean" Nullable="false"/>
<!-- Instance feature-control results (RAP "features : instance"), consumed by Core.OperationAvailable below --> <Property Name="IsRetriable" Type="Edm.Boolean" Nullable="false"/>
<Property Name="IsApprovable" Type="Edm.Boolean"/> <Property Name="LocalLastChangedAt" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="IsRetriable" Type="Edm.Boolean"/> <Property Name="__OperationControl" Type="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalOperationControl"/>
<Property Name="SAP__Messages" Type="Collection(com.sap.gateway.srvd.zui_arena_change_approval.v0001.SAP__Message)" Nullable="false"/>
</EntityType> </EntityType>
<ComplexType Name="ChangeApprovalOperationControl">
<!-- Bound actions (binding parameter "_it" as emitted by RAP service bindings) --> <Property Name="approve" Type="Edm.Boolean" Nullable="false"/>
<Action Name="approve" IsBound="true"> <Property Name="reject" Type="Edm.Boolean" Nullable="false"/>
<Parameter Name="_it" Type="SAP__self.ChangeApproval_Type" Nullable="false"/> <Property Name="retrySync" Type="Edm.Boolean" Nullable="false"/>
<ReturnType Type="SAP__self.ChangeApproval_Type" Nullable="false"/> </ComplexType>
<ComplexType Name="SAP__Message">
<Property Name="code" Type="Edm.String" Nullable="false"/>
<Property Name="message" Type="Edm.String" Nullable="false"/>
<Property Name="target" Type="Edm.String"/>
<Property Name="additionalTargets" Type="Collection(Edm.String)" Nullable="false"/>
<Property Name="transition" Type="Edm.Boolean" Nullable="false"/>
<Property Name="numericSeverity" Type="Edm.Byte" Nullable="false"/>
<Property Name="longtextUrl" Type="Edm.String"/>
</ComplexType>
<Action Name="retrySync" EntitySetPath="_it" IsBound="true">
<Parameter Name="_it" Type="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType" Nullable="false"/>
<ReturnType Type="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType" Nullable="false"/>
</Action> </Action>
<Action Name="reject" IsBound="true"> <Action Name="approve" EntitySetPath="_it" IsBound="true">
<Parameter Name="_it" Type="SAP__self.ChangeApproval_Type" Nullable="false"/> <Parameter Name="_it" Type="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType" Nullable="false"/>
<ReturnType Type="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType" Nullable="false"/>
</Action>
<Action Name="reject" EntitySetPath="_it" IsBound="true">
<Parameter Name="_it" Type="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType" Nullable="false"/>
<Parameter Name="RejectReason" Type="Edm.String" Nullable="false" MaxLength="255"/> <Parameter Name="RejectReason" Type="Edm.String" Nullable="false" MaxLength="255"/>
<ReturnType Type="SAP__self.ChangeApproval_Type" Nullable="false"/> <ReturnType Type="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType" Nullable="false"/>
</Action> </Action>
<Action Name="retrySync" IsBound="true">
<Parameter Name="_it" Type="SAP__self.ChangeApproval_Type" Nullable="false"/>
<ReturnType Type="SAP__self.ChangeApproval_Type" Nullable="false"/>
</Action>
<EntityContainer Name="Container"> <EntityContainer Name="Container">
<EntitySet Name="ChangeApproval" EntityType="com.sap.gateway.srvd.zui_arena_change_approval_o4.v0001.ChangeApproval_Type"/> <EntitySet Name="ChangeApproval" EntityType="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType"/>
</EntityContainer> </EntityContainer>
<Annotations Target="SAP__self.ChangeApprovalType/ID">
<!-- Service-side (metadata) annotations, as a RAP service binding would emit them --> <Annotation Term="SAP__core.Computed"/>
<Annotations Target="SAP__self.ChangeApproval_Type/ID"> <Annotation Term="SAP__UI.Hidden"/>
<Annotation Term="SAP__common.Label" String="UUID"/>
<Annotation Term="SAP__common.QuickInfo" String="16 Byte UUID in 16 Bytes (Raw Format)"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/ItemNumber">
<Annotation Term="SAP__common.Label" String="Item number"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/ItemName">
<Annotation Term="SAP__common.Label" String="Item name"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/ChangeType">
<Annotation Term="SAP__common.Label" String="Change type"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/ArenaEcoId">
<Annotation Term="SAP__common.Label" String="Arena ECO"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/Status">
<Annotation Term="SAP__common.Label" String="Status"/>
<Annotation Term="SAP__common.Text" Path="StatusText">
<Annotation Term="SAP__UI.TextArrangement" EnumMember="SAP__UI.TextArrangementType/TextOnly"/>
</Annotation>
<Annotation Term="SAP__core.Computed"/> <Annotation Term="SAP__core.Computed"/>
</Annotations> </Annotations>
<Annotations Target="SAP__self.approve"> <Annotations Target="SAP__self.ChangeApprovalType/StatusText">
<Annotation Term="SAP__core.OperationAvailable" Path="IsApprovable"/> <Annotation Term="SAP__core.Computed"/>
<Annotation Term="SAP__UI.Hidden"/>
</Annotations> </Annotations>
<Annotations Target="SAP__self.reject"> <Annotations Target="SAP__self.ChangeApprovalType/StatusCriticality">
<Annotation Term="SAP__core.OperationAvailable" Path="IsApprovable"/> <Annotation Term="SAP__core.Computed"/>
<Annotation Term="SAP__UI.Hidden"/>
</Annotations> </Annotations>
<Annotations Target="SAP__self.retrySync"> <Annotations Target="SAP__self.ChangeApprovalType/RequestedBy">
<Annotation Term="SAP__core.OperationAvailable" Path="IsRetriable"/> <Annotation Term="SAP__common.Label" String="Requested by"/>
<Annotation Term="SAP__core.Computed"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/RequestedOn">
<Annotation Term="SAP__common.Label" String="Requested on"/>
<Annotation Term="SAP__core.Computed"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/DecidedBy">
<Annotation Term="SAP__common.Label" String="Decided by"/>
<Annotation Term="SAP__core.Computed"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/DecidedOn">
<Annotation Term="SAP__common.Label" String="Decided on"/>
<Annotation Term="SAP__core.Computed"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/SapChangeNumber">
<Annotation Term="SAP__common.Label" String="SAP change number"/>
<Annotation Term="SAP__core.Computed"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/ErrorMessage">
<Annotation Term="SAP__common.Label" String="Error message"/>
<Annotation Term="SAP__core.Computed"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/RejectReason">
<Annotation Term="SAP__common.Label" String="Reject reason"/>
<Annotation Term="SAP__core.Computed"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/SapPreview">
<Annotation Term="SAP__common.Label" String="Staged payload"/>
<Annotation Term="SAP__UI.MultiLineText"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/IsApprovable">
<Annotation Term="SAP__core.Computed"/>
<Annotation Term="SAP__UI.Hidden"/>
<Annotation Term="SAP__common.Label" String="Truth Value"/>
<Annotation Term="SAP__common.QuickInfo" String="Truth Value: True/False"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/IsRetriable">
<Annotation Term="SAP__core.Computed"/>
<Annotation Term="SAP__UI.Hidden"/>
<Annotation Term="SAP__common.Label" String="Truth Value"/>
<Annotation Term="SAP__common.QuickInfo" String="Truth Value: True/False"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/LocalLastChangedAt">
<Annotation Term="SAP__core.Computed"/>
<Annotation Term="SAP__common.Label" String="Changed On"/>
<Annotation Term="SAP__common.QuickInfo" String="Local Instance Last Change Date Time"/>
<Annotation Term="SAP__common.DocumentationRef" String="urn:sap-com:documentation:key?=type=DE&amp;id=ABP_LOCINST_LASTCHANGE_TSTMPL"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/__OperationControl">
<Annotation Term="SAP__core.Computed"/>
<Annotation Term="SAP__UI.HiddenFilter"/>
<Annotation Term="SAP__UI.Hidden"/>
</Annotations>
<Annotations Target="SAP__self.retrySync(SAP__self.ChangeApprovalType)">
<Annotation Term="SAP__core.OperationAvailable" Path="_it/__OperationControl/retrySync"/>
</Annotations>
<Annotations Target="SAP__self.approve(SAP__self.ChangeApprovalType)">
<Annotation Term="SAP__core.OperationAvailable" Path="_it/__OperationControl/approve"/>
</Annotations>
<Annotations Target="SAP__self.reject(SAP__self.ChangeApprovalType)/RejectReason">
<Annotation Term="SAP__common.Label" String="Reject reason"/>
</Annotations>
<Annotations Target="SAP__self.reject(SAP__self.ChangeApprovalType)">
<Annotation Term="SAP__core.OperationAvailable" Path="_it/__OperationControl/reject"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType">
<Annotation Term="SAP__common.Label" String="Arena change approval - UI projection"/>
<Annotation Term="SAP__common.SemanticKey">
<Collection>
<PropertyPath>ArenaEcoId</PropertyPath>
<PropertyPath>ChangeType</PropertyPath>
<PropertyPath>ItemNumber</PropertyPath>
</Collection>
</Annotation>
<Annotation Term="SAP__UI.DataPoint" Qualifier="StatusPoint">
<Record>
<PropertyValue Property="Value" Path="Status"/>
<PropertyValue Property="Title" String="Status"/>
<PropertyValue Property="Criticality" Path="StatusCriticality"/>
</Record>
</Annotation>
<Annotation Term="SAP__UI.HeaderFacets">
<Collection>
<Record Type="SAP__UI.ReferenceFacet">
<PropertyValue Property="Label" String="Reference numbers"/>
<PropertyValue Property="ID" String="Keys"/>
<PropertyValue Property="Target" AnnotationPath="@SAP__UI.FieldGroup#Keys"/>
</Record>
<Record Type="SAP__UI.ReferenceFacet">
<PropertyValue Property="ID" String="StatusPoint"/>
<PropertyValue Property="Target" AnnotationPath="@SAP__UI.DataPoint#StatusPoint"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="SAP__UI.Facets">
<Collection>
<Record Type="SAP__UI.ReferenceFacet">
<PropertyValue Property="Label" String="Change"/>
<PropertyValue Property="ID" String="Change"/>
<PropertyValue Property="Target" AnnotationPath="@SAP__UI.FieldGroup#Change"/>
</Record>
<Record Type="SAP__UI.ReferenceFacet">
<PropertyValue Property="Label" String="Staged payload"/>
<PropertyValue Property="ID" String="Payload"/>
<PropertyValue Property="Target" AnnotationPath="@SAP__UI.FieldGroup#Payload"/>
</Record>
<Record Type="SAP__UI.ReferenceFacet">
<PropertyValue Property="Label" String="Approval"/>
<PropertyValue Property="ID" String="Approval"/>
<PropertyValue Property="Target" AnnotationPath="@SAP__UI.FieldGroup#Approval"/>
</Record>
<Record Type="SAP__UI.ReferenceFacet">
<PropertyValue Property="Label" String="Execution"/>
<PropertyValue Property="ID" String="Execution"/>
<PropertyValue Property="Target" AnnotationPath="@SAP__UI.FieldGroup#Execution"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="SAP__UI.FieldGroup" Qualifier="Approval">
<Record>
<PropertyValue Property="Data">
<Collection>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Criticality" Path="StatusCriticality"/>
<PropertyValue Property="Value" Path="Status"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="RequestedBy"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="RequestedOn"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="DecidedBy"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="DecidedOn"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="RejectReason"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__UI.FieldGroup" Qualifier="Change">
<Record>
<PropertyValue Property="Data">
<Collection>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ItemNumber"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ItemName"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ChangeType"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ArenaEcoId"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__UI.FieldGroup" Qualifier="Execution">
<Record>
<PropertyValue Property="Data">
<Collection>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="SapChangeNumber"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ErrorMessage"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__UI.FieldGroup" Qualifier="Keys">
<Record>
<PropertyValue Property="Data">
<Collection>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ArenaEcoId"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="SapChangeNumber"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__UI.FieldGroup" Qualifier="Payload">
<Record>
<PropertyValue Property="Data">
<Collection>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="SapPreview"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__UI.HeaderInfo">
<Record>
<PropertyValue Property="TypeName" String="Change Approval"/>
<PropertyValue Property="TypeNamePlural" String="Change Approvals"/>
<PropertyValue Property="TypeImageUrl" String="sap-icon://approvals"/>
<PropertyValue Property="Title">
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ItemNumber"/>
</Record>
</PropertyValue>
<PropertyValue Property="Description">
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ItemName"/>
</Record>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__UI.Identification">
<Collection>
<Record Type="SAP__UI.DataFieldForAction">
<PropertyValue Property="Label" String="Approve"/>
<PropertyValue Property="Action" String="com.sap.gateway.srvd.zui_arena_change_approval.v0001.approve(com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType)"/>
<PropertyValue Property="InvocationGrouping" EnumMember="SAP__UI.OperationGroupingType/Isolated"/>
</Record>
<Record Type="SAP__UI.DataFieldForAction">
<PropertyValue Property="Label" String="Reject"/>
<PropertyValue Property="Action" String="com.sap.gateway.srvd.zui_arena_change_approval.v0001.reject(com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType)"/>
<PropertyValue Property="InvocationGrouping" EnumMember="SAP__UI.OperationGroupingType/Isolated"/>
</Record>
<Record Type="SAP__UI.DataFieldForAction">
<PropertyValue Property="Label" String="Retry sync"/>
<PropertyValue Property="Action" String="com.sap.gateway.srvd.zui_arena_change_approval.v0001.retrySync(com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType)"/>
<PropertyValue Property="InvocationGrouping" EnumMember="SAP__UI.OperationGroupingType/Isolated"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="SAP__UI.LineItem">
<Collection>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ItemNumber"/>
<Annotation Term="SAP__UI.Importance" EnumMember="SAP__UI.ImportanceType/High"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ItemName"/>
<Annotation Term="SAP__UI.Importance" EnumMember="SAP__UI.ImportanceType/Low"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ChangeType"/>
<Annotation Term="SAP__UI.Importance" EnumMember="SAP__UI.ImportanceType/High"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Criticality" Path="StatusCriticality"/>
<PropertyValue Property="Value" Path="Status"/>
<Annotation Term="SAP__UI.Importance" EnumMember="SAP__UI.ImportanceType/High"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="RequestedOn"/>
<Annotation Term="SAP__UI.Importance" EnumMember="SAP__UI.ImportanceType/Medium"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ArenaEcoId"/>
<Annotation Term="SAP__UI.Importance" EnumMember="SAP__UI.ImportanceType/Medium"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="SAP__UI.PresentationVariant">
<Record>
<PropertyValue Property="SortOrder">
<Collection>
<Record Type="SAP__common.SortOrderType">
<PropertyValue Property="Property" PropertyPath="Status"/>
<PropertyValue Property="Descending" Bool="false"/>
</Record>
<Record Type="SAP__common.SortOrderType">
<PropertyValue Property="Property" PropertyPath="RequestedOn"/>
<PropertyValue Property="Descending" Bool="true"/>
</Record>
</Collection>
</PropertyValue>
<PropertyValue Property="Visualizations">
<Collection>
<AnnotationPath>@SAP__UI.LineItem</AnnotationPath>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__UI.SelectionFields">
<Collection>
<PropertyPath>ItemNumber</PropertyPath>
<PropertyPath>Status</PropertyPath>
<PropertyPath>ChangeType</PropertyPath>
<PropertyPath>ArenaEcoId</PropertyPath>
</Collection>
</Annotation>
<Annotation Term="SAP__common.Messages" Path="SAP__Messages"/>
</Annotations> </Annotations>
<Annotations Target="SAP__self.Container/ChangeApproval"> <Annotations Target="SAP__self.Container/ChangeApproval">
<Annotation Term="SAP__capabilities.SearchRestrictions">
<Record>
<PropertyValue Property="Searchable" Bool="false"/>
</Record>
</Annotation>
<Annotation Term="SAP__capabilities.FilterRestrictions">
<Record>
<PropertyValue Property="Filterable" Bool="true"/>
<PropertyValue Property="FilterExpressionRestrictions">
<Collection>
<Record>
<PropertyValue Property="Property" PropertyPath="SapPreview"/>
<PropertyValue Property="AllowedExpressions" String="SearchExpression"/>
</Record>
</Collection>
</PropertyValue>
<PropertyValue Property="NonFilterableProperties">
<Collection>
<PropertyPath>__OperationControl</PropertyPath>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__capabilities.SortRestrictions">
<Record>
<PropertyValue Property="NonSortableProperties">
<Collection>
<PropertyPath>SapPreview</PropertyPath>
<PropertyPath>__OperationControl</PropertyPath>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__capabilities.InsertRestrictions"> <Annotation Term="SAP__capabilities.InsertRestrictions">
<Record><PropertyValue Property="Insertable" Bool="false"/></Record> <Record>
<PropertyValue Property="Insertable" Bool="false"/>
</Record>
</Annotation> </Annotation>
<Annotation Term="SAP__capabilities.DeleteRestrictions"> <Annotation Term="SAP__capabilities.DeleteRestrictions">
<Record><PropertyValue Property="Deletable" Bool="false"/></Record> <Record>
<PropertyValue Property="Deletable" Bool="false"/>
</Record>
</Annotation> </Annotation>
<Annotation Term="SAP__capabilities.UpdateRestrictions"> <Annotation Term="SAP__capabilities.UpdateRestrictions">
<Record><PropertyValue Property="Updatable" Bool="false"/></Record> <Record>
<PropertyValue Property="Updatable" Bool="false"/>
<PropertyValue Property="QueryOptions">
<Record>
<PropertyValue Property="SelectSupported" Bool="true"/>
</Record>
</PropertyValue>
</Record>
</Annotation> </Annotation>
<Annotation Term="SAP__core.OptimisticConcurrency">
<Collection>
<PropertyPath>LocalLastChangedAt</PropertyPath>
</Collection>
</Annotation>
</Annotations>
<Annotations Target="SAP__self.Container">
<Annotation Term="SAP__common.ApplyMultiUnitBehaviorForSortingAndFiltering" Bool="true"/>
<Annotation Term="SAP__capabilities.FilterFunctions">
<Collection>
<String>eq</String>
<String>ne</String>
<String>gt</String>
<String>ge</String>
<String>lt</String>
<String>le</String>
<String>and</String>
<String>or</String>
<String>contains</String>
<String>startswith</String>
<String>endswith</String>
<String>any</String>
<String>all</String>
</Collection>
</Annotation>
<Annotation Term="SAP__support.TechnicalInfoLinks">
<Record>
<PropertyValue Property="Url" String="../../../../default/iwbep/common/0001/$metadata"/>
<PropertyValue Property="FunctionImport" String="GetTechnicalInfoLinks"/>
</Record>
</Annotation>
<Annotation Term="SAP__capabilities.SupportedFormats">
<Collection>
<String>application/json</String>
<String>application/pdf</String>
</Collection>
</Annotation>
<Annotation Term="SAP__PDF.Features">
<Record>
<PropertyValue Property="DocumentDescriptionReference" String="../../../../default/iwbep/common/0001/$metadata"/>
<PropertyValue Property="DocumentDescriptionCollection" String="MyDocumentDescriptions"/>
<PropertyValue Property="ArchiveFormat" Bool="true"/>
<PropertyValue Property="Border" Bool="true"/>
<PropertyValue Property="CoverPage" Bool="true"/>
<PropertyValue Property="FitToPage" Bool="true"/>
<PropertyValue Property="FontName" Bool="true"/>
<PropertyValue Property="FontSize" Bool="true"/>
<PropertyValue Property="HeaderFooter" Bool="true"/>
<PropertyValue Property="IANATimezoneFormat" Bool="true"/>
<PropertyValue Property="Margin" Bool="true"/>
<PropertyValue Property="Padding" Bool="true"/>
<PropertyValue Property="ResultSizeDefault" Int="20000"/>
<PropertyValue Property="ResultSizeMaximum" Int="20000"/>
<PropertyValue Property="Signature" Bool="true"/>
<PropertyValue Property="TextDirectionLayout" Bool="true"/>
<PropertyValue Property="Treeview" Bool="true"/>
<PropertyValue Property="UploadToFileShare" Bool="true"/>
</Record>
</Annotation>
<Annotation Term="SAP__capabilities.BatchSupport">
<Record>
<PropertyValue Property="SupportedFormats">
<Collection>
<String>application/json</String>
<String>multipart/mixed</String>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__capabilities.KeyAsSegmentSupported"/>
<Annotation Term="SAP__capabilities.AsynchronousRequestsSupported"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalOperationControl/approve">
<Annotation Term="SAP__common.Label" String="Dyn. Action Control"/>
<Annotation Term="SAP__common.Heading" String="Dynamic Action Control"/>
<Annotation Term="SAP__common.QuickInfo" String="Dynamic Action Property"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalOperationControl/reject">
<Annotation Term="SAP__common.Label" String="Dyn. Action Control"/>
<Annotation Term="SAP__common.Heading" String="Dynamic Action Control"/>
<Annotation Term="SAP__common.QuickInfo" String="Dynamic Action Property"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalOperationControl/retrySync">
<Annotation Term="SAP__common.Label" String="Dyn. Action Control"/>
<Annotation Term="SAP__common.Heading" String="Dynamic Action Control"/>
<Annotation Term="SAP__common.QuickInfo" String="Dynamic Action Property"/>
</Annotations> </Annotations>
</Schema> </Schema>
</edmx:DataServices> </edmx:DataServices>
+1 -1
View File
@@ -24,7 +24,7 @@
} }
}, },
"mainService": { "mainService": {
"uri": "/sap/opu/odata4/sap/zui_arena_change_approval_o4/srvd/sap/zui_arena_change_approval/0001/", "uri": "/sap/opu/odata4/sap/zui_arena_change_appr_o4/srvd/sap/zui_arena_change_approval/0001/",
"type": "OData", "type": "OData",
"settings": { "settings": {
"annotations": [ "annotations": [
+607 -151
View File
@@ -1,176 +1,632 @@
# RAP handover — Arena change-approval BO (stage 1, 2026-09-15) # RAP handover: Arena change-approval BO, built from an empty package (rev. 2026-09-16, corrected after the UI leg went live)
Target: development tenant `my431353` (ADT destination `0M7DEJI`), package `ZARENA_PLM` Target: development tenant `my431353` (ADT destination `0M7DEJI`), package `ZARENA_PLM`, a
(sub-package of `ZLOCAL`, no transport). The ADT MCP Server can create objects and activate sub-package of `ZLOCAL` with no transport. No generator is used. Every object is created empty
them but cannot write source (PRD V-4), so every file in this folder is a **ready-to-paste through the ADT MCP `abap_creation-create_object`, filled by pasting a file from this folder into
source** for the VS Code ADT editor. Each file header names the target object, its the VS Code ADT editor, saved, and activated through `abap_activate_objects`. The ADT MCP cannot
`abap:` URI and says "replace entire content". write source (PRD V-4).
Every SAP behavioral claim in these files carries a `[source: … , verified 2026-09-15]` Most source files start with a header comment that names the target object, its `abap:` URI and its
tag; the sources are `abap-docs` (ABAP Cloud keyword docs + SAP samples), `sap-docs` README step. **The two service definition sources (U13 `zui_arena_change_approval.srvd.srvdsrv`, W4
(SAP Help) and `sap-released-objects` (release states). `zapi_arena_change_approval.srvd.srvdsrv`) carry no comment at all**, because a comment blocks SRVD
activation on this tenant (see Landmines). Their former header content is in the U13 and W4 notes
below. Where a header's `abap:` URI disagrees with this README, this README and the `filePath`
returned by `create_object` win. The U6 and U9 BDEF headers still name the generator-era `.bdef.json`;
they are left as they were, because those objects are already active. The W3 header, not yet pasted,
names `.bdef.abdl`.
## What exists in the tenant right now The entity set, properties and actions match the Fiori app contract: entity set `ChangeApproval`,
key `ID`, bound actions `approve`, `reject(RejectReason)`, `retrySync`. The UI service is published
at `/sap/opu/odata4/sap/zui_arena_change_appr_o4/srvd/sap/zui_arena_change_approval/0001/` (the UI
binding was shortened to fit the 26-character limit, D-1). The app was reconciled to that live
service on 2026-09-16 (CHANGELOG): dataSource URL, namespace
`com.sap.gateway.srvd.zui_arena_change_approval.v0001` (taken from the **service definition**, not
the binding) and entity type `ChangeApprovalType` (no underscore), all from the live `$metadata` in
`docs/v2/zui_arena_change_appr_o4.metadata.xml` (live 2026-09-16; KNOWLEDGE 2026-09-16). Text in
this folder that says `ChangeApproval_Type`, or that the binding name sets the namespace, predates
that capture and is marked where it remains.
Generated by `x-ui-service` (all **active**, ATC 0 findings on 12 objects, 2026-09-15): Citations use `[source: <server> <doc-id>, verified <date>]`. The servers are `abap-docs`,
`sap-docs`, `sap-released-objects`, and `adt-mcp` for live tool answers on `0M7DEJI`. Observations
made in the editor or browser, with no tool answer to cite, are marked `(live <date>)`.
| Object | Type | Notes | ## Status (2026-09-16)
|---|---|---|
| `ZZCHNGPPROVAL000` | TABL | persistent table (generator naming) |
| `ZZR_CHANGEAPPROVAL000` | DDLS / BDEF / DCLS | root view entity, managed BDEF, access control |
| `ZZBP_R_CHANGEAPPROVAL000` | CLAS | behavior pool of the base BDEF |
| `ZZC_CHANGEAPPROVAL000` | DDLS / BDEF / DDLX / DCLS | UI projection view, projection BDEF, metadata extension, access control |
| `ZZBP_C_CHANGEAPPROVAL000` | CLAS | projection behavior pool — **delete it** (step 9) |
| `ZZUI_ARENA_CHANGE_APPRVL000_O4` | SRVD + SRVB | service definition and OData V4 **UI** binding (same name), **unpublished** |
| `ZZARENA_CHANGE_APPROVAL000` | RONT + NONT | SAP object type / node type |
Created empty via `create_object` (exist, **no source, inactive**): - **UI leg U1–U14: ACTIVE and ATC-clean.** All 14 objects answered "Activation successful." ATC over
the 14 objects: 0 errors, 0 warnings, 43 infos (worklist `12ACF6BD93811FE1ACB8096AC6763BD6`)
[source: adt-mcp abap_activate_objects, abap_atc_get_result on 0M7DEJI, verified 2026-09-16]. The
MCP returns only totals by priority, so the 43 infos are read in the ADT ATC view. Infos do not
gate publishing.
- **UI binding `ZUI_ARENA_CHANGE_APPR_O4`: PUBLISHED 2026-09-16 04:26**, with the **Publish code
lens** in the binding's `.srvb.json` source editor (U15). The ADT MCP has no publish tool, and VS
Code shows no confirmation beyond "<binding> published locally". The publish was therefore verified
by tool: `fetch_services` reports `isPublished: true`, and `fetch_service_information` returns
`https://my431353-api.s4hana.cloud.sap/sap/opu/odata4/sap/zui_arena_change_appr_o4/srvd/sap/zui_arena_change_approval/0001/?sap-client=080`
with entity set `ChangeApproval` [source: adt-mcp abap_business_services-fetch_services,
fetch_service_information on 0M7DEJI, verified 2026-09-16].
- **Where the live service answers:** on the UI host `my431353.s4hana.cloud.sap`. The same path on
the `-api` host that `fetch_service_information` reports is refused to a business user as
"Forbidden / blocked by UCON" (live 2026-09-16; KNOWLEDGE 2026-09-16). The `-api` host is the
communication-user entry point.
- **Worker leg W1–W5:** started on the tenant 2026-09-16 06:14 (PRD status). Run it exactly as in
"Worker leg" below. This README records no worker-leg result.
- **Package:** `ZARENA_PLM` exists again under `ZLOCAL` (recreated 2026-09-16; validation in it
succeeded live at 02:27, CHANGELOG). Historical, before step 0: the package had been deleted
together with its contents, and `abap_creation-run_validation` answered "Package 'ZARENA_PLM' does
not exist." for TABL, DDLS and CLAS [source: adt-mcp on 0M7DEJI, verified 2026-09-16].
| Object | Type | Purpose | ## Object inventory and name limits
|---|---|---|
| `ZD_CHANGEAPPROVAL_REJECTP` | DDLS | abstract entity — `reject` parameter (`RejectReason`) |
| `ZD_CHANGEAPPROVAL_SYNCEDP` | DDLS | abstract entity — `markSynced` parameter (`SapChangeNumber`) |
| `ZD_CHANGEAPPROVAL_FAILEDP` | DDLS | abstract entity — `markFailed` parameter (`ErrorMessage`) |
| `ZC_CHANGEAPPROVAL_A2X` | DDLS + DCLS | A2X projection view (worker) and its access control |
| `ZAPI_ARENA_CHANGE_APPROVAL` | SRVD | Web API service definition (worker) |
Not yet creatable (the MCP validation refuses until the referenced objects are active): | Step | Object | Type (create code) | Source file(s) | Length / limit |
|---|---|---|---|---|
| U1 | `ZCHANGEAPPROVAL` | TABL (`TABL/DT`) | `zchangeapproval.tabl.asddls` | 15 / 16 |
| U2 | `ZD_CHANGEAPPROVAL_REJECTP` | DDLS (`DDLS/DF`) | `zd_changeapproval_rejectp.ddls.asddls` | 25 / 30 |
| U3 | `ZD_CHANGEAPPROVAL_SYNCEDP` | DDLS (`DDLS/DF`) | `zd_changeapproval_syncedp.ddls.asddls` | 25 / 30 |
| U4 | `ZD_CHANGEAPPROVAL_FAILEDP` | DDLS (`DDLS/DF`) | `zd_changeapproval_failedp.ddls.asddls` | 25 / 30 |
| U5 | `ZR_CHANGEAPPROVAL` | DDLS (`DDLS/DF`) | `zr_changeapproval.ddls.asddls` | 17 / 30 |
| U6 | `ZR_CHANGEAPPROVAL` | BDEF (`BDEF/BDO`) | `zr_changeapproval.bdef.asbdef` | 17 / 30 |
| U7 | `ZBP_R_CHANGEAPPROVAL` | CLAS (`CLAS/OC`) | `zbp_r_changeapproval.clas.abap` + `.clas.locals_imp.abap` | 20 / 30 |
| U8 | `ZC_CHANGEAPPROVAL` | DDLS (`DDLS/DF`) | `zc_changeapproval.ddls.asddls` | 17 / 30 |
| U9 | `ZC_CHANGEAPPROVAL` | BDEF (`BDEF/BDO`) | `zc_changeapproval.bdef.asbdef` | 17 / 30 |
| U10 | `ZR_CHANGEAPPROVAL` | DCLS (`DCLS/DL`) | `zr_changeapproval.dcls.asdcls` | 17 / 30 |
| U11 | `ZC_CHANGEAPPROVAL` | DCLS (`DCLS/DL`) | `zc_changeapproval.dcls.asdcls` | 17 / 30 |
| U12 | `ZC_CHANGEAPPROVAL` | DDLX (`DDLX/EX`) | `zc_changeapproval.ddlx.asddlx` | 17 / 40 |
| U13 | `ZUI_ARENA_CHANGE_APPROVAL` | SRVD (`SRVD/SRV`) | `zui_arena_change_approval.srvd.srvdsrv` (no comments) | 25 / 30 |
| U14 | `ZUI_ARENA_CHANGE_APPR_O4` | SRVB (`SRVB/SVB`), OData V4 - UI | none in this folder; `create_object` returns `.srvb.json` | 24 / 26 (D-1) |
| W1 | `ZC_CHANGEAPPROVAL_A2X` | DDLS (`DDLS/DF`) | `zc_changeapproval_a2x.ddls.asddls` | 21 / 30 |
| W2 | `ZC_CHANGEAPPROVAL_A2X` | DCLS (`DCLS/DL`) | `zc_changeapproval_a2x.dcls.asdcls` | 21 / 30 |
| W3 | `ZC_CHANGEAPPROVAL_A2X` | BDEF (`BDEF/BDO`) | `zc_changeapproval_a2x.bdef.asbdef` | 21 / 30 |
| W4 | `ZAPI_ARENA_CHANGE_APPROVAL` | SRVD (`SRVD/SRV`) | `zapi_arena_change_approval.srvd.srvdsrv` (no comments) | 26 / 30 |
| W5 | `ZAPI_ARENA_CHANGE_APPR_A2X` | SRVB (`SRVB/SVB`), Web API (see W5 for the exact `bindingType` value) | none in this folder; `.srvb.json` expected, as U14 | 26 / 26 (D-1) |
| Object | Type | Blocker seen 2026-09-15 | Limits: `TABL/DT` 16; `DDLS/DF`, `BDEF/BDO` and `SRVD/SRV` 30 [source: adt-mcp
|---|---|---| `abap_creation-get_object_type_details` on 0M7DEJI, verified 2026-09-16]. Service bindings: 26 per
| `ZC_CHANGEAPPROVAL_A2X` | BDEF (projection) | "The referenced STOB object ZC_CHANGEAPPROVAL_A2X does not exist" | SAP Help. The ADT MCP accepts 30 for `SRVB/SVB`, but that is undocumented and not used here (D-1).
| `ZAPI_ARENA_CHANGE_APPROVAL_A2X` | SRVB (OData V4 - Web API) | "An active version of the Service Definition ZAPI_ARENA_CHANGE_APPROVAL does not exist" | The documentation agrees
for tables ("maximum of 16 characters") [source: abap-docs /abap-docs-cloud/ABENDDIC_DBTAB_NAMES,
verified 2026-09-16] and for CDS view entities ("maximum of 30 characters"; the DDL source and the
CDS entity must have the same name) [source: abap-docs /abap-docs-cloud/ABENCDS_DEFINE_VIEW_ENTITY,
verified 2026-09-16]. `CLAS/OC` and `DCLS/DL` allow 30 and `DDLX/EX` allows 40 [source: adt-mcp
abap_creation-get_object_type_details on 0M7DEJI, verified 2026-09-16] (KNOWLEDGE 2026-09-16; these
were "not recorded" in the pre-build revision).
## Paste-and-activate sequence Not created: a projection behavior pool (see design decisions) and an SAP object type or node type.
No documentation found makes an SAP object type mandatory for a RAP BO. SAP Help lists it only as
one of the objects the "OData UI Service from Scratch" wizard generates [source: sap-docs sap-help
loio d3f5e90a306a44b6b37b8b05f04d2c10, verified 2026-09-16]. Business events are the case that
requires `@ObjectModel.sapObjectNodeType.name` [source: sap-docs
/btp-cloud-platform/30-development/log-custom-business-events-6ecc451, verified 2026-09-16]. Add
one if the BO later raises business events.
Open each object in the VS Code ADT editor (or ADT Eclipse), replace the whole source with Split: the **UI leg** has 14 source files (13 objects with source + the U14 binding). The **worker
the file content, save, activate. Activation order matters — dependencies are top-down. leg** has 4 source files (+ the W5 binding). The `markSynced`/`markFailed` parameter entities
(U3, U4) belong to the UI leg because the base BDEF (U6) names them. They must be active before
U6 activates.
1. `zzchngpproval000.tabl.asddls` → table `ZZCHNGPPROVAL000`. (File name uses the ## Resolved decisions
abap-file-formats extension by analogy with `.ddls.asddls`; the tenant object is the
same regardless.)
2. `zd_changeapproval_rejectp.ddls.asddls`, `zd_changeapproval_syncedp.ddls.asddls`,
`zd_changeapproval_failedp.ddls.asddls` → the three abstract entities.
3. `zzr_changeapproval000.ddls.asddls` → root view entity.
4. `zzr_changeapproval000.bdef.asbdef` → base BDEF.
Watch for a message about the unmapped elements `StatusText`, `StatusCriticality`,
`IsApprovable`, `IsRetriable` (CDS-calculated, deliberately not in the mapping). A
warning is acceptable; an **error** means the fallback in the file header applies.
5. `zzbp_r_changeapproval000.clas.abap` (global class) and
`zzbp_r_changeapproval000.clas.locals_imp.abap` (Local Types tab) → behavior pool.
6. `zzc_changeapproval000.ddls.asddls` and `zc_changeapproval_a2x.ddls.asddls` → both
projection views.
7. `zzc_changeapproval000.bdef.asbdef` → UI projection BDEF.
8. Create the A2X projection BDEF (now possible): ADT *New Behavior Definition* on
`ZC_CHANGEAPPROVAL_A2X`, implementation type *Projection* — or via MCP:
`abap_creation-run_validation` + `create_object` with objectType `BDEF/BDO` and
`{"behaviorDefinitionType":"definition","packageName":"ZARENA_PLM","rootEntity":"ZC_CHANGEAPPROVAL_A2X","name":"ZC_CHANGEAPPROVAL_A2X","description":"Arena change approval: A2X projection behavior","implementationType":"Projection"}`,
transport `""`. Then paste `zc_changeapproval_a2x.bdef.asbdef`, activate.
9. Delete the generated class `ZZBP_C_CHANGEAPPROVAL000` (no longer referenced by the
projection BDEF; a projection needs a behavior pool only for new actions/functions
[source: abap-docs /abap-docs-cloud/ABENBDL_BDEF_PROJECTION_HEADER, verified 2026-09-15]).
10. `zzr_changeapproval000.dcls.asdcls`, then `zzc_changeapproval000.dcls.asdcls` and
`zc_changeapproval_a2x.dcls.asdcls` → access controls.
11. `zzc_changeapproval000.ddlx.asddlx` → metadata extension.
12. `zzui_arena_change_apprvl000_o4.srvd.srvdsrv` and
`zapi_arena_change_approval.srvd.srvdsrv` → service definitions.
13. Create the Web API binding (now possible): ADT *New Service Binding* on
`ZAPI_ARENA_CHANGE_APPROVAL`, binding type *OData V4 - Web API*, name
`ZAPI_ARENA_CHANGE_APPROVAL_A2X` — or via MCP `create_object` with objectType
`SRVB/SVB` and
`{"packageName":"ZARENA_PLM","name":"ZAPI_ARENA_CHANGE_APPROVAL_A2X","description":"Arena change approval: OData V4 Web API binding (worker)","bindingType":"OData V4 - Web API","serviceDefinition":"ZAPI_ARENA_CHANGE_APPROVAL"}`.
Activate. **Do not publish** either binding (publishing is a later brief, together
with the IAM app / communication scenario).
## Activation URIs for `abap_activate_objects` (after pasting) - **D-1: service binding names stay within 26 characters (resolved 2026-09-16).** SAP Help gives
26 characters as the maximum length for a service binding name in every version checked: ABAP
Cloud [source: sap-docs sap-help loio 4bad0d7280b742a68dd2d42b1441f09a, verified 2026-09-16], SAP
Cloud Platform [source: sap-docs sap-help loio f3e608ace2a64aada33bdd3e2419113a and
6709cab6cb5b4c01b28463d760429a9a, verified 2026-09-16], and ABAP Platform 2021 [source: sap-docs
sap-help loio 777e027f61c3490dba0433443d9143a6, verified 2026-09-16]. The ADT MCP's 30 is
undocumented. The bindings are therefore:
- UI binding `ZUI_ARENA_CHANGE_APPR_O4` (24), replacing the 28-character long form.
- Web API binding `ZAPI_ARENA_CHANGE_APPR_A2X` (26), replacing the 30-character long form.
Pattern learned live 2026-09-15: `abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/<folder>/<NAME>/<name>.<ext>` The service definitions `ZUI_ARENA_CHANGE_APPROVAL` (25) and `ZAPI_ARENA_CHANGE_APPROVAL` (26)
with these folders/extensions (all verified by a successful activation): and every other object name are unchanged. Consequence: the binding name is the first URL
segment, so the app's dataSource URL had to change. **Live correction (2026-09-16):** the
pre-build text here also said the binding name sets the schema namespace. It does not; the live
namespace `com.sap.gateway.srvd.zui_arena_change_approval.v0001` comes from the service definition
(live 2026-09-16, `docs/v2/zui_arena_change_appr_o4.metadata.xml`; KNOWLEDGE 2026-09-16). The app
was reconciled to the live service on 2026-09-16 (CHANGELOG). The PRD worker contract row now
names `ZAPI_ARENA_CHANGE_APPR_A2X` (PRD read 2026-09-16), so that governance item is closed. U14
published under its 24-character name, so a name within 26 is proven to publish. Whether
publishing accepts more than 26 is still unknown (KNOWLEDGE 2026-09-16).
| Type | Folder | Extension | ## Preconditions
|---|---|---|
| TABL | `Dictionary/Database%20Tables` | `.tabl.json` |
| DDLS | `Core%20Data%20Services/Data%20Definitions` | `.ddls.acds` |
| BDEF | `Core%20Data%20Services/Behavior%20Definitions` | `.bdef.json` (`.bdef.acds`, `.bdef.asbdef`, `.bdef.abap` all fail with "array element 0 is null") |
| DDLX | `Core%20Data%20Services/Metadata%20Extensions` | `.ddlx.acds` |
| DCLS | `Core%20Data%20Services/Access%20Controls` | `.dcls.acds` |
| CLAS | `Source%20Code%20Library/Classes` | `.clas.abap` |
| SRVD | `Business%20Services/Service%20Definitions` | `.srvd.acds` |
| SRVB | `Business%20Services/Service%20Bindings` | `.srvb.json` |
| RONT | `Business%20Services/SAP%20Object%20Types` | `.ront.json` |
| NONT | `Business%20Services/SAP%20Object%20Node%20Types` | `.nont.json` |
In sequence order (`B` = `abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM`): 1. VS Code with ADT (`sapse.adt-vscode`) is running, destination `0M7DEJI` is logged on, and the
ADT MCP answers on `http://127.0.0.1:2236/mcp`. After a VS Code reload, refresh the bearer
token in `.mcp.json` (CLAUDE.md). A Claude Code session exposes the `adt-mcp` tools only if the
server was up when the session started (KNOWLEDGE 2026-09-16).
2. `abap_list_destinations` lists `0M7DEJI`.
## Step 0: recreate the package (done 2026-09-16)
Done: `ZARENA_PLM` exists again under `ZLOCAL`, validation in it succeeded live at 02:27, and
`abap_transport-get` answered `isRecordingRequired: false` (CHANGELOG 2026-09-16). The steps below
are kept as the record.
In ADT (VS Code or Eclipse), create development package **`ZARENA_PLM` with super-package
`ZLOCAL`**, no transport. The ADT MCP has no package tool. Use a `ZLOCAL` sub-package because
`ZLOCAL` itself is a structure package, `$TMP` fails the language-version authorization, and
sub-packages of `ZLOCAL` created from release 2508 need no workbench request [source: sap-docs
sap-help loio a0d80f14fcb74cb1ac8fb6f3d9ad48cc (2608), verified 2026-09-15; refusals live
2026-09-15, KNOWLEDGE].
Check it read-only: `abap_creation-run_validation` for `TABL/DT` with the U1 JSON must no longer
answer "Package 'ZARENA_PLM' does not exist". Then call `abap_transport-get` once for U1
(`objectType` `TABL/DT`, `developmentPackage` `ZARENA_PLM`, `isCreation` true) and expect "recording
not required". Every `create_object` below uses `"transportRequestNumber": ""`.
## Per-object routine
For every step:
1. `abap_creation-get_object_type_details` (objectType, name, description). It lists the required
fields and limits. If it names a required field that the JSON below lacks, add it and note it.
2. `abap_creation-run_validation` with the JSON.
3. `abap_creation-create_object` with the same JSON, `destination` `0M7DEJI`, transport `""`.
**Record the `filePath` it returns.** That path is both the paste target and the activation URI.
4. Open that **source** file in the VS Code ADT editor, replace the whole content with the handover
file (its header comment included where it has one; the SRVD files have none and must stay
comment-free), and save. A DDIC database table is edited as Dictionary DDL source generated from its
metadata [source: abap-docs /abap-docs-cloud/ABENDDIC_DEFINE_TABLE, verified 2026-09-16].
5. `abap_activate_objects` with the returned `filePath` (URL-encoded, spaces as `%20`). The
activation table below is a cross-check, not a substitute. Fix errors before the next step.
**General rule (live 2026-09-16): in the VS Code ADT workspace, the `.json` file in an object folder
is header metadata and never receives pasted source. Always use the `filePath` returned by
`create_object`.** For example, `zchangeapproval.tabl.json` holds only formatVersion, description,
originalLanguage and `abapLanguageVersion`, and the table source lives in `zchangeapproval.tabl.ddic`
[source: adt-mcp abap_creation-create_object / abap_activate_objects on 0M7DEJI, verified
2026-09-16]. The returned source files are TABL `.tabl.ddic`, DDLS `.ddls.acds`, DCLS `.dcls.acds`,
DDLX `.ddlx.acds`, BDEF `.bdef.abdl`, CLAS `.clas.abap` and SRVD `.srvd.acds`. Among the types in
this build, the one returned `filePath` that is a `.json` is the service binding's `.srvb.json`.
Nothing is pasted into it, the binding is activated through it (U14), and its editor carries the
Publish code lens (U15).
Create-metadata tags per type [source: adt-mcp `abap_creation-get_object_type_details` on 0M7DEJI,
verified 2026-09-16]:
- `TABL/DT`: `packageName`, `name`, `description`.
- `DDLS/DF`: `packageName`, `name`, `description`, optional `referencedObject` (omitted here).
- `SRVD/SRV`: `packageName`, `name`, `description`, `sourceType` (`S` | `X` | none). Use `"S"`: it
worked live, and omitting it fails with "serviceDefinitionInfo.sourceType is null" (live 2026-09-15).
`SRVD/SRV` also offers an optional `referencedObject` (omitted here) [source: adt-mcp
get_object_type_details on 0M7DEJI, verified 2026-09-16].
- `SRVB/SVB`: `packageName`, `name`, `description`, `bindingType`, `serviceDefinition`. Proven
`bindingType` value: `OData V4 - UI` (U14, 2026-09-16). The Web API value is not proven; see W5.
- `BDEF/BDO`: `behaviorDefinitionType`, `packageName`, `rootEntity`, `name`, `description`,
`implementationType` (`Managed` | `Unmanaged` | `Projection` | `Abstract` | `Interface`).
- `CLAS/OC`: `packageName`, `name` (≤30), `description`, optional superclass and interfaces (none
needed here). `DCLS/DL`: `packageName`, `name` (≤30), `description`, optional `protectedEntity`.
`DDLX/EX`: `packageName`, `name` (≤40), `description`, optional `extendedEntity` [source: adt-mcp
abap_creation-get_object_type_details on 0M7DEJI, verified 2026-09-16]. The pre-build revision
marked these three "tags not recorded"; the JSON in the UI-leg table below still carries that
marker as the record.
Descriptions are kept at 40 characters or fewer. The per-type description limit was not recorded.
BDEF and SRVB creation validates the referenced DDLS/SRVD's **active** version (live 2026-09-15:
"The referenced STOB object … does not exist"; "An active version of the Service Definition … does
not exist"). The sequence below always activates the referenced object first.
## UI leg (first; done 2026-09-16)
**Done: U1–U14 are active, ATC-clean, and the U14 binding is published (see Status).** Do not re-run
this table. It is kept as the build record. The JSON column is the pre-build plan: the `(tags
unconfirmed)` markers were settled on 2026-09-16 (per-object routine), and U10 was created with
`protectedEntity` added (CHANGELOG 2026-09-16).
| Step | create_object JSON (objectType) | Paste | Depends on (active) |
|---|---|---|---|
| U1 | `TABL/DT` `{"packageName":"ZARENA_PLM","name":"ZCHANGEAPPROVAL","description":"Arena change approval"}` | `zchangeapproval.tabl.asddls` | none |
| U2 | `DDLS/DF` `{"packageName":"ZARENA_PLM","name":"ZD_CHANGEAPPROVAL_REJECTP","description":"Arena approval: reject parameter"}` | `zd_changeapproval_rejectp.ddls.asddls` | none |
| U3 | `DDLS/DF` `{"packageName":"ZARENA_PLM","name":"ZD_CHANGEAPPROVAL_SYNCEDP","description":"Arena approval: markSynced parameter"}` | `zd_changeapproval_syncedp.ddls.asddls` | none |
| U4 | `DDLS/DF` `{"packageName":"ZARENA_PLM","name":"ZD_CHANGEAPPROVAL_FAILEDP","description":"Arena approval: markFailed parameter"}` | `zd_changeapproval_failedp.ddls.asddls` | none |
| U5 | `DDLS/DF` `{"packageName":"ZARENA_PLM","name":"ZR_CHANGEAPPROVAL","description":"Arena change approval"}` | `zr_changeapproval.ddls.asddls` | U1 |
| U6 | `BDEF/BDO` `{"behaviorDefinitionType":"definition","packageName":"ZARENA_PLM","rootEntity":"ZR_CHANGEAPPROVAL","name":"ZR_CHANGEAPPROVAL","description":"Arena change approval behavior","implementationType":"Managed"}` | `zr_changeapproval.bdef.asbdef` | U2, U3, U4, U5 |
| U7 | `CLAS/OC` `{"packageName":"ZARENA_PLM","name":"ZBP_R_CHANGEAPPROVAL","description":"Arena change approval behavior pool"}` (tags unconfirmed) | `zbp_r_changeapproval.clas.abap` (global class) + `zbp_r_changeapproval.clas.locals_imp.abap` (Local Types) | U6 |
| U8 | `DDLS/DF` `{"packageName":"ZARENA_PLM","name":"ZC_CHANGEAPPROVAL","description":"Arena change approval UI projection"}` | `zc_changeapproval.ddls.asddls` | U5 (U6/U7 done) |
| U9 | `BDEF/BDO` `{"behaviorDefinitionType":"definition","packageName":"ZARENA_PLM","rootEntity":"ZC_CHANGEAPPROVAL","name":"ZC_CHANGEAPPROVAL","description":"Arena approval UI projection behavior","implementationType":"Projection"}` | `zc_changeapproval.bdef.asbdef` | U6, U7, U8 |
| U10 | `DCLS/DL` `{"packageName":"ZARENA_PLM","name":"ZR_CHANGEAPPROVAL","description":"Arena change approval access"}` (tags unconfirmed) | `zr_changeapproval.dcls.asdcls` | U5 |
| U11 | `DCLS/DL` `{"packageName":"ZARENA_PLM","name":"ZC_CHANGEAPPROVAL","description":"Arena approval UI projection access"}` (tags unconfirmed) | `zc_changeapproval.dcls.asdcls` | U8, U10 |
| U12 | `DDLX/EX` `{"packageName":"ZARENA_PLM","name":"ZC_CHANGEAPPROVAL","description":"Arena approval UI annotations"}` (tags unconfirmed) | `zc_changeapproval.ddlx.asddlx` | U8 |
| U13 | `SRVD/SRV` `{"packageName":"ZARENA_PLM","name":"ZUI_ARENA_CHANGE_APPROVAL","description":"Arena change approval UI service","sourceType":"S"}` | `zui_arena_change_approval.srvd.srvdsrv` | U8, U9 |
| U14 | `SRVB/SVB` `{"packageName":"ZARENA_PLM","name":"ZUI_ARENA_CHANGE_APPR_O4","description":"Arena change approval UI binding","bindingType":"OData V4 - UI","serviceDefinition":"ZUI_ARENA_CHANGE_APPROVAL"}` | none | U13 |
Notes per step:
- **U1:** paste `zchangeapproval.tabl.asddls` into **`zchangeapproval.tabl.ddic`**, never into
`zchangeapproval.tabl.json` (metadata only, see the per-object routine). Proven: U1 activated
through `.tabl.ddic` [source: adt-mcp abap_activate_objects on 0M7DEJI, verified 2026-09-16].
- **U6 then U7 (order matters).** **Resolved live 2026-09-16:** U6 activated through `.bdef.abdl` on
the first attempt, before class `ZBP_R_CHANGEAPPROVAL` existed, so the fallback below was not
needed. U6 and U7 were then activated together in one call [source: adt-mcp abap_activate_objects
on 0M7DEJI, verified 2026-09-16]. The pre-build instruction follows as the record.
Activate the base BDEF **before** the behavior pool class exists,
then create the class. SAP's RAP exercise does it that way [source: abap-docs
/abap-platform-rap-opensap/week4/unit3#step-3-create-and-implement-the-behavior-implementation-bil-class-for-the-travel-entity,
verified 2026-09-16]. That exercise is an unmanaged, non-strict BDEF. If U6 activation fails with
an **error** saying class `ZBP_R_CHANGEAPPROVAL` does not exist, run U7's create and paste, then
activate U6 and U7 together in one `abap_activate_objects` call. Also watch U6 for messages about
the unmapped CDS-calculated elements `StatusText`, `StatusCriticality`, `IsApprovable` and
`IsRetriable`. A warning is acceptable. An error triggers the fallback in the BDEF file header.
- **U7:** paste the global class, then switch to the Local Types include and paste the handlers.
Save both. Activate through the `.clas.abap` URI. The Local Types include also implements the
`checkDuplicate` validation. Its duplicate query uses `OPTIONS PRIVILEGED ACCESS`. If the syntax
check refuses that form, the documented fallback is in the include header. `create_object` for
the class returned only the global `.clas.abap` path, no Local Types path (KNOWLEDGE 2026-09-16).
- **U9:** no behavior pool is created for the projection (see design decisions). `use etag` is in
the entity header, not the body (design decisions). `create_object` returned `.bdef.abdl`, and U9
activated through it [source: adt-mcp abap_creation-create_object, abap_activate_objects on
0M7DEJI, verified 2026-09-16].
- **U13: service definition `ZUI_ARENA_CHANGE_APPROVAL` (UI service).** This note holds what the
source file's header comment used to say. The header is gone, because it blocked activation.
- `abap:` URI:
`abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Business%20Services/Service%20Definitions/ZUI_ARENA_CHANGE_APPROVAL/zui_arena_change_approval.srvd.acds`
- Action: create empty through the ADT MCP (`SRVD/SRV`, `sourceType` `"S"`), replace the entire
content with `zui_arena_change_approval.srvd.srvdsrv`, save, and activate after the
`ZC_CHANGEAPPROVAL` view (U8) and BDEF (U9). Then create the OData V4 UI binding
`ZUI_ARENA_CHANGE_APPR_O4` on it (U14).
- **What happened live (2026-09-16):** the first activation failed with "Comments are not supported
and will be deleted on save" (Ln 1, the header block comment) and "oData V4 services with no
draft capability will be primarily READ-ONLY" (Ln 21). The same definition, re-saved without the
comment, activated. The comment was the blocker; the draft message was not (the BO has no draft,
by design) (live 2026-09-16; CHANGELOG 2026-09-16).
- Contract: exposes exactly one entity set, `ChangeApproval`. The service URL (live, published) is
`/sap/opu/odata4/sap/zui_arena_change_appr_o4/srvd/sap/zui_arena_change_approval/0001/`. The
first segment is the binding (service group `ZUI_ARENA_CHANGE_APPR_O4`, 24 characters, within
SAP Help's 26-character binding limit, D-1). The segment after `srvd/sap/` is the binding's
service alias (the service name in the binding editor), which can differ from this definition's
name and can be renamed only while the binding is unpublished [source: sap-docs community-14433266
"How to rename an OData V4 service?" (SAP, 2026-07-04), verified 2026-09-16]. The former header
called the app reconciliation pending. It was done on 2026-09-16 (CHANGELOG).
- `expose … as <alias>`: the alias becomes the entity set (`ChangeApproval`) and the entity type
(`ChangeApproval_Type`) [source: abap-docs /abap-fiori-showcase/06_object_page_content (Service
Definition), verified 2026-09-15]. **Live correction:** the entity set is `ChangeApproval`, but
the entity type is `ChangeApprovalType`, with no underscore (live 2026-09-16,
`docs/v2/zui_arena_change_appr_o4.metadata.xml`; KNOWLEDGE 2026-09-16).
- `provider contracts odata_v4_ui` switches on the UI-binding syntax checks and must match the
binding type [source: abap-docs /abap-docs-cloud/ABENSRVD_PROVIDER_CONTRACT, verified 2026-09-15].
- **U14:** **Obsolete, removed:** the pre-build instruction to open the binding in the editor
*before activating* to check the alias. What was done and proven on 2026-09-16: `create_object`
returned `.srvb.json`, U14 was activated through that `filePath`, and the alias was verified
**after** activation with `fetch_services`: service `ZUI_ARENA_CHANGE_APPROVAL`, version `0001`,
OData V4, unpublished [source: adt-mcp abap_creation-create_object, abap_activate_objects,
abap_business_services-fetch_services on 0M7DEJI, verified 2026-09-16]. The alias check must
come before publishing, not before activating. The URL segment after `srvd/sap/` is that alias,
not necessarily the definition name, and it can be renamed only while the binding is unpublished
[source: sap-docs community-14433266 "How to rename an OData V4 service?", verified 2026-09-16].
The binding name `ZUI_ARENA_CHANGE_APPR_O4` (24) is within SAP Help's 26-character limit (D-1).
### U15: checks and publish of the UI binding (done 2026-09-16)
1. `abap_atc_run` on all 14 UI-leg objects: `ZCHANGEAPPROVAL` TABL; `ZD_CHANGEAPPROVAL_REJECTP`,
`ZD_CHANGEAPPROVAL_SYNCEDP`, `ZD_CHANGEAPPROVAL_FAILEDP`, `ZR_CHANGEAPPROVAL`, `ZC_CHANGEAPPROVAL`
DDLS; `ZR_CHANGEAPPROVAL`, `ZC_CHANGEAPPROVAL` BDEF; `ZBP_R_CHANGEAPPROVAL` CLAS;
`ZR_CHANGEAPPROVAL`, `ZC_CHANGEAPPROVAL` DCLS; `ZC_CHANGEAPPROVAL` DDLX;
`ZUI_ARENA_CHANGE_APPROVAL` SRVD; `ZUI_ARENA_CHANGE_APPR_O4` SRVB. Then
`abap_atc_get_result`. **Result:** 0 errors, 0 warnings, 43 infos (worklist
`12ACF6BD93811FE1ACB8096AC6763BD6`) [source: adt-mcp abap_atc_get_result on 0M7DEJI, verified
2026-09-16].
2. `abap_business_services-fetch_services` on `ZUI_ARENA_CHANGE_APPR_O4`. **Result:**
`isPublished: false`, service `ZUI_ARENA_CHANGE_APPROVAL`, version `0001`, OData V4.
3. **Publish with the Publish code lens, not a button.** Open the binding's `.srvb.json` source in
the VS Code ADT editor and choose the **Publish** code lens in the source. In ADT for VS Code a
service binding is a source-based editor, and publish, unpublish and preview are code lenses
[source: sap-docs sap-help loio dd6f5de63e7245a7abe7e39389841596 "ABAP RAP Tools", verified
2026-09-16]. (Superseded wording: "the Publish button in the ADT service binding editor" describes
the Eclipse form-based editor.) The binding must be active first [source: sap-docs sap-help loio
b58a3c27df4e406f9335d4b346f6be04, verified 2026-09-16]. The ADT MCP has no publish tool. VS Code
shows no confirmation beyond "<binding> published locally", so step 4 is the proof. **Done
2026-09-16 04:26.**
4. `fetch_services` again (expect `isPublished: true`), then
`abap_business_services-fetch_service_information`. The service URL path must equal
`/sap/opu/odata4/sap/zui_arena_change_appr_o4/srvd/sap/zui_arena_change_approval/0001/`
exactly, and the entity set must be `ChangeApproval`. **Result:** both hold, on host
`my431353-api.s4hana.cloud.sap` with `sap-client=080` [source: adt-mcp
abap_business_services-fetch_services, fetch_service_information on 0M7DEJI, verified
2026-09-16]. Business users and browser checks use the UI host `my431353.s4hana.cloud.sap`
instead; `-api` refuses them (Landmines).
5. Hand back the live `$metadata` for PRD V-2. **Done:** it is captured in
`docs/v2/zui_arena_change_appr_o4.metadata.xml`, and the app was reconciled to it on 2026-09-16
(CHANGELOG). The pre-build expectation `ChangeApproval_Type` was wrong; the live entity type is
`ChangeApprovalType`. See "App contract check" for the settled types.
## Worker leg (second; run exactly this sequence)
Prerequisites, all met on 2026-09-16: the U5 root view, U6 base BDEF, U7 behavior pool and U10 root
access control are active. Every step uses the per-object routine (details → validation → create →
paste into the returned `filePath` → activate through the same `filePath`).
| Step | create_object JSON (objectType) | Paste | Needs (active) |
|---|---|---|---|
| W1 | `DDLS/DF` `{"packageName":"ZARENA_PLM","name":"ZC_CHANGEAPPROVAL_A2X","description":"Arena approval A2X projection"}` | `zc_changeapproval_a2x.ddls.asddls` into `.ddls.acds` | activation: U5 (U6, U7 done) |
| W2 | `DCLS/DL` `{"packageName":"ZARENA_PLM","name":"ZC_CHANGEAPPROVAL_A2X","description":"Arena approval A2X projection access"}` (no `protectedEntity`, see round 1) | `zc_changeapproval_a2x.dcls.asdcls` into `.dcls.acds` | activation: W1, U10 |
| W3 | `BDEF/BDO` `{"behaviorDefinitionType":"definition","packageName":"ZARENA_PLM","rootEntity":"ZC_CHANGEAPPROVAL_A2X","name":"ZC_CHANGEAPPROVAL_A2X","description":"Arena approval A2X behavior","implementationType":"Projection"}` | `zc_changeapproval_a2x.bdef.asbdef` into `.bdef.abdl` | **creation: W1**; activation: W1, U6, U7 |
| W4 | `SRVD/SRV` `{"packageName":"ZARENA_PLM","name":"ZAPI_ARENA_CHANGE_APPROVAL","description":"Arena change approval Web API","sourceType":"S"}` | `zapi_arena_change_approval.srvd.srvdsrv` (no comments) into `.srvd.acds` | activation: W1, W3 |
| W5 | `SRVB/SVB` `{"packageName":"ZARENA_PLM","name":"ZAPI_ARENA_CHANGE_APPR_A2X","description":"Arena change approval Web API binding","bindingType":"OData V4 - Web API","serviceDefinition":"ZAPI_ARENA_CHANGE_APPROVAL"}` (check `bindingType`, step 7) | none | **creation: W4**; activation: W4 |
**Round 1: W1, W2 and W4 created empty and pasted; W1 and W2 activated.**
1. Create W1 (DDLS), W2 (DCLS) and W4 (SRVD) empty. Record each returned `filePath`.
- W2: leave out the optional `protectedEntity`. W1 has no active version yet. BDEF creation is
known to validate its referenced entity's active version; whether DCLS creation does the same
for `protectedEntity` is untested. The pasted source replaces the skeleton anyway.
- W4: `"sourceType":"S"` is required. Leave out the optional `referencedObject`.
2. Paste W1, W2 and W4 into their returned source files and save all three. Never paste into a
`.json`. W4 is pasted now but activated in round 2.
3. Activate W1, then W2, in that order.
**Round 2: W3 created and pasted; W3 then W4 activated.**
4. Create W3 (BDEF, `Projection`). This needs W1 **active**, because BDEF creation validates the
referenced entity's active version (live 2026-09-15). Expect a `filePath` ending in `.bdef.abdl`.
5. Paste W3 into that `.bdef.abdl` file and save.
6. Activate W3, then W4.
**Binding: W5 created and activated, not published.**
7. **Check the `bindingType` string before creating.** `OData V4 - UI` is proven (U14). The Web API
value has not been seen live. SAP Help names the type `OData V4 - Web API` [source: sap-docs
/btp-cloud-platform/30-development/service-bindings-c6dee8e, verified 2026-09-16], but the 2608
release note for ADT for VS Code calls it "OData V4 – API" [source: sap-docs sap-help loio
7f674e6190564228a5bb217ebf382611 (APPL-VSC-0011), verified 2026-09-16]. Take the exact value from
`get_object_type_details` for `SRVB/SVB` if it lists one; otherwise `run_validation` with
`OData V4 - Web API` settles it. If validation refuses the value and no allowed value is listed,
stop and report the message rather than trying label variants. The type must be the Web API one: the provider contract
`odata_v4_webapi` and the binding type must match, or an error occurs [source: abap-docs
/abap-docs-cloud/ABENSRVD_PROVIDER_CONTRACT, verified 2026-09-16].
8. Create W5 `ZAPI_ARENA_CHANGE_APPR_A2X` (needs W4 active). Activate it through its returned
`filePath` (`.srvb.json` expected, as U14).
9. Verify with `abap_business_services-fetch_services` on `ZAPI_ARENA_CHANGE_APPR_A2X`. Expect
service `ZAPI_ARENA_CHANGE_APPROVAL`, version `0001`, OData V4, and `isPublished: false`. That
unpublished state is the intended end state of this handover. Do not follow the tool's prompt to
have it published, and do not call `fetch_service_information` for W5.
**W5 is NOT published in this handover.** Web API exposure to the worker's communication user goes
through a custom communication scenario (PRD V-2), together with the IAM app. That is a separate
brief. The communication user will reach it on the `-api` host, the communication-user entry point
(KNOWLEDGE 2026-09-16). The W5 name `ZAPI_ARENA_CHANGE_APPR_A2X` is 26 characters, exactly SAP Help's
limit (D-1), and the PRD worker contract row names it.
Follow-up, not part of the sequence above: `abap_atc_run` on W1–W5 (DDLS, DCLS, BDEF, SRVD, SRVB),
as U15 step 1 did for the UI leg.
Notes per step:
- **W3:** the source header's `abap:` URI line was corrected from `.bdef.json` to `.bdef.abdl` on
2026-09-16 (comment only; no BDL change). Still use the returned `filePath`.
- **W4: service definition `ZAPI_ARENA_CHANGE_APPROVAL` (Web API service for the worker).** This note
holds what the source file's header comment used to say. The header is gone, for the U13 reason.
- `abap:` URI:
`abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Business%20Services/Service%20Definitions/ZAPI_ARENA_CHANGE_APPROVAL/zapi_arena_change_approval.srvd.acds`
- Action: create empty through the ADT MCP (`SRVD/SRV`, `sourceType` `"S"`), replace the entire
content with `zapi_arena_change_approval.srvd.srvdsrv`, save, and activate after the
`ZC_CHANGEAPPROVAL_A2X` view (W1) and BDEF (W3). Then create the Web API binding
`ZAPI_ARENA_CHANGE_APPR_A2X` on it (W5), and do not publish it.
- **Keep the source comment-free.** A leading comment blocked U13's activation (U13 note).
On U13 the "no draft capability … READ-ONLY" message did not block. If W4 activation fails and
lists only that message, read its severity in the editor's Problems view before changing
anything; the BO has no draft by design (CHANGELOG 2026-09-16).
- Sources: as `ZUI_ARENA_CHANGE_APPROVAL` (U13 note); `odata_v4_webapi` is the Web API contract
[ABENSRVD_PROVIDER_CONTRACT], re-read as [source: abap-docs
/abap-docs-cloud/ABENSRVD_PROVIDER_CONTRACT, verified 2026-09-16].
## Activation URIs for `abap_activate_objects`
**Rule: activate through the `filePath` that `create_object` returned for the object** (per-object
routine). The table below is a cross-check. Pattern, learned live 2026-09-15 and unchanged for every
object built on 2026-09-16:
`abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/<folder>/<NAME>/<name>.<ext>`.
In the VS Code workspace the `.json` file in an object folder is header metadata and never receives
pasted source. Among the types in this build, SRVB is the one whose returned `filePath` is the
`.json` (nothing is pasted there).
Extensions as returned by `create_object` for this build and proven by activation on 2026-09-16
[source: adt-mcp abap_creation-create_object, abap_activate_objects on 0M7DEJI, verified
2026-09-16]:
| Type | Folder | Extension | Proven by |
|---|---|---|---|
| TABL | `Dictionary/Database%20Tables` | `.tabl.ddic` | U1. `.tabl.json` is header metadata only; it activated a **generator-era** table on 2026-09-15 and is not used |
| DDLS | `Core%20Data%20Services/Data%20Definitions` | `.ddls.acds` | U2–U5, U8 |
| BDEF | `Core%20Data%20Services/Behavior%20Definitions` | `.bdef.abdl` | U6, U9. **Generator-era record (2026-09-15, not for this build):** `.bdef.json` activated a generator BDEF, and `.bdef.acds`, `.bdef.asbdef` and `.bdef.abap` failed with "array element 0 is null" |
| DDLX | `Core%20Data%20Services/Metadata%20Extensions` | `.ddlx.acds` | U12 |
| DCLS | `Core%20Data%20Services/Access%20Controls` | `.dcls.acds` | U10, U11 |
| CLAS | `Source%20Code%20Library/Classes` | `.clas.abap` | U7 (no Local Types path is returned) |
| SRVD | `Business%20Services/Service%20Definitions` | `.srvd.acds` | U13 (after the comment-free re-save) |
| SRVB | `Business%20Services/Service%20Bindings` | `.srvb.json` | U14 (returned by `create_object`, activated through it) |
In step order. The U lines are the URIs of the active objects. The W lines follow the proven
pattern; confirm each against the returned `filePath`.
``` ```
B/Dictionary/Database%20Tables/ZZCHNGPPROVAL000/zzchngpproval000.tabl.json U1 abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Dictionary/Database%20Tables/ZCHANGEAPPROVAL/zchangeapproval.tabl.ddic
B/Core%20Data%20Services/Data%20Definitions/ZD_CHANGEAPPROVAL_REJECTP/zd_changeapproval_rejectp.ddls.acds U2 abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Data%20Definitions/ZD_CHANGEAPPROVAL_REJECTP/zd_changeapproval_rejectp.ddls.acds
B/Core%20Data%20Services/Data%20Definitions/ZD_CHANGEAPPROVAL_SYNCEDP/zd_changeapproval_syncedp.ddls.acds U3 abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Data%20Definitions/ZD_CHANGEAPPROVAL_SYNCEDP/zd_changeapproval_syncedp.ddls.acds
B/Core%20Data%20Services/Data%20Definitions/ZD_CHANGEAPPROVAL_FAILEDP/zd_changeapproval_failedp.ddls.acds U4 abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Data%20Definitions/ZD_CHANGEAPPROVAL_FAILEDP/zd_changeapproval_failedp.ddls.acds
B/Core%20Data%20Services/Data%20Definitions/ZZR_CHANGEAPPROVAL000/zzr_changeapproval000.ddls.acds U5 abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Data%20Definitions/ZR_CHANGEAPPROVAL/zr_changeapproval.ddls.acds
B/Core%20Data%20Services/Behavior%20Definitions/ZZR_CHANGEAPPROVAL000/zzr_changeapproval000.bdef.json U6 abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Behavior%20Definitions/ZR_CHANGEAPPROVAL/zr_changeapproval.bdef.abdl
B/Source%20Code%20Library/Classes/ZZBP_R_CHANGEAPPROVAL000/zzbp_r_changeapproval000.clas.abap U7 abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Source%20Code%20Library/Classes/ZBP_R_CHANGEAPPROVAL/zbp_r_changeapproval.clas.abap
B/Core%20Data%20Services/Data%20Definitions/ZZC_CHANGEAPPROVAL000/zzc_changeapproval000.ddls.acds U8 abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Data%20Definitions/ZC_CHANGEAPPROVAL/zc_changeapproval.ddls.acds
B/Core%20Data%20Services/Data%20Definitions/ZC_CHANGEAPPROVAL_A2X/zc_changeapproval_a2x.ddls.acds U9 abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Behavior%20Definitions/ZC_CHANGEAPPROVAL/zc_changeapproval.bdef.abdl
B/Core%20Data%20Services/Behavior%20Definitions/ZZC_CHANGEAPPROVAL000/zzc_changeapproval000.bdef.json U10 abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Access%20Controls/ZR_CHANGEAPPROVAL/zr_changeapproval.dcls.acds
B/Core%20Data%20Services/Behavior%20Definitions/ZC_CHANGEAPPROVAL_A2X/zc_changeapproval_a2x.bdef.json U11 abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Access%20Controls/ZC_CHANGEAPPROVAL/zc_changeapproval.dcls.acds
B/Core%20Data%20Services/Access%20Controls/ZZR_CHANGEAPPROVAL000/zzr_changeapproval000.dcls.acds U12 abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Metadata%20Extensions/ZC_CHANGEAPPROVAL/zc_changeapproval.ddlx.acds
B/Core%20Data%20Services/Access%20Controls/ZZC_CHANGEAPPROVAL000/zzc_changeapproval000.dcls.acds U13 abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Business%20Services/Service%20Definitions/ZUI_ARENA_CHANGE_APPROVAL/zui_arena_change_approval.srvd.acds
B/Core%20Data%20Services/Access%20Controls/ZC_CHANGEAPPROVAL_A2X/zc_changeapproval_a2x.dcls.acds U14 abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Business%20Services/Service%20Bindings/ZUI_ARENA_CHANGE_APPR_O4/zui_arena_change_appr_o4.srvb.json
B/Core%20Data%20Services/Metadata%20Extensions/ZZC_CHANGEAPPROVAL000/zzc_changeapproval000.ddlx.acds W1 abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Data%20Definitions/ZC_CHANGEAPPROVAL_A2X/zc_changeapproval_a2x.ddls.acds
B/Business%20Services/Service%20Definitions/ZZUI_ARENA_CHANGE_APPRVL000_O4/zzui_arena_change_apprvl000_o4.srvd.acds W2 abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Access%20Controls/ZC_CHANGEAPPROVAL_A2X/zc_changeapproval_a2x.dcls.acds
B/Business%20Services/Service%20Definitions/ZAPI_ARENA_CHANGE_APPROVAL/zapi_arena_change_approval.srvd.acds W3 abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Behavior%20Definitions/ZC_CHANGEAPPROVAL_A2X/zc_changeapproval_a2x.bdef.abdl
B/Business%20Services/Service%20Bindings/ZZUI_ARENA_CHANGE_APPRVL000_O4/zzui_arena_change_apprvl000_o4.srvb.json W4 abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Business%20Services/Service%20Definitions/ZAPI_ARENA_CHANGE_APPROVAL/zapi_arena_change_approval.srvd.acds
B/Business%20Services/Service%20Bindings/ZAPI_ARENA_CHANGE_APPROVAL_A2X/zapi_arena_change_approval_a2x.srvb.json W5 abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Business%20Services/Service%20Bindings/ZAPI_ARENA_CHANGE_APPR_A2X/zapi_arena_change_appr_a2x.srvb.json
``` ```
Then re-run ATC on the package objects (`abap_atc_run`; omit RONT/NONT — the ATC API ## App contract check (source vs `app/webapp/localService/mainService/metadata.xml`, 2026-09-16)
answers "No valid data model exists for object type RONT/ROT") and
`abap_business_services-fetch_services` on both bindings.
## Design decisions baked into the files (falsifiable, dated 2026-09-15) **Settled by the live `$metadata` (2026-09-16).** The table below is the pre-activation comparison,
kept as the record. The live capture `docs/v2/zui_arena_change_appr_o4.metadata.xml` (live
2026-09-16; read for this revision) settles every open row:
- **Two projections, two service definitions.** The brief asked for both bindings on the - Namespace `com.sap.gateway.srvd.zui_arena_change_approval.v0001`. Entity type
generated service definition *and* for the UI to expose only approve/reject/retrySync `ChangeApprovalType`, not `ChangeApproval_Type`. Entity set `ChangeApproval`.
while the worker gets create + mark*. A service definition exposes one projection, and a - `ID` `Edm.Guid`. `StatusText` `Edm.String` MaxLength 29. `StatusCriticality` `Edm.Byte`.
projection BDEF exposes exactly what it lists (no automatic inheritance `IsApprovable`/`IsRetriable` `Edm.Boolean`. `RequestedOn`/`DecidedOn` `Edm.DateTimeOffset`
[source: abap-docs /abap-docs-cloud/ABENBDL_USE_PROJECTION]). Both requirements cannot Precision 7. `SapPreview` `Edm.String` without MaxLength.
hold on one definition, so the A2X path got its own projection view/BDEF/definition and - `reject` parameter `RejectReason` `Edm.String` `Nullable="false"` MaxLength 255, the same as the app.
the Web API binding sits on `ZAPI_ARENA_CHANGE_APPROVAL`. The RAP-2 "read-only from the - Action enablement `Core.OperationAvailable Path="_it/__OperationControl/<action>"` for `approve`,
UI" requirement wins over the structural wording. `reject` and `retrySync`.
- **`authorization master ( none )`** instead of the generator's `( global )`: ADR-002 - Insertable, Updatable and Deletable are all `false`.
makes the IAM app / business catalog the only access control and DCL governs reads;
`( none )` is the documented way to declare "no RAP authorization checks"
[source: abap-docs /abap-docs-cloud/ABENBDL_AUTHORIZATION]. Flip to `( global )` plus a
`FOR GLOBAL AUTHORIZATION` handler when a custom authorization object exists.
- **Derived fields are computed, not persisted.** `StatusText`, `StatusCriticality`,
`IsApprovable`, `IsRetriable` are CASE expressions in the root view; a transactional
query projection cannot hold expressions
[source: abap-docs /abap-docs-cloud/ABENCDS_DEFINE_VIEW_AS_PROJECTION]. They are
`field ( readonly )` and absent from the mapping — see step 4.
- **Action parameters are abstract entities** (`ZD_*`), one element each, because action
input must be an abstract entity or DDIC type
[source: abap-docs /abap-docs-cloud/ABENBDL_ACTION_INPUT_PARAM]. `markSynced` and
`markFailed` therefore take `SapChangeNumber` / `ErrorMessage` as parameters — the only
way an action can "set" them from the worker.
- **`internal update`** keeps `update` out of every OData surface while the actions and
the determination still update fields `IN LOCAL MODE`
[source: abap-docs /abap-docs-cloud/ABENBDL_INTERNAL].
- **RequestedBy** is the technical user of the creator (the worker's communication user in
the A2X flow). Change to `mandatory : create` if the Arena requester should be shown.
## Landmines learned live (KNOWLEDGE.md candidates) The app was reconciled to this metadata on 2026-09-16, including backend-owned action enablement
(CHANGELOG). Rows below marked UNVERIFIED, "unknown until live" or "intended difference" are resolved
by this list.
- [2026-09-15] `x-ui-service` generator: every field needs `semanticType` (e.g. `custom`) All 17 app properties exist in `ZC_CHANGEAPPROVAL` with the same names and case. The three actions
or validation fails with "Builtin type of field X is invalid"; semantic-key fields pass `approve`/`reject`/`retrySync` are in `ZC_CHANGEAPPROVAL` (projection BDEF), and
without it. The generator prepends its own `Z` to the `prefix` (`Z` → `ZZ…`) and appends `ZUI_ARENA_CHANGE_APPROVAL` exposes exactly `ZC_CHANGEAPPROVAL as ChangeApproval`. `expose … as`
`000` to every artifact name even with `suffix: ""`; service definition and UI binding sets the entity set name and `<alias>_Type` [source: abap-docs /abap-fiori-showcase/06_object_page_content,
get the same name (`ZZUI_ARENA_CHANGE_APPRVL000_O4`). There is no delete tool in the ADT verified 2026-09-15] (live: `<alias>Type`, see above). The source is not changed for the open items below. Decisions sit with the
MCP, so a naming mistake is permanent from the agent side. coordinator and are confirmed at U15 / V-2.
- [2026-09-15] `create_object` for SRVD needs `sourceType` (`"S"` worked; omitted →
| Item | App mock | Source | Status |
|---|---|---|---|
| Service URL / schema namespace | `manifest.json` dataSource `/sap/opu/odata4/sap/<UI binding, 28-character long form>/srvd/sap/zui_arena_change_approval/0001/`; namespace `com.sap.gateway.srvd.<same long form>.v0001` | binding `ZUI_ARENA_CHANGE_APPR_O4` (D-1): intended `/sap/opu/odata4/sap/zui_arena_change_appr_o4/srvd/sap/zui_arena_change_approval/0001/` | **intended difference**; the app's dataSource URL, mock namespace and annotation targets are reconciled at V-2 from the live `$metadata` (separate Fiori brief) |
| Entity set / entity type / key | `ChangeApproval` / `ChangeApproval_Type` / `ID` | `expose ZC_CHANGEAPPROVAL as ChangeApproval` | match |
| String lengths (ItemNumber 18, ItemName 40, ChangeType 15, Status 10, ArenaEcoId 20, RequestedBy/DecidedBy 12, SapChangeNumber 12, ErrorMessage/RejectReason 255) | as listed | same CHAR lengths in `ZCHANGEAPPROVAL` | match |
| `SapPreview` | Edm.String, no MaxLength | `abap.string(0)` (RAP-7) | match expected |
| `StatusText` | absent | CASE result, CHAR(29) by the max-type rule [source: abap-docs /abap-docs-cloud/ABENCDS_CASE_EXPRESSION_V2, verified 2026-09-16]; RAP-3 text element | extra; keep |
| `LocalLastChangedAt` | absent | etag field for `use etag` | extra; keep |
| `StatusCriticality` | Edm.Byte | integer literals typed INT1..INT8 by value [source: abap-docs /abap-docs-cloud/ABENCDS_UNTYPED_LITERAL_V2, verified 2026-09-16] | INT1 → Edm.Byte UNVERIFIED |
| `IsApprovable`, `IsRetriable` | Edm.Boolean | `cast(… as abap_boolean)` | ABAP_BOOLEAN → Edm.Boolean UNVERIFIED |
| `RequestedOn`, `DecidedOn` | Edm.DateTimeOffset Precision 7 | `abap.utclong` | UTCLONG mapping UNVERIFIED |
| `ID` | Edm.Guid, `Core.Computed` | `sysuuid_x16`, `readonly, numbering : managed` | expected, UNVERIFIED |
| `reject` parameter `RejectReason` | Nullable="false", MaxLength 255 | `ZD_CHANGEAPPROVAL_REJECTP` CHAR(255), not declared mandatory; the handler rejects an empty reason | differs in metadata, behavior meets RAP-2 |
| Action enablement | `Core.OperationAvailable Path="IsApprovable"/"IsRetriable"` (mock only) | RAP generates it from `( features : instance )` | path form unknown until live |
| Insert/Update/Delete restrictions | all false | UI projection BDEF exposes no create/update/delete | expected |
## Design decisions baked into the files (falsifiable, dated 2026-09-15 unless noted)
- **State machine** (PRD ARN-002; `retrySync` target decided 2026-09-16):
| Action | From | To | Caller |
|---|---|---|---|
| create (determination `setInitialStatus`) | none | PENDING | worker (A2X) |
| `approve` | PENDING | APPROVED | business user (UI) |
| `reject(RejectReason)` | PENDING | REJECTED (terminal) | business user (UI) |
| `markExecuting` | APPROVED | EXECUTING | worker (A2X) |
| `markSynced(SapChangeNumber)` | EXECUTING | SYNCED (terminal) | worker (A2X) |
| `markFailed(ErrorMessage)` | EXECUTING | FAILED | worker (A2X) |
| `retrySync` | FAILED | **APPROVED** (clears `ErrorMessage`, keeps `DecidedBy`/`DecidedOn`) | business user (UI) |
Only `markExecuting` writes EXECUTING. A retried record goes back through `markExecuting`, so the
worker re-claims it the same way as a first approval. Feature control is unchanged: `retrySync` is
enabled only for FAILED. Each action re-checks the status in the handler as well.
- **RAP action implementation contract** (2026-09-16). Every action handler returns failed rows
with `%fail-cause` (not_found or unspecific; `locked` is mapped to unspecific) and
`%op-%action-<name>`. Reported messages carry `%op-%action-<name>`. Failures and messages of the
internal UPDATE are merged into `failed`/`reported`, and `result` holds only keys that did not
fail [source: sap-docs sap-help loio de6569d4b92e40a0911c926170140beb (Action contract),
1040b81372d44ed38b07a409fa0e1769 (General contract), abaprap_impl_rules (allowed fail causes),
verified 2026-09-16]. Under `strict(2)`, contract violations are runtime errors [source: abap-docs
/abap-docs-cloud/ABENBDL_STRICT_2, verified 2026-09-16].
- **Semantic key enforced on create** (2026-09-16). `validation checkDuplicate on save { create; }`
fails a create when another record with the same `ItemNumber` + `ChangeType` + `ArenaEcoId`
exists and is not REJECTED, or when the same request contains that key twice. Validation syntax,
handler signature and effect (the whole transactional buffer is rejected) [source: abap-docs
/abap-docs-cloud/ABENBDL_VALIDATIONS, ABAPHANDLER_METH_VALIDATE, verified 2026-09-16].
- Fail cause: validations may only use `cause-unspecific`.
- Messages are state messages with `%state_area` and `%element`.
- Persisted rows are read from `ZR_CHANGEAPPROVAL` with `OPTIONS PRIVILEGED ACCESS`, because a
handler SELECT on an access-controlled view without it violates the RAP BO contract [source:
sap-docs sap-help abaprap_impl_rules; abap-docs /abap-docs-cloud/ABAPSELECT_PRIVILEGED_ACCESS,
ABAPDERIVED_TYPES_STATE_AREA, verified 2026-09-16].
- A validation is not something a projection lists [source: abap-docs
/abap-docs-cloud/ABENBDL_USE_PROJECTION, verified 2026-09-16], so both projections are unchanged.
- **`use etag` in the entity header** (2026-09-16). In both projection BDEFs `use etag` sits between
`define behavior for … alias ChangeApproval` and `{`, without a semicolon. It is an entity behavior
characteristic, not a body statement [source: abap-docs /abap-docs-cloud/ABENBDL_CHARACTER_PROJECTION,
ABENBDL_DEFINE_BEH_PROJECTION, verified 2026-09-16].
- **Two projections, two service definitions.** The UI projection `ZC_CHANGEAPPROVAL` exposes only
approve/reject/retrySync on `ZUI_ARENA_CHANGE_APPROVAL`. The worker's `ZC_CHANGEAPPROVAL_A2X`
exposes create + markExecuting/markSynced/markFailed on `ZAPI_ARENA_CHANGE_APPROVAL`. A projection
BDEF exposes exactly what it lists, with no automatic inheritance [source: abap-docs
/abap-docs-cloud/ABENBDL_USE_PROJECTION]. PRD RAP-5's wording (two bindings on one definition) is
superseded by this design and is a governance item. (Closed: PRD RAP-5 was amended to this
design on 2026-09-16; PRD read 2026-09-16.)
- **No projection behavior pool** (2026-09-16: never created). A projection BDEF needs one only for
new actions/functions [source: abap-docs /abap-docs-cloud/ABENBDL_BDEF_PROJECTION_HEADER].
- **`authorization master ( none )`**: ADR-002 makes the IAM app / business catalog the only access
control, and DCL governs reads [source: abap-docs /abap-docs-cloud/ABENBDL_AUTHORIZATION]. Switch
to `( global )` plus a `FOR GLOBAL AUTHORIZATION` handler when a custom authorization object
exists. **Not final:** `( none )` versus global authorization is open question PRD OQ-8, to be
decided before business roles are assigned.
- **Derived fields are computed, not persisted.** `StatusText`, `StatusCriticality`, `IsApprovable`
and `IsRetriable` are CASE expressions in `ZR_CHANGEAPPROVAL`, because a transactional query
projection cannot hold expressions [source: abap-docs
/abap-docs-cloud/ABENCDS_DEFINE_VIEW_AS_PROJECTION]. They are `field ( readonly )` and absent from
the mapping (see U6).
- **Action parameters are abstract entities** (`ZD_*`, one element each), because action input must
be an abstract entity or DDIC type [source: abap-docs /abap-docs-cloud/ABENBDL_ACTION_INPUT_PARAM].
- **`internal update`** keeps `update` off every OData surface. Actions and the determination still
update fields `IN LOCAL MODE` [source: abap-docs /abap-docs-cloud/ABENBDL_INTERNAL].
- **RequestedBy** holds the technical user of the creator (the worker's communication user in the
A2X flow). Change it to `mandatory : create` if the Arena requester should be shown.
- **No SAP object type** (2026-09-16), see the inventory section.
- **Released objects used** (all level A, public_cloud): `SYSUUID_X16`, `ABP_CREATION_USER`,
`ABP_CREATION_TSTMPL`, `ABP_LASTCHANGE_USER`, `ABP_LASTCHANGE_TSTMPL`,
`ABP_LOCINST_LASTCHANGE_TSTMPL`, `ABAP_BOOLEAN`, `CL_ABAP_BEHAVIOR_HANDLER`, `CL_ABAP_CONTEXT_INFO`,
`IF_ABAP_BEHV`, `IF_ABAP_BEHV_MESSAGE` [source: sap-released-objects
sap_check_clean_core_compliance, verified 2026-09-16].
## Landmines (live-learned, dated)
- [2026-09-15] `create_object` for SRVD needs `sourceType` (`"S"` worked; omitting it fails with
"serviceDefinitionInfo.sourceType is null"). BDEF/SRVB creation validates the referenced "serviceDefinitionInfo.sourceType is null"). BDEF/SRVB creation validates the referenced
DDLS/SRVD **active** version, so source-less objects block dependent creations. DDLS/SRVD's **active** version, so source-less objects block dependent creations.
- [2026-09-15] Activation URIs: see table above; BDEF is `.bdef.json`, table `.tabl.json`, - [2026-09-15, **superseded 2026-09-16**] Activation URIs recorded on 2026-09-15 came from
SAP object types live under `Business Services`. generator objects: BDEF `.bdef.json` and TABL `.tabl.json` (both generator-era). They are wrong for
- [2026-09-15] The MCP session id from `get_schema` `referenceContent` is single-use in objects created with `create_object`; see the per-type entry below.
practice — fetch a fresh one before each `generate_objects` attempt. - [2026-09-16] **Per-type file extensions: use the `filePath` returned by `create_object`.** Returned
and proven by activation: TABL `.tabl.ddic` (U1), DDLS `.ddls.acds`, DCLS `.dcls.acds`, DDLX
`.ddlx.acds`, BDEF **`.bdef.abdl`** (U6, U9), CLAS `.clas.abap` (global class only; no Local Types
path is returned), SRVD `.srvd.acds` (U13), SRVB **`.srvb.json`** (U14) [source: adt-mcp
abap_creation-create_object / abap_activate_objects on 0M7DEJI, verified 2026-09-16].
- [2026-09-16] A `.json` file in an object folder of the VS Code ADT workspace is metadata only
(`zchangeapproval.tabl.json`: formatVersion, description, originalLanguage,
`abapLanguageVersion: cloudDevelopment`). Never paste source into it. The table source goes into
`.tabl.ddic` (live observation 2026-09-16). The binding's `.srvb.json` is the exception by type:
it is the returned `filePath`, nothing is pasted into it, and activation and publish go through
it (U14, U15).
- [2026-09-16] **Service definition sources must carry no comments.** A leading `/* … */` header made
U13's activation fail with "Comments are not supported and will be deleted on save" (Ln 1), and the
same definition without it activated (live 2026-09-16). The DCLS, DDLX and BDEF headers did not
block. CDS SDL follows the general CDS syntax rules, which allow comments [source: abap-docs
/abap-docs-cloud/ABENCDS_F1_SDL_SYNTAX, ABENCDS_GENERAL_SYNTAX_RULES, verified 2026-09-16], so this
is the tooling, not the language. Both SRVD files in this folder are comment-free; keep them so.
- [2026-09-16] **`abap_activate_objects` output is thin.** Success returns only "Activation
successful.", with no warnings. Failure lists messages with line and column but no severity, so read
severity in the editor [source: adt-mcp abap_activate_objects on 0M7DEJI, verified 2026-09-16].
- [2026-09-16] **Publishing a binding in ADT for VS Code is a code lens.** Open the binding's
`.srvb.json` source and choose the **Publish** code lens; Unpublish and Preview… work the same way
[source: sap-docs sap-help loio dd6f5de63e7245a7abe7e39389841596 "ABAP RAP Tools", verified
2026-09-16]. The extension contributes no publish command, the SAP ADT MCP has no publish tool, and
the only visible feedback is "<binding> published locally". Verify with `fetch_services`
(`isPublished: true`) (KNOWLEDGE 2026-09-16; U14 published 2026-09-16 04:26).
- [2026-09-16] **UI host vs `-api` host.** `fetch_service_information` reports the service URL on
`my431353-api.s4hana.cloud.sap` with `sap-client=080` [source: adt-mcp
abap_business_services-fetch_service_information on 0M7DEJI, verified 2026-09-16]. A business user
calling that host from the browser is refused as "Forbidden / blocked by UCON". The same path on
`my431353.s4hana.cloud.sap` returns `$metadata` (live 2026-09-16; KNOWLEDGE 2026-09-16). The UI
service and the Fiori app use the UI host. The `-api` host is the communication-user entry point,
so the worker's Web API (W5) is reached there only through a custom communication scenario (PRD
V-2), never through the UI service.
- [2026-09-15] The ADT MCP has no delete tool and no package tool. A wrong name is permanent from
the agent side, and a missing package must be created in ADT.
- [2026-09-15] ATC via MCP rejects RONT/NONT ("No valid data model exists for object type
RONT/ROT"). Not relevant here because no SAP object type is created.
- [2026-09-16] A missing package shows up read-only as "Package '<name>' does not exist" from
`abap_creation-run_validation` [source: adt-mcp on 0M7DEJI, verified 2026-09-16].
- [2026-09-16] Service binding name limit: SAP Help says 26 in every version checked, while the
ADT MCP validation accepts 30 (undocumented). The 26 limit is used here (D-1, resolved). U14 (24
characters) published on 2026-09-16.
- [2026-09-16] The V4 URL's second segment is the binding's service alias, renamable only while
unpublished (U14). For U14 it was verified after activation with `fetch_services`, before
publishing; opening the binding before activation is not needed. U14 is now published, so its
alias is fixed.
@@ -1,7 +1,3 @@
/* TARGET OBJECT : service definition ZAPI_ARENA_CHANGE_APPROVAL (created empty via ADT MCP 2026-09-15)
abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Business%20Services/Service%20Definitions/ZAPI_ARENA_CHANGE_APPROVAL/zapi_arena_change_approval.srvd.acds
ACTION : replace entire content, then activate (after ZC_CHANGEAPPROVAL_A2X view + BDEF).
Sources: as ZZUI_ARENA_CHANGE_APPRVL000_O4; odata_v4_webapi is the Web API contract [ABENSRVD_PROVIDER_CONTRACT]. */
@EndUserText.label: 'Arena change approval - Web API (worker)' @EndUserText.label: 'Arena change approval - Web API (worker)'
define service ZAPI_ARENA_CHANGE_APPROVAL define service ZAPI_ARENA_CHANGE_APPROVAL
provider contracts odata_v4_webapi provider contracts odata_v4_webapi
@@ -0,0 +1,16 @@
" TARGET OBJECT : class ZBP_R_CHANGEAPPROVAL - GLOBAL CLASS include (behavior pool) - README step U7, UI leg
" abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Source%20Code%20Library/Classes/ZBP_R_CHANGEAPPROVAL/zbp_r_changeapproval.clas.abap
" ACTION : after BDEF ZR_CHANGEAPPROVAL is active (README U6), create empty via ADT MCP (objectType CLAS/OC,
" README U7), replace entire content of the global class; the handlers go into "Local Types"
" (locals_imp file). Save both, then activate the class.
" Source: FOR BEHAVIOR OF defines the ABAP behavior pool; the global class implements nothing itself
" [source: abap-docs /abap-docs-cloud/ABAPCLASS_FOR_BEHAVIOR_OF, ABENABAP_BEHAVIOR_POOLS, verified 2026-09-15].
CLASS zbp_r_changeapproval DEFINITION
PUBLIC
ABSTRACT
FINAL
FOR BEHAVIOR OF zr_changeapproval.
ENDCLASS.
CLASS zbp_r_changeapproval IMPLEMENTATION.
ENDCLASS.
@@ -0,0 +1,765 @@
" TARGET OBJECT : class ZBP_R_CHANGEAPPROVAL - LOCAL TYPES include (Local Types tab in ADT) - README step U7, UI leg
" abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Source%20Code%20Library/Classes/ZBP_R_CHANGEAPPROVAL/zbp_r_changeapproval.clas.locals_imp.abap
" ACTION : replace entire content, save, then activate the class (README U7; BDEF ZR_CHANGEAPPROVAL must be
" active first). Activation goes through the class URI (.clas.abap), not this include.
"
" Sources (verified 2026-09-15 unless dated otherwise):
" handler class inherits from CL_ABAP_BEHAVIOR_HANDLER (released) [sap-released-objects public_cloud A, verified 2026-09-16]
" FOR INSTANCE FEATURES signature + result via %tky / %features-%action-<name> / if_abap_behv=>fc-o-enabled|disabled
" [abap-docs /abap-docs-cloud/ABAPHANDLER_METH_FEATURES, ABAPDERIVED_TYPES_FEATURES, ABENBDL_ACTIONS_FC]
" FOR MODIFY ... FOR ACTION with RESULT; %param carries the action parameter (keys) and the result ($self)
" [abap-docs /abap-docs-cloud/ABENBDL_ACTION2_ABEXA, ABAPDERIVED_TYPES_PARAM]
" READ ENTITIES ... IN LOCAL MODE ... FIELDS ( ) WITH CORRESPONDING #( keys ) RESULT
" [abap-docs /abap-docs-cloud/ABENBDL_ACTIONS_FC example]
" MODIFY ENTITIES ... IN LOCAL MODE (skips feature control and authorization) with UPDATE FIELDS ( ) WITH <table>
" [abap-docs /abap-docs-cloud/ABAPMODIFY_ENTITY_ENTITIES, ABAPMODIFY_ENTITY_ENTITIES_OP, ABENBDL_AUTHORIZATION;
" sap-docs sap-help loio e75275489fe648f6b00977be76f0bc06 (deductDiscount: collect valid keys, update, read, result), verified 2026-09-16]
" FOR DETERMINE ON MODIFY signature [abap-docs /abap-docs-cloud/ABAPHANDLER_METH_DET]
" utclong_current( ) [abap-docs /abap-docs-cloud/ABENUTCLONG_CURRENT]
" cl_abap_context_info=>get_user_technical_name( ) (released class) [sap-docs sap-help loio 7be24435b3e24540a9edad9a2535175a;
" sap-released-objects CL_ABAP_CONTEXT_INFO public_cloud A, verified 2026-09-16]
" if_abap_behv / if_abap_behv_message are released [sap-released-objects INTF public_cloud A, verified 2026-09-16]
" RAP action implementation contract (verified 2026-09-16):
" - an unsuccessful action returns one row per failed input identifier in failed "with the correct fail cause and
" flags the %action component on the correct action"; for every failed identifier with a cause other than
" not_found there is one error message in reported [sap-docs sap-help loio de6569d4b92e40a0911c926170140beb]
" - for fail causes other than not_found/locked the failed operation in %op must be provided; reported always fills
" %op; result contains only input identifiers not in failed, without duplicates
" [sap-docs sap-help loio 1040b81372d44ed38b07a409fa0e1769]
" - %op groups the operation flags incl. %action [abap-docs /abap-docs-cloud/ABAPDERIVED_TYPES_OP]; %fail-cause values
" are defined in IF_ABAP_BEHV [abap-docs /abap-docs-cloud/ABAPDERIVED_TYPES_FAIL]; %op-%action-<name> = mk-on in
" reported [sap-docs loio e75275489fe648f6b00977be76f0bc06]
" - allowed fail causes: actions any cause except if_abap_behv=>cause-locked; validations on save only
" if_abap_behv=>cause-unspecific; with a non-initial %state_area only %element may be flagged in %op
" [sap-docs sap-help abaprap_impl_rules "RAP Implementation Rules"]
" - strict(2) turns RAP BO contract violations into runtime errors [abap-docs /abap-docs-cloud/ABENBDL_STRICT_2]
" - merging a MODIFY response: CORRESPONDING #( DEEP APPENDING BASE ( reported ) ... ). DEEP alone replaces a nested
" table such as reported-changeapproval and would drop the messages appended earlier in the handler; DEEP APPENDING
" BASE "ensures that the nested target tables are not deleted" and "appends the lines of the nested source tables"
" [abap-docs /abap-docs-cloud/ABENCORRESPONDING_CONSTR_ARG_TYPE, ABENNEWS-783-ASSIGNMENTS, verified 2026-09-16]
" Validation (verified 2026-09-16):
" - METHODS meth FOR VALIDATE ON SAVE IMPORTING keys FOR bdef~val; failed/reported implicit
" [abap-docs /abap-docs-cloud/ABAPHANDLER_METH_VALIDATE]; no EML MODIFY in validations; a failing validation
" rejects the whole transactional buffer [abap-docs /abap-docs-cloud/ABENBDL_VALIDATIONS]
" - state message pattern: invalidate %tky + %state_area first, then fail + message with %state_area and %element
" [abap-docs /abap-docs-cloud/ABAPDERIVED_TYPES_STATE_AREA, ABENBDL_VALIDATIONS example]
" - a SELECT on an access-controlled CDS view without OPTIONS PRIVILEGED ACCESS inside a RAP handler is a RAP BO
" contract violation [sap-docs sap-help abaprap_impl_rules "Non-privileged access"]; PRIVILEGED ACCESS disables CDS
" access control for the SELECT, OPTIONS goes at the end of the statement
" [abap-docs /abap-docs-cloud/ABAPSELECT_PRIVILEGED_ACCESS, ABAPSELECT_OPTIONS]
" Design notes:
" - State machine (PRD ARN-002; retrySync decision 2026-09-16): create -> PENDING; approve PENDING -> APPROVED;
" reject PENDING -> REJECTED (terminal); markExecuting APPROVED -> EXECUTING; markSynced EXECUTING -> SYNCED
" (terminal); markFailed EXECUTING -> FAILED; retrySync FAILED -> APPROVED. Only markExecuting writes EXECUTING, so a
" retried record is re-claimed by the worker through markExecuting and cannot be executed twice in parallel.
" - Status transitions are enforced twice: get_instance_features disables the action (UI greys it out / EML fails
" with an entry in failed [ABENBDL_ACTIONS_FC]) AND each action re-checks the current status so an EML caller
" IN LOCAL MODE cannot skip the state machine.
" - The six actions repeat the same read/check/update/read pattern on purpose: each action flags its own
" %op-%action-<name> component, so a generic helper would need additional derived-type plumbing.
" - A failed UPDATE with cause locked is reported as unspecific, because locked is not an allowed cause for actions.
" - RequestedBy is the technical user of the creator (for the A2X flow: the worker's communication user).
" If the Arena requester should be shown instead, make RequestedBy mandatory:create in the BDEF and drop it
" from the determination.
" - checkDuplicate: the SELECT only sees persisted rows (a validation runs in the save sequence before the new
" instances are written), so duplicates inside the same request are checked separately on the buffer data.
" UNVERIFIED 2026-09-16: the list of SELECT additions that cannot be combined with PRIVILEGED ACCESS was not
" retrievable; this uses SELECT SINGLE without FOR ALL ENTRIES. The syntax check at U7 activation is the proof.
" abaplint (abap-docs abap_lint, 2026-09-16) does not parse `OPTIONS PRIVILEGED ACCESS` but parses the FROM-clause
" form. If the tenant syntax check refuses the OPTIONS form, use the equally documented
" `SELECT SINGLE FROM zr_changeapproval WITH PRIVILEGED ACCESS FIELDS ID WHERE ... INTO @DATA(existing_id).`
" (affects only that entity; not combinable with OPTIONS PRIVILEGED ACCESS)
" [source: abap-docs /abap-docs-cloud/ABAPSELECT_DATA_SOURCE, verified 2026-09-16].
CLASS lhc_changeapproval DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
CONSTANTS:
BEGIN OF status,
pending TYPE zchangeapproval-status VALUE 'PENDING',
approved TYPE zchangeapproval-status VALUE 'APPROVED',
executing TYPE zchangeapproval-status VALUE 'EXECUTING',
synced TYPE zchangeapproval-status VALUE 'SYNCED',
rejected TYPE zchangeapproval-status VALUE 'REJECTED',
failed TYPE zchangeapproval-status VALUE 'FAILED',
END OF status.
CONSTANTS state_area_duplicate TYPE string VALUE `CHECK_DUPLICATE`.
METHODS get_instance_features FOR INSTANCE FEATURES
IMPORTING keys REQUEST requested_features FOR ChangeApproval RESULT result.
METHODS approve FOR MODIFY
IMPORTING keys FOR ACTION ChangeApproval~approve RESULT result.
METHODS reject FOR MODIFY
IMPORTING keys FOR ACTION ChangeApproval~reject RESULT result.
METHODS retrySync FOR MODIFY
IMPORTING keys FOR ACTION ChangeApproval~retrySync RESULT result.
METHODS markExecuting FOR MODIFY
IMPORTING keys FOR ACTION ChangeApproval~markExecuting RESULT result.
METHODS markSynced FOR MODIFY
IMPORTING keys FOR ACTION ChangeApproval~markSynced RESULT result.
METHODS markFailed FOR MODIFY
IMPORTING keys FOR ACTION ChangeApproval~markFailed RESULT result.
METHODS setInitialStatus FOR DETERMINE ON MODIFY
IMPORTING keys FOR ChangeApproval~setInitialStatus.
METHODS checkDuplicate FOR VALIDATE ON SAVE
IMPORTING keys FOR ChangeApproval~checkDuplicate.
ENDCLASS.
CLASS lhc_changeapproval IMPLEMENTATION.
METHOD get_instance_features.
READ ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(approvals)
FAILED failed.
result = VALUE #( FOR approval IN approvals
( %tky = approval-%tky
%features-%action-approve = COND #( WHEN approval-Status = status-pending
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
%features-%action-reject = COND #( WHEN approval-Status = status-pending
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
%features-%action-retrySync = COND #( WHEN approval-Status = status-failed
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
%features-%action-markExecuting = COND #( WHEN approval-Status = status-approved
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
%features-%action-markSynced = COND #( WHEN approval-Status = status-executing
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
%features-%action-markFailed = COND #( WHEN approval-Status = status-executing
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled ) ) ).
ENDMETHOD.
METHOD approve.
" PENDING -> APPROVED; records the decision, never writes SapChangeNumber (PRD RAP-1)
DATA approvals_for_update TYPE TABLE FOR UPDATE zr_changeapproval.
READ ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(approvals).
LOOP AT keys INTO DATA(key).
ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(<approval>).
IF sy-subrc <> 0.
APPEND VALUE #( %tky = key-%tky
%fail-cause = if_abap_behv=>cause-not_found
%op-%action-approve = if_abap_behv=>mk-on ) TO failed-changeapproval.
CONTINUE.
ENDIF.
IF <approval>-Status <> status-pending.
APPEND VALUE #( %tky = key-%tky
%fail-cause = if_abap_behv=>cause-unspecific
%op-%action-approve = if_abap_behv=>mk-on ) TO failed-changeapproval.
APPEND VALUE #( %tky = key-%tky
%op-%action-approve = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Only PENDING records can be approved (status is { <approval>-Status })| ) )
TO reported-changeapproval.
CONTINUE.
ENDIF.
APPEND VALUE #( %tky = key-%tky
Status = status-approved
DecidedBy = cl_abap_context_info=>get_user_technical_name( )
DecidedOn = utclong_current( ) ) TO approvals_for_update.
ENDLOOP.
IF approvals_for_update IS NOT INITIAL.
MODIFY ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
UPDATE FIELDS ( Status DecidedBy DecidedOn )
WITH approvals_for_update
FAILED DATA(update_failed)
REPORTED DATA(update_reported).
" Propagate the internal UPDATE response into the action response
LOOP AT update_reported-changeapproval ASSIGNING FIELD-SYMBOL(<update_message>).
<update_message>-%op-%action-approve = if_abap_behv=>mk-on.
ENDLOOP.
reported = CORRESPONDING #( DEEP APPENDING BASE ( reported ) update_reported ).
LOOP AT update_failed-changeapproval INTO DATA(update_failure).
APPEND VALUE #( %tky = update_failure-%tky
%fail-cause = COND #( WHEN update_failure-%fail-cause = if_abap_behv=>cause-locked
THEN if_abap_behv=>cause-unspecific
ELSE update_failure-%fail-cause )
%op-%action-approve = if_abap_behv=>mk-on ) TO failed-changeapproval.
IF update_failure-%fail-cause <> if_abap_behv=>cause-not_found
AND NOT line_exists( update_reported-changeapproval[ ID = update_failure-ID ] ).
APPEND VALUE #( %tky = update_failure-%tky
%op-%action-approve = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'The approval could not be recorded' ) )
TO reported-changeapproval.
ENDIF.
ENDLOOP.
ENDIF.
READ ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(updated).
LOOP AT updated INTO DATA(row).
IF NOT line_exists( failed-changeapproval[ ID = row-ID ] ).
APPEND VALUE #( %tky = row-%tky %param = row ) TO result.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD reject.
" PENDING -> REJECTED with a mandatory reason (PRD RAP-2); REJECTED is terminal
DATA approvals_for_update TYPE TABLE FOR UPDATE zr_changeapproval.
READ ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(approvals).
LOOP AT keys INTO DATA(key).
ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(<approval>).
IF sy-subrc <> 0.
APPEND VALUE #( %tky = key-%tky
%fail-cause = if_abap_behv=>cause-not_found
%op-%action-reject = if_abap_behv=>mk-on ) TO failed-changeapproval.
CONTINUE.
ENDIF.
IF <approval>-Status <> status-pending.
APPEND VALUE #( %tky = key-%tky
%fail-cause = if_abap_behv=>cause-unspecific
%op-%action-reject = if_abap_behv=>mk-on ) TO failed-changeapproval.
APPEND VALUE #( %tky = key-%tky
%op-%action-reject = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Only PENDING records can be rejected (status is { <approval>-Status })| ) )
TO reported-changeapproval.
CONTINUE.
ENDIF.
IF key-%param-RejectReason IS INITIAL.
APPEND VALUE #( %tky = key-%tky
%fail-cause = if_abap_behv=>cause-unspecific
%op-%action-reject = if_abap_behv=>mk-on ) TO failed-changeapproval.
APPEND VALUE #( %tky = key-%tky
%op-%action-reject = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'A reject reason is required' ) )
TO reported-changeapproval.
CONTINUE.
ENDIF.
APPEND VALUE #( %tky = key-%tky
Status = status-rejected
RejectReason = key-%param-RejectReason
DecidedBy = cl_abap_context_info=>get_user_technical_name( )
DecidedOn = utclong_current( ) ) TO approvals_for_update.
ENDLOOP.
IF approvals_for_update IS NOT INITIAL.
MODIFY ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
UPDATE FIELDS ( Status RejectReason DecidedBy DecidedOn )
WITH approvals_for_update
FAILED DATA(update_failed)
REPORTED DATA(update_reported).
LOOP AT update_reported-changeapproval ASSIGNING FIELD-SYMBOL(<update_message>).
<update_message>-%op-%action-reject = if_abap_behv=>mk-on.
ENDLOOP.
reported = CORRESPONDING #( DEEP APPENDING BASE ( reported ) update_reported ).
LOOP AT update_failed-changeapproval INTO DATA(update_failure).
APPEND VALUE #( %tky = update_failure-%tky
%fail-cause = COND #( WHEN update_failure-%fail-cause = if_abap_behv=>cause-locked
THEN if_abap_behv=>cause-unspecific
ELSE update_failure-%fail-cause )
%op-%action-reject = if_abap_behv=>mk-on ) TO failed-changeapproval.
IF update_failure-%fail-cause <> if_abap_behv=>cause-not_found
AND NOT line_exists( update_reported-changeapproval[ ID = update_failure-ID ] ).
APPEND VALUE #( %tky = update_failure-%tky
%op-%action-reject = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'The rejection could not be recorded' ) )
TO reported-changeapproval.
ENDIF.
ENDLOOP.
ENDIF.
READ ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(updated).
LOOP AT updated INTO DATA(row).
IF NOT line_exists( failed-changeapproval[ ID = row-ID ] ).
APPEND VALUE #( %tky = row-%tky %param = row ) TO result.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD retrySync.
" FAILED -> APPROVED (decision 2026-09-16): clears the previous error and keeps DecidedBy/DecidedOn of the original
" approval. The worker re-claims the record through markExecuting (APPROVED -> EXECUTING) and re-executes the staged
" payload (ARN-002 amendment, ARN-010). retrySync never writes EXECUTING itself.
DATA approvals_for_update TYPE TABLE FOR UPDATE zr_changeapproval.
READ ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(approvals).
LOOP AT keys INTO DATA(key).
ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(<approval>).
IF sy-subrc <> 0.
APPEND VALUE #( %tky = key-%tky
%fail-cause = if_abap_behv=>cause-not_found
%op-%action-retrySync = if_abap_behv=>mk-on ) TO failed-changeapproval.
CONTINUE.
ENDIF.
IF <approval>-Status <> status-failed.
APPEND VALUE #( %tky = key-%tky
%fail-cause = if_abap_behv=>cause-unspecific
%op-%action-retrySync = if_abap_behv=>mk-on ) TO failed-changeapproval.
APPEND VALUE #( %tky = key-%tky
%op-%action-retrySync = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Only FAILED records can be retried (status is { <approval>-Status })| ) )
TO reported-changeapproval.
CONTINUE.
ENDIF.
APPEND VALUE #( %tky = key-%tky
Status = status-approved
ErrorMessage = '' ) TO approvals_for_update.
ENDLOOP.
IF approvals_for_update IS NOT INITIAL.
MODIFY ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
UPDATE FIELDS ( Status ErrorMessage )
WITH approvals_for_update
FAILED DATA(update_failed)
REPORTED DATA(update_reported).
LOOP AT update_reported-changeapproval ASSIGNING FIELD-SYMBOL(<update_message>).
<update_message>-%op-%action-retrySync = if_abap_behv=>mk-on.
ENDLOOP.
reported = CORRESPONDING #( DEEP APPENDING BASE ( reported ) update_reported ).
LOOP AT update_failed-changeapproval INTO DATA(update_failure).
APPEND VALUE #( %tky = update_failure-%tky
%fail-cause = COND #( WHEN update_failure-%fail-cause = if_abap_behv=>cause-locked
THEN if_abap_behv=>cause-unspecific
ELSE update_failure-%fail-cause )
%op-%action-retrySync = if_abap_behv=>mk-on ) TO failed-changeapproval.
IF update_failure-%fail-cause <> if_abap_behv=>cause-not_found
AND NOT line_exists( update_reported-changeapproval[ ID = update_failure-ID ] ).
APPEND VALUE #( %tky = update_failure-%tky
%op-%action-retrySync = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'The retry could not be recorded' ) )
TO reported-changeapproval.
ENDIF.
ENDLOOP.
ENDIF.
READ ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(updated).
LOOP AT updated INTO DATA(row).
IF NOT line_exists( failed-changeapproval[ ID = row-ID ] ).
APPEND VALUE #( %tky = row-%tky %param = row ) TO result.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD markExecuting.
" APPROVED -> EXECUTING (worker claims the record; the UI never offers Approve on an executing record, PRD RAP-6).
" The only place that writes EXECUTING.
DATA approvals_for_update TYPE TABLE FOR UPDATE zr_changeapproval.
READ ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(approvals).
LOOP AT keys INTO DATA(key).
ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(<approval>).
IF sy-subrc <> 0.
APPEND VALUE #( %tky = key-%tky
%fail-cause = if_abap_behv=>cause-not_found
%op-%action-markExecuting = if_abap_behv=>mk-on ) TO failed-changeapproval.
CONTINUE.
ENDIF.
IF <approval>-Status <> status-approved.
APPEND VALUE #( %tky = key-%tky
%fail-cause = if_abap_behv=>cause-unspecific
%op-%action-markExecuting = if_abap_behv=>mk-on ) TO failed-changeapproval.
APPEND VALUE #( %tky = key-%tky
%op-%action-markExecuting = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Only APPROVED records can start executing (status is { <approval>-Status })| ) )
TO reported-changeapproval.
CONTINUE.
ENDIF.
APPEND VALUE #( %tky = key-%tky
Status = status-executing ) TO approvals_for_update.
ENDLOOP.
IF approvals_for_update IS NOT INITIAL.
MODIFY ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
UPDATE FIELDS ( Status )
WITH approvals_for_update
FAILED DATA(update_failed)
REPORTED DATA(update_reported).
LOOP AT update_reported-changeapproval ASSIGNING FIELD-SYMBOL(<update_message>).
<update_message>-%op-%action-markExecuting = if_abap_behv=>mk-on.
ENDLOOP.
reported = CORRESPONDING #( DEEP APPENDING BASE ( reported ) update_reported ).
LOOP AT update_failed-changeapproval INTO DATA(update_failure).
APPEND VALUE #( %tky = update_failure-%tky
%fail-cause = COND #( WHEN update_failure-%fail-cause = if_abap_behv=>cause-locked
THEN if_abap_behv=>cause-unspecific
ELSE update_failure-%fail-cause )
%op-%action-markExecuting = if_abap_behv=>mk-on ) TO failed-changeapproval.
IF update_failure-%fail-cause <> if_abap_behv=>cause-not_found
AND NOT line_exists( update_reported-changeapproval[ ID = update_failure-ID ] ).
APPEND VALUE #( %tky = update_failure-%tky
%op-%action-markExecuting = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'The execution start could not be recorded' ) )
TO reported-changeapproval.
ENDIF.
ENDLOOP.
ENDIF.
READ ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(updated).
LOOP AT updated INTO DATA(row).
IF NOT line_exists( failed-changeapproval[ ID = row-ID ] ).
APPEND VALUE #( %tky = row-%tky %param = row ) TO result.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD markSynced.
" EXECUTING -> SYNCED (terminal success); SAP is the ledger, the change number is the receipt (ARN-006)
DATA approvals_for_update TYPE TABLE FOR UPDATE zr_changeapproval.
READ ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(approvals).
LOOP AT keys INTO DATA(key).
ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(<approval>).
IF sy-subrc <> 0.
APPEND VALUE #( %tky = key-%tky
%fail-cause = if_abap_behv=>cause-not_found
%op-%action-markSynced = if_abap_behv=>mk-on ) TO failed-changeapproval.
CONTINUE.
ENDIF.
IF <approval>-Status <> status-executing.
APPEND VALUE #( %tky = key-%tky
%fail-cause = if_abap_behv=>cause-unspecific
%op-%action-markSynced = if_abap_behv=>mk-on ) TO failed-changeapproval.
APPEND VALUE #( %tky = key-%tky
%op-%action-markSynced = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Only EXECUTING records can be marked synced (status is { <approval>-Status })| ) )
TO reported-changeapproval.
CONTINUE.
ENDIF.
IF key-%param-SapChangeNumber IS INITIAL.
APPEND VALUE #( %tky = key-%tky
%fail-cause = if_abap_behv=>cause-unspecific
%op-%action-markSynced = if_abap_behv=>mk-on ) TO failed-changeapproval.
APPEND VALUE #( %tky = key-%tky
%op-%action-markSynced = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'The SAP change number is required to mark a record synced' ) )
TO reported-changeapproval.
CONTINUE.
ENDIF.
APPEND VALUE #( %tky = key-%tky
Status = status-synced
SapChangeNumber = key-%param-SapChangeNumber
ErrorMessage = '' ) TO approvals_for_update.
ENDLOOP.
IF approvals_for_update IS NOT INITIAL.
MODIFY ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
UPDATE FIELDS ( Status SapChangeNumber ErrorMessage )
WITH approvals_for_update
FAILED DATA(update_failed)
REPORTED DATA(update_reported).
LOOP AT update_reported-changeapproval ASSIGNING FIELD-SYMBOL(<update_message>).
<update_message>-%op-%action-markSynced = if_abap_behv=>mk-on.
ENDLOOP.
reported = CORRESPONDING #( DEEP APPENDING BASE ( reported ) update_reported ).
LOOP AT update_failed-changeapproval INTO DATA(update_failure).
APPEND VALUE #( %tky = update_failure-%tky
%fail-cause = COND #( WHEN update_failure-%fail-cause = if_abap_behv=>cause-locked
THEN if_abap_behv=>cause-unspecific
ELSE update_failure-%fail-cause )
%op-%action-markSynced = if_abap_behv=>mk-on ) TO failed-changeapproval.
IF update_failure-%fail-cause <> if_abap_behv=>cause-not_found
AND NOT line_exists( update_reported-changeapproval[ ID = update_failure-ID ] ).
APPEND VALUE #( %tky = update_failure-%tky
%op-%action-markSynced = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'The sync result could not be recorded' ) )
TO reported-changeapproval.
ENDIF.
ENDLOOP.
ENDIF.
READ ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(updated).
LOOP AT updated INTO DATA(row).
IF NOT line_exists( failed-changeapproval[ ID = row-ID ] ).
APPEND VALUE #( %tky = row-%tky %param = row ) TO result.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD markFailed.
" EXECUTING -> FAILED with the error text; distinct from REJECTED (predecessor defect, ARN-002)
DATA approvals_for_update TYPE TABLE FOR UPDATE zr_changeapproval.
READ ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(approvals).
LOOP AT keys INTO DATA(key).
ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(<approval>).
IF sy-subrc <> 0.
APPEND VALUE #( %tky = key-%tky
%fail-cause = if_abap_behv=>cause-not_found
%op-%action-markFailed = if_abap_behv=>mk-on ) TO failed-changeapproval.
CONTINUE.
ENDIF.
IF <approval>-Status <> status-executing.
APPEND VALUE #( %tky = key-%tky
%fail-cause = if_abap_behv=>cause-unspecific
%op-%action-markFailed = if_abap_behv=>mk-on ) TO failed-changeapproval.
APPEND VALUE #( %tky = key-%tky
%op-%action-markFailed = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Only EXECUTING records can be marked failed (status is { <approval>-Status })| ) )
TO reported-changeapproval.
CONTINUE.
ENDIF.
IF key-%param-ErrorMessage IS INITIAL.
APPEND VALUE #( %tky = key-%tky
%fail-cause = if_abap_behv=>cause-unspecific
%op-%action-markFailed = if_abap_behv=>mk-on ) TO failed-changeapproval.
APPEND VALUE #( %tky = key-%tky
%op-%action-markFailed = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'An error message is required to mark a record failed' ) )
TO reported-changeapproval.
CONTINUE.
ENDIF.
APPEND VALUE #( %tky = key-%tky
Status = status-failed
ErrorMessage = key-%param-ErrorMessage ) TO approvals_for_update.
ENDLOOP.
IF approvals_for_update IS NOT INITIAL.
MODIFY ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
UPDATE FIELDS ( Status ErrorMessage )
WITH approvals_for_update
FAILED DATA(update_failed)
REPORTED DATA(update_reported).
LOOP AT update_reported-changeapproval ASSIGNING FIELD-SYMBOL(<update_message>).
<update_message>-%op-%action-markFailed = if_abap_behv=>mk-on.
ENDLOOP.
reported = CORRESPONDING #( DEEP APPENDING BASE ( reported ) update_reported ).
LOOP AT update_failed-changeapproval INTO DATA(update_failure).
APPEND VALUE #( %tky = update_failure-%tky
%fail-cause = COND #( WHEN update_failure-%fail-cause = if_abap_behv=>cause-locked
THEN if_abap_behv=>cause-unspecific
ELSE update_failure-%fail-cause )
%op-%action-markFailed = if_abap_behv=>mk-on ) TO failed-changeapproval.
IF update_failure-%fail-cause <> if_abap_behv=>cause-not_found
AND NOT line_exists( update_reported-changeapproval[ ID = update_failure-ID ] ).
APPEND VALUE #( %tky = update_failure-%tky
%op-%action-markFailed = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'The failure could not be recorded' ) )
TO reported-changeapproval.
ENDIF.
ENDLOOP.
ENDIF.
READ ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(updated).
LOOP AT updated INTO DATA(row).
IF NOT line_exists( failed-changeapproval[ ID = row-ID ] ).
APPEND VALUE #( %tky = row-%tky %param = row ) TO result.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD setInitialStatus.
" Every created record starts PENDING with the requester audit fields (ARN-002)
READ ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(approvals).
DELETE approvals WHERE Status IS NOT INITIAL.
IF approvals IS INITIAL.
RETURN.
ENDIF.
MODIFY ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
UPDATE FIELDS ( Status RequestedBy RequestedOn )
WITH VALUE #( FOR approval IN approvals
( %tky = approval-%tky
Status = status-pending
RequestedBy = cl_abap_context_info=>get_user_technical_name( )
RequestedOn = utclong_current( ) ) )
REPORTED DATA(update_reported).
reported = CORRESPONDING #( DEEP update_reported ).
ENDMETHOD.
METHOD checkDuplicate.
" Semantic key ItemNumber + ChangeType + ArenaEcoId (PRD RAP-8) must be unique among records that are not REJECTED.
" Checks (1) duplicates among the instances of this request and (2) persisted records, read through the CDS view
" with privileged access (released access only; the view is access-controlled, see header sources).
READ ENTITIES OF zr_changeapproval IN LOCAL MODE
ENTITY ChangeApproval
FIELDS ( ItemNumber ChangeType ArenaEcoId )
WITH CORRESPONDING #( keys )
RESULT DATA(approvals).
LOOP AT approvals INTO DATA(approval).
" Invalidate a state message left by an earlier run of this validation for the instance
APPEND VALUE #( %tky = approval-%tky
%state_area = state_area_duplicate ) TO reported-changeapproval.
DATA(same_key_in_request) = REDUCE i( INIT count = 0
FOR other IN approvals
WHERE ( ItemNumber = approval-ItemNumber
AND ChangeType = approval-ChangeType
AND ArenaEcoId = approval-ArenaEcoId )
NEXT count = count + 1 ).
DATA(is_duplicate) = xsdbool( same_key_in_request > 1 ).
IF is_duplicate = abap_false.
SELECT SINGLE FROM zr_changeapproval
FIELDS ID
WHERE ItemNumber = @approval-ItemNumber
AND ChangeType = @approval-ChangeType
AND ArenaEcoId = @approval-ArenaEcoId
AND Status <> @status-rejected
AND ID <> @approval-ID
INTO @DATA(existing_id)
OPTIONS PRIVILEGED ACCESS.
is_duplicate = xsdbool( sy-subrc = 0 ).
ENDIF.
IF is_duplicate = abap_true.
APPEND VALUE #( %tky = approval-%tky
%fail-cause = if_abap_behv=>cause-unspecific ) TO failed-changeapproval.
APPEND VALUE #( %tky = approval-%tky
%state_area = state_area_duplicate
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |A change approval for item { approval-ItemNumber }, | &&
|change type { approval-ChangeType } and ECO { approval-ArenaEcoId } | &&
|already exists and is not rejected| )
%element-ItemNumber = if_abap_behv=>mk-on
%element-ChangeType = if_abap_behv=>mk-on
%element-ArenaEcoId = if_abap_behv=>mk-on ) TO reported-changeapproval.
ENDIF.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
@@ -0,0 +1,26 @@
// TARGET OBJECT : behavior definition ZC_CHANGEAPPROVAL (UI projection BDEF) - README step U9, UI leg
// abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Behavior%20Definitions/ZC_CHANGEAPPROVAL/zc_changeapproval.bdef.json
// ACTION : create via ADT MCP (objectType BDEF/BDO, implementationType Projection, README U9) once the view
// ZC_CHANGEAPPROVAL is active, replace entire content, save, then activate (BDEF ZR_CHANGEAPPROVAL and
// class ZBP_R_CHANGEAPPROVAL must be active).
// No behavior pool is created for this projection: a projection BDEF needs one only when it defines
// new actions/functions, and this one reuses everything
// [source: abap-docs /abap-docs-cloud/ABENBDL_BDEF_PROJECTION_HEADER, verified 2026-09-15].
// Contract : approve / reject / retrySync are the bound-action names the Fiori app calls (exact case).
// Sources (verified 2026-09-15): use action, use etag - no automatic inheritance - only what is listed is exposed,
// which is how create/update/delete and the worker actions are kept out of the UI service (PRD RAP-2)
// [abap-docs /abap-docs-cloud/ABENBDL_USE_PROJECTION]. Feature control of reused actions is passed on automatically (same source).
// `use etag` is an entity behavior characteristic: it sits between `define behavior for ... alias ...` and `{`,
// without a semicolon [source: abap-docs /abap-docs-cloud/ABENBDL_CHARACTER_PROJECTION and
// /abap-docs-cloud/ABENBDL_DEFINE_BEH_PROJECTION, verified 2026-09-16]; it must be stated per entity or optimistic
// concurrency control is lost [source: abap-docs /abap-docs-cloud/ABENBDL_USE_PROJECTION, verified 2026-09-16].
projection;
strict ( 2 );
define behavior for ZC_CHANGEAPPROVAL alias ChangeApproval
use etag
{
use action approve;
use action reject;
use action retrySync;
}
@@ -0,0 +1,13 @@
/* TARGET OBJECT : access control ZC_CHANGEAPPROVAL - README step U11, UI leg
abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Access%20Controls/ZC_CHANGEAPPROVAL/zc_changeapproval.dcls.acds
ACTION : create empty via ADT MCP (objectType DCLS/DL, README U11), replace entire content, save, then activate
(after view ZC_CHANGEAPPROVAL and access control ZR_CHANGEAPPROVAL).
A transactional query needs its own DCL, inheriting the base view's conditions - there is no implicit inheritance
[source: abap-docs /abap-platform-rap-opensap/week2/unit7#step-5-create-cds-access-control-for-travel-bo-projection-view-and-check-the-result;
/abap-docs-cloud/ABENCDS_DEFINE_VIEW_AS_PROJECTION (access control must be symmetric), verified 2026-09-15]. */
@EndUserText.label: 'Access control for ZC_CHANGEAPPROVAL'
@MappingRole: true
define role ZC_CHANGEAPPROVAL {
grant select on ZC_CHANGEAPPROVAL
where inheriting conditions from entity ZR_CHANGEAPPROVAL;
}
@@ -1,6 +1,10 @@
/* TARGET OBJECT : data definition ZZC_CHANGEAPPROVAL000 (UI projection, generated 2026-09-15) /* TARGET OBJECT : data definition ZC_CHANGEAPPROVAL (UI projection) - README step U8, UI leg
abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Data%20Definitions/ZZC_CHANGEAPPROVAL000/zzc_changeapproval000.ddls.acds abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Data%20Definitions/ZC_CHANGEAPPROVAL/zc_changeapproval.ddls.acds
ACTION : replace entire content, then activate (after ZZR_CHANGEAPPROVAL000). ACTION : create empty via ADT MCP (objectType DDLS/DF, README U8), replace entire content, save, then activate
(after ZR_CHANGEAPPROVAL).
Contract : element names are the OData property names of entity type ChangeApproval_Type in the Fiori app
(app/webapp/localService/mainService/metadata.xml, 17 properties, exact case). StatusText (RAP-3 text
element) and LocalLastChangedAt (etag) are in addition to the app's mock - see README "App contract check".
Notes: provider contract transactional_query [source: abap-docs /abap-docs-cloud/ABENCDS_DEFINE_VIEW_AS_PROJECTION, verified 2026-09-15]; Notes: provider contract transactional_query [source: abap-docs /abap-docs-cloud/ABENCDS_DEFINE_VIEW_AS_PROJECTION, verified 2026-09-15];
@ObjectModel.semanticKey (PRD RAP-8) [source: abap-docs /abap-fiori-showcase/05_object_page_header#handling-semantic-key-fields, verified 2026-09-15]; @ObjectModel.semanticKey (PRD RAP-8) [source: abap-docs /abap-fiori-showcase/05_object_page_header#handling-semantic-key-fields, verified 2026-09-15];
@Metadata.allowExtensions is required for the metadata extension [source: abap-docs /abap-docs-cloud/ABENCDS_F1_ANNOTATE_VIEW, verified 2026-09-15]. */ @Metadata.allowExtensions is required for the metadata extension [source: abap-docs /abap-docs-cloud/ABENCDS_F1_ANNOTATE_VIEW, verified 2026-09-15]. */
@@ -8,9 +12,9 @@
@AccessControl.authorizationCheck: #CHECK @AccessControl.authorizationCheck: #CHECK
@Metadata.allowExtensions: true @Metadata.allowExtensions: true
@ObjectModel.semanticKey: ['ItemNumber', 'ChangeType', 'ArenaEcoId'] @ObjectModel.semanticKey: ['ItemNumber', 'ChangeType', 'ArenaEcoId']
define root view entity ZZC_CHANGEAPPROVAL000 define root view entity ZC_CHANGEAPPROVAL
provider contract transactional_query provider contract transactional_query
as projection on ZZR_CHANGEAPPROVAL000 as projection on ZR_CHANGEAPPROVAL
{ {
key ID, key ID,
ItemNumber, ItemNumber,
@@ -1,6 +1,7 @@
/* TARGET OBJECT : metadata extension ZZC_CHANGEAPPROVAL000 (generated 2026-09-15) /* TARGET OBJECT : metadata extension ZC_CHANGEAPPROVAL - README step U12, UI leg
abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Metadata%20Extensions/ZZC_CHANGEAPPROVAL000/zzc_changeapproval000.ddlx.acds abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Metadata%20Extensions/ZC_CHANGEAPPROVAL/zc_changeapproval.ddlx.acds
ACTION : replace entire content, then activate (after ZZC_CHANGEAPPROVAL000 view). ACTION : create empty via ADT MCP (objectType DDLX/EX, README U12), replace entire content, save, then activate
(after ZC_CHANGEAPPROVAL view).
Mirrors app/webapp/annotations/annotation.xml (line items, filter bar, header, facets, actions); the app's local Mirrors app/webapp/annotations/annotation.xml (line items, filter bar, header, facets, actions); the app's local
annotations still apply on top at runtime. Labels are literals here; the app carries the i18n texts. annotations still apply on top at runtime. Labels are literals here; the app carries the i18n texts.
Sources (all verified 2026-09-15, abap-docs /abap-fiori-showcase): Sources (all verified 2026-09-15, abap-docs /abap-fiori-showcase):
@@ -31,7 +32,7 @@
visualizations: [ { type: #AS_LINEITEM } ] visualizations: [ { type: #AS_LINEITEM } ]
} ] } ]
} }
annotate entity ZZC_CHANGEAPPROVAL000 with annotate entity ZC_CHANGEAPPROVAL with
{ {
@UI.facet: [ @UI.facet: [
{ id: 'StatusPoint', purpose: #HEADER, type: #DATAPOINT_REFERENCE, targetQualifier: 'StatusPoint' }, { id: 'StatusPoint', purpose: #HEADER, type: #DATAPOINT_REFERENCE, targetQualifier: 'StatusPoint' },
@@ -1,16 +1,20 @@
// TARGET OBJECT : behavior definition ZC_CHANGEAPPROVAL_A2X (A2X projection BDEF) - DOES NOT EXIST YET. // TARGET OBJECT : behavior definition ZC_CHANGEAPPROVAL_A2X (A2X projection BDEF) - README step W3, worker leg
// The ADT MCP refused to create it while ZC_CHANGEAPPROVAL_A2X (DDLS) had no active source // abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Behavior%20Definitions/ZC_CHANGEAPPROVAL_A2X/zc_changeapproval_a2x.bdef.abdl
// ("The referenced STOB object ZC_CHANGEAPPROVAL_A2X does not exist", 2026-09-15). // ACTION : create via ADT MCP (objectType BDEF/BDO, implementationType Projection, README W3) only once the view
// Create it after the projection view is active: ADT > New Behavior Definition on ZC_CHANGEAPPROVAL_A2X, // ZC_CHANGEAPPROVAL_A2X is active - the MCP validates the referenced entity's active version (live
// implementation type Projection (or re-run abap_creation-create_object BDEF/BDO, see README), paste, activate. // 2026-09-15: "The referenced STOB object ZC_CHANGEAPPROVAL_A2X does not exist" while the view had no
// Sources: as ZZC_CHANGEAPPROVAL000. use create lets the worker stage records (ARN-010: the staged payload is what // active source). Replace entire content, save, then activate.
// gets executed); the worker never sees approve/reject/retrySync (PRD RAP-6). // Sources: as ZC_CHANGEAPPROVAL (incl. `use etag` as an entity behavior characteristic in the header, not in the body).
// use create lets the worker stage records (ARN-010: the staged payload is what gets executed); the worker never
// sees approve/reject/retrySync (PRD RAP-6). The base BDEF's validation checkDuplicate runs on every create; it is
// not something a projection lists (validations are not in the `use` list [source: abap-docs
// /abap-docs-cloud/ABENBDL_USE_PROJECTION, verified 2026-09-16]).
projection; projection;
strict ( 2 ); strict ( 2 );
define behavior for ZC_CHANGEAPPROVAL_A2X alias ChangeApproval define behavior for ZC_CHANGEAPPROVAL_A2X alias ChangeApproval
use etag
{ {
use etag;
use create; use create;
use action markExecuting; use action markExecuting;
@@ -1,10 +1,11 @@
/* TARGET OBJECT : access control ZC_CHANGEAPPROVAL_A2X (created empty via ADT MCP 2026-09-15) /* TARGET OBJECT : access control ZC_CHANGEAPPROVAL_A2X - README step W2, worker leg
abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Access%20Controls/ZC_CHANGEAPPROVAL_A2X/zc_changeapproval_a2x.dcls.acds abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Access%20Controls/ZC_CHANGEAPPROVAL_A2X/zc_changeapproval_a2x.dcls.acds
ACTION : replace entire content, then activate (after ZC_CHANGEAPPROVAL_A2X view and ZZR_CHANGEAPPROVAL000 DCL). ACTION : create empty via ADT MCP (objectType DCLS/DL, README W2), replace entire content, save, then activate
Sources: as ZZC_CHANGEAPPROVAL000 DCL. */ (after ZC_CHANGEAPPROVAL_A2X view and ZR_CHANGEAPPROVAL DCL).
Sources: as ZC_CHANGEAPPROVAL DCL. */
@EndUserText.label: 'Access control for ZC_CHANGEAPPROVAL_A2X' @EndUserText.label: 'Access control for ZC_CHANGEAPPROVAL_A2X'
@MappingRole: true @MappingRole: true
define role ZC_CHANGEAPPROVAL_A2X { define role ZC_CHANGEAPPROVAL_A2X {
grant select on ZC_CHANGEAPPROVAL_A2X grant select on ZC_CHANGEAPPROVAL_A2X
where inheriting conditions from entity ZZR_CHANGEAPPROVAL000; where inheriting conditions from entity ZR_CHANGEAPPROVAL;
} }
@@ -1,15 +1,16 @@
/* TARGET OBJECT : data definition ZC_CHANGEAPPROVAL_A2X (A2X projection for the worker; created empty via ADT MCP 2026-09-15) /* TARGET OBJECT : data definition ZC_CHANGEAPPROVAL_A2X (A2X projection for the worker) - README step W1, worker leg
abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Data%20Definitions/ZC_CHANGEAPPROVAL_A2X/zc_changeapproval_a2x.ddls.acds abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Data%20Definitions/ZC_CHANGEAPPROVAL_A2X/zc_changeapproval_a2x.ddls.acds
ACTION : replace entire content, then activate (after ZZR_CHANGEAPPROVAL000). ACTION : create empty via ADT MCP (objectType DDLS/DF, README W1), replace entire content, save, then activate
(after the UI leg; needs ZR_CHANGEAPPROVAL).
Notes: separate projection so the Web API exposes create + mark* while the UI projection stays read-only Notes: separate projection so the Web API exposes create + mark* while the UI projection stays read-only
(PRD RAP-2 / RAP-6); same syntax sources as ZZC_CHANGEAPPROVAL000. No metadata extension here - (PRD RAP-2 / RAP-6); same syntax sources as ZC_CHANGEAPPROVAL. No metadata extension here -
a Web API binding carries no UI annotations [source: sap-docs sap-help loio 248b36b7ebce4f7e8c7670c9371adee1, verified 2026-09-15]. */ a Web API binding carries no UI annotations [source: sap-docs sap-help loio 248b36b7ebce4f7e8c7670c9371adee1, verified 2026-09-15]. */
@EndUserText.label: 'Arena change approval - A2X projection (worker)' @EndUserText.label: 'Arena change approval - A2X projection (worker)'
@AccessControl.authorizationCheck: #CHECK @AccessControl.authorizationCheck: #CHECK
@ObjectModel.semanticKey: ['ItemNumber', 'ChangeType', 'ArenaEcoId'] @ObjectModel.semanticKey: ['ItemNumber', 'ChangeType', 'ArenaEcoId']
define root view entity ZC_CHANGEAPPROVAL_A2X define root view entity ZC_CHANGEAPPROVAL_A2X
provider contract transactional_query provider contract transactional_query
as projection on ZZR_CHANGEAPPROVAL000 as projection on ZR_CHANGEAPPROVAL
{ {
key ID, key ID,
ItemNumber, ItemNumber,
@@ -1,17 +1,24 @@
/* TARGET OBJECT : database table ZZCHNGPPROVAL000 (generated 2026-09-15 by x-ui-service) /* TARGET OBJECT : database table ZCHANGEAPPROVAL (persistence of the approval BO) - README step U1, UI leg
abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Dictionary/Database%20Tables/ZZCHNGPPROVAL000/zzchngpproval000.tabl.json abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Dictionary/Database%20Tables/ZCHANGEAPPROVAL/zchangeapproval.tabl.ddic
ACTION : replace entire content, then activate. (returned by create_object 2026-09-16; activation extension pending live proof - README activation table)
ACTION : create empty via ADT MCP (objectType TABL/DT, README U1), then paste into the .tabl.ddic file, NEVER the
.tabl.json file: in the VS Code ADT workspace zchangeapproval.tabl.json holds only header metadata
(formatVersion, description, originalLanguage, abapLanguageVersion) - live observation 2026-09-16.
Replace entire content of the .tabl.ddic file, save, then activate.
A DDIC database table is edited as Dictionary DDL source code generated from, and saved back into, the
table metadata [source: abap-docs /abap-docs-cloud/ABENDDIC_DEFINE_TABLE, verified 2026-09-16].
Name : 15 characters; TABL/DT allows 16 [source: adt-mcp abap_creation-get_object_type_details on 0M7DEJI, verified 2026-09-16].
Notes: sap_preview is abap.string(0) (RAP-7; char is capped at 1333 for table fields Notes: sap_preview is abap.string(0) (RAP-7; char is capped at 1333 for table fields
[source: abap-docs /abap-docs-cloud/ABENDDIC_BUILTIN_TYPES, verified 2026-09-15]; [source: abap-docs /abap-docs-cloud/ABENDDIC_BUILTIN_TYPES, verified 2026-09-15];
abap.string(0) table-field syntax [source: abap-docs /abap-docs-cloud/ABENDDICDDL_DEFINE_TABLE_COMPS, verified 2026-09-15]). abap.string(0) table-field syntax [source: abap-docs /abap-docs-cloud/ABENDDICDDL_DEFINE_TABLE_COMPS, verified 2026-09-15]).
Timestamps are abap.utclong (UTCLONG built-in type, same source). Admin fields use the released Timestamps are abap.utclong (UTCLONG built-in type, same source). Admin fields use the released
ABP_* data elements and SYSUUID_X16 [source: sap-released-objects public_cloud level A, verified 2026-09-15]. */ ABP_* data elements and SYSUUID_X16 [source: sap-released-objects public_cloud level A, verified 2026-09-16]. */
@EndUserText.label : 'Arena change approval (staging + state machine)' @EndUserText.label : 'Arena change approval (staging + state machine)'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE @AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT @AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A @AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED @AbapCatalog.dataMaintenance : #RESTRICTED
define table zzchngpproval000 { define table zchangeapproval {
key client : abap.clnt not null; key client : abap.clnt not null;
key id : sysuuid_x16 not null; key id : sysuuid_x16 not null;
item_number : abap.char(18); item_number : abap.char(18);
@@ -1,6 +1,8 @@
/* TARGET OBJECT : data definition ZD_CHANGEAPPROVAL_FAILEDP (abstract entity = parameter of action markFailed; created empty via ADT MCP 2026-09-15) /* TARGET OBJECT : data definition ZD_CHANGEAPPROVAL_FAILEDP (abstract entity = parameter of action markFailed) - README step U4, UI leg
abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Data%20Definitions/ZD_CHANGEAPPROVAL_FAILEDP/zd_changeapproval_failedp.ddls.acds abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Data%20Definitions/ZD_CHANGEAPPROVAL_FAILEDP/zd_changeapproval_failedp.ddls.acds
ACTION : replace entire content, then activate. ACTION : create empty via ADT MCP (objectType DDLS/DF, README U4), replace entire content, save, then activate.
Leg : UI leg although only the worker calls markFailed - the base BDEF ZR_CHANGEAPPROVAL (U6) names this
entity as the markFailed parameter, so it is ordered before that BDEF.
Sources: as ZD_CHANGEAPPROVAL_REJECTP. */ Sources: as ZD_CHANGEAPPROVAL_REJECTP. */
@EndUserText.label: 'Arena change approval: markFailed parameter' @EndUserText.label: 'Arena change approval: markFailed parameter'
define abstract entity ZD_CHANGEAPPROVAL_FAILEDP define abstract entity ZD_CHANGEAPPROVAL_FAILEDP
@@ -1,6 +1,6 @@
/* TARGET OBJECT : data definition ZD_CHANGEAPPROVAL_REJECTP (abstract entity = parameter of action reject; created empty via ADT MCP 2026-09-15) /* TARGET OBJECT : data definition ZD_CHANGEAPPROVAL_REJECTP (abstract entity = parameter of action reject) - README step U2, UI leg
abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Data%20Definitions/ZD_CHANGEAPPROVAL_REJECTP/zd_changeapproval_rejectp.ddls.acds abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Data%20Definitions/ZD_CHANGEAPPROVAL_REJECTP/zd_changeapproval_rejectp.ddls.acds
ACTION : replace entire content, then activate. ACTION : create empty via ADT MCP (objectType DDLS/DF, README U2), replace entire content, save, then activate.
Notes: action input parameters must be a CDS abstract entity or DDIC type; @EndUserText.label on the element Notes: action input parameters must be a CDS abstract entity or DDIC type; @EndUserText.label on the element
gives the UI label [source: abap-docs /abap-docs-cloud/ABENBDL_ACTION_INPUT_PARAM, verified 2026-09-15]; gives the UI label [source: abap-docs /abap-docs-cloud/ABENBDL_ACTION_INPUT_PARAM, verified 2026-09-15];
abstract-entity syntax [source: abap-docs /abap-docs-cloud/ABENBDL_ACTION2_ABEXA, verified 2026-09-15]. abstract-entity syntax [source: abap-docs /abap-docs-cloud/ABENBDL_ACTION2_ABEXA, verified 2026-09-15].
@@ -1,6 +1,8 @@
/* TARGET OBJECT : data definition ZD_CHANGEAPPROVAL_SYNCEDP (abstract entity = parameter of action markSynced; created empty via ADT MCP 2026-09-15) /* TARGET OBJECT : data definition ZD_CHANGEAPPROVAL_SYNCEDP (abstract entity = parameter of action markSynced) - README step U3, UI leg
abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Data%20Definitions/ZD_CHANGEAPPROVAL_SYNCEDP/zd_changeapproval_syncedp.ddls.acds abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Data%20Definitions/ZD_CHANGEAPPROVAL_SYNCEDP/zd_changeapproval_syncedp.ddls.acds
ACTION : replace entire content, then activate. ACTION : create empty via ADT MCP (objectType DDLS/DF, README U3), replace entire content, save, then activate.
Leg : UI leg although only the worker calls markSynced - the base BDEF ZR_CHANGEAPPROVAL (U6) names this
entity as the markSynced parameter, so it is ordered before that BDEF.
Sources: as ZD_CHANGEAPPROVAL_REJECTP. */ Sources: as ZD_CHANGEAPPROVAL_REJECTP. */
@EndUserText.label: 'Arena change approval: markSynced parameter' @EndUserText.label: 'Arena change approval: markSynced parameter'
define abstract entity ZD_CHANGEAPPROVAL_SYNCEDP define abstract entity ZD_CHANGEAPPROVAL_SYNCEDP
@@ -1,6 +1,12 @@
// TARGET OBJECT : behavior definition ZZR_CHANGEAPPROVAL000 (base BDEF, managed; generated 2026-09-15) // TARGET OBJECT : behavior definition ZR_CHANGEAPPROVAL (base BDEF, managed) - README step U6, UI leg
// abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Behavior%20Definitions/ZZR_CHANGEAPPROVAL000/zzr_changeapproval000.bdef.json // abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Behavior%20Definitions/ZR_CHANGEAPPROVAL/zr_changeapproval.bdef.json
// ACTION : replace entire content, then activate (after the three ZD_* abstract entities and ZZR_CHANGEAPPROVAL000). // ACTION : create via ADT MCP (objectType BDEF/BDO, implementationType Managed, README U6) once the view
// ZR_CHANGEAPPROVAL is active, replace entire content, save, then activate BEFORE the behavior pool
// class exists. SAP's RAP exercise activates the BDEF first and only then creates the behavior
// implementation class [source: abap-docs /abap-platform-rap-opensap/week4/unit3#step-3-create-and-implement-the-behavior-implementation-bil-class-for-the-travel-entity,
// verified 2026-09-16]. The three ZD_* abstract entities (U2-U4) must be active. That exercise is an
// unmanaged, non-strict BDEF, so if activation refuses because class ZBP_R_CHANGEAPPROVAL does not exist
// (an error, not a warning), create the class (README U7), paste it, and activate BDEF + class in one call.
// Sources (all verified 2026-09-15): // Sources (all verified 2026-09-15):
// action syntax incl. ( features : instance ), parameter, result [1] $self // action syntax incl. ( features : instance ), parameter, result [1] $self
// [abap-docs /abap-docs-cloud/ABENBDL_ACTION, ABENBDL_ACTIONS_FC, ABENBDL_ACTION_INPUT_PARAM, ABENBDL_ACTION2_ABEXA] // [abap-docs /abap-docs-cloud/ABENBDL_ACTION, ABENBDL_ACTIONS_FC, ABENBDL_ACTION_INPUT_PARAM, ABENBDL_ACTION2_ABEXA]
@@ -11,16 +17,25 @@
// readonly fields written by determinations/actions IN LOCAL MODE [same source: field ( readonly ) AmountSum + TotalPrice determination] // readonly fields written by determinations/actions IN LOCAL MODE [same source: field ( readonly ) AmountSum + TotalPrice determination]
// etag master [abap-docs /abap-docs-cloud/ABENBDL_ETAG]; explicit mapping because CDS aliases differ from table fields [abap-docs /abap-docs-cloud/ABENBDL_TYPE_MAPPING] // etag master [abap-docs /abap-docs-cloud/ABENBDL_ETAG]; explicit mapping because CDS aliases differ from table fields [abap-docs /abap-docs-cloud/ABENBDL_TYPE_MAPPING]
// authorization master ( none ): "no authorization checks for the entire RAP BO" - access is governed exclusively by the // authorization master ( none ): "no authorization checks for the entire RAP BO" - access is governed exclusively by the
// IAM app / business catalog (ADR-002, ARN-007) and read access by DCL [abap-docs /abap-docs-cloud/ABENBDL_AUTHORIZATION] // IAM app / business catalog (ADR-002, ARN-007) and read access by DCL [abap-docs /abap-docs-cloud/ABENBDL_AUTHORIZATION].
// NOT FINAL: ( none ) vs global authorization is open question PRD OQ-8, decided before business roles are assigned.
// Sources (verified 2026-09-16):
// validation ValName on save { create; } - invoked in the save sequence; a failing validation rejects the complete
// transactional buffer of the RAP transaction; EML MODIFY is not allowed in a validation
// [abap-docs /abap-docs-cloud/ABENBDL_VALIDATIONS]
// State machine (PRD ARN-002, M1 decision 2026-09-16):
// create -> PENDING; approve: PENDING -> APPROVED; reject: PENDING -> REJECTED (terminal);
// markExecuting: APPROVED -> EXECUTING; markSynced: EXECUTING -> SYNCED (terminal); markFailed: EXECUTING -> FAILED;
// retrySync: FAILED -> APPROVED (the worker re-claims it through markExecuting). Only markExecuting writes EXECUTING.
// Open point (falsifiable at activation): StatusText/StatusCriticality/IsApprovable/IsRetriable are CDS-calculated and // Open point (falsifiable at activation): StatusText/StatusCriticality/IsApprovable/IsRetriable are CDS-calculated and
// deliberately absent from the mapping. ABENBDL_TYPE_MAPPING only requires a mapping where table and entity field // deliberately absent from the mapping. ABENBDL_TYPE_MAPPING only requires a mapping where table and entity field
// NAMES differ; it is silent on calculated elements. If activation reports an error (not a warning) for the unmapped // NAMES differ; it is silent on calculated elements. If activation reports an error (not a warning) for the unmapped
// elements, fall back to persisting them (add the four columns to the table + mapping) and maintain them in the actions. // elements, fall back to persisting them (add the four columns to the table + mapping) and maintain them in the actions.
managed implementation in class zzbp_r_changeapproval000 unique; managed implementation in class zbp_r_changeapproval unique;
strict ( 2 ); strict ( 2 );
define behavior for ZZR_CHANGEAPPROVAL000 alias ChangeApproval define behavior for ZR_CHANGEAPPROVAL alias ChangeApproval
persistent table zzchngpproval000 persistent table zchangeapproval
lock master lock master
authorization master ( none ) authorization master ( none )
etag master LocalLastChangedAt etag master LocalLastChangedAt
@@ -36,7 +51,7 @@ etag master LocalLastChangedAt
SapChangeNumber, ErrorMessage, RejectReason, SapChangeNumber, ErrorMessage, RejectReason,
CreatedBy, CreatedAt, LastChangedBy, LastChangedAt, LocalLastChangedAt; CreatedBy, CreatedAt, LastChangedBy, LastChangedAt, LocalLastChangedAt;
// Business-user actions (UI projection): PENDING -> APPROVED | REJECTED, FAILED -> EXECUTING (PRD RAP-1/RAP-2) // Business-user actions (UI projection): PENDING -> APPROVED | REJECTED, FAILED -> APPROVED (PRD RAP-1/RAP-2)
action ( features : instance ) approve result [1] $self; action ( features : instance ) approve result [1] $self;
action ( features : instance ) reject parameter ZD_CHANGEAPPROVAL_REJECTP result [1] $self; action ( features : instance ) reject parameter ZD_CHANGEAPPROVAL_REJECTP result [1] $self;
action ( features : instance ) retrySync result [1] $self; action ( features : instance ) retrySync result [1] $self;
@@ -48,7 +63,10 @@ etag master LocalLastChangedAt
determination setInitialStatus on modify { create; } determination setInitialStatus on modify { create; }
mapping for zzchngpproval000 // Semantic key ItemNumber + ChangeType + ArenaEcoId (PRD RAP-8) is unique among non-REJECTED records
validation checkDuplicate on save { create; }
mapping for zchangeapproval
{ {
ID = id; ID = id;
ItemNumber = item_number; ItemNumber = item_number;
@@ -1,12 +1,13 @@
/* TARGET OBJECT : access control ZZR_CHANGEAPPROVAL000 (generated 2026-09-15) /* TARGET OBJECT : access control ZR_CHANGEAPPROVAL - README step U10, UI leg
abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Access%20Controls/ZZR_CHANGEAPPROVAL000/zzr_changeapproval000.dcls.acds abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Access%20Controls/ZR_CHANGEAPPROVAL/zr_changeapproval.dcls.acds
ACTION : replace entire content, then activate. ACTION : create empty via ADT MCP (objectType DCLS/DL, README U10), replace entire content, save, then activate
(view ZR_CHANGEAPPROVAL must be active).
Full access rule: read access is governed by the IAM app / business catalog (ADR-002); SAP delivers no full-access Full access rule: read access is governed by the IAM app / business catalog (ADR-002); SAP delivers no full-access
roles but customers may use them [source: abap-docs /abap-docs-cloud/ABENCDS_DCL_ROLE_GRANT_RULE, verified 2026-09-15]. roles but customers may use them [source: abap-docs /abap-docs-cloud/ABENCDS_DCL_ROLE_GRANT_RULE, verified 2026-09-15].
@MappingRole: true assigns the role to every user [source: abap-docs /abap-platform-rap-opensap/week2/unit7, verified 2026-09-15]. @MappingRole: true assigns the role to every user [source: abap-docs /abap-platform-rap-opensap/week2/unit7, verified 2026-09-15].
Tighten later with a custom authorization object + pfcg_auth condition if per-user data restrictions are needed. */ Tighten later with a custom authorization object + pfcg_auth condition if per-user data restrictions are needed. */
@EndUserText.label: 'Access control for ZZR_CHANGEAPPROVAL000' @EndUserText.label: 'Access control for ZR_CHANGEAPPROVAL'
@MappingRole: true @MappingRole: true
define role ZZR_CHANGEAPPROVAL000 { define role ZR_CHANGEAPPROVAL {
grant select on ZZR_CHANGEAPPROVAL000; grant select on ZR_CHANGEAPPROVAL;
} }
@@ -0,0 +1,100 @@
/* TARGET OBJECT : data definition ZR_CHANGEAPPROVAL (root view entity) - README step U5, UI leg
abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Data%20Definitions/ZR_CHANGEAPPROVAL/zr_changeapproval.ddls.acds
ACTION : create empty via ADT MCP (objectType DDLS/DF, README U5), replace entire content, save, then activate
(table ZCHANGEAPPROVAL must be active first).
Notes: StatusText / StatusCriticality / IsApprovable / IsRetriable are derived from Status with CASE
expressions here, because a transactional_query projection list may only contain elements of the
projected entity, path expressions, associations and virtual elements - no expressions
[source: abap-docs /abap-docs-cloud/ABENCDS_DEFINE_VIEW_AS_PROJECTION, verified 2026-09-15].
CASE syntax [source: abap-docs /abap-docs-cloud/ABENCDS_CASE_EXPRESSION_V2, verified 2026-09-15];
cast of a case distinction to a data element (abap_boolean, released) [source: abap-docs /abap-docs-cloud/ABENCDS_CAST_EXPRESSION_V2, verified 2026-09-16].
@ObjectModel.text.element [source: abap-docs /abap-fiori-showcase/06_object_page_content#displaying-text-instead-of-id, verified 2026-09-15].
@Semantics admin-field annotations [source: abap-docs /abap-platform-rap-opensap/week2/unit3#step-1-create-the-interface-cds-view-for-the-travel-entity, verified 2026-09-15].
Criticality values 2/2/5/3/1/1 per PRD RAP-1.
ABAP_BOOLEAN is released [source: sap-released-objects DTEL ABAP_BOOLEAN public_cloud level A, verified 2026-09-16].
Result types of the CASE expressions:
- A CASE result gets the maximum technical data type of all THEN and ELSE branches, and keeps a data element
only if every branch with that type carries the same one [source: abap-docs /abap-docs-cloud/ABENCDS_CASE_EXPRESSION_V2,
verified 2026-09-16]. StatusText is therefore CHAR as long as its longest literal (29), not Status's CHAR(10).
- IsApprovable / IsRetriable cast the WHOLE case distinction to abap_boolean once. Casting inside the branches does
not work: a cast to a data element passes on its semantics, "An exception to this is the use of the CAST expression
within a case distinction" [source: abap-docs /abap-docs-cloud/ABENCDS_CAST_EXPRESSION_V2, verified 2026-09-16].
- The branches use typed literals abap.char'X' / abap.char' ': length is "derived from the literal value" and "the type
length includes the trimmed trailing blanks", so both are CHAR(1) and the CASE result is CHAR(1) [source: abap-docs
/abap-docs-cloud/ABENCDS_TYPED_LITERAL_V2, verified 2026-09-16]. (Untyped 'X' would not prove it: its DB length is only
"at least as large as" the literal [source: abap-docs /abap-docs-cloud/ABENCDS_UNTYPED_LITERAL_V2, verified 2026-09-16].)
- PRESERVING TYPE needs the operand's built-in type and length to match the data element exactly [source: abap-docs
/abap-docs-cloud/ABENCDS_CAST_EXPRESSION_V2, verified 2026-09-16]. ABAP_BOOLEAN is "of data type CHAR of length 1"
[source: abap-docs /abap-docs-standard/ABENDATAOBJECTS_TRUE_VALUE_GUIDL, verified 2026-09-16], so the types match.
If activation rejects the cast anyway, delete
"preserving type": a plain cast still inherits the data element's semantics, and a same-type warning "can be ignored"
[source: abap-docs /abap-docs-cloud/ABENCDS_CAST_EXPRESSION_V2, verified 2026-09-16].
- An untyped integer literal is INT1, INT2, INT4 or INT8 depending on its value [source: abap-docs
/abap-docs-cloud/ABENCDS_UNTYPED_LITERAL_V2, verified 2026-09-16], so StatusCriticality (0..5) is expected INT1.
Open (UNVERIFIED 2026-09-16 - no doc found): the OData EDM types the UI binding emits for INT1 (app expects
Edm.Byte), ABAP_BOOLEAN (app expects Edm.Boolean) and UTCLONG (app expects Edm.DateTimeOffset Precision 7).
Read them from the live $metadata after publishing (README step U15); do not change this source without a cited rule. */
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Arena change approval'
define root view entity ZR_CHANGEAPPROVAL
as select from zchangeapproval
{
key id as ID,
item_number as ItemNumber,
item_name as ItemName,
change_type as ChangeType,
arena_eco_id as ArenaEcoId,
@ObjectModel.text.element: ['StatusText']
status as Status,
case status
when 'PENDING' then 'Pending approval'
when 'APPROVED' then 'Approved - awaiting execution'
when 'EXECUTING' then 'Executing in SAP'
when 'SYNCED' then 'Synced to SAP'
when 'REJECTED' then 'Rejected'
when 'FAILED' then 'Execution failed'
else status
end as StatusText,
case status
when 'PENDING' then 2
when 'APPROVED' then 2
when 'EXECUTING' then 5
when 'SYNCED' then 3
when 'REJECTED' then 1
when 'FAILED' then 1
else 0
end as StatusCriticality,
requested_by as RequestedBy,
requested_on as RequestedOn,
decided_by as DecidedBy,
decided_on as DecidedOn,
sap_change_number as SapChangeNumber,
error_message as ErrorMessage,
reject_reason as RejectReason,
sap_preview as SapPreview,
cast( case status
when 'PENDING' then abap.char'X'
else abap.char' '
end as abap_boolean preserving type ) as IsApprovable,
cast( case status
when 'FAILED' then abap.char'X'
else abap.char' '
end as abap_boolean preserving type ) as IsRetriable,
@Semantics.user.createdBy: true
created_by as CreatedBy,
@Semantics.systemDateTime.createdAt: true
created_at as CreatedAt,
@Semantics.user.lastChangedBy: true
last_changed_by as LastChangedBy,
@Semantics.systemDateTime.lastChangedAt: true
last_changed_at as LastChangedAt,
@Semantics.systemDateTime.localInstanceLastChangedAt: true
local_last_changed_at as LocalLastChangedAt
}
@@ -0,0 +1,6 @@
@EndUserText.label: 'Arena change approval - UI service'
define service ZUI_ARENA_CHANGE_APPROVAL
provider contracts odata_v4_ui
{
expose ZC_CHANGEAPPROVAL as ChangeApproval;
}
@@ -1,14 +0,0 @@
" TARGET OBJECT : class ZZBP_R_CHANGEAPPROVAL000 - GLOBAL CLASS include (behavior pool; generated 2026-09-15)
" abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Source%20Code%20Library/Classes/ZZBP_R_CHANGEAPPROVAL000/zzbp_r_changeapproval000.clas.abap
" ACTION : replace entire content of the global class; the handlers go into "Local Types" (locals_imp file).
" Source: FOR BEHAVIOR OF defines the ABAP behavior pool; the global class implements nothing itself
" [source: abap-docs /abap-docs-cloud/ABAPCLASS_FOR_BEHAVIOR_OF, ABENABAP_BEHAVIOR_POOLS, verified 2026-09-15].
CLASS zzbp_r_changeapproval000 DEFINITION
PUBLIC
ABSTRACT
FINAL
FOR BEHAVIOR OF zzr_changeapproval000.
ENDCLASS.
CLASS zzbp_r_changeapproval000 IMPLEMENTATION.
ENDCLASS.
@@ -1,426 +0,0 @@
" TARGET OBJECT : class ZZBP_R_CHANGEAPPROVAL000 - LOCAL TYPES include (Local Types tab in ADT)
" abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Source%20Code%20Library/Classes/ZZBP_R_CHANGEAPPROVAL000/zzbp_r_changeapproval000.clas.locals_imp.abap
" ACTION : replace entire content, then activate the class (BDEF ZZR_CHANGEAPPROVAL000 must be active first).
"
" Sources (all verified 2026-09-15):
" handler class inherits from CL_ABAP_BEHAVIOR_HANDLER (released) [sap-released-objects public_cloud A]
" FOR INSTANCE FEATURES signature + result via %tky / %features-%action-<name> / if_abap_behv=>fc-o-enabled|disabled
" [abap-docs /abap-docs-cloud/ABAPHANDLER_METH_FEATURES, ABAPDERIVED_TYPES_FEATURES, ABENBDL_ACTIONS_FC]
" FOR MODIFY ... FOR ACTION with RESULT; %param carries the action parameter (keys) and the result ($self)
" [abap-docs /abap-docs-cloud/ABENBDL_ACTION2_ABEXA, ABAPDERIVED_TYPES_PARAM]
" READ ENTITIES ... IN LOCAL MODE ... FIELDS ( ) WITH CORRESPONDING #( keys ) RESULT ... FAILED failed
" [abap-docs /abap-docs-cloud/ABENBDL_ACTIONS_FC example]
" MODIFY ENTITIES ... IN LOCAL MODE (skips feature control and authorization) with UPDATE FIELDS
" [abap-docs /abap-docs-cloud/ABAPMODIFY_ENTITY_ENTITIES, ABAPMODIFY_ENTITY_ENTITIES_OP, ABENBDL_AUTHORIZATION]
" %msg = new_message_with_text( ... ) into reported [abap-docs /abap-docs-cloud/ABENBDL_ACTIONS_FC example]
" FOR DETERMINE ON MODIFY signature [abap-docs /abap-docs-cloud/ABAPHANDLER_METH_DET]
" utclong_current( ) [abap-docs /abap-docs-cloud/ABENUTCLONG_CURRENT]
" cl_abap_context_info=>get_user_technical_name( ) (released class) [sap-docs sap-help loio 7be24435b3e24540a9edad9a2535175a;
" sap-released-objects CL_ABAP_CONTEXT_INFO public_cloud A]
" Design notes:
" - Status transitions are enforced twice: get_instance_features disables the action (UI greys it out / EML fails
" with an entry in failed [ABENBDL_ACTIONS_FC]) AND each action re-checks the current status so an EML caller
" IN LOCAL MODE cannot skip the state machine.
" - The six actions repeat the same read/check/update/read pattern on purpose: each action has its own derived
" key type, so a generic helper would need additional derived-type plumbing that is not worth it for six methods.
" - RequestedBy is the technical user of the creator (for the A2X flow: the worker's communication user).
" If the Arena requester should be shown instead, make RequestedBy mandatory:create in the BDEF and drop it
" from the determination.
CLASS lhc_changeapproval DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
CONSTANTS:
BEGIN OF status,
pending TYPE zzchngpproval000-status VALUE 'PENDING',
approved TYPE zzchngpproval000-status VALUE 'APPROVED',
executing TYPE zzchngpproval000-status VALUE 'EXECUTING',
synced TYPE zzchngpproval000-status VALUE 'SYNCED',
rejected TYPE zzchngpproval000-status VALUE 'REJECTED',
failed TYPE zzchngpproval000-status VALUE 'FAILED',
END OF status.
METHODS get_instance_features FOR INSTANCE FEATURES
IMPORTING keys REQUEST requested_features FOR ChangeApproval RESULT result.
METHODS approve FOR MODIFY
IMPORTING keys FOR ACTION ChangeApproval~approve RESULT result.
METHODS reject FOR MODIFY
IMPORTING keys FOR ACTION ChangeApproval~reject RESULT result.
METHODS retrySync FOR MODIFY
IMPORTING keys FOR ACTION ChangeApproval~retrySync RESULT result.
METHODS markExecuting FOR MODIFY
IMPORTING keys FOR ACTION ChangeApproval~markExecuting RESULT result.
METHODS markSynced FOR MODIFY
IMPORTING keys FOR ACTION ChangeApproval~markSynced RESULT result.
METHODS markFailed FOR MODIFY
IMPORTING keys FOR ACTION ChangeApproval~markFailed RESULT result.
METHODS setInitialStatus FOR DETERMINE ON MODIFY
IMPORTING keys FOR ChangeApproval~setInitialStatus.
ENDCLASS.
CLASS lhc_changeapproval IMPLEMENTATION.
METHOD get_instance_features.
READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(approvals)
FAILED failed.
result = VALUE #( FOR approval IN approvals
( %tky = approval-%tky
%features-%action-approve = COND #( WHEN approval-Status = status-pending
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
%features-%action-reject = COND #( WHEN approval-Status = status-pending
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
%features-%action-retrySync = COND #( WHEN approval-Status = status-failed
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
%features-%action-markExecuting = COND #( WHEN approval-Status = status-approved
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
%features-%action-markSynced = COND #( WHEN approval-Status = status-executing
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
%features-%action-markFailed = COND #( WHEN approval-Status = status-executing
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled ) ) ).
ENDMETHOD.
METHOD approve.
" PENDING -> APPROVED; records the decision, never writes SapChangeNumber (PRD RAP-1)
READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(approvals)
FAILED failed.
LOOP AT keys INTO DATA(key).
ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(<approval>).
IF sy-subrc <> 0.
CONTINUE.
ENDIF.
IF <approval>-Status <> status-pending.
APPEND VALUE #( %tky = <approval>-%tky ) TO failed-changeapproval.
APPEND VALUE #( %tky = <approval>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Only PENDING records can be approved (status is { <approval>-Status })| ) )
TO reported-changeapproval.
CONTINUE.
ENDIF.
MODIFY ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
UPDATE FIELDS ( Status DecidedBy DecidedOn )
WITH VALUE #( ( %tky = <approval>-%tky
Status = status-approved
DecidedBy = cl_abap_context_info=>get_user_technical_name( )
DecidedOn = utclong_current( ) ) )
FAILED DATA(update_failed)
REPORTED DATA(update_reported).
ENDLOOP.
READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(updated).
result = VALUE #( FOR row IN updated ( %tky = row-%tky %param = row ) ).
ENDMETHOD.
METHOD reject.
" PENDING -> REJECTED with a mandatory reason (PRD RAP-2); REJECTED is terminal
READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(approvals)
FAILED failed.
LOOP AT keys INTO DATA(key).
ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(<approval>).
IF sy-subrc <> 0.
CONTINUE.
ENDIF.
IF <approval>-Status <> status-pending.
APPEND VALUE #( %tky = <approval>-%tky ) TO failed-changeapproval.
APPEND VALUE #( %tky = <approval>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Only PENDING records can be rejected (status is { <approval>-Status })| ) )
TO reported-changeapproval.
CONTINUE.
ENDIF.
IF key-%param-RejectReason IS INITIAL.
APPEND VALUE #( %tky = <approval>-%tky ) TO failed-changeapproval.
APPEND VALUE #( %tky = <approval>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'A reject reason is required' ) )
TO reported-changeapproval.
CONTINUE.
ENDIF.
MODIFY ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
UPDATE FIELDS ( Status RejectReason DecidedBy DecidedOn )
WITH VALUE #( ( %tky = <approval>-%tky
Status = status-rejected
RejectReason = key-%param-RejectReason
DecidedBy = cl_abap_context_info=>get_user_technical_name( )
DecidedOn = utclong_current( ) ) )
FAILED DATA(update_failed)
REPORTED DATA(update_reported).
ENDLOOP.
READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(updated).
result = VALUE #( FOR row IN updated ( %tky = row-%tky %param = row ) ).
ENDMETHOD.
METHOD retrySync.
" FAILED -> EXECUTING; clears the previous error so the worker re-executes the staged payload (ARN-002 amendment)
READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(approvals)
FAILED failed.
LOOP AT keys INTO DATA(key).
ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(<approval>).
IF sy-subrc <> 0.
CONTINUE.
ENDIF.
IF <approval>-Status <> status-failed.
APPEND VALUE #( %tky = <approval>-%tky ) TO failed-changeapproval.
APPEND VALUE #( %tky = <approval>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Only FAILED records can be retried (status is { <approval>-Status })| ) )
TO reported-changeapproval.
CONTINUE.
ENDIF.
MODIFY ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
UPDATE FIELDS ( Status ErrorMessage )
WITH VALUE #( ( %tky = <approval>-%tky
Status = status-executing
ErrorMessage = '' ) )
FAILED DATA(update_failed)
REPORTED DATA(update_reported).
ENDLOOP.
READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(updated).
result = VALUE #( FOR row IN updated ( %tky = row-%tky %param = row ) ).
ENDMETHOD.
METHOD markExecuting.
" APPROVED -> EXECUTING (worker claims the record; the UI never offers Approve on an executing record, PRD RAP-6)
READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(approvals)
FAILED failed.
LOOP AT keys INTO DATA(key).
ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(<approval>).
IF sy-subrc <> 0.
CONTINUE.
ENDIF.
IF <approval>-Status <> status-approved.
APPEND VALUE #( %tky = <approval>-%tky ) TO failed-changeapproval.
APPEND VALUE #( %tky = <approval>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Only APPROVED records can start executing (status is { <approval>-Status })| ) )
TO reported-changeapproval.
CONTINUE.
ENDIF.
MODIFY ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
UPDATE FIELDS ( Status )
WITH VALUE #( ( %tky = <approval>-%tky
Status = status-executing ) )
FAILED DATA(update_failed)
REPORTED DATA(update_reported).
ENDLOOP.
READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(updated).
result = VALUE #( FOR row IN updated ( %tky = row-%tky %param = row ) ).
ENDMETHOD.
METHOD markSynced.
" EXECUTING -> SYNCED (terminal success); SAP is the ledger, the change number is the receipt (ARN-006)
READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(approvals)
FAILED failed.
LOOP AT keys INTO DATA(key).
ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(<approval>).
IF sy-subrc <> 0.
CONTINUE.
ENDIF.
IF <approval>-Status <> status-executing.
APPEND VALUE #( %tky = <approval>-%tky ) TO failed-changeapproval.
APPEND VALUE #( %tky = <approval>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Only EXECUTING records can be marked synced (status is { <approval>-Status })| ) )
TO reported-changeapproval.
CONTINUE.
ENDIF.
IF key-%param-SapChangeNumber IS INITIAL.
APPEND VALUE #( %tky = <approval>-%tky ) TO failed-changeapproval.
APPEND VALUE #( %tky = <approval>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'The SAP change number is required to mark a record synced' ) )
TO reported-changeapproval.
CONTINUE.
ENDIF.
MODIFY ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
UPDATE FIELDS ( Status SapChangeNumber ErrorMessage )
WITH VALUE #( ( %tky = <approval>-%tky
Status = status-synced
SapChangeNumber = key-%param-SapChangeNumber
ErrorMessage = '' ) )
FAILED DATA(update_failed)
REPORTED DATA(update_reported).
ENDLOOP.
READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(updated).
result = VALUE #( FOR row IN updated ( %tky = row-%tky %param = row ) ).
ENDMETHOD.
METHOD markFailed.
" EXECUTING -> FAILED with the error text; distinct from REJECTED (predecessor defect, ARN-002)
READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(approvals)
FAILED failed.
LOOP AT keys INTO DATA(key).
ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(<approval>).
IF sy-subrc <> 0.
CONTINUE.
ENDIF.
IF <approval>-Status <> status-executing.
APPEND VALUE #( %tky = <approval>-%tky ) TO failed-changeapproval.
APPEND VALUE #( %tky = <approval>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Only EXECUTING records can be marked failed (status is { <approval>-Status })| ) )
TO reported-changeapproval.
CONTINUE.
ENDIF.
IF key-%param-ErrorMessage IS INITIAL.
APPEND VALUE #( %tky = <approval>-%tky ) TO failed-changeapproval.
APPEND VALUE #( %tky = <approval>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'An error message is required to mark a record failed' ) )
TO reported-changeapproval.
CONTINUE.
ENDIF.
MODIFY ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
UPDATE FIELDS ( Status ErrorMessage )
WITH VALUE #( ( %tky = <approval>-%tky
Status = status-failed
ErrorMessage = key-%param-ErrorMessage ) )
FAILED DATA(update_failed)
REPORTED DATA(update_reported).
ENDLOOP.
READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(updated).
result = VALUE #( FOR row IN updated ( %tky = row-%tky %param = row ) ).
ENDMETHOD.
METHOD setInitialStatus.
" Every created record starts PENDING with the requester audit fields (ARN-002)
READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(approvals).
DELETE approvals WHERE Status IS NOT INITIAL.
IF approvals IS INITIAL.
RETURN.
ENDIF.
MODIFY ENTITIES OF zzr_changeapproval000 IN LOCAL MODE
ENTITY ChangeApproval
UPDATE FIELDS ( Status RequestedBy RequestedOn )
WITH VALUE #( FOR approval IN approvals
( %tky = approval-%tky
Status = status-pending
RequestedBy = cl_abap_context_info=>get_user_technical_name( )
RequestedOn = utclong_current( ) ) )
REPORTED DATA(update_reported).
reported = CORRESPONDING #( DEEP update_reported ).
ENDMETHOD.
ENDCLASS.
@@ -1,20 +0,0 @@
// TARGET OBJECT : behavior definition ZZC_CHANGEAPPROVAL000 (UI projection BDEF; generated 2026-09-15)
// abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Behavior%20Definitions/ZZC_CHANGEAPPROVAL000/zzc_changeapproval000.bdef.json
// ACTION : replace entire content, then activate (after ZZR_CHANGEAPPROVAL000 BDEF + ZZC_CHANGEAPPROVAL000 view).
// Then DELETE the generated class ZZBP_C_CHANGEAPPROVAL000 in ADT: a projection BDEF needs no behavior
// pool unless it defines new actions/functions, and this one reuses everything
// [source: abap-docs /abap-docs-cloud/ABENBDL_BDEF_PROJECTION_HEADER, verified 2026-09-15].
// Sources (verified 2026-09-15): use action / use etag; no automatic inheritance - only what is listed is exposed,
// which is how create/update/delete and the worker actions are kept out of the UI service (PRD RAP-2)
// [abap-docs /abap-docs-cloud/ABENBDL_USE_PROJECTION]. Feature control of reused actions is passed on automatically (same source).
projection;
strict ( 2 );
define behavior for ZZC_CHANGEAPPROVAL000 alias ChangeApproval
{
use etag;
use action approve;
use action reject;
use action retrySync;
}
@@ -1,12 +0,0 @@
/* TARGET OBJECT : access control ZZC_CHANGEAPPROVAL000 (generated 2026-09-15)
abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Access%20Controls/ZZC_CHANGEAPPROVAL000/zzc_changeapproval000.dcls.acds
ACTION : replace entire content, then activate (after ZZR_CHANGEAPPROVAL000 DCL).
A transactional query needs its own DCL, inheriting the base view's conditions - there is no implicit inheritance
[source: abap-docs /abap-platform-rap-opensap/week2/unit7#step-5-create-cds-access-control-for-travel-bo-projection-view-and-check-the-result;
/abap-docs-cloud/ABENCDS_DEFINE_VIEW_AS_PROJECTION (access control must be symmetric), verified 2026-09-15]. */
@EndUserText.label: 'Access control for ZZC_CHANGEAPPROVAL000'
@MappingRole: true
define role ZZC_CHANGEAPPROVAL000 {
grant select on ZZC_CHANGEAPPROVAL000
where inheriting conditions from entity ZZR_CHANGEAPPROVAL000;
}
@@ -1,76 +0,0 @@
/* TARGET OBJECT : data definition ZZR_CHANGEAPPROVAL000 (root view entity, generated 2026-09-15)
abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Core%20Data%20Services/Data%20Definitions/ZZR_CHANGEAPPROVAL000/zzr_changeapproval000.ddls.acds
ACTION : replace entire content, then activate (table ZZCHNGPPROVAL000 must be active first).
Notes: StatusText / StatusCriticality / IsApprovable / IsRetriable are derived from Status with CASE
expressions here, because a transactional_query projection list may only contain elements of the
projected entity, path expressions, associations and virtual elements - no expressions
[source: abap-docs /abap-docs-cloud/ABENCDS_DEFINE_VIEW_AS_PROJECTION, verified 2026-09-15].
CASE syntax [source: abap-docs /abap-docs-cloud/ABENCDS_CASE_EXPRESSION_V2, verified 2026-09-15];
cast of a literal to a data element (abap_boolean, released) [source: abap-docs /abap-docs-cloud/ABENCDS_CAST_EXPRESSION_V2, verified 2026-09-15].
@ObjectModel.text.element [source: abap-docs /abap-fiori-showcase/06_object_page_content#displaying-text-instead-of-id, verified 2026-09-15].
@Semantics admin-field annotations [source: abap-docs /abap-platform-rap-opensap/week2/unit3#step-1-create-the-interface-cds-view-for-the-travel-entity, verified 2026-09-15].
Criticality values 2/2/5/3/1/1 per PRD RAP-1. */
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Arena change approval'
define root view entity ZZR_CHANGEAPPROVAL000
as select from zzchngpproval000
{
key id as ID,
item_number as ItemNumber,
item_name as ItemName,
change_type as ChangeType,
arena_eco_id as ArenaEcoId,
@ObjectModel.text.element: ['StatusText']
status as Status,
case status
when 'PENDING' then 'Pending approval'
when 'APPROVED' then 'Approved - awaiting execution'
when 'EXECUTING' then 'Executing in SAP'
when 'SYNCED' then 'Synced to SAP'
when 'REJECTED' then 'Rejected'
when 'FAILED' then 'Execution failed'
else status
end as StatusText,
case status
when 'PENDING' then 2
when 'APPROVED' then 2
when 'EXECUTING' then 5
when 'SYNCED' then 3
when 'REJECTED' then 1
when 'FAILED' then 1
else 0
end as StatusCriticality,
requested_by as RequestedBy,
requested_on as RequestedOn,
decided_by as DecidedBy,
decided_on as DecidedOn,
sap_change_number as SapChangeNumber,
error_message as ErrorMessage,
reject_reason as RejectReason,
sap_preview as SapPreview,
case status
when 'PENDING' then cast( 'X' as abap_boolean )
else cast( ' ' as abap_boolean )
end as IsApprovable,
case status
when 'FAILED' then cast( 'X' as abap_boolean )
else cast( ' ' as abap_boolean )
end as IsRetriable,
@Semantics.user.createdBy: true
created_by as CreatedBy,
@Semantics.systemDateTime.createdAt: true
created_at as CreatedAt,
@Semantics.user.lastChangedBy: true
last_changed_by as LastChangedBy,
@Semantics.systemDateTime.lastChangedAt: true
last_changed_at as LastChangedAt,
@Semantics.systemDateTime.localInstanceLastChangedAt: true
local_last_changed_at as LocalLastChangedAt
}
@@ -1,13 +0,0 @@
/* TARGET OBJECT : service definition ZZUI_ARENA_CHANGE_APPRVL000_O4 (generated 2026-09-15)
abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Business%20Services/Service%20Definitions/ZZUI_ARENA_CHANGE_APPRVL000_O4/zzui_arena_change_apprvl000_o4.srvd.acds
ACTION : replace entire content, then activate (after ZZC_CHANGEAPPROVAL000 view + BDEF).
expose ... as <alias>: the alias becomes the entity set (ChangeApproval) and the entity type (ChangeApproval_Type)
[source: abap-docs /abap-fiori-showcase/06_object_page_content (Service Definition), verified 2026-09-15].
provider contracts odata_v4_ui switches on the UI-binding syntax checks and must match the binding type
[source: abap-docs /abap-docs-cloud/ABENSRVD_PROVIDER_CONTRACT, verified 2026-09-15]. */
@EndUserText.label: 'Arena change approval - UI service'
define service ZZUI_ARENA_CHANGE_APPRVL000_O4
provider contracts odata_v4_ui
{
expose ZZC_CHANGEAPPROVAL000 as ChangeApproval;
}
@@ -0,0 +1,593 @@
<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" xmlns="http://docs.oasis-open.org/odata/ns/edm" Version="4.0">
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_AGGREGATION',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="Org.OData.Aggregation.V1" Alias="SAP__aggregation"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_ANALYTICS',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.Analytics.v1" Alias="SAP__analytics"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_AUTHORIZATION',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="Org.OData.Authorization.V1" Alias="SAP__authorization"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_CAPABILITIES',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="Org.OData.Capabilities.V1" Alias="SAP__capabilities"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_CODELIST',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.CodeList.v1" Alias="SAP__CodeList"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_COMMON',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.Common.v1" Alias="SAP__common"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_COMMUNICATION',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.Communication.v1" Alias="SAP__communication"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_CORE',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="Org.OData.Core.V1" Alias="SAP__core"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_HIERARCHY',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.Hierarchy.v1" Alias="SAP__hierarchy"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_HTML5',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.HTML5.v1" Alias="SAP__HTML5"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_MEASURES',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="Org.OData.Measures.V1" Alias="SAP__measures"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_ODM',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.ODM.v1" Alias="SAP__ODM"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_PDF',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.PDF.v1" Alias="SAP__PDF"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_PERSONALDATA',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.PersonalData.v1" Alias="SAP__personalData"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_SESSION',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.Session.v1" Alias="SAP__session"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_SUPPORT',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.Support.v1" Alias="SAP__support"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_UI',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="com.sap.vocabularies.UI.v1" Alias="SAP__UI"/>
</edmx:Reference>
<edmx:Reference Uri="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Vocabularies(TechnicalName='%2FIWBEP%2FVOC_VALIDATION',Version='0001',SAP__Origin='LOCAL')/$value">
<edmx:Include Namespace="Org.OData.Validation.V1" Alias="SAP__validation"/>
</edmx:Reference>
<edmx:DataServices>
<Schema Namespace="com.sap.gateway.srvd.zui_arena_change_approval.v0001" Alias="SAP__self">
<Annotation Term="SAP__core.SchemaVersion" String="1.0.0"/>
<EntityType Name="ChangeApprovalType">
<Key>
<PropertyRef Name="ID"/>
</Key>
<Property Name="ID" Type="Edm.Guid" Nullable="false"/>
<Property Name="ItemNumber" Type="Edm.String" Nullable="false" MaxLength="18"/>
<Property Name="ItemName" Type="Edm.String" Nullable="false" MaxLength="40"/>
<Property Name="ChangeType" Type="Edm.String" Nullable="false" MaxLength="15"/>
<Property Name="ArenaEcoId" Type="Edm.String" Nullable="false" MaxLength="20"/>
<Property Name="Status" Type="Edm.String" Nullable="false" MaxLength="10"/>
<Property Name="StatusText" Type="Edm.String" Nullable="false" MaxLength="29"/>
<Property Name="StatusCriticality" Type="Edm.Byte" Nullable="false"/>
<Property Name="RequestedBy" Type="Edm.String" Nullable="false" MaxLength="12"/>
<Property Name="RequestedOn" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="DecidedBy" Type="Edm.String" Nullable="false" MaxLength="12"/>
<Property Name="DecidedOn" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="SapChangeNumber" Type="Edm.String" Nullable="false" MaxLength="12"/>
<Property Name="ErrorMessage" Type="Edm.String" Nullable="false" MaxLength="255"/>
<Property Name="RejectReason" Type="Edm.String" Nullable="false" MaxLength="255"/>
<Property Name="SapPreview" Type="Edm.String" Nullable="false"/>
<Property Name="IsApprovable" Type="Edm.Boolean" Nullable="false"/>
<Property Name="IsRetriable" Type="Edm.Boolean" Nullable="false"/>
<Property Name="LocalLastChangedAt" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="__OperationControl" Type="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalOperationControl"/>
<Property Name="SAP__Messages" Type="Collection(com.sap.gateway.srvd.zui_arena_change_approval.v0001.SAP__Message)" Nullable="false"/>
</EntityType>
<ComplexType Name="ChangeApprovalOperationControl">
<Property Name="approve" Type="Edm.Boolean" Nullable="false"/>
<Property Name="reject" Type="Edm.Boolean" Nullable="false"/>
<Property Name="retrySync" Type="Edm.Boolean" Nullable="false"/>
</ComplexType>
<ComplexType Name="SAP__Message">
<Property Name="code" Type="Edm.String" Nullable="false"/>
<Property Name="message" Type="Edm.String" Nullable="false"/>
<Property Name="target" Type="Edm.String"/>
<Property Name="additionalTargets" Type="Collection(Edm.String)" Nullable="false"/>
<Property Name="transition" Type="Edm.Boolean" Nullable="false"/>
<Property Name="numericSeverity" Type="Edm.Byte" Nullable="false"/>
<Property Name="longtextUrl" Type="Edm.String"/>
</ComplexType>
<Action Name="retrySync" EntitySetPath="_it" IsBound="true">
<Parameter Name="_it" Type="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType" Nullable="false"/>
<ReturnType Type="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType" Nullable="false"/>
</Action>
<Action Name="approve" EntitySetPath="_it" IsBound="true">
<Parameter Name="_it" Type="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType" Nullable="false"/>
<ReturnType Type="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType" Nullable="false"/>
</Action>
<Action Name="reject" EntitySetPath="_it" IsBound="true">
<Parameter Name="_it" Type="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType" Nullable="false"/>
<Parameter Name="RejectReason" Type="Edm.String" Nullable="false" MaxLength="255"/>
<ReturnType Type="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType" Nullable="false"/>
</Action>
<EntityContainer Name="Container">
<EntitySet Name="ChangeApproval" EntityType="com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType"/>
</EntityContainer>
<Annotations Target="SAP__self.ChangeApprovalType/ID">
<Annotation Term="SAP__core.Computed"/>
<Annotation Term="SAP__UI.Hidden"/>
<Annotation Term="SAP__common.Label" String="UUID"/>
<Annotation Term="SAP__common.QuickInfo" String="16 Byte UUID in 16 Bytes (Raw Format)"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/ItemNumber">
<Annotation Term="SAP__common.Label" String="Item number"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/ItemName">
<Annotation Term="SAP__common.Label" String="Item name"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/ChangeType">
<Annotation Term="SAP__common.Label" String="Change type"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/ArenaEcoId">
<Annotation Term="SAP__common.Label" String="Arena ECO"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/Status">
<Annotation Term="SAP__common.Label" String="Status"/>
<Annotation Term="SAP__common.Text" Path="StatusText">
<Annotation Term="SAP__UI.TextArrangement" EnumMember="SAP__UI.TextArrangementType/TextOnly"/>
</Annotation>
<Annotation Term="SAP__core.Computed"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/StatusText">
<Annotation Term="SAP__core.Computed"/>
<Annotation Term="SAP__UI.Hidden"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/StatusCriticality">
<Annotation Term="SAP__core.Computed"/>
<Annotation Term="SAP__UI.Hidden"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/RequestedBy">
<Annotation Term="SAP__common.Label" String="Requested by"/>
<Annotation Term="SAP__core.Computed"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/RequestedOn">
<Annotation Term="SAP__common.Label" String="Requested on"/>
<Annotation Term="SAP__core.Computed"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/DecidedBy">
<Annotation Term="SAP__common.Label" String="Decided by"/>
<Annotation Term="SAP__core.Computed"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/DecidedOn">
<Annotation Term="SAP__common.Label" String="Decided on"/>
<Annotation Term="SAP__core.Computed"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/SapChangeNumber">
<Annotation Term="SAP__common.Label" String="SAP change number"/>
<Annotation Term="SAP__core.Computed"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/ErrorMessage">
<Annotation Term="SAP__common.Label" String="Error message"/>
<Annotation Term="SAP__core.Computed"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/RejectReason">
<Annotation Term="SAP__common.Label" String="Reject reason"/>
<Annotation Term="SAP__core.Computed"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/SapPreview">
<Annotation Term="SAP__common.Label" String="Staged payload"/>
<Annotation Term="SAP__UI.MultiLineText"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/IsApprovable">
<Annotation Term="SAP__core.Computed"/>
<Annotation Term="SAP__UI.Hidden"/>
<Annotation Term="SAP__common.Label" String="Truth Value"/>
<Annotation Term="SAP__common.QuickInfo" String="Truth Value: True/False"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/IsRetriable">
<Annotation Term="SAP__core.Computed"/>
<Annotation Term="SAP__UI.Hidden"/>
<Annotation Term="SAP__common.Label" String="Truth Value"/>
<Annotation Term="SAP__common.QuickInfo" String="Truth Value: True/False"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/LocalLastChangedAt">
<Annotation Term="SAP__core.Computed"/>
<Annotation Term="SAP__common.Label" String="Changed On"/>
<Annotation Term="SAP__common.QuickInfo" String="Local Instance Last Change Date Time"/>
<Annotation Term="SAP__common.DocumentationRef" String="urn:sap-com:documentation:key?=type=DE&amp;id=ABP_LOCINST_LASTCHANGE_TSTMPL"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType/__OperationControl">
<Annotation Term="SAP__core.Computed"/>
<Annotation Term="SAP__UI.HiddenFilter"/>
<Annotation Term="SAP__UI.Hidden"/>
</Annotations>
<Annotations Target="SAP__self.retrySync(SAP__self.ChangeApprovalType)">
<Annotation Term="SAP__core.OperationAvailable" Path="_it/__OperationControl/retrySync"/>
</Annotations>
<Annotations Target="SAP__self.approve(SAP__self.ChangeApprovalType)">
<Annotation Term="SAP__core.OperationAvailable" Path="_it/__OperationControl/approve"/>
</Annotations>
<Annotations Target="SAP__self.reject(SAP__self.ChangeApprovalType)/RejectReason">
<Annotation Term="SAP__common.Label" String="Reject reason"/>
</Annotations>
<Annotations Target="SAP__self.reject(SAP__self.ChangeApprovalType)">
<Annotation Term="SAP__core.OperationAvailable" Path="_it/__OperationControl/reject"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalType">
<Annotation Term="SAP__common.Label" String="Arena change approval - UI projection"/>
<Annotation Term="SAP__common.SemanticKey">
<Collection>
<PropertyPath>ArenaEcoId</PropertyPath>
<PropertyPath>ChangeType</PropertyPath>
<PropertyPath>ItemNumber</PropertyPath>
</Collection>
</Annotation>
<Annotation Term="SAP__UI.DataPoint" Qualifier="StatusPoint">
<Record>
<PropertyValue Property="Value" Path="Status"/>
<PropertyValue Property="Title" String="Status"/>
<PropertyValue Property="Criticality" Path="StatusCriticality"/>
</Record>
</Annotation>
<Annotation Term="SAP__UI.HeaderFacets">
<Collection>
<Record Type="SAP__UI.ReferenceFacet">
<PropertyValue Property="Label" String="Reference numbers"/>
<PropertyValue Property="ID" String="Keys"/>
<PropertyValue Property="Target" AnnotationPath="@SAP__UI.FieldGroup#Keys"/>
</Record>
<Record Type="SAP__UI.ReferenceFacet">
<PropertyValue Property="ID" String="StatusPoint"/>
<PropertyValue Property="Target" AnnotationPath="@SAP__UI.DataPoint#StatusPoint"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="SAP__UI.Facets">
<Collection>
<Record Type="SAP__UI.ReferenceFacet">
<PropertyValue Property="Label" String="Change"/>
<PropertyValue Property="ID" String="Change"/>
<PropertyValue Property="Target" AnnotationPath="@SAP__UI.FieldGroup#Change"/>
</Record>
<Record Type="SAP__UI.ReferenceFacet">
<PropertyValue Property="Label" String="Staged payload"/>
<PropertyValue Property="ID" String="Payload"/>
<PropertyValue Property="Target" AnnotationPath="@SAP__UI.FieldGroup#Payload"/>
</Record>
<Record Type="SAP__UI.ReferenceFacet">
<PropertyValue Property="Label" String="Approval"/>
<PropertyValue Property="ID" String="Approval"/>
<PropertyValue Property="Target" AnnotationPath="@SAP__UI.FieldGroup#Approval"/>
</Record>
<Record Type="SAP__UI.ReferenceFacet">
<PropertyValue Property="Label" String="Execution"/>
<PropertyValue Property="ID" String="Execution"/>
<PropertyValue Property="Target" AnnotationPath="@SAP__UI.FieldGroup#Execution"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="SAP__UI.FieldGroup" Qualifier="Approval">
<Record>
<PropertyValue Property="Data">
<Collection>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Criticality" Path="StatusCriticality"/>
<PropertyValue Property="Value" Path="Status"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="RequestedBy"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="RequestedOn"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="DecidedBy"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="DecidedOn"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="RejectReason"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__UI.FieldGroup" Qualifier="Change">
<Record>
<PropertyValue Property="Data">
<Collection>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ItemNumber"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ItemName"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ChangeType"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ArenaEcoId"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__UI.FieldGroup" Qualifier="Execution">
<Record>
<PropertyValue Property="Data">
<Collection>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="SapChangeNumber"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ErrorMessage"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__UI.FieldGroup" Qualifier="Keys">
<Record>
<PropertyValue Property="Data">
<Collection>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ArenaEcoId"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="SapChangeNumber"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__UI.FieldGroup" Qualifier="Payload">
<Record>
<PropertyValue Property="Data">
<Collection>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="SapPreview"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__UI.HeaderInfo">
<Record>
<PropertyValue Property="TypeName" String="Change Approval"/>
<PropertyValue Property="TypeNamePlural" String="Change Approvals"/>
<PropertyValue Property="TypeImageUrl" String="sap-icon://approvals"/>
<PropertyValue Property="Title">
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ItemNumber"/>
</Record>
</PropertyValue>
<PropertyValue Property="Description">
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ItemName"/>
</Record>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__UI.Identification">
<Collection>
<Record Type="SAP__UI.DataFieldForAction">
<PropertyValue Property="Label" String="Approve"/>
<PropertyValue Property="Action" String="com.sap.gateway.srvd.zui_arena_change_approval.v0001.approve(com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType)"/>
<PropertyValue Property="InvocationGrouping" EnumMember="SAP__UI.OperationGroupingType/Isolated"/>
</Record>
<Record Type="SAP__UI.DataFieldForAction">
<PropertyValue Property="Label" String="Reject"/>
<PropertyValue Property="Action" String="com.sap.gateway.srvd.zui_arena_change_approval.v0001.reject(com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType)"/>
<PropertyValue Property="InvocationGrouping" EnumMember="SAP__UI.OperationGroupingType/Isolated"/>
</Record>
<Record Type="SAP__UI.DataFieldForAction">
<PropertyValue Property="Label" String="Retry sync"/>
<PropertyValue Property="Action" String="com.sap.gateway.srvd.zui_arena_change_approval.v0001.retrySync(com.sap.gateway.srvd.zui_arena_change_approval.v0001.ChangeApprovalType)"/>
<PropertyValue Property="InvocationGrouping" EnumMember="SAP__UI.OperationGroupingType/Isolated"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="SAP__UI.LineItem">
<Collection>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ItemNumber"/>
<Annotation Term="SAP__UI.Importance" EnumMember="SAP__UI.ImportanceType/High"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ItemName"/>
<Annotation Term="SAP__UI.Importance" EnumMember="SAP__UI.ImportanceType/Low"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ChangeType"/>
<Annotation Term="SAP__UI.Importance" EnumMember="SAP__UI.ImportanceType/High"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Criticality" Path="StatusCriticality"/>
<PropertyValue Property="Value" Path="Status"/>
<Annotation Term="SAP__UI.Importance" EnumMember="SAP__UI.ImportanceType/High"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="RequestedOn"/>
<Annotation Term="SAP__UI.Importance" EnumMember="SAP__UI.ImportanceType/Medium"/>
</Record>
<Record Type="SAP__UI.DataField">
<PropertyValue Property="Value" Path="ArenaEcoId"/>
<Annotation Term="SAP__UI.Importance" EnumMember="SAP__UI.ImportanceType/Medium"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="SAP__UI.PresentationVariant">
<Record>
<PropertyValue Property="SortOrder">
<Collection>
<Record Type="SAP__common.SortOrderType">
<PropertyValue Property="Property" PropertyPath="Status"/>
<PropertyValue Property="Descending" Bool="false"/>
</Record>
<Record Type="SAP__common.SortOrderType">
<PropertyValue Property="Property" PropertyPath="RequestedOn"/>
<PropertyValue Property="Descending" Bool="true"/>
</Record>
</Collection>
</PropertyValue>
<PropertyValue Property="Visualizations">
<Collection>
<AnnotationPath>@SAP__UI.LineItem</AnnotationPath>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__UI.SelectionFields">
<Collection>
<PropertyPath>ItemNumber</PropertyPath>
<PropertyPath>Status</PropertyPath>
<PropertyPath>ChangeType</PropertyPath>
<PropertyPath>ArenaEcoId</PropertyPath>
</Collection>
</Annotation>
<Annotation Term="SAP__common.Messages" Path="SAP__Messages"/>
</Annotations>
<Annotations Target="SAP__self.Container/ChangeApproval">
<Annotation Term="SAP__capabilities.SearchRestrictions">
<Record>
<PropertyValue Property="Searchable" Bool="false"/>
</Record>
</Annotation>
<Annotation Term="SAP__capabilities.FilterRestrictions">
<Record>
<PropertyValue Property="Filterable" Bool="true"/>
<PropertyValue Property="FilterExpressionRestrictions">
<Collection>
<Record>
<PropertyValue Property="Property" PropertyPath="SapPreview"/>
<PropertyValue Property="AllowedExpressions" String="SearchExpression"/>
</Record>
</Collection>
</PropertyValue>
<PropertyValue Property="NonFilterableProperties">
<Collection>
<PropertyPath>__OperationControl</PropertyPath>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__capabilities.SortRestrictions">
<Record>
<PropertyValue Property="NonSortableProperties">
<Collection>
<PropertyPath>SapPreview</PropertyPath>
<PropertyPath>__OperationControl</PropertyPath>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__capabilities.InsertRestrictions">
<Record>
<PropertyValue Property="Insertable" Bool="false"/>
</Record>
</Annotation>
<Annotation Term="SAP__capabilities.DeleteRestrictions">
<Record>
<PropertyValue Property="Deletable" Bool="false"/>
</Record>
</Annotation>
<Annotation Term="SAP__capabilities.UpdateRestrictions">
<Record>
<PropertyValue Property="Updatable" Bool="false"/>
<PropertyValue Property="QueryOptions">
<Record>
<PropertyValue Property="SelectSupported" Bool="true"/>
</Record>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__core.OptimisticConcurrency">
<Collection>
<PropertyPath>LocalLastChangedAt</PropertyPath>
</Collection>
</Annotation>
</Annotations>
<Annotations Target="SAP__self.Container">
<Annotation Term="SAP__common.ApplyMultiUnitBehaviorForSortingAndFiltering" Bool="true"/>
<Annotation Term="SAP__capabilities.FilterFunctions">
<Collection>
<String>eq</String>
<String>ne</String>
<String>gt</String>
<String>ge</String>
<String>lt</String>
<String>le</String>
<String>and</String>
<String>or</String>
<String>contains</String>
<String>startswith</String>
<String>endswith</String>
<String>any</String>
<String>all</String>
</Collection>
</Annotation>
<Annotation Term="SAP__support.TechnicalInfoLinks">
<Record>
<PropertyValue Property="Url" String="../../../../default/iwbep/common/0001/$metadata"/>
<PropertyValue Property="FunctionImport" String="GetTechnicalInfoLinks"/>
</Record>
</Annotation>
<Annotation Term="SAP__capabilities.SupportedFormats">
<Collection>
<String>application/json</String>
<String>application/pdf</String>
</Collection>
</Annotation>
<Annotation Term="SAP__PDF.Features">
<Record>
<PropertyValue Property="DocumentDescriptionReference" String="../../../../default/iwbep/common/0001/$metadata"/>
<PropertyValue Property="DocumentDescriptionCollection" String="MyDocumentDescriptions"/>
<PropertyValue Property="ArchiveFormat" Bool="true"/>
<PropertyValue Property="Border" Bool="true"/>
<PropertyValue Property="CoverPage" Bool="true"/>
<PropertyValue Property="FitToPage" Bool="true"/>
<PropertyValue Property="FontName" Bool="true"/>
<PropertyValue Property="FontSize" Bool="true"/>
<PropertyValue Property="HeaderFooter" Bool="true"/>
<PropertyValue Property="IANATimezoneFormat" Bool="true"/>
<PropertyValue Property="Margin" Bool="true"/>
<PropertyValue Property="Padding" Bool="true"/>
<PropertyValue Property="ResultSizeDefault" Int="20000"/>
<PropertyValue Property="ResultSizeMaximum" Int="20000"/>
<PropertyValue Property="Signature" Bool="true"/>
<PropertyValue Property="TextDirectionLayout" Bool="true"/>
<PropertyValue Property="Treeview" Bool="true"/>
<PropertyValue Property="UploadToFileShare" Bool="true"/>
</Record>
</Annotation>
<Annotation Term="SAP__capabilities.BatchSupport">
<Record>
<PropertyValue Property="SupportedFormats">
<Collection>
<String>application/json</String>
<String>multipart/mixed</String>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="SAP__capabilities.KeyAsSegmentSupported"/>
<Annotation Term="SAP__capabilities.AsynchronousRequestsSupported"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalOperationControl/approve">
<Annotation Term="SAP__common.Label" String="Dyn. Action Control"/>
<Annotation Term="SAP__common.Heading" String="Dynamic Action Control"/>
<Annotation Term="SAP__common.QuickInfo" String="Dynamic Action Property"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalOperationControl/reject">
<Annotation Term="SAP__common.Label" String="Dyn. Action Control"/>
<Annotation Term="SAP__common.Heading" String="Dynamic Action Control"/>
<Annotation Term="SAP__common.QuickInfo" String="Dynamic Action Property"/>
</Annotations>
<Annotations Target="SAP__self.ChangeApprovalOperationControl/retrySync">
<Annotation Term="SAP__common.Label" String="Dyn. Action Control"/>
<Annotation Term="SAP__common.Heading" String="Dynamic Action Control"/>
<Annotation Term="SAP__common.QuickInfo" String="Dynamic Action Property"/>
</Annotations>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
@@ -1,653 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-5WLCZXC');</script>
<!-- End Google Tag Manager --> <meta name="author" content="Denis Roy"/>
<meta name="keywords" content="eclipse,project,plug-ins,plugins,java,ide,swt,refactoring,free java ide,tools,platform,open source,development environment,development,ide"/>
<link rel="shortcut icon" href="/eclipse.org-common/themes/solstice/public/images/favicon.ico"/>
<title>Eclipse downloads - Select a mirror | The Eclipse Foundation</title>
<link rel="preconnect stylesheet" href="https://www.eclipse.org/public/css/neptune.css"/>
<meta name="description" content="The Eclipse Foundation - home to a global community, the Eclipse IDE, Jakarta EE and over 415 open source projects, including runtimes, tools and frameworks."/>
<meta property="og:description" content="The Eclipse Foundation - home to a global community, the Eclipse IDE, Jakarta EE and over 415 open source projects, including runtimes, tools and frameworks."/>
<meta property="og:image" content="https://www.eclipse.org/images/og-image.jpg"/>
<meta property="og:title" content="Eclipse downloads - Select a mirror | The Eclipse Foundation"/>
<meta property="og:image:width" content="1200"/>
<meta property="og:image:height" content="630"/>
<meta name="twitter:site" content="@EclipseFdn"/>
<meta name="twitter:card" content="summary"/>
<meta name="twitter:title" content="Eclipse downloads - Select a mirror | The Eclipse Foundation"/>
<meta name="twitter:url" content="https://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip"/>
<meta name="twitter:description" content="The Eclipse Foundation - home to a global community, the Eclipse IDE, Jakarta EE and over 415 open source projects, including runtimes, tools and frameworks."/>
<meta name="twitter:image" content="https://www.eclipse.org/eclipse.org-common/themes/solstice/public/images/logo/eclipse-foundation-400x400.png"/>
<link href="/downloads/assets/public/stylesheets/mirror.min.css" media="screen" rel="stylesheet" type="text/css"/><link href="https://fonts.googleapis.com/css2?family=Libre+Franklin:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="preconnect stylesheet" type="text/css"/>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@eclipsefdn/solstice-components@0.9.0/themes/compatibility/ef-light.css"
integrity="sha384-OwhjLHnTIrR0xdp4DimHPw/OeD7m47zoau2qQEXbBR8yjwN1QI5zkxHnZUZ1CW7m"
crossorigin="anonymous">
<script type="module"
src="https://cdn.jsdelivr.net/npm/@eclipsefdn/solstice-components@0.9.0/dist-cdn/solstice-components.loader.js"
integrity="sha384-UcWSQMj1c1b1VRiaHycuVGCSJ7ltwyF88VcYGthnJauU0cyXHJXhPWBuMA5vHM/a"
crossorigin="anonymous"></script>
<script> var eclipse_org_common = {"settings":{"cookies_class":{"name":"eclipse_settings","enabled":1}}}</script> </head>
<body class="hidden-cfa-button" id="body_solstice">
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-5WLCZXC"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) --> <a class="sr-only" href="#content">Skip to main content</a>
<div class="eclipsefdn-featured-story-banner" data-publish-target="eclipse_org"></div>
<header class="header-wrapper" id="header">
<div class="navigation-bar dark">
<div class="container">
<div class="navigation-bar-inner">
<div class="navigation-bar-left">
<div class="wrapper-logo-default"><a href="https://www.eclipse.org/"><img class="logo-eclipse-default img-responsive navigation-bar-brand" alt="Eclipse.org logo" width="120" src="//www.eclipse.org/eclipse.org-common/themes/solstice/public/images/logo/eclipse-foundation-white-orange.svg"/></a></div> </div>
<nav class="navigation-bar-nav hidden-xs hidden-sm">
<ul>
<li>
<button class="btn btn-link" type="button" data-target="join-us-menu" aria-expanded="false">
Join us
</button>
</li> <li>
<button class="btn btn-link" type="button" data-target="what-we-do-menu" aria-expanded="false">
What we do
</button>
</li> <li>
<button class="btn btn-link" type="button" data-target="resources-menu" aria-expanded="false">
Resources
</button>
</li> <li>
<button class="btn btn-link" type="button" data-target="about-us-menu" aria-expanded="false">
About us
</button>
</li> </ul>
</nav>
<div class="navigation-bar-right">
<div class="navigation-bar-controls hidden-xs hidden-sm">
<ul>
<li class="dropdown">
<a class="dropdown-toggle link-unstyled focus-highlight" tabindex="0" id="search-dropdown-toggle" data-toggle="dropdown" href="#" aria-label="Search">
<i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
</a>
<form class="search-bar dropdown-menu dropdown-menu-right" aria-labelledby="search-dropdown-toggle" action="https://www.eclipse.org/home/search/" method="get">
<input class="search-bar-input" name="q" placeholder="Search">
<button class="btn btn-link" type="submit">
<i class="fa-solid fa-arrow-right"></i>
</button>
</form>
</li>
<li class="dropdown">
<a class="dropdown-toggle focus-highlight" id="user-menu" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" tabindex="0">
<i class="fa-regular fa-user" aria-hidden="true"></i>
</a>
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="user-menu">
<li>
<a href="https://accounts.eclipse.org/user">
<i class="fa-solid fa-user margin-right-10" aria-hidden="true"></i>
View my account
</a>
</li>
<li>
<a href="https://accounts.eclipse.org/user/edit">
<i class="fa-solid fa-edit margin-right-10" aria-hidden="true"></i>
Edit my account
</a>
</li>
<li>
<a role="link" class="toolbar-manage-cookies" href="#" tabindex="0">
<i class="fa-solid fa-wrench margin-right-10" aria-hidden=""></i>
Manage cookies
</a>
</li>
</ul>
</li>
</ul>
</div>
<div class="navigation-bar-ctas hidden-xs hidden-sm">
<ul>
<li>
</li>
</ul>
</div>
<a class="mobile-menu-toggle visible-xs visible-sm" aria-expanded="false"></a>
</div>
</div>
</div>
</div>
<div class="mobile-menu hidden">
<nav class="mobile-menu-nav"><ul> <li>
<a role="button" href="#mobile-menu-join-us" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-join-us">
Join us
<i class="fa-solid fa-chevron-down" aria-hidden="true"></i>
</a>
<ul class="collapse" id="mobile-menu-join-us">
<li>
<a role="button" href="#mobile-menu-membership" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-membership">Membership <i class="fa-solid fa-chevron-down" aria-hidden="true"></i></a>
<ul class="collapse" id="mobile-menu-membership">
<li><a href="/membership/" target="">Become a member</a></li><li><a href="/membership/#tab-fees" target="">Review membership fees</a></li><li><a href="https://membership.eclipse.org/portal" target="">Access member portal</a></li>
</ul>
</li> <li>
<a role="button" href="#mobile-menu-sponsorship" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-sponsorship">Sponsorship <i class="fa-solid fa-chevron-down" aria-hidden="true"></i></a>
<ul class="collapse" id="mobile-menu-sponsorship">
<li><a href="/sponsor/" target="">Become a sponsor</a></li><li><a href="/org/our-sponsors/" target="">Sponsor directory</a></li>
</ul>
</li> <li>
<a role="button" href="#mobile-menu-contribute" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-contribute">Contribute <i class="fa-solid fa-chevron-down" aria-hidden="true"></i></a>
<ul class="collapse" id="mobile-menu-contribute">
<li><a href="/contribute/" target="">How to contribute</a></li><li><a href="/projects/bring-your-project/" target="">Bring us your project</a></li><li><a href="/projects/training/" target="">Committer training</a></li>
</ul>
</li> <li>
<a role="button" href="#mobile-menu-collaborate-with-us" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-collaborate-with-us">Collaborate with us <i class="fa-solid fa-chevron-down" aria-hidden="true"></i></a>
<ul class="collapse" id="mobile-menu-collaborate-with-us">
<li><a href="/org/working-groups/explore/" target="">Join an existing industry collaboration</a></li><li><a href="/collaborations/#start-collaboration" target="">Start a new collaboration</a></li>
</ul>
</li>
</ul>
</li> <li>
<a role="button" href="#mobile-menu-what-we-do" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-what-we-do">
What we do
<i class="fa-solid fa-chevron-down" aria-hidden="true"></i>
</a>
<ul class="collapse" id="mobile-menu-what-we-do">
<li>
<a role="button" href="#mobile-menu-projects" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-projects">Projects <i class="fa-solid fa-chevron-down" aria-hidden="true"></i></a>
<ul class="collapse" id="mobile-menu-projects">
<li><a href="/projects/" target="">Explore projects</a></li><li><a href="/specifications/" target="">View specifications</a></li>
</ul>
</li> <li>
<a role="button" href="#mobile-menu-industry-collaborations" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-industry-collaborations">Industry collaborations <i class="fa-solid fa-chevron-down" aria-hidden="true"></i></a>
<ul class="collapse" id="mobile-menu-industry-collaborations">
<li><a href="/org/working-groups/explore/" target="">Explore collaborations</a></li><li><a href="/org/working-groups/about/" target="">Join a working group</a></li><li><a href="/collaborations/interest-groups/" target="">Join an interest group</a></li><li><a href="/org/value/" target="">Read success stories</a></li>
</ul>
</li> <li>
<a role="button" href="#mobile-menu-core-services" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-core-services">Core services <i class="fa-solid fa-chevron-down" aria-hidden="true"></i></a>
<ul class="collapse" id="mobile-menu-core-services">
<li><a href="/org/services/" target="">Explore core services</a></li><li><a href="/security/" target="">Ensure project security</a></li>
</ul>
</li> <li>
<a role="button" href="#mobile-menu-key-initiatives" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-key-initiatives">Key initiatives <i class="fa-solid fa-chevron-down" aria-hidden="true"></i></a>
<ul class="collapse" id="mobile-menu-key-initiatives">
<li><a href="/topics/edge-and-iot/" target="">Embedded &amp; IoT</a></li><li><a href="/topics/cloud-native/" target="">Enterprise Java</a></li><li><a href="https://dataspaces.eclipse.org/" target="">Data sovereignty</a></li><li><a href="/security/" target="">Security &amp; privacy</a></li><li><a href="/topics/ide/" target="">Developer tools &amp; IDEs</a></li><li><a href="https://aieclipse.org/" target="">AI &amp; emerging technologies <i class="fa-solid fa-arrow-up-right-from-square external-icon" aria-hidden="true"></i></a></li><li><a href="/topics/automotive-and-mobility/" target="">Automotive &amp; mobility</a></li><li><a href="https://orcwg.org/" target="">Policy &amp; compliance <i class="fa-solid fa-arrow-up-right-from-square external-icon" aria-hidden="true"></i></a></li>
</ul>
</li> <li>
<a role="button" href="#mobile-menu-strategic-services" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-strategic-services">Strategic services <i class="fa-solid fa-chevron-down" aria-hidden="true"></i></a>
<ul class="collapse" id="mobile-menu-strategic-services">
<li><a href="/research/" target="">Transform research into open source</a></li><li><a href="/os4biz/services/" target="">Engage professional services</a></li><li><a href="/os4biz/ospo/" target="">Build your OSPO</a></li>
</ul>
</li>
</ul>
</li> <li>
<a role="button" href="#mobile-menu-resources" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-resources">
Resources
<i class="fa-solid fa-chevron-down" aria-hidden="true"></i>
</a>
<ul class="collapse" id="mobile-menu-resources">
<li>
<a role="button" href="#mobile-menu-what's-happening" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-what's-happening">What's happening <i class="fa-solid fa-chevron-down" aria-hidden="true"></i></a>
<ul class="collapse" id="mobile-menu-what's-happening">
<li><a href="https://blogs.eclipse.org/" target="">Read our blog</a></li><li><a href="https://accounts.eclipse.org/mailing-list" target="">Join mailing lists</a></li><li><a href="https://newsroom.eclipse.org/" target="">Get news updates</a></li><li><a href="/community/eclipse_newsletter" target="">Subscribe to newsletter</a></li><li><a href="https://opensourceglobalvoices.org" target="_blank">Explore OSS global voices <i class="fa-solid fa-arrow-up-right-from-square external-icon" aria-hidden="true"></i></a></li>
</ul>
</li> <li>
<a role="button" href="#mobile-menu-events" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-events">Events <i class="fa-solid fa-chevron-down" aria-hidden="true"></i></a>
<ul class="collapse" id="mobile-menu-events">
<li><a href="https://events.eclipse.org/" target="">Explore events</a></li><li><a href="/events/open-community-meetups/" target="">Join a community meetup</a></li><li><a href="/events/open-community-webinars/" target="">Attend a webinar</a></li><li><a href="https://www.ocxconf.org/" target="_blank">OCX <i class="fa-solid fa-arrow-up-right-from-square external-icon" aria-hidden="true"></i></a></li>
</ul>
</li> <li>
<a role="button" href="#mobile-menu-developer-resources" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-developer-resources">Developer resources <i class="fa-solid fa-chevron-down" aria-hidden="true"></i></a>
<ul class="collapse" id="mobile-menu-developer-resources">
<li><a href="/projects/" target="">Explore projects hub</a></li><li><a href="/projects/calendar/#office-hours" target="">Attend office hours</a></li><li><a href="/security/" target="">Report a vulnerability</a></li><li><a href="/security/known/" target="">View known vulnerabilities</a></li><li><a href="https://github.com/eclipse-csi/security-handbook/blob/main/docs/vulnerabilities/advisories.md" target="_blank">Use the security handbook</a></li>
</ul>
</li> <li>
<a role="button" href="#mobile-menu-industry-insights" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-industry-insights">Industry insights <i class="fa-solid fa-chevron-down" aria-hidden="true"></i></a>
<ul class="collapse" id="mobile-menu-industry-insights">
<li><a href="/org/value/#success_stories" target="">View case studies</a></li><li><a href="/org/value/#whitepapers" target="">View whitepapers</a></li><li><a href="/org/value/#surveyReports" target="">View surveys &amp; reports</a></li>
</ul>
</li> <li>
<a role="button" href="#mobile-menu-marketplaces" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-marketplaces">Marketplaces <i class="fa-solid fa-chevron-down" aria-hidden="true"></i></a>
<ul class="collapse" id="mobile-menu-marketplaces">
<li><a href="https://adoptium.net/marketplace" target="_blank">Adoptium <i class="fa-solid fa-arrow-up-right-from-square external-icon" aria-hidden="true"></i></a></li><li><a href="https://marketplace.eclipse.org/" target="_blank">Eclipse IDE <i class="fa-solid fa-arrow-up-right-from-square external-icon" aria-hidden="true"></i></a></li><li><a href="https://open-vsx.org/" target="_blank">Open VSX <i class="fa-solid fa-arrow-up-right-from-square external-icon" aria-hidden="true"></i></a></li>
</ul>
</li>
</ul>
</li> <li>
<a role="button" href="#mobile-menu-about-us" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-about-us">
About us
<i class="fa-solid fa-chevron-down" aria-hidden="true"></i>
</a>
<ul class="collapse" id="mobile-menu-about-us">
<li>
<a role="button" href="#mobile-menu-the-foundation" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-the-foundation">The Foundation <i class="fa-solid fa-chevron-down" aria-hidden="true"></i></a>
<ul class="collapse" id="mobile-menu-the-foundation">
<li><a href="/about/" target="">About</a></li><li><a href="/org/foundation/staff/" target="">Meet the team</a></li><li><a href="/org/governance/" target="">Board &amp; governance</a></li><li><a href="/org/foundation/eclipseawards/" target="">Awards</a></li>
</ul>
</li> <li>
<a role="button" href="#mobile-menu-our-members" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-our-members">Our members <i class="fa-solid fa-chevron-down" aria-hidden="true"></i></a>
<ul class="collapse" id="mobile-menu-our-members">
<li><a href="/membership/" target="">Become a member</a></li><li><a href="/membership/explore-membership/" target="">View member directory</a></li><li><a href="https://membership.eclipse.org/portal" target="_blank">Access member portal</a></li>
</ul>
</li> <li>
<a role="button" href="#mobile-menu-sponsorship" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-sponsorship">Sponsorship <i class="fa-solid fa-chevron-down" aria-hidden="true"></i></a>
<ul class="collapse" id="mobile-menu-sponsorship">
<li><a href="/sponsor/" target="">Become a sponsor</a></li><li><a href="/org/our-sponsors/" target="">Sponsor directory</a></li>
</ul>
</li> <li>
<a role="button" href="#mobile-menu-more" data-toggle="collapse" aria-expanded="false" aria-controls="mobile-menu-more">More <i class="fa-solid fa-chevron-down" aria-hidden="true"></i></a>
<ul class="collapse" id="mobile-menu-more">
<li><a href="/careers/" target="">Join our team</a></li><li><a href="/org/artwork/" target="">Explore our brand</a></li><li><a href="/org/foundation/contact.php" target="">Contact us</a></li>
</ul>
</li>
</ul>
</li></ul></nav>
</div> <div class="mega-menu"> <div class="menu hidden" id="join-us-menu" aria-expanded="false">
<div class="container">
<nav>
<div class="submenu" style="">
<div class="menu-heading-group">
<span class="menu-pre"><i class="fa-solid fa-user-group" aria-hidden="true"></i></span><h2 class="menu-heading">Membership</h2>
</div>
<ul>
<li><a href="/membership/" target="">Become a member</a></li><li><a href="/membership/#tab-fees" target="">Review membership fees</a></li><li><a href="https://membership.eclipse.org/portal" target="">Access member portal</a></li>
</ul>
</div> <div class="submenu" style="">
<div class="menu-heading-group">
<span class="menu-pre"><i class="fa-solid fa-bookmark" aria-hidden="true"></i></span><h2 class="menu-heading">Sponsorship</h2>
</div>
<ul>
<li><a href="/sponsor/" target="">Become a sponsor</a></li><li><a href="/org/our-sponsors/" target="">Sponsor directory</a></li>
</ul>
</div> <div class="submenu" style="">
<div class="menu-heading-group">
<span class="menu-pre"><i class="fa-solid fa-handshake-simple" aria-hidden="true"></i></span><h2 class="menu-heading">Contribute</h2>
</div>
<ul>
<li><a href="/contribute/" target="">How to contribute</a></li><li><a href="/projects/bring-your-project/" target="">Bring us your project</a></li><li><a href="/projects/training/" target="">Committer training</a></li>
</ul>
</div> <div class="submenu" style="">
<div class="menu-heading-group">
<span class="menu-pre"><i class="fa-solid fa-users" aria-hidden="true"></i></span><h2 class="menu-heading">Collaborate with us</h2>
</div>
<ul>
<li><a href="/org/working-groups/explore/" target="">Join an existing industry collaboration</a></li><li><a href="/collaborations/#start-collaboration" target="">Start a new collaboration</a></li>
</ul>
</div>
</nav>
</div>
</div> <div class="menu hidden" id="what-we-do-menu" aria-expanded="false">
<div class="container">
<nav>
<div class="submenu" style="">
<div class="menu-heading-group">
<span class="menu-pre"><i class="fa-solid fa-puzzle-piece" aria-hidden="true"></i></span><h2 class="menu-heading">Projects</h2>
</div>
<ul>
<li><a href="/projects/" target="">Explore projects</a></li><li><a href="/specifications/" target="">View specifications</a></li>
</ul>
</div> <div class="submenu" style="">
<div class="menu-heading-group">
<span class="menu-pre"><i class="fa-solid fa-building" aria-hidden="true"></i></span><h2 class="menu-heading">Industry collaborations</h2>
</div>
<ul>
<li><a href="/org/working-groups/explore/" target="">Explore collaborations</a></li><li><a href="/org/working-groups/about/" target="">Join a working group</a></li><li><a href="/collaborations/interest-groups/" target="">Join an interest group</a></li><li><a href="/org/value/" target="">Read success stories</a></li>
</ul>
</div> <div class="submenu" style="">
<div class="menu-heading-group">
<span class="menu-pre"><i class="fa-solid fa-laptop-code" aria-hidden="true"></i></span><h2 class="menu-heading">Core services</h2>
</div>
<ul>
<li><a href="/org/services/" target="">Explore core services</a></li><li><a href="/security/" target="">Ensure project security</a></li>
</ul>
</div> <div class="submenu" style="">
<div class="menu-heading-group">
<span class="menu-pre"><i class="fa-solid fa-bullseye" aria-hidden="true"></i></span><h2 class="menu-heading">Key initiatives</h2>
</div>
<ul>
<li><a href="/topics/edge-and-iot/" target="">Embedded &amp; IoT</a></li><li><a href="/topics/cloud-native/" target="">Enterprise Java</a></li><li><a href="https://dataspaces.eclipse.org/" target="">Data sovereignty</a></li><li><a href="/security/" target="">Security &amp; privacy</a></li><li><a href="/topics/ide/" target="">Developer tools &amp; IDEs</a></li><li><a href="https://aieclipse.org/" target="">AI &amp; emerging technologies <i class="fa-solid fa-arrow-up-right-from-square external-icon" aria-hidden="true"></i></a></li><li><a href="/topics/automotive-and-mobility/" target="">Automotive &amp; mobility</a></li><li><a href="https://orcwg.org/" target="">Policy &amp; compliance <i class="fa-solid fa-arrow-up-right-from-square external-icon" aria-hidden="true"></i></a></li>
</ul>
</div> <div class="submenu" style="grid-column: -3; margin-top: -20rem;">
<div class="menu-heading-group">
<span class="menu-pre"><i class="fa-solid fa-flag" aria-hidden="true"></i></span><h2 class="menu-heading">Strategic services</h2>
</div>
<ul>
<li><a href="/research/" target="">Transform research into open source</a></li><li><a href="/os4biz/services/" target="">Engage professional services</a></li><li><a href="/os4biz/ospo/" target="">Build your OSPO</a></li>
</ul>
</div>
</nav>
</div>
</div> <div class="menu hidden" id="resources-menu" aria-expanded="false">
<div class="container">
<nav>
<div class="submenu" style="">
<div class="menu-heading-group">
<span class="menu-pre"><i class="fa-solid fa-bullhorn" aria-hidden="true"></i></span><h2 class="menu-heading">What's happening</h2>
</div>
<ul>
<li><a href="https://blogs.eclipse.org/" target="">Read our blog</a></li><li><a href="https://accounts.eclipse.org/mailing-list" target="">Join mailing lists</a></li><li><a href="https://newsroom.eclipse.org/" target="">Get news updates</a></li><li><a href="/community/eclipse_newsletter" target="">Subscribe to newsletter</a></li><li><a href="https://opensourceglobalvoices.org" target="_blank">Explore OSS global voices <i class="fa-solid fa-arrow-up-right-from-square external-icon" aria-hidden="true"></i></a></li>
</ul>
</div> <div class="submenu" style="">
<div class="menu-heading-group">
<span class="menu-pre"><i class="fa-solid fa-calendar-days" aria-hidden="true"></i></span><h2 class="menu-heading">Events</h2>
</div>
<ul>
<li><a href="https://events.eclipse.org/" target="">Explore events</a></li><li><a href="/events/open-community-meetups/" target="">Join a community meetup</a></li><li><a href="/events/open-community-webinars/" target="">Attend a webinar</a></li><li><a href="https://www.ocxconf.org/" target="_blank">OCX <i class="fa-solid fa-arrow-up-right-from-square external-icon" aria-hidden="true"></i></a></li>
</ul>
</div> <div class="submenu" style="">
<div class="menu-heading-group">
<span class="menu-pre"><i class="fa-solid fa-code" aria-hidden="true"></i></span><h2 class="menu-heading">Developer resources</h2>
</div>
<ul>
<li><a href="/projects/" target="">Explore projects hub</a></li><li><a href="/projects/calendar/#office-hours" target="">Attend office hours</a></li><li><a href="/security/" target="">Report a vulnerability</a></li><li><a href="/security/known/" target="">View known vulnerabilities</a></li><li><a href="https://github.com/eclipse-csi/security-handbook/blob/main/docs/vulnerabilities/advisories.md" target="_blank">Use the security handbook</a></li>
</ul>
</div> <div class="submenu" style="">
<div class="menu-heading-group">
<span class="menu-pre"><i class="fa-solid fa-gear" aria-hidden="true"></i></span><h2 class="menu-heading">Industry insights</h2>
</div>
<ul>
<li><a href="/org/value/#success_stories" target="">View case studies</a></li><li><a href="/org/value/#whitepapers" target="">View whitepapers</a></li><li><a href="/org/value/#surveyReports" target="">View surveys &amp; reports</a></li>
</ul>
</div> <div class="submenu" style="grid-column: -2; margin-top: -6rem;">
<div class="menu-heading-group">
<span class="menu-pre"><i class="fa-solid fa-store" aria-hidden="true"></i></span><h2 class="menu-heading">Marketplaces</h2>
</div>
<ul>
<li><a href="https://adoptium.net/marketplace" target="_blank">Adoptium <i class="fa-solid fa-arrow-up-right-from-square external-icon" aria-hidden="true"></i></a></li><li><a href="https://marketplace.eclipse.org/" target="_blank">Eclipse IDE <i class="fa-solid fa-arrow-up-right-from-square external-icon" aria-hidden="true"></i></a></li><li><a href="https://open-vsx.org/" target="_blank">Open VSX <i class="fa-solid fa-arrow-up-right-from-square external-icon" aria-hidden="true"></i></a></li>
</ul>
</div>
</nav>
</div>
</div> <div class="menu hidden" id="about-us-menu" aria-expanded="false">
<div class="container">
<nav>
<div class="submenu" style="">
<div class="menu-heading-group">
<span class="menu-pre"><i class="fa-solid fa-moon rotate-90" aria-hidden="true"></i></span><h2 class="menu-heading">The Foundation</h2>
</div>
<ul>
<li><a href="/about/" target="">About</a></li><li><a href="/org/foundation/staff/" target="">Meet the team</a></li><li><a href="/org/governance/" target="">Board &amp; governance</a></li><li><a href="/org/foundation/eclipseawards/" target="">Awards</a></li>
</ul>
</div> <div class="submenu" style="">
<div class="menu-heading-group">
<span class="menu-pre"><i class="fa-solid fa-user-group" aria-hidden="true"></i></span><h2 class="menu-heading">Our members</h2>
</div>
<ul>
<li><a href="/membership/" target="">Become a member</a></li><li><a href="/membership/explore-membership/" target="">View member directory</a></li><li><a href="https://membership.eclipse.org/portal" target="_blank">Access member portal</a></li>
</ul>
</div> <div class="submenu" style="">
<div class="menu-heading-group">
<span class="menu-pre"><i class="fa-solid fa-bookmark" aria-hidden="true"></i></span><h2 class="menu-heading">Sponsorship</h2>
</div>
<ul>
<li><a href="/sponsor/" target="">Become a sponsor</a></li><li><a href="/org/our-sponsors/" target="">Sponsor directory</a></li>
</ul>
</div> <div class="submenu" style="">
<div class="menu-heading-group">
<span class="menu-pre"><i class="fa-solid fa-cube" aria-hidden="true"></i></span><h2 class="menu-heading">More</h2>
</div>
<ul>
<li><a href="/careers/" target="">Join our team</a></li><li><a href="/org/artwork/" target="">Explore our brand</a></li><li><a href="/org/foundation/contact.php" target="">Contact us</a></li>
</ul>
</div>
</nav>
</div>
</div></div>
</header> <main id="content">
<div class="novaContent container" id="novaContent">
<div class="row">
<section class="default-breadcrumbs hidden-print breadcrumbs-default-margin" id="breadcrumb">
<div class="container">
<h3 class="sr-only">Breadcrumbs</h3>
<div class="row">
<div class="col-sm-24"><ol class="breadcrumb"><li><a href="/">Home</a></li><li><a href="https://www.eclipse.org/downloads">Downloads</a></li><li class="active">Eclipse downloads - Select a mirror</li></ol></div>
</div>
</div>
</section> <!-- /#breadcrumb -->
</div>
<div class="row">
<div class="col-md-24 main-col-content">
<div class=" main-col-content">
<div class="col-md-18">
<p>All downloads are provided under the terms and conditions of the <a href="/legal/epl/notice.php">Eclipse Foundation
Software User Agreement</a> unless otherwise specified.</p>
<div class="row">
<div class="col-md-24">
<div class="mirror-well text-center">
<a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=1135" class="btn btn-primary margin-bottom-15 btn-lg"><i class="fa fa-download"></i> Download</a>
<p><strong>Download from:</strong> Canada - Rafal Rzeczkowski (https)</p>
<p><strong>File:</strong> <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=1135" class="orange">eclipse-java-2026-09-R-win32-x86_64.zip</a> <a class="btn btn-ajax-checksum btn-xs btn-default" data-checksum-file="/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip" data-checksum-type="sha512" data-toggle="tab" role="tab" aria-controls="settings" href="#btn-ajax-checksum-sha512">SHA-512</a></p>
<div class="tab-content">
<div id="btn-ajax-checksum-sha512" class="tab-pane fade" role="tabpanel">
<div class="well well-sha512">
<p></p>
</div>
</div>
</div>
<p><a role="button" data-toggle="collapse" href="#collapseExample" aria-expanded="false" aria-controls="collapseExample">>> Select Another Mirror</a></p>
<div class="collapse" id="collapseExample">
<div class="well choose-mirror-well">
<i class="fa fa-times-circle close-choose-mirror-well" aria-hidden="true"></i>
<h2>Choose a mirror close to you</h2>
<hr>
<div class="choose-mirror">
<div class="mirror-continent mirror-section-1">
<h3>North America</h3>
<ul class="list-unstyled">
<li class="margin-bottom-10">United States - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=1281" target="">University of Maryland</a></li>
<li class="margin-bottom-10">United States - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=1323" target="">Old Dominion University Computer Science Departmen</a></li>
<li class="margin-bottom-10">Canada - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=492" target="">University of Waterloo Computer Science Club</a></li>
<li class="margin-bottom-10">Canada - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=1317" target="">CICKU</a></li>
<li class="margin-bottom-10">United States - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=272" target="">OSU Open Source Lab</a></li>
<li class="margin-bottom-10">United States - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=518" target="">XMission Internet</a></li>
</ul>
</div>
<div class="mirror-continent mirror-section-2">
<h3>Europe</h3>
<ul class="list-unstyled">
<li class="margin-bottom-10">Netherlands - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=1285" target="">LiteServer</a></li>
<li class="margin-bottom-10">Germany - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=1324" target="">Hochschule Esslingen</a></li>
<li class="margin-bottom-10">Czech Republic - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=1099" target="">UPC Ceska republika, a.s.</a></li>
<li class="margin-bottom-10">Russian Federation - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=1029" target="">Tomsk State Pedagogical University</a></li>
<li class="margin-bottom-10">Germany - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=1045" target="">RWTH Aachen University</a></li>
<li class="margin-bottom-10">Netherlands - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=1186" target="">SNT, University of Twente</a></li>
<li class="margin-bottom-10">France - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=1208" target="">CNRS IBCP</a></li>
<li class="margin-bottom-10">Serbia & Montenegro - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=1325" target="">Serbian Open eXchange</a></li>
<li class="margin-bottom-10">Sweden - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=1156" target="">Academic Computer Club in Umeå, Sweden</a></li>
<li class="margin-bottom-10">Denmark - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=1260" target="">dotsrc.org</a></li>
</ul>
</div>
<div class="mirror-continent mirror-section-3">
<h3>Asia</h3>
<ul class="list-unstyled">
<li class="margin-bottom-10">Japan - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=105" target="">Japan Advanced Institute of Science and Technology</a></li>
<li class="margin-bottom-10">Japan - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=1287" target="">Yamagata University</a></li>
<li class="margin-bottom-10">China - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=1290" target="">eScience Center, Nanjing University</a></li>
</ul>
</div>
<div class="mirror-continent mirror-section-4">
<h3>Australia/Oceania</h3>
<ul class="list-unstyled">
<li class="margin-bottom-10">Australia - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=1051" target="">Australian Academic Research Network</a></li>
</ul>
</div>
<div class="mirror-continent mirror-section-5">
<h3>South America</h3>
<ul class="list-unstyled">
<li class="margin-bottom-10">Brazil - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=576" target="">C3SL - Federal University of Parana</a></li>
</ul>
</div>
<a class="btn btn-sm btn-primary" id="show_all_mirrors" href="#show_all_mirrors">Show all</a>
</div>
<div class="choose-mirror">
<h3>Main Download Site</h3>
<ul class="list-unstyled">
<li>Canada - <a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&mirror_id=1">Main eclipse.org downloads area</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="download-faster col-md-24">
</div>
</div>
</div>
<div id="rightcolumn" class="col-sm-6">
<div class="padding-top-10 padding-bottom-30">
<div class="eclipsefdn-promo-content text-center" data-ad-format="ads_square" data-ad-publish-to="eclipse_org_downloads"></div>
</div>
<div class="sideitem">
<h6>Other options for this file</h6>
<ul>
<li><a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip">All mirrors</a> (<a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&format=xml">xml</a>)</li>
<li><a href="download.php?file=/technology/epp/downloads/release/2026-09/R/eclipse-java-2026-09-R-win32-x86_64.zip&r=1">Direct link to file</a> (download starts immediately from best mirror)</li>
</ul>
</div>
<div class="sideitem">
<h6>Related Links</h6>
<ul>
<li><a href="/sponsor/">Sponsor</a></li>
<li><a href="mir_request.php">Becoming a mirror site</a></li>
<li><a href="https://help.eclipse.org/2020-06/index.jsp?topic=/org.eclipse.platform.doc.user/tasks/tasks-129.htm">Updating and installing Eclipse components</a></li>
<li><a href="/forums/">Eclipse forums</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<aside class="eclipsefdn-featured-footer featured-footer" id="featured-footer" data-publish-target="eclipse_org"><div class="container"><div class="row"><div class="col-sm-24 featured-container"></div></div></div></aside>
<footer class="footer">
<div class="container">
<div class="footer-inner">
<section class="links-section">
<ul>
<li>Eclipse Foundation</li>
<ul><li><a href="https://www.eclipse.org/org/" class="">About</a></li><li><a href="https://projects.eclipse.org/" class="">Projects</a></li><li><a href="https://www.eclipse.org/collaborations/" class="">Collaborations</a></li><li><a href="https://www.eclipse.org/membership/" class="">Membership</a></li><li><a href="https://www.eclipse.org/sponsor/" class="">Sponsor</a></li></ul>
</ul> <ul>
<li>Legal</li>
<ul><li><a href="https://www.eclipse.org/legal/privacy.php" class="">Privacy Policy</a></li><li><a href="https://www.eclipse.org/legal/termsofuse.php" class="">Terms of Use</a></li><li><a href="https://www.eclipse.org/legal/compliance/" class="">Compliance</a></li><li><a href="https://www.eclipse.org/org/documents/Community_Code_of_Conduct.php" class="">Code of Conduct</a></li><li><a href="https://www.eclipse.org/legal/" class="">Legal Resources</a></li><li><a href="#" class="toolbar-manage-cookies">Manage Cookies</a></li></ul>
</ul> <ul>
<li>More</li>
<ul><li><a href="https://www.eclipse.org/security/" class="">Report a Vulnerability</a></li><li><a href="https://www.eclipsestatus.io/" class="">Service Status</a></li><li><a href="https://www.eclipse.org/org/foundation/contact.php" class="">Contact</a></li><li><a href="https://www.eclipse.org//projects/support/" class="">Support</a></li></ul>
</ul> </section>
<section class="social-media-section">
<h2 class="subheading">See what we're up to</h2>
<ul class="social-media-list">
<li>
<a href="https://x.com/EclipseFdn" aria-label="X">
<span class="fa-stack">
<i class="fa-solid fa-circle fa-stack-2x" aria-hidden="true"></i>
<i class="fa-brands fa-x-twitter fa-stack-1x fa-inverse" aria-hidden="true"></i>
</span>
</a>
</li>
<li>
<a href="https://bsky.app/profile/eclipse.org" aria-label="Bluesky">
<span class="fa-stack">
<i class="fa-solid fa-circle fa-stack-2x" aria-hidden="true"></i>
<i class="fa-brands fa-bluesky fa-stack-1x fa-inverse" aria-hidden="true"></i>
</span>
</a>
</li>
<li>
<a href="https://mastodon.social/@EclipseFdn" aria-label="Mastodon">
<span class="fa-stack">
<i class="fa-solid fa-circle fa-stack-2x" aria-hidden="true"></i>
<i class="fa-brands fa-mastodon fa-stack-1x fa-inverse" aria-hidden="true"></i>
</span>
</a>
</li>
<li>
<a href="https://www.facebook.com/eclipse.org" aria-label="Facebook">
<span class="fa-stack">
<i class="fa-solid fa-circle fa-stack-2x" aria-hidden="true"></i>
<i class="fa-brands fa-facebook-f fa-stack-1x fa-inverse" aria-hidden="true"></i>
</span>
</a>
</li>
<li>
<a href="https://www.instagram.com/eclipsefoundation/" aria-label="Instagram">
<span class="fa-stack">
<i class="fa-solid fa-circle fa-stack-2x" aria-hidden="true"></i>
<i class="fa-brands fa-instagram fa-stack-1x fa-inverse" aria-hidden="true"></i>
</span>
</a>
</li>
<li>
<a href="https://www.youtube.com/user/EclipseFdn" aria-label="YouTube">
<span class="fa-stack">
<i class="fa-solid fa-circle fa-stack-2x" aria-hidden="true"></i>
<i class="fa-brands fa-youtube fa-stack-1x fa-inverse" aria-hidden="true"></i>
</span>
</a>
</li>
<li>
<a href="https://www.linkedin.com/company/eclipse-foundation" aria-label="LinkedIn">
<span class="fa-stack">
<i class="fa-solid fa-circle fa-stack-2x" aria-hidden="true"></i>
<i class="fa-brands fa-linkedin-in fa-stack-1x fa-inverse" aria-hidden="true"></i>
</span>
</a>
</li>
</ul>
</section>
<section class="newsletter-section">
<p class="subheading">Stay up to date</p>
<h2>Subscribe to our newsletter</h2>
<form class="newsletter-form margin-top-40">
<div class="text-field-outlined">
<input type="email" placeholder="Your email">
<button class="newsletter-form-submit" type="submit" aria-label="Submit">
<i class="fa-solid fa-arrow-right" aria-hidden="true"></i>
</button>
</div>
</form>
</section>
<section class="footer-bottom-section">
<img class="img-responsive" src="https://webdev.eclipse.org/docs/solstice/legacy/dist/images/logo/eclipse-foundation-white-orange.svg" alt="Eclipse Foundation">
<div class="legal-information">
<p id="copyright-text">Copyright &copy; Eclipse Foundation AISBL. All Rights Reserved.</p>
<ul class="legal-links">
<li><a href="https://www.eclipse.org/legal/privacy/">Privacy policy</a></li>
<li><a href="https://www.eclipse.org/legal/terms-of-use/">Terms of use</a></li>
<li><a href="https://www.eclipse.org/legal/compliance/">Compliance</a></li>
<li><a href="https://www.eclipse.org/legal/">Legal</a></li>
</ul>
</div>
</section>
</div>
</div>
</footer>
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://www.eclipse.org/public/js/neptune/main.js?v=0.1.3"></script>
<script type="text/javascript" src="/downloads/assets/public/javascript/mirror.min.js"></script>
</body>
</html>
+232
View File
@@ -0,0 +1,232 @@
# adt-reentrance-login
Capture an SAP **ADT reentrance ticket** through a **localhost redirect**, so
`abap-adt-mcp` can log on to the S/4HANA Cloud development tenant from this
headless / SSH-only host — without a browser window on the server's desktop.
Node 24, standard library only, no dependencies, no install step.
---
## Why this exists
`abap-adt-mcp` 2.1.0 can do browser SSO, but only by launching a **server-side**
Chrome window (`headless: false`) and harvesting cookies over the DevTools
protocol; there is no localhost redirect in that path, so over multi-hop SSH
neither the desktop nor a DevTools relay is reachable in practice
[KNOWLEDGE.md, 2026-09-16].
ADT for VS Code does it differently. It opens
```
https://<tenant>/sap/bc/adt/core/http/reentranceticket?redirect-url=http://localhost:<port>/adt/redirect
```
and reads the ticket back out of the redirect
```
http://localhost:<port>/adt/redirect?...&reentrance-ticket=<ticket>
```
(observed 2026-09-16). That half is portable: **your** browser does the SSO, the
tenant redirects to `localhost`, and a forwarded TCP port puts "localhost" back
on this host. This script is that listener.
---
## Run it
Three steps, three terminals-worth of context. Values below use the defaults
(tenant `my431353`, port `42010`, 300 s timeout).
### 1. Start the helper on this host
```bash
node /home/alexz/projects/sap-integrations-mcl/arena-plm/tools/adt-reentrance-login/login.mjs
```
It prints (to stderr) the port to forward and the URL to open, then waits up to
300 seconds for exactly one redirect.
### 2. Forward the port to the machine running your browser
**VS Code:** open the **Ports** panel (`Ctrl+Shift+P` → *Ports: Focus on Ports
View*) → **Forward a Port** → enter `42010`. VS Code forwards it to your local
`localhost:42010`.
**Plain SSH**, from your laptop:
```bash
ssh -L 42010:127.0.0.1:42010 <this-host>
```
The browser must reach **this host's** localhost. Without the forward the tenant
redirects your browser to its own machine and nothing is captured.
### 3. Open the URL in that browser and complete SSO
```
https://my431353.s4hana.cloud.sap/sap/bc/adt/core/http/reentranceticket?redirect-url=http://localhost:42010/adt/redirect
```
(Or capture it programmatically: `node login.mjs --print-url-only`.)
After SSO the tab lands on "Ticket captured", the helper writes
`~/.abap-adt-mcp/ticket.json` (mode 0600) and exits 0.
### 4. Make the MCP server pick it up
`~/.abap-adt-mcp/ticket-provider.sh` already `cat`s that file, and
`abap-adt-mcp` caches the login per destination — it only re-runs the provider
on an explicit `login` call or a `reauthenticate`
(`dist/index.js:268-292`, `615-625`, read 2026-09-18). So after capturing a
fresh ticket, call the MCP `login` tool for the destination.
---
## Options
| Flag | Default | Meaning |
|---|---|---|
| `--tenant <url>` | `https://my431353.s4hana.cloud.sap` | Tenant base URL. The UI host, not the `-api` host. |
| `--port <n>` | `42010` | Loopback port. Bound to `127.0.0.1` **only**; there is no flag to change the bind address. |
| `--timeout <seconds>` | `300` | Give up and write nothing. |
| `--out <path>` | `~/.abap-adt-mcp/ticket.json` | Output file, mode 0600 (directory created 0700). |
| `--client <nnn>` | *not sent* | Adds `sap-client=<nnn>`. See "Which client?" below. |
| `--allow-host <name>` | — | Additionally accept this `Host` header (repeatable), for tunnelled forwards that are not `localhost`. |
| `--probe` | off | After capture, test cookie vs header transport (below). |
| `--print-url-only` | off | Print the logon URL to **stdout** and exit; start no listener. |
| `--help` | — | Usage. |
Exit codes: `0` captured · `2` usage · `3` timeout · `4` cannot bind · `5`
redirect arrived without a usable ticket.
### Which client?
Nothing is hard-coded. The client the browser session uses is unconfirmed; the
published service URL shows `sap-client=080`, but that is the API host's
service URL, not proof about the UI logon. Run without `--client` first; only
add `--client 080` if the tenant needs it.
---
## Probe mode (`--probe`)
Open question this settles: SAP documents a **logon ticket** in the `MYSAPSSO2`
**cookie** and an **assertion ticket** in a `mysapsso2` **header**
[source: sap-docs sap-help loio 3354605668 and 3361378221, verified
2026-09-18]. Which one a *reentrance* ticket behaves as is **unknown**, and it
decides whether `abap-adt-mcp`'s `sso2` mode can work at all — that mode sends
the value strictly as a **cookie** (`dist/lib/sso2TicketProvider.js`, read
2026-09-18).
With `--probe`, after capture the helper makes two GETs to
`<tenant>/sap/bc/adt/core/discovery` with `Accept: application/atomsvc+xml` and
**no other credentials**:
- **(a)** `Cookie: MYSAPSSO2=<ticket>`
- **(b)** `MYSAPSSO2: <ticket>`
and reports only the HTTP status of each, whether a `set-cookie` with
`SAP_SESSIONID` came back, and which succeeded. Bodies are discarded unread. A
redirect is reported by **host only** (a SAML `Location` can carry request
material).
Read the verdict as:
- **cookie worked** → `abap-adt-mcp`'s `sso2` mode works as-is.
- **only the header worked** → `sso2` mode **cannot** use this ticket; the MCP
server would need a header transport it does not have.
- **neither** → expired ticket, wrong client, or wrong mechanism entirely.
---
## Ticket lifetime
- **The lifetime of a reentrance ticket is not verified here.** Treat it as
short-lived and re-run the helper whenever logon fails. Do not assume the
8-hour default of a classic SAP logon ticket applies.
- Whether a reentrance ticket is **single-use** is also unverified. If a
captured ticket authenticates once and then stops, assume single-use and
re-capture.
- Expiry surfaces in the ADT MCP only as **"Your user was logged off."**
`abap_list_destinations` and `get_object_type_details` still answer, so
neither proves a live session; `abap_creation-run_validation` is the cheapest
read-only liveness check [KNOWLEDGE.md, 2026-09-16].
---
## Security rules
These are contracts, not preferences. The ticket is a **live logon credential**
for the tenant.
1. **The ticket is never printed, logged, or put in an error message, argv, or
an environment variable.** The only place it is written is `--out`, mode
0600. Validation failures report the *reason* and the parameter *names*
present — never values, never offending characters or positions.
2. **stdout stays empty.** Every human-readable message goes to stderr, so
nothing sensitive can be captured by a `$(...)` around the helper. The single
exception is `--print-url-only`, which prints the (secret-free) logon URL.
3. **The listener binds `127.0.0.1` only** and is one-shot. There is no flag to
bind a wider interface. A `Host` header that is not a loopback name is
refused (cheap DNS-rebinding guard); allow a specific tunnel name with
`--allow-host`.
4. **Never paste the redirect URL** into a chat, issue, log, or file — it
contains the ticket. Note that the URL also lands in **your browser's
history**; clear it on a shared machine.
5. The output file is written with permissions set **before** any ticket byte
reaches the disk (`open` + `fchmod` + `write`), so a pre-existing
world-readable file cannot briefly expose it.
6. Ticket values are validated against the RFC 6265 cookie-octet set copied
verbatim from `abap-adt-mcp` — a ticket the MCP server would reject is
rejected here instead of being written to a file that cannot work.
### Percent-decoding, and why not `URLSearchParams`
The ticket is URL-decoded **exactly once** with `decodeURIComponent` over the
raw query pair, and never re-encoded.
`URLSearchParams` is deliberately **not** used: it applies
`application/x-www-form-urlencoded` rules and turns a literal `+` into a space.
Measured on Node v24.15.0, 2026-09-18:
```
'?reentrance-ticket=AB%2BCD+EF%3d'
searchParams.get(...) -> 'AB+CD EF=' <-- space, corrupt
decodeURIComponent(raw pair) -> 'AB+CD+EF=' <-- correct
```
A space is not a legal cookie-octet, so a base64 ticket carrying `+` would be
silently corrupted and then rejected.
---
## Configuration (handled by the repo owner, documented here for reference)
This script does **not** touch `~/.abap-adt-mcp/systems.json` or `.mcp.json`.
For the record, `abap-adt-mcp` 2.1.0 requires for `authType: "sso2"`
(`dist/lib/systems.js:73-149`, read 2026-09-18):
- `sso2.command` must be an **absolute executable path** (`sso2.args` optional,
`sso2.timeoutMs` 1000–300000);
- the system `url` must be **HTTPS** ("so the ticket is never sent in
plaintext");
- `insecureTls` is refused in combination with `sso2`;
- `authType: "ticket"` is accepted as an alias for `sso2`;
- the server warns at startup that every remote caller of an `sso2` destination
shares the SAP identity of the user running it — one server instance per
person.
---
## Troubleshooting
| Symptom | Cause |
|---|---|
| Browser cannot reach `localhost:42010` | Port not forwarded, or forwarded to the wrong host. Re-check step 2. |
| `port ... is already in use` (exit 4) | Another listener. Use `--port <n>` and forward **that** port. |
| `Timed out ... Nothing was written.` (exit 3) | SSO never completed, or the redirect went to the wrong machine. |
| `the redirect carried no reentrance-ticket parameter` (exit 5) | The tenant redirected without a ticket — usually a failed or cancelled SSO. The parameter names it *did* send are listed. |
| `ticket contains characters outside the RFC 6265 cookie-octet set` (exit 5) | The value is not usable as a `MYSAPSSO2` cookie; `abap-adt-mcp` would reject it too. |
| `Refused a redirect with Host: ...` | Forward is not a plain localhost forward. Re-run with `--allow-host <that-host>`. |
+441
View File
@@ -0,0 +1,441 @@
#!/usr/bin/env node
/**
* adt-reentrance-login — capture an SAP ADT reentrance ticket over a localhost
* redirect, so `abap-adt-mcp` can log on to an S/4HANA Cloud tenant from a
* headless / SSH-only host.
*
* ADT for VS Code logs on by opening
* https://<tenant>/sap/bc/adt/core/http/reentranceticket?redirect-url=http://localhost:<port>/adt/redirect
* and reading the ticket out of the redirect
* http://localhost:<port>/adt/redirect?...&reentrance-ticket=<ticket>
* (observed 2026-09-16). This script plays the "localhost" half of that dance:
* it listens on 127.0.0.1 only, captures exactly one ticket, and writes it as
* {"ticket":"..."} to a 0600 file for ~/.abap-adt-mcp/ticket-provider.sh.
*
* SECURITY CONTRACT (do not relax):
* - The ticket is a live logon credential. It is NEVER printed, logged, put
* in an error message, passed in argv, or exported to the environment.
* The only place it is written is the --out file, mode 0600.
* - The listener binds 127.0.0.1 exclusively. There is no flag to change it.
* - All human-facing output goes to stderr. stdout stays empty, except in
* --print-url-only mode, which prints the (secret-free) logon URL so it can
* be captured with $(...).
*
* Node 24, standard library only, no dependencies.
*/
import { createServer } from 'node:http';
import { request as httpsRequest } from 'node:https';
import { parseArgs } from 'node:util';
import { chmodSync, closeSync, fchmodSync, mkdirSync, openSync, writeSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { homedir } from 'node:os';
const DEFAULT_TENANT = 'https://my431353.s4hana.cloud.sap';
const DEFAULT_PORT = 42010;
const DEFAULT_TIMEOUT_SECONDS = 300;
const DEFAULT_OUT = join(homedir(), '.abap-adt-mcp', 'ticket.json');
const BIND_ADDRESS = '127.0.0.1'; // loopback only, by design
const REDIRECT_PATH = '/adt/redirect';
const TICKET_PARAM = 'reentrance-ticket';
const TICKET_ENDPOINT = '/sap/bc/adt/core/http/reentranceticket';
const DISCOVERY_PATH = '/sap/bc/adt/core/discovery';
/**
* RFC 6265 cookie-octet. Copied verbatim from abap-adt-mcp 2.1.0
* dist/lib/sso2TicketProvider.js (read 2026-09-18): a ticket that fails this
* test is rejected by the MCP server itself, so fail here instead of writing a
* file that cannot work. Note \x3d (=) IS allowed, so base64 padding is fine;
* space, comma, semicolon, double quote and backslash are not.
*/
const COOKIE_OCTET = /^[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]+$/;
const MAX_TICKET_CHARS = 64 * 1024; // same ceiling as the MCP server
/** Hosts a browser may legitimately use to reach this listener. */
const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1', '[::1]']);
const EXIT_OK = 0;
const EXIT_USAGE = 2;
const EXIT_TIMEOUT = 3;
const EXIT_BIND = 4;
const EXIT_BAD_TICKET = 5;
const EXIT_INTERRUPTED = 130;
/** Every human-readable message goes to stderr. Never pass ticket material. */
const note = (msg = '') => process.stderr.write(`${msg}\n`);
function usage() {
note(`adt-reentrance-login — capture an ADT reentrance ticket via a localhost redirect
node login.mjs [options]
--tenant <url> S/4HANA tenant base URL (default ${DEFAULT_TENANT})
--port <n> loopback port to listen on (default ${DEFAULT_PORT}, bound to ${BIND_ADDRESS} only)
--timeout <seconds> give up after this long (default ${DEFAULT_TIMEOUT_SECONDS})
--out <path> where to write {"ticket":"..."} (default ${DEFAULT_OUT}, mode 0600)
--client <nnn> add sap-client=<nnn> to the logon and probe URLs (default: not sent)
--allow-host <name> additionally accept this Host header (for tunnelled forwards)
--probe after capture, test the ticket as a cookie and as a header
--print-url-only print the logon URL to stdout and exit; start no listener
--help this text
Exit codes: 0 captured, 2 usage, 3 timeout, 4 cannot bind, 5 bad/absent ticket.`);
}
function fail(code, msg) {
note(`ERROR: ${msg}`);
process.exit(code);
}
// ---------------------------------------------------------------------------
// Arguments
// ---------------------------------------------------------------------------
let values;
try {
({ values } = parseArgs({
options: {
tenant: { type: 'string', default: DEFAULT_TENANT },
port: { type: 'string', default: String(DEFAULT_PORT) },
timeout: { type: 'string', default: String(DEFAULT_TIMEOUT_SECONDS) },
out: { type: 'string', default: DEFAULT_OUT },
client: { type: 'string' },
'allow-host': { type: 'string', multiple: true, default: [] },
probe: { type: 'boolean', default: false },
'print-url-only': { type: 'boolean', default: false },
help: { type: 'boolean', default: false },
},
strict: true,
allowPositionals: false,
}));
} catch (err) {
usage();
fail(EXIT_USAGE, err.message);
}
if (values.help) {
usage();
process.exit(EXIT_OK);
}
let tenant;
try {
const parsed = new URL(values.tenant);
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
throw new Error('tenant must be an http(s) URL');
}
tenant = `${parsed.protocol}//${parsed.host}`;
} catch (err) {
fail(EXIT_USAGE, `--tenant is not a usable URL: ${err.message}`);
}
const port = Number(values.port);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
fail(EXIT_USAGE, `--port must be an integer 1..65535 (got ${JSON.stringify(values.port)})`);
}
const timeoutSeconds = Number(values.timeout);
if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) {
fail(EXIT_USAGE, `--timeout must be a positive number of seconds (got ${JSON.stringify(values.timeout)})`);
}
const client = values.client;
if (client !== undefined && !/^[0-9]{3}$/.test(client)) {
fail(EXIT_USAGE, '--client must be a three-digit SAP client number');
}
const outPath = values.out;
const allowedHosts = new Set([...LOOPBACK_HOSTS, ...values['allow-host']]);
// ---------------------------------------------------------------------------
// Logon URL
// ---------------------------------------------------------------------------
/**
* Built by string concatenation on purpose: ADT for VS Code sends the
* redirect-url value literally (observed 2026-09-16), and URLSearchParams would
* percent-encode the ':' and '/'. Colons and slashes are legal in a query
* string (RFC 3986), so the literal form is both valid and wire-identical to
* what the tenant already accepts from ADT.
*/
function buildLogonUrl() {
const redirect = `http://localhost:${port}${REDIRECT_PATH}`;
const clientParam = client ? `&sap-client=${encodeURIComponent(client)}` : '';
return `${tenant}${TICKET_ENDPOINT}?redirect-url=${redirect}${clientParam}`;
}
const logonUrl = buildLogonUrl();
if (values['print-url-only']) {
// The only thing this script ever writes to stdout. Contains no secret.
process.stdout.write(`${logonUrl}\n`);
process.exit(EXIT_OK);
}
// ---------------------------------------------------------------------------
// Ticket extraction
// ---------------------------------------------------------------------------
/**
* Pull the ticket out of the raw request target.
*
* Deliberately NOT URLSearchParams: it applies
* application/x-www-form-urlencoded rules and turns a literal '+' into a space
* (measured on Node v24.15.0, 2026-09-18: '?t=AB%2BCD+EF%3d' -> 'AB+CD EF=').
* A space is not a legal cookie-octet, so a base64 ticket carrying '+' would be
* silently corrupted and then rejected. Splitting the raw query and decoding
* once with decodeURIComponent preserves '+' ('AB+CD+EF=').
*
* Returns { ok: true, ticket } or { ok: false, reason, names }.
* `reason` NEVER contains ticket material.
*/
function extractTicket(requestTarget) {
const q = requestTarget.indexOf('?');
if (q < 0) return { ok: false, reason: 'the redirect carried no query string', names: [] };
const names = [];
let raw = null;
for (const pair of requestTarget.slice(q + 1).split('&')) {
if (!pair) continue;
const eq = pair.indexOf('=');
const rawName = eq < 0 ? pair : pair.slice(0, eq);
let name = rawName;
try { name = decodeURIComponent(rawName); } catch { /* keep the raw name */ }
names.push(name);
if (name === TICKET_PARAM && raw === null) raw = eq < 0 ? '' : pair.slice(eq + 1);
}
if (raw === null) return { ok: false, reason: `the redirect carried no ${TICKET_PARAM} parameter`, names };
if (raw === '') return { ok: false, reason: `${TICKET_PARAM} was empty`, names };
let ticket;
try {
ticket = decodeURIComponent(raw); // exactly once; do not re-encode
} catch {
return { ok: false, reason: `${TICKET_PARAM} is not valid percent-encoding`, names };
}
if (ticket.length === 0) return { ok: false, reason: `${TICKET_PARAM} decoded to an empty string`, names };
if (ticket.length > MAX_TICKET_CHARS) {
return { ok: false, reason: `ticket is longer than ${MAX_TICKET_CHARS} characters; abap-adt-mcp would reject it`, names };
}
if (!COOKIE_OCTET.test(ticket)) {
// No positions, no sample characters — that would leak ticket material.
return { ok: false, reason: 'ticket contains characters outside the RFC 6265 cookie-octet set', names };
}
return { ok: true, ticket };
}
/** Write {"ticket":"..."} with 0600 before any ticket byte reaches the disk. */
function writeTicketFile(path, ticket) {
const dir = dirname(path);
const created = mkdirSync(dir, { recursive: true, mode: 0o700 });
if (created) chmodSync(created, 0o700); // only tighten what we created
// 'w' truncates on open; fchmod before write closes the window in which a
// pre-existing world-readable file would briefly hold the ticket.
const fd = openSync(path, 'w', 0o600);
try {
fchmodSync(fd, 0o600);
writeSync(fd, `${JSON.stringify({ ticket })}\n`);
} finally {
closeSync(fd);
}
}
// ---------------------------------------------------------------------------
// Probe (--probe)
// ---------------------------------------------------------------------------
/**
* One GET to the ADT discovery endpoint carrying the ticket either as a
* MYSAPSSO2 cookie or as a MYSAPSSO2 header, and nothing else. Resolves to
* status/redirect-host/session-cookie facts only — never headers, never a body.
*/
function probeOnce(mode, ticket) {
return new Promise((resolve) => {
const target = new URL(DISCOVERY_PATH, tenant);
if (client) target.searchParams.set('sap-client', client);
const headers = { Accept: 'application/atomsvc+xml' };
if (mode === 'cookie') headers.Cookie = `MYSAPSSO2=${ticket}`;
else headers.MYSAPSSO2 = ticket;
const req = httpsRequest(
{
hostname: target.hostname,
port: target.port || 443,
path: `${target.pathname}${target.search}`,
method: 'GET',
headers,
timeout: 30_000,
},
(res) => {
const setCookie = res.headers['set-cookie'] ?? [];
const sessionCookie = setCookie.some((c) => /^\s*SAP_SESSIONID/i.test(c));
let redirectHost = null;
if (res.headers.location) {
// Host only: a SAML redirect URL can carry request material.
try { redirectHost = new URL(res.headers.location, target).host; }
catch { redirectHost = '(unparseable)'; }
}
res.resume(); // discard the body; it is never read or logged
res.on('end', () => resolve({ status: res.statusCode, sessionCookie, redirectHost }));
},
);
req.on('timeout', () => req.destroy(new Error('request timed out')));
req.on('error', (err) => resolve({ error: err.code ?? err.message }));
req.end();
});
}
function describeProbe(label, result) {
if (result.error) return ` ${label}: transport error (${result.error})`;
const bits = [`HTTP ${result.status}`];
bits.push(result.sessionCookie ? 'set-cookie SAP_SESSIONID: yes' : 'set-cookie SAP_SESSIONID: no');
if (result.redirectHost) bits.push(`redirect host: ${result.redirectHost}`);
return ` ${label}: ${bits.join(' ')}`;
}
async function runProbe(ticket) {
note('');
note(`Probing ${tenant}${DISCOVERY_PATH} (Accept: application/atomsvc+xml, no other credentials)...`);
const asCookie = await probeOnce('cookie', ticket);
const asHeader = await probeOnce('header', ticket);
note(describeProbe('(a) MYSAPSSO2 cookie', asCookie));
note(describeProbe('(b) MYSAPSSO2 header', asHeader));
const cookieOk = asCookie.status === 200;
const headerOk = asHeader.status === 200;
if (cookieOk && headerOk) note("verdict: both worked — abap-adt-mcp's cookie-based sso2 mode can work.");
else if (cookieOk) note("verdict: the COOKIE worked — abap-adt-mcp's sso2 mode (MYSAPSSO2 cookie) can work.");
else if (headerOk) note('verdict: only the HEADER worked — abap-adt-mcp 2.1.0 sends a cookie, so its sso2 mode cannot use this ticket as-is.');
else note('verdict: neither returned HTTP 200 — this ticket is not accepted by either transport (expired, wrong client, or wrong mechanism).');
}
// ---------------------------------------------------------------------------
// One-shot loopback listener
// ---------------------------------------------------------------------------
const PAGE_OK = `<!doctype html>
<html lang="en"><head><meta charset="utf-8">
<meta name="referrer" content="no-referrer">
<title>ADT logon captured</title></head>
<body style="font-family:system-ui,sans-serif;max-width:34rem;margin:4rem auto">
<h1>Ticket captured</h1>
<p>The reentrance ticket was written to the login helper's output file. You can close this tab.</p>
<p><small>This URL contains a live credential and stays in your browser history. Clear it if the machine is shared.</small></p>
</body></html>
`;
const PAGE_FAIL = `<!doctype html>
<html lang="en"><head><meta charset="utf-8">
<meta name="referrer" content="no-referrer">
<title>ADT logon failed</title></head>
<body style="font-family:system-ui,sans-serif;max-width:34rem;margin:4rem auto">
<h1>No usable ticket</h1>
<p>The redirect did not carry a usable reentrance ticket. See the helper's terminal output.</p>
</body></html>
`;
let finished = false;
const server = createServer((req, res) => {
// Keep the shared secret-free headers on every answer.
const send = (status, body, cb) => {
res.writeHead(status, {
'Content-Type': 'text/html; charset=utf-8',
'Cache-Control': 'no-store',
'Referrer-Policy': 'no-referrer',
Connection: 'close',
});
if (req.method === 'HEAD') res.end(cb);
else res.end(body, cb);
};
if (finished) { send(503, PAGE_FAIL); return; }
const target = req.url ?? '/';
const pathOnly = target.split('?')[0];
// Dropped rather than fatal: browsers ask for /favicon.ico unprompted.
if (pathOnly !== REDIRECT_PATH) { send(404, PAGE_FAIL); return; }
// Cheap DNS-rebinding guard. The socket is loopback-only anyway.
const hostHeader = (req.headers.host ?? '').replace(/:\d+$/, '');
if (hostHeader && !allowedHosts.has(hostHeader)) {
note(`Refused a redirect with Host: ${hostHeader} (expected localhost). Re-run with --allow-host ${hostHeader} if that forward is yours.`);
send(403, PAGE_FAIL);
return;
}
const result = extractTicket(target);
if (!result.ok) {
finished = true;
send(400, PAGE_FAIL, () => {
note(`Redirect received on ${REDIRECT_PATH}, but ${result.reason}.`);
if (result.names?.length) note(`Parameter names present (values withheld): ${result.names.join(', ')}`);
server.close();
process.exit(EXIT_BAD_TICKET);
});
return;
}
finished = true;
try {
writeTicketFile(outPath, result.ticket);
} catch (err) {
send(500, PAGE_FAIL, () => {
note(`ERROR: could not write ${outPath}: ${err.message}`);
server.close();
process.exit(EXIT_BAD_TICKET);
});
return;
}
send(200, PAGE_OK, async () => {
note(`Ticket captured and written to ${outPath} (mode 0600). Its value is not shown anywhere.`);
if (values.probe) {
try { await runProbe(result.ticket); }
catch (err) { note(`Probe failed to run: ${err.message}`); }
}
server.close();
process.exit(EXIT_OK);
});
});
server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
fail(EXIT_BIND, `port ${port} is already in use on ${BIND_ADDRESS}. Pick another with --port (and forward that one).`);
}
fail(EXIT_BIND, `cannot listen on ${BIND_ADDRESS}:${port}: ${err.message}`);
});
const timer = setTimeout(() => {
note(`Timed out after ${timeoutSeconds}s without a redirect. Nothing was written.`);
server.close();
process.exit(EXIT_TIMEOUT);
}, timeoutSeconds * 1000);
process.on('SIGINT', () => {
clearTimeout(timer);
note('Interrupted. Nothing was written.');
server.close();
process.exit(EXIT_INTERRUPTED);
});
server.listen({ host: BIND_ADDRESS, port, exclusive: true }, () => {
note(`Listening on http://${BIND_ADDRESS}:${port}${REDIRECT_PATH} (loopback only) for ${timeoutSeconds}s.`);
note('');
note(`1. Forward TCP port ${port} from this host to your machine.`);
note(` VS Code: Ports panel -> Forward a Port -> ${port} (or: ssh -L ${port}:127.0.0.1:${port} <this-host>)`);
note(' Your browser must reach THIS host\'s localhost, or the redirect lands nowhere.');
note('2. Open this URL in that browser and complete SSO:');
note('');
note(` ${logonUrl}`);
note('');
note(`3. The tenant redirects to http://localhost:${port}${REDIRECT_PATH}?... and the ticket is captured here.`);
note(` It is written to ${outPath} only. Never paste the redirect URL into a chat, log or file.`);
});