From ARC-1 — SAP ABAP for Claude
Reverse-engineers classic SAP SEGW OData V2 services (MPC/DPC) into modern RAP V4 services with CDS views, behavior definitions, and service bindings.
How this skill is triggered — by the user, by Claude, or both
Slash command
/arc-1:migrate-segw-to-rapThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Reverse-engineer a classic SEGW-built OData V2 service (MPC/DPC/MPC_EXT/DPC_EXT) into a modern
Reverse-engineer a classic SEGW-built OData V2 service (MPC/DPC/MPC_EXT/DPC_EXT) into a modern RAP service (CDS root + projection + BDEF + SRVD + SRVB + behavior pool) on the same SAP system. Runs side-by-side: the legacy service stays live; the new RAP service lands in a separate, resettable package.
Domain example. Templates in this skill use an illustrative
Project → Tasks → TimeEntriesdomain (entity names likeZR_DM_PROJECT,ZBP_DM_PROJECT,ZDM_PROJECT_D) so the shape is concrete. Substitute the user's entities throughout — the LLM running this skill should rewrite every entity identifier to match the source service.
| Setting | Default | Rationale |
|---|---|---|
| Discovery strategy | MPC class source (Tier 1) | ARC-1 reads it natively; richer than $metadata |
| Target package | User-provided. If absent, create a child of the source package via SAPManage(action="create_package"); default name <source_package>_RAP. Only fall back to $TMP if the user explicitly asks. | Keeps the migration output isolated and resettable. |
| Target transport | User-provided existing, or auto-created via SAPTransport(action="create", description="RAP migration of <legacy_service>", package="<target_package>"). | Required for non-$TMP packages. |
| OData version on target | V4 | Current SAP standard; FE-ready |
| RAP scenario | Managed with internal numbering | Simplest read-mostly; matches legacy SEGW behaviour |
| Draft | Off for read-mostly entities. ON when the legacy service exposed CUD (function imports, deep inserts, or sap:updatable=true on entity sets) and the user wants Fiori Elements compatibility. | Match the legacy service's behavior contract; FE list+OP work best with draft on. |
| Strict mode | strict ( 2 ) | Current best practice |
| Projection layer | Mandatory — always create a ZC_* projection alongside every ZR_* root view. Service binding exposes the projection, never the root. | Run 1 of the skill skipped projections after a misread CDS error; spell out so the LLM never drops them. |
provider contract on projections | On the root projection only, not on the root view, not on child projections. The contract sits at exactly one level. Use transactional_query when combining projection BDEF + use draft on 7.58; transactional_interface for read-mostly without draft. | Putting it on a root view → "only valid on projection views". Putting it on child projections → "inappropriate provider contract on …". Children are exposed via redirected to parent and inherit the BO contract from the root projection. Run 3 confirmed _query is needed for draft on 7.58. |
| CDS composition syntax (managed) | composition [0..*] of <child> as <name> — no on clause. Key linking is implicit via matching key fields / with foreign key in the child | 7.58 rejects on on managed compositions. The recovery is dropping on, not switching to association to. |
@Semantics.* on 7.58 | createdAt, lastChangedAt, and localInstanceLastChangedAt are all valid on 7.58 — put localInstanceLastChangedAt on the local-instance etag field (abp_locinst_lastchange_tstmpl), exactly as SAP's own RAP generator does. @Semantics.businessDate.from/to also work. | Verified live on S/4HANA 2023 (758, 2026-06-12): standalone CDS views with localInstanceLastChangedAt and with businessDate.from/to both activate. An earlier "unknown annotation on 7.58" note was a misdiagnosis — draft annotations are genuinely absent only on pre-7.55 releases. |
| Draft table field names (when draft=ON) | Use BO-alias casing without underscores — e.g. projectid, startdate, NOT project_id, start_date. ABAP normalizes BDEF aliases (ProjectId, StartDate) to PROJECTID, STARTDATE — the draft table lookup is by that normalized name, not by the active table's snake_case. | Run 2: BDEF activation failed with "key field PROJECTID expected at position 2, found PROJECT_ID". The active table can keep snake_case (BDEF mapping handles it); the draft table cannot — it has no mapping clause. |
| Naming | SAP-standard Z<prefix>_<entity>: ZR_ root, ZC_ projection, ZI_<entity>_BEH BDEF, ZBP_ behavior pool, ZUI_<service>_O4 V4 SRVB | Aligns with SAP-internal conventions; the leading Z is the customer namespace |
| Pre-write lint | On | Set SAP_ABAP_RELEASE=<your_release> in ARC-1 config (PR #255) so the lint preset matches the system release. The older SAP_LINT_BEFORE_WRITE=false workaround is no longer needed. |
| Pre-write SAP check | On for activation-blockers only | We rely on activation feedback, not --check-before-write |
The user must provide at minimum:
Z<SOMETHING>_SRV), a package containing
the SEGW-generated classes, or an MPC class name directly. Ask if not given.<source_package>_RAP)
if not given and the source package is known; otherwise asks. Only defaults to $TMP if
the user explicitly asks.$TMP writes — existing or auto-created (see Smart Defaults).Optional:
Z<prefix>_<entity>).If only the legacy identifier is given, skill applies smart defaults and surfaces the resolved
plan in Phase 5 for user ok before any writes.
Run before any modelling work. Aborts cleanly if anything is missing, with the specific SAP auth object the user needs.
SAPManage(action="probe")
Assert from the response:
systemType ∈ {onprem, btp} — needed for naming + admin-field choicesrap.available == true — fail with: "This system doesn't expose RAP/CDS endpoints. Migration
cannot proceed."transport.available == true — fail with: "Transport API unavailable; check ICF
/sap/bc/adt/cts/transports."authProbe.searchAccess == true — fail with: "User cannot search ADT objects; need
S_DEVELOP ACTVT=03 OBJTYPE=CLAS DEVCLASS=<source-pkg>."authProbe.transportAccess == true — fail with: "User cannot read transports; need
S_TRANSPRT ACTVT=03."SAPRead(type="CLAS", name="<MPC>", method="*")
On HTTP 403: stop with "Cannot read <MPC> — need S_DEVELOP ACTVT=03 OBJTYPE=CLAS
DEVCLASS=<source-pkg> P_GROUP=<auth-group>. Run SU53 in SAP GUI to see the missing field."
SAPTransport(action="check", objectType="DDLS", objectName="ZX_PRECHECK", package="<target_package>")
Then dry-run a write+delete on a throwaway DDLS:
SAPWrite(action="create", type="DDLS", name="ZR_DM_PRECHECK",
source="@AccessControl.authorizationCheck: #NOT_REQUIRED\ndefine root view entity ZR_DM_PRECHECK as select from t000 { client }",
description="ARC-1 preflight",
package="<target_package>",
transport="<transport>")
SAPWrite(action="delete", type="DDLS", name="ZR_DM_PRECHECK")
On 403 from create: stop with "Cannot write to <target_package> — need S_DEVELOP ACTVT=01,02,07
OBJTYPE=DDLS DEVCLASS=<target_package>."
If all three pass, print "Phase 0 ✓ Authorizations OK — proceeding to discovery." and continue.
If user gave a service name (e.g. <legacy_service>):
_SRV → append _MPC → <MPC_class>. Verify with
SAPRead.SAPQuery(action="sql", sql="SELECT obj_name, devclass FROM tadir
WHERE pgmid = 'R3TR' AND object = 'CLAS'
AND obj_name LIKE 'Z%_MPC' ORDER BY obj_name")
and ask the user to pick.If user gave a package (e.g. <source_package>):
SAPRead(type="DEVC", name="<package>")
Filter for classes ending _MPC.If user gave an MPC class directly: use it as-is.
Determine the corresponding _MPC_EXT, _DPC, _DPC_EXT by name convention.
SAPSearch(action="object", query="<base>_*", maxResults=10)
Expected: 4 hits. Record objectName + packageName for each.
Print to user:
Legacy service: <legacy_service>
Package: <source_package>
MPC class: <MPC_class> (model)
MPC_EXT: <MPC_EXT_class> (model overrides — usually empty)
DPC class: <DPC_class> (data provider — generated)
DPC_EXT: <DPC_EXT_class> (data provider — your custom code)
The MPC class's private DEFINE_* methods carry the full model. Read them.
SAPRead(type="CLAS", name="<MPC>", method="*")
You're looking for:
DEFINE_<entity> — one per entity typeDEFINE_ASSOCIATIONS — all associations + nav propertiesDEFINE_ACTIONS — all function importsFor each DEFINE_<entity> method:
SAPRead(type="CLAS", name="<MPC>", method="DEFINE_<ENTITY>")
Parse the body for:
create_entity_type(...) and
create_entity_set(...))bind_structure(...) or set_data_source(...))create_property(...) followed by set_* calls)SAPRead(type="CLAS", name="<MPC>", method="DEFINE_ASSOCIATIONS")
Parse for each create_association(...):
SAPRead(type="CLAS", name="<MPC>", method="DEFINE_ACTIONS")
Parse for each create_action(...):
After Phase 2, you should be able to print a complete table like:
=== Extracted OData model ===
Entity types (3):
Project bound to ZDM_PROJECT key: ProjectId 12 properties
Task bound to ZDM_TASK key: TaskId 15 properties (FK ProjectId)
TimeEntry bound to ZDM_TIMEENTRY key: EntryId 15 properties (FKs TaskId, ProjectId)
Entity sets (3): ProjectSet, TaskSet, TimeEntrySet
Associations (2):
Project_Tasks Project [1] ↔ Task [0..n] ref: ProjectId ↔ ProjectId nav on Project: Tasks
Task_TimeEntries Task [1] ↔ TimeEntry [0..n] ref: TaskId ↔ TaskId nav on Task: TimeEntries
Function imports (1):
ApproveProject POST in: ProjectId (Edm.String, len 10) return: Project (Entity, 1)
Print it and ask the user "Does this match what you expected?" before proceeding.
SAPRead(type="CLAS", name="<DPC_EXT>", method="*")
Categorize each redefined method:
| Method pattern | Maps to RAP |
|---|---|
<EntitySet>_GET_ENTITYSET | CDS view's read access — usually free in managed scenario |
<EntitySet>_GET_ENTITY | Same — free in managed scenario |
<EntitySet>_CREATE_ENTITY | BDEF create enabled (with optional determination/validation) |
<EntitySet>_UPDATE_ENTITY | BDEF update enabled |
<EntitySet>_DELETE_ENTITY | BDEF delete enabled |
/IWBEP/IF_MGW_APPL_SRV_RUNTIME~EXECUTE_ACTION | BDEF static or instance action(...) per function import |
For each redefined method, decide:
SELECT * FROM <table> WHERE <key> + MOVE-CORRESPONDING): drop entirely
— RAP managed scenario does this for free.SAPRead(type="CLAS", name="<DPC_EXT>", method="<METHOD_NAME>")
For mostly-trivial methods, this is a 30-line read. For complex business logic, expect 100+ lines and plan to rewrite carefully.
=== Extracted behavior ===
Read-only (drop, free in RAP managed):
PROJECTSET_GET_ENTITYSET, PROJECTSET_GET_ENTITY,
TASKSET_GET_ENTITYSET, TASKSET_GET_ENTITY,
TIMEENTRYSET_GET_ENTITYSET, TIMEENTRYSET_GET_ENTITY
Function imports → BDEF actions:
ApproveProject(ProjectId) ➜ static action ApproveProject parameter $self
sets Status='A' + admin fields, returns Project
(legacy did UPDATE + COMMIT WORK + reread; RAP managed
handles persistence — no manual COMMIT needed)
Custom create/update/delete: none (read-only service)
If _CREATE_ENTITY / _UPDATE_ENTITY / _DELETE_ENTITY were present, list them with notes
on what would become BDEF determinations / validations.
For each table identified in Phase 2 (e.g. ZDM_PROJECT, ZDM_TASK, ZDM_TIMEENTRY):
SAPRead(type="TABL", name="<table>")
Record:
ERNAM/ERDAT/ERZET/AENAM/AEDAT/AEZET vs modern abp_*)with foreign key clauses)The tables stay as-is — the new RAP CDS views read from them.
Before any write operation, present the complete RAP design as a design plan, get explicit user approval, then proceed.
Legacy → New RAP
ZDM_PROJECT (table, untouched) ─► ZR_DM_PROJECT (CDS root view entity)
ZC_DM_PROJECT (CDS projection view, exposed via service)
ZI_DM_PROJECT (BDEF for root)
ZDM_TASK (table, untouched) ─► ZR_DM_TASK + ZC_DM_TASK
ZDM_TIMEENTRY (table, untouched) ─► ZR_DM_TIMEENTRY + ZC_DM_TIMEENTRY
(no SEGW project on the new side) ─► ZUI_DM_PROJECTS (service definition — exposes ZC_*)
ZUI_DM_PROJECTS_O4 (service binding, OData V4 UI)
ZBP_DM_PROJECT (behavior pool class for actions)
root: ZR_DM_PROJECT
composition [0..*] of ZR_DM_TASK as _Tasks
composition [0..*] of ZR_DM_TIMEENTRY as _TimeEntries
Maps the SEGW associations 1:1 — except now they're compositions (parent owns children), which gives RAP managed lifecycle semantics for free.
ZI_DM_PROJECT (BDEF):
define behavior for ZR_DM_PROJECT alias Project
persistent table zdm_project
lock master
authorization master ( instance )
etag master last_changed_at
{
field ( readonly ) ProjectId;
action approve_project result [1] $self; ← matches SEGW ApproveProject
}
The approve_project action → handler in ZBP_DM_PROJECT → sets status='A' + admin fields
via the framework's MODIFY ENTITIES interface (no manual UPDATE + COMMIT).
Old (SEGW V2): /sap/opu/odata/sap/<legacy_service>
New (RAP V4): /sap/opu/odata4/sap/zui_dm_projects_o4/srvd_a2x/sap/zui_dm_projects/0001
(or the FLP-bound URL if the service binding is published as UI.)
Print the full plan to the user. Wait for explicit "yes, proceed" or modifications. If the user wants changes (different naming, draft enabled, different RAP scenario), revise and re-present.
Non-interactive mode. If the user's initial prompt already supplied the Phase-5 equivalent inputs (legacy service, target package, transport, scenario, draft on/off) AND said "run end-to-end" / "don't stop for review", skip the rhetorical "yes, proceed?" gate. Print the plan as a manifest and advance directly to Phase 5f / Phase 6. Only stop if Phase 5 surfaced a genuine conflict (missing input, contradictory scenario flags, etc.). See Run 6 findings — full-chain automations otherwise hit a phantom gate.
Before leaving Phase 5, the skill MUST emit an artifact-list contract that Phase 6 will echo back with status. Print this verbatim:
=== Phase 6 will create + activate these artifacts (Phase 5 → Phase 6 contract) ===
Roots (CDS): [ ] ZR_DM_PROJECT [ ] ZR_DM_TASK [ ] ZR_DM_TIMEENTRY
Projections (CDS): [ ] ZC_DM_PROJECT [ ] ZC_DM_TASK [ ] ZC_DM_TIMEENTRY
(projections are MANDATORY — service binding never exposes roots directly)
Draft tables (TABL): [ ] ZDM_PROJECT_D [ ] ZDM_TASK_D [ ] ZDM_TIMEENTRY_D
(only when draft is on — based on Phase 5d)
BDEFs: [ ] ZR_DM_PROJECT (root behavior) [ ] ZC_DM_PROJECT (projection behavior)
Behavior pool (CLAS): [ ] ZBP_DM_PROJECT (empty shell — generate_behavior_implementation will populate)
Service definition: [ ] ZUI_DM_PROJECTS (exposes projections, NOT roots)
Service binding: [ ] ZUI_DM_PROJECTS_O4 (OData V4 UI)
Manual steps remaining after Phase 6 build:
[ ] /IWFND/MAINT_SERVICE — register V4 service group ZUI_DM_PROJECTS_O4 (else 403 at runtime)
Phase 6 must walk through this list at the end and print the same checkbox list with each
item marked ✓ or ✗ (with the failing tool call args + response on ✗). Do NOT declare
Phase 6 done until every box is ✓ or explicitly waived by the user.
If <target_package> package contains objects from a previous run, delete them first:
SAPRead(type="DEVC", name="<target_package>")
For each object in reverse dependency order (SRVB → SRVD → BDEF → DDLS_C → DDLS → CLAS → TABL):
SAPWrite(action="delete", type="<type>", name="<name>", transport="<transport>")
(SAP recommends releasing the transport before re-running, but for resettable demos we just
reuse <transport> — it's fine.)
Plus a TADIR cross-check (Run 1 found a stub draft table sitting in a different transport
that the package scan missed; Run 6 found that ADT 404s can coexist with TADIR ghost rows —
the "split-brain" failure mode). On ARC-1 ≥ 0.9.5 (PR #270), the canonical reset truth
comes from source="both" mode, which queries ADT and DB TADIR in one call and emits a
splitBrain warning array for any divergent names:
SAPSearch(searchType="tadir_lookup",
source="both",
names=["ZDM_PROJECT_D","ZDM_TASK_D","ZDM_TIMEENTRY_D",
"ZR_DM_PROJECT","ZR_DM_TASK","ZR_DM_TIMEENTRY",
"ZC_DM_PROJECT","ZC_DM_TASK","ZC_DM_TIMEENTRY",
"ZI_DM_PROJECT_BEH","ZI_DM_TASK_BEH","ZI_DM_TIMEENTRY_BEH",
"ZBP_DM_PROJECT","ZUI_DM_PROJECTS","ZUI_DM_PROJECTS_O4"])
Read the response in this order:
results — names found via ADT discovery (the "is the object actually live"
question). Empty = the package is clean from ADT's perspective.splitBrain — names where ADT and DB disagree (the "ghost" cases). For each, the
array entry tells you which source saw it. The pragmatic interpretation: if a name is
in DB-only (TADIR row, no ADT object), it's a ghost — treat as already absent for the
purposes of SAPWrite create. If it's in ADT-only (rare), there's a stale ADT cache —
SAPSearch again after a few seconds.source="both" requires sql scope (the DB leg uses the free-SQL path). If your profile
doesn't have it, fall back to source="adt" (default) for the live check + SAPQuery for
a broader package sweep:
SAPQuery(action="sql", sql="SELECT obj_name, object, devclass, korrnum FROM tadir
WHERE devclass = '<target_package>' AND obj_name LIKE 'Z%'")
If a planned name appears in a different package, it's a leftover stub — delete via its actual transport before proceeding.
If the official SAP ABAP MCP server is connected alongside ARC-1 (ships with ABAP Development
Tools for VS Code; enabled in Eclipse ADT 3.60+; appears as the abap-mcp server), its
Generate ABAP Repository Objects framework can scaffold the root managed+draft BO for you.
It does not replace this skill — Phases 1–5 (discovery + translation) and most of Phase 6 are
still ARC-1's job, because the generator is single-entity and one-shot:
"a maximum of one entity, no compositions or hierarchies" and "not intended for post-generation — subsequent changes require manual developer intervention."
So it fits a SEGW migration only when the legacy service is effectively a single root entity,
managed+draft, whose table carries the modern timestamp fields last_changed : abp_lastchange_tstmpl
and local_last_changed : abp_locinst_lastchange_tstmpl. Most SEGW services are multi-entity with
classic ERDAT/AEDAT admin fields and function imports → the generator does not apply; use the
manual build (6b) as normal. Where it does apply, it only seeds the root; ARC-1 still adds the
children, compositions, actions, and field mappings afterward (the generator can't, being one-shot).
If you use it:
abap_generators-list_generators tool (the abap-mcp server) → skip to 6b, build manually.abap_generators-list_generators,
match the "OData UI Service" generator by display name, use the returned id (proven live:
uiservice on SAP_BASIS 758 / S4 2023; ui-service on 816 / ABAP Platform 2025; 816 also has
x-ui-service "from scratch"). Not listed → skip to 6b.abap_generators-get_schema(generatorId, packageName="<target_package>", referencedObjectType="TABL", referencedObjectName="<root table, e.g. ZDM_PROJECT>"), fill every
required field from the returned schema, then abap_generators-generate_objects(generatorId, …).
This is a mutation — target <target_package> + <transport>, same guardrails as any write.approve_project) + handler body, and activation —
adapting the names the generator chose for the root. Reconcile its root CDS/BDEF naming with this
skill's ZR_*/ZC_*/ZI_*_BEH plan, or accept the generator's names and update Phase 5f's contract.State which path you took so the run stays auditable. When in doubt, prefer the manual build (6b) — it is the proven path for the multi-entity SEGW shape and never depends on a second MCP server.
The order below was learned the hard way (Run 1, calls #22–33). Do not reorder. Each step prevents a specific failure mode in the next.
SAPWrite(action="batch_create", objects=[
{ type: "TABL", name: "ZDM_PROJECT_D", source: "<draft table source — see template below>", package: "<target_package>", transport: "<transport>" },
{ type: "TABL", name: "ZDM_TASK_D", source: "<...>", package: "<target_package>", transport: "<transport>" },
{ type: "TABL", name: "ZDM_TIMEENTRY_D", source: "<...>", package: "<target_package>", transport: "<transport>" }
])
SAPActivate(action="activate", objects=[ {type:"TABL", name:"ZDM_PROJECT_D"}, ... ])
Draft table template — three rules learned in Run 2:
projectid, startdate,
not project_id, start_date). ABAP normalizes BDEF aliases to uppercase no-underscore;
draft binding looks up by that name, not by the active table's snake_case.creationtimestamp and lastchangedstamp columns required for total etag +
etag master clauses in the BDEF to bind.@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE is mandatory — without it
the TABL save fails on 7.58.@EndUserText.label : 'Demo: Project (draft shadow)'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #ALLOWED
define table zdm_project_d {
" Field names match the BO aliases (no underscore) — required by RAP draft binding.
" The active table ZDM_PROJECT can keep its snake_case names; the BDEF mapping bridges
" them. The draft table has no mapping clause, so its names must align with the BO directly.
key client : abap.clnt not null;
key projectid : abap.char(10) not null;
title : abap.char(100);
description : abap.char(255);
status : abap.char(1);
startdate : abap.dats;
enddate : abap.dats;
erdat : abap.dats;
erzet : abap.tims;
ernam : abap.char(12);
aedat : abap.dats;
aezet : abap.tims;
aenam : abap.char(12);
" Required for `total etag CreationTimeStamp` + `etag master LastChangedStamp` in BDEF.
" Computed in CDS root view via dats_tims_to_tstmp(...); persisted on draft instances.
creationtimestamp : abap.dec(15,0);
lastchangedstamp : abap.dec(15,0);
include sych_bdl_draft_admin_inc;
}
ZDM_TASK_D mirrors with taskid + projectid (FK), all task-specific columns aliased
no-underscore. ZDM_TIMEENTRY_D mirrors with entryid + taskid + projectid (FKs).
ZR_DM_*)Top-down: parent first, then children. Use composition [0..*] of <child> as <name> —
no on clause for managed scenario.
If your legacy DPC_EXT did Aedat → Erdat fallback (run 1 found this), bake it into the root
view's timestamp computation. On 7.58 use @Semantics.systemDateTime.createdAt,
lastChangedAt, and localInstanceLastChangedAt — the last annotates the local-instance
etag field (abp_locinst_lastchange_tstmpl) that a managed-draft BO needs, and is exactly what
SAP's own RAP generator emits on 758. Verified live on S/4HANA 2023: standalone views with
localInstanceLastChangedAt and with @Semantics.businessDate.from/to both activate. Only
genuinely old releases (< 7.55, before draft support) lack these — do NOT strip them on 7.58.
@Semantics.systemDateTime.createdAt : true
dats_tims_to_tstmp(
zdm_project.erdat,
zdm_project.erzet,
abap_system_timezone($session.client, 'NULL'),
$session.client,
'NULL'
) as CreationTimeStamp,
@Semantics.systemDateTime.lastChangedAt : true
dats_tims_to_tstmp(
case zdm_project.aedat when '00000000' then zdm_project.erdat else zdm_project.aedat end,
case zdm_project.aedat when '00000000' then zdm_project.erzet else zdm_project.aezet end,
abap_system_timezone($session.client, 'NULL'),
$session.client,
'NULL'
) as LastChangedStamp
Activation order for composition CDS. The default
batch_createactivates each object inline in array order, so the parentcomposition [0..*] of ZR_DM_TASK as _Tasksactivates beforeZR_DM_TASKexists, hitting "data sourceZR_DM_TASKdoes not exist" (Run 6 reproducer). Three safe paths, pick whichever fits the run:
batch_createwithactivateAtEnd: true(ARC-1 ≥ 0.9.5, PR #270). Writes inactive drafts for every object, then issues one terminalactivateBatchso SAP's activator resolves the cross-references in a single pass. This is the recommended pattern when ARC-1 supports it — single tool call, no recovery dance:SAPWrite(action="batch_create", activateAtEnd: true, objects=[...])- Per-file
SAPWrite(action="create")for each root, then a singleSAPActivate(action="activate", objects=[...])batch at the end (after every DDL source has landed). Works on every ARC-1 release.- Manual bottom-up:
SAPWrite createparent →SAPActivatechild first → activate parent last. More tool calls; rarely needed.The same fix applies to Step 3 projections (composition chain mirrored). Activate the projection trio together at the end, not inline.
ZC_DM_*) — DO NOT SKIPThis is where Run 1 silently dropped the layer. A provider contract error here means
"create projections", NOT "remove the contract".
Critical: provider contract transactional_interface goes on the root projection
ONLY (ZC_DM_PROJECT). Child projections (ZC_DM_TASK, ZC_DM_TIMEENTRY) use bare
as projection on … and inherit the BO contract via redirected to parent. Putting
the contract on children yields "inappropriate provider contract on …" on activation
(Run 2).
Pick the contract based on scenario:
provider contract transactional_query
(Run 3 evidence on 7.58: combining projection BDEF + use draft rejects transactional_interface).provider contract transactional_interface.@AccessControl.authorizationCheck : #NOT_REQUIRED
@EndUserText.label : 'Demo project — UI projection (root)'
@Metadata.allowExtensions: true
define root view entity ZC_DM_PROJECT
provider contract transactional_query
as projection on ZR_DM_PROJECT
{
key ProjectId,
Title,
Description,
Status,
StartDate,
EndDate,
Erdat, Erzet, Ernam, Aedat, Aezet, Aenam,
CreationTimeStamp,
LastChangedStamp,
_Tasks : redirected to composition child ZC_DM_TASK
}
redirected to parent@AccessControl.authorizationCheck : #NOT_REQUIRED
@EndUserText.label : 'Demo task — UI projection (child)'
@Metadata.allowExtensions: true
define view entity ZC_DM_TASK
as projection on ZR_DM_TASK
{
key TaskId,
ProjectId,
Title, Description, Status, Priority, DueDate, AssignedTo, EstimatedHours,
Erdat, Erzet, Ernam, Aedat, Aezet, Aenam,
_Project : redirected to parent ZC_DM_PROJECT,
_TimeEntries : redirected to composition child ZC_DM_TIMEENTRY
}
Same shape for ZC_DM_TIMEENTRY (no contract, _Task : redirected to parent ZC_DM_TASK).
Note
define view entity(notdefine root view entity) on children — only the BO root hasdefine root view entity.
Write only the empty global class shell. The local handler classes (lhc_project,
lhc_task, lhc_timeentry) are no longer pre-created here — Step 7's
SAPWrite action=generate_behavior_implementation (PR-C, ARC-1 ≥ post-2026-05-10)
auto-creates them when missing, then injects every required handler signature and stub
in one call. Run 2's mandatory ADT-paste pause for CCDEF/CCIMP is gone.
SAPWrite(action="create", type="CLAS", name="ZBP_DM_PROJECT",
source="CLASS zbp_dm_project DEFINITION
PUBLIC ABSTRACT FINAL
FOR BEHAVIOR OF zr_dm_project.
ENDCLASS.
CLASS zbp_dm_project IMPLEMENTATION.
ENDCLASS.",
description="Behavior pool for ZR_DM_PROJECT BO",
package="<target_package>", transport="<transport>")
SAPActivate(type="CLAS", name="ZBP_DM_PROJECT")
Expected activation behavior. The
SAPActivatehere may return "no behavior definition forzr_dm_project" — that's expected and OK. The BDEF doesn't exist yet; it's created in Step 5. Don't treat this as a real failure. The class will activate cleanly once the BDEF is in place; Step 7 (generate_behavior_implementation) re-activates the class with handlers anyway. Either:
- Skip activation here (write only) and rely on Step 7 to activate.
- Or accept the warning and re-activate the class as part of Step 10's batch activate.
That's it. No CCDEF/CCIMP write here. No ADT pause. Continue to Step 5.
Run 1 calls #22–31 discovered ten managed-with-draft rules the hard way. Bake them in:
managed implementation in class zbp_dm_project unique;
strict ( 2 );
with draft;
define behavior for ZR_DM_PROJECT alias Project
persistent table zdm_project
draft table zdm_project_d
lock master
total etag CreationTimeStamp " <- required by `with draft`
etag master LastChangedStamp
authorization master ( instance )
{
create;
update;
delete;
field ( readonly : update ) ProjectId;
field ( readonly ) CreationTimeStamp, LastChangedStamp;
" composition associations: only `create` is allowed in the inline block
association _Tasks { create; with draft; }
draft action Edit;
draft action Activate optimized;
draft action Discard;
draft action Resume;
draft determine action Prepare; " <- 'determine' keyword required, not just 'draft action'
action approve_project result [1] $self; " <- DO NOT remove on activation failure;
" Step 7 (generate_behavior_implementation)
" auto-creates the lhc_project skeleton and
" injects the handler in one call.
mapping for zdm_project { … all field maps … }
}
define behavior for ZR_DM_TASK alias Task
persistent table zdm_task
draft table zdm_task_d
lock dependent by _Project " <- not _Project of root; use the cross-BO assoc you added in roots
etag dependent by _Project
authorization dependent by _Project
{
update; delete;
field ( readonly : update ) TaskId, ProjectId; " <- lock-by reference fields MUST be readonly
association _Project { with draft; }
association _TimeEntries { create; with draft; }
" NO draft Edit/Activate/Discard/Resume — those belong on the root only.
mapping for zdm_task { … }
}
define behavior for ZR_DM_TIMEENTRY alias TimeEntry
persistent table zdm_timeentry
draft table zdm_timeentry_d
lock dependent by _Project " <- not by _Task — chain must reach a `lock master`
etag dependent by _Project
authorization dependent by _Project
{
update; delete;
field ( readonly : update ) EntryId, TaskId, ProjectId;
association _Project { with draft; }
association _Task { with draft; }
mapping for zdm_timeentry { … }
}
If TimeEntry can only reach _Task and not _Project, you must add a cross-BO
association to ZR_DM_PROJECT as _Project in ZR_DM_TIMEENTRY (root view) — the lock
chain has to terminate at the entity that holds lock master.
Declares the behavior alias for the projection. Run 6 found that 7.58 enforces two non-obvious syntax rules in projection BDEFs — bake them in:
use draft; at the top (not inside the body) when the root BDEF declared
with draft. Without it, use action Edit/Activate/Discard/Resume/Prepare is rejected.use association _X { ... } blocks, write bare operation names (create;),
not use create; — the use keyword belongs at the top level only.Canonical projection BDEF for the root projection (managed + draft + CUD scenario):
projection;
strict ( 2 );
use draft;
define behavior for ZC_DM_PROJECT alias Project
use etag
{
use create;
use update;
use delete;
use action Edit;
use action Activate;
use action Discard;
use action Resume;
use action Prepare;
use action approve_project;
use association _Tasks { create; } " <- bare 'create;' inside, NOT 'use create;'
}
Child projections (ZC_DM_TASK, ZC_DM_TIMEENTRY) need their own projection BDEF blocks
in the same DDLS or as separate definitions, each using
use association _Project / _Task to expose the upward composition for the lock-master
chain. Bare create; inside association bodies, same rule.
SAPWrite(action="generate_behavior_implementation", type="CLAS", name="ZBP_DM_PROJECT")
That single call (PR-C, ARC-1 ≥ post-2026-05-10) does:
<class:rootEntityRef> to auto-discover the bound
BDEF (no need to pass bdefName).FOR BEHAVIOR OF zr_dm_project and the BDEF's
managed implementation in class zbp_dm_project unique agree. Refuses to mutate
on mismatch.scaffold_rap_handlers uses, with autoApply=true:
lhc_<alias> skeletons (CCDEF + CCIMP).METHODS … signatures for every action / determination / validation /
authorization the BDEF requires.METHOD … ENDMETHOD. stubs in CCIMP.Returns a structured JSON report including discovery, validation, scaffoldChanged,
counts, and activation.success. If activation fails with the well-known "Local classes
of CL_ABAP_BEHAVIOR_HANDLER…" stale-active coupling, the response includes a guided
activation.hint with concrete recovery options instead of throwing — the just-written
CCDEF/CCIMP source remains useful for both recovery paths.
If activation fails for any non-obvious reason, run SAPDiagnose(action="object_state", type="CLAS", name="ZBP_DM_PROJECT") (PR #254) to see active vs inactive divergence per
include before retrying. It surfaces the exact include that's out of sync without dumping
raw source.
Lower-level alternative: if you need to scaffold against an existing populated class
or want a dry-run-style preview without auto-activating, use scaffold_rap_handlers
directly with autoApply=true|false and bdefName explicit. Prefer
generate_behavior_implementation for fresh behavior pools.
edit_methodSAPWrite(action="edit_method", type="CLAS", name="ZBP_DM_PROJECT",
method="lhc_project~approve_project",
source="
METHOD approve_project.
READ ENTITIES OF zr_dm_project IN LOCAL MODE
ENTITY Project FIELDS ( ProjectId Status Aedat Aezet Aenam )
WITH CORRESPONDING #( keys )
RESULT DATA(projects).
MODIFY ENTITIES OF zr_dm_project IN LOCAL MODE
ENTITY Project
UPDATE FIELDS ( Status Aedat Aezet Aenam )
WITH VALUE #( FOR p IN projects (
%tky = p-%tky
Status = 'A'
Aedat = sy-datum
Aezet = sy-uzeit
Aenam = sy-uname
) ).
READ ENTITIES OF zr_dm_project IN LOCAL MODE
ENTITY Project ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(updated).
result = VALUE #( FOR u IN updated ( %tky = u-%tky %param = u ) ).
ENDMETHOD.",
transport="<transport>")
(No COMMIT WORK — the framework saves automatically on action commit.)
SAPWrite(action="batch_create", objects=[
{ type: "SRVD", name: "ZUI_DM_PROJECTS",
source: "@EndUserText.label : 'Demo Project Manager (V4)'\ndefine service ZUI_DM_PROJECTS {\n expose ZC_DM_PROJECT as Project;\n expose ZC_DM_TASK as Task;\n expose ZC_DM_TIMEENTRY as TimeEntry;\n}",
package: "<target_package>", transport: "<transport>" },
{ type: "SRVB", name: "ZUI_DM_PROJECTS_O4",
bindingType: "ODataV4-UI",
serviceDefinition: "ZUI_DM_PROJECTS",
package: "<target_package>", transport: "<transport>" }
])
Note: SRVD exposes
ZC_DM_*(projections), neverZR_DM_*(roots). If you exposed roots, the projection layer is missing — go back to Step 3.
SAPActivate(action="activate", objects=[
{ type: "DDLS", name: "ZR_DM_PROJECT" },
{ type: "DDLS", name: "ZR_DM_TASK" },
{ type: "DDLS", name: "ZR_DM_TIMEENTRY" },
{ type: "DDLS", name: "ZC_DM_PROJECT" },
{ type: "DDLS", name: "ZC_DM_TASK" },
{ type: "DDLS", name: "ZC_DM_TIMEENTRY" },
{ type: "BDEF", name: "ZR_DM_PROJECT" },
{ type: "BDEF", name: "ZC_DM_PROJECT" },
{ type: "CLAS", name: "ZBP_DM_PROJECT" },
{ type: "SRVD", name: "ZUI_DM_PROJECTS" },
{ type: "SRVB", name: "ZUI_DM_PROJECTS_O4" }
])
SAPActivate(action="publish_srvb", name="ZUI_DM_PROJECTS_O4")
publish_srvb activates the binding at ADT level. On most 7.5x systems there are two
runtime URL shapes that consume the binding, and only one needs Gateway-hub registration:
| Path | Behavior on a fresh publish_srvb (no IWFND step) |
|---|---|
Gateway hub — http://<host>:50000/sap/opu/odata4/sap/<srvb>_o4/srvd_a2x/sap/<srvb_short>/0001/ | Returns 403 with IWBEP/CM_V4_COS/136 — Service 'ZUI_DM_PROJECTS' repository 'SRVD_A2X' is not assigned to group 'ZUI_DM_PROJECTS_O4'. Needs registration. |
SRVD-direct — https://<host>:50001/sap/opu/odata4/sap/<srvb>_o4/srvd/sap/<srvb>/0001/ | Returns 200 without IWFND registration — verified Run 5 + Run 6. Path segment srvd/sap/<srvb> (not srvd_a2x/sap/<srvb_short>). |
Decision:
If you do need the manual step, in SAP GUI:
/n/IWFND/MAINT_SERVICE
Add Service → System Alias = LOCAL → Filter "ZUI_DM_PROJECTS*" → Get Services
Select ZUI_DM_PROJECTS_O4 → Add Selected Services → Package <target_package> → Transport <transport>
(Or, on systems with the V4-specific transaction:)
/n/IWFND/V4_ADMIN
→ Service Group "ZUI_DM_PROJECTS_O4" → Assign SRVD_A2X service "ZUI_DM_PROJECTS"
Note: the hub path can return a transient 503 CM_V4_RUNTIME/000 ("Service alias cache
outdated") immediately after publish_srvb, followed by 403 COS/136 on the next call.
Distinct symptoms — the 503 is an alias-cache wedge, the 403 is the missing registration.
Wait a few seconds and re-test before assuming the registration is the cause.
Walk the artifact-list contract from Phase 5f and print every box's status:
=== Phase 6 contract echo ===
Roots (CDS): [✓] ZR_DM_PROJECT [✓] ZR_DM_TASK [✓] ZR_DM_TIMEENTRY
Projections (CDS): [✓] ZC_DM_PROJECT [✓] ZC_DM_TASK [✓] ZC_DM_TIMEENTRY
Draft tables (TABL): [✓] ZDM_PROJECT_D [✓] ZDM_TASK_D [✓] ZDM_TIMEENTRY_D
BDEFs: [✓] ZR_DM_PROJECT (root) [✓] ZC_DM_PROJECT (projection)
Behavior pool (CLAS): [✓] ZBP_DM_PROJECT (with lhc_project, lhc_task, lhc_timeentry skeletons)
[✓] approve_project handler stub injected via scaffold_rap_handlers
[✓] approve_project body filled via edit_method
Service definition: [✓] ZUI_DM_PROJECTS (exposes 3 projections)
Service binding: [✓] ZUI_DM_PROJECTS_O4 (published at ADT level)
Manual steps remaining:
[✗ — waiting on user] /IWFND/MAINT_SERVICE V4 group registration
If any box is ✗ and not waived, do not advance to Phase 7. Either fix or escalate to
the user.
Verify the new V4 service returns the same data as the legacy V2.
curl -u "$USER:$PASS" "<base>/sap/opu/odata/sap/<legacy_service>/ProjectSet/\$count"
# expect: 5
curl -u "$USER:$PASS" "<base>/sap/opu/odata/sap/<legacy_service>/ProjectSet('PRJ-0001')/Tasks/\$count"
# expect: 3
Try both in parallel. The SRVD-direct path usually works without the Step 12 manual registration; the Gateway hub path requires it.
# SRVD-direct (HTTPS, port 50001, /srvd/sap/<srvb>/ path) — preferred, no Step 12 needed
NEW_BASE_DIRECT="https://<host>:50001/sap/opu/odata4/sap/zui_dm_projects_o4/srvd/sap/zui_dm_projects_o4/0001"
# Gateway hub (HTTP, port 50000, /srvd_a2x/sap/<srvb_short>/ path) — needs Step 12
NEW_BASE_HUB="<base>/sap/opu/odata4/sap/zui_dm_projects_o4/srvd_a2x/sap/zui_dm_projects/0001"
# Smoke-test the SRVD-direct path first
curl -s -o /dev/null -w "%{http_code}\n" -u "$USER:$PASS" \
"$NEW_BASE_DIRECT/Project?\$top=1&sap-client=001"
# 200 = good; 403 = check Gateway hub registration; 503 = transient alias cache, retry
Pick whichever path returned 200, then use it for the rest of Phase 7. Pin the working URL into RUN-NOTES for the demo.
For a service generated from a managed-with-draft BDEF, the entity keys exposed in
$metadata are composite: every primary key field PLUS IsActiveEntity (Edm.Boolean).
Bare Project('PRJ-0001') returns 400; the correct form is:
NEW_BASE="$NEW_BASE_DIRECT" # or $NEW_BASE_HUB after Step 12
# Counts — same as legacy V2 if the migration is byte-equivalent
curl -u "$USER:$PASS" "$NEW_BASE/Project/\$count?sap-client=001"
# expect: 5
# Composite key — note ProjectId AND IsActiveEntity, not just ProjectId
curl -u "$USER:$PASS" \
"$NEW_BASE/Project(ProjectId='PRJ-0001',IsActiveEntity=true)/_Tasks/\$count?sap-client=001"
# expect: 3
Filter the active row set explicitly via ?$filter=IsActiveEntity eq true if you want
parity with legacy V2's no-draft semantics.
V4 bound actions on draft entities need both X-CSRF-Token (with session cookies) and
If-Match: <etag>. Missing If-Match returns 428 CX_OD_PRECOND_REQUIRED.
# 1. Fetch CSRF token + session cookies
curl -s -u "$USER:$PASS" -c /tmp/cookies.txt \
-H "X-CSRF-Token: Fetch" \
"$NEW_BASE/?sap-client=001" -D - -o /dev/null | grep -i 'x-csrf-token'
# 2. Get the current etag for the target row
ETAG=$(curl -s -u "$USER:$PASS" -b /tmp/cookies.txt \
"$NEW_BASE/Project(ProjectId='PRJ-0003',IsActiveEntity=true)?sap-client=001" \
-D - -o /dev/null | grep -i 'etag' | awk '{print $2}' | tr -d '\r')
# 3. POST the action with both headers
curl -u "$USER:$PASS" -b /tmp/cookies.txt -X POST \
"$NEW_BASE/Project(ProjectId='PRJ-0003',IsActiveEntity=true)/com.sap.gateway.srvd.zui_dm_projects.v0001.approve_project?sap-client=001" \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <from step 1>" \
-H "If-Match: $ETAG" \
-d '{}'
# expect: 200 + entity with Status="A"
If counts don't match, the CDS where-clauses are likely wrong — re-read the legacy DPC_EXT
methods for filters you missed. If actions fail with 412/428 even with both headers, the
service likely needs a fresh publish_srvb to pick up newer BDEF action declarations.
Re-run this skill — Phase 6a deletes everything in <target_package> first, so multiple runs
are safe. Transport <transport> stays in D (modifiable) state until you explicitly release
it. To clear the transport too:
SAPTransport(action="release_recursive", number="<transport>")
SAPTransport(action="create", description="ARC-1 RAP migration outputs (resettable)", transportType="K")
…then update the transport ID in this skill's smart defaults.
| Symptom | Likely cause | Fix |
|---|---|---|
Phase 0 — rap.available=false | System release < 7.54 OR ABAP-Cloud-mode disabled | Migration cannot proceed; tell user |
| Phase 0 — preflight DDLS create returns 403 | S_DEVELOP ACTVT=01,02 missing for DDLS in target package | User runs SU53 in SAP GUI; basis admin grants role |
| Phase 1 — MPC class not found by convention | Service uses non-standard naming or is in a sub-package | Fall back to TADIR query (1a alt path) |
Phase 2 — DEFINE_<entity> body uses helper methods you can't parse | SEGW uses define_<entity>_property_<n>(...) calls | Read those helper methods too — SAPRead method="DEFINE_PROJECT_PROPERTY_1" |
Phase 3 — EXECUTE_ACTION body has multiple IF iv_action_name = 'X' branches | One DPC_EXT, multiple function imports | Translate each branch to a separate BDEF action |
| Phase 6 — activation fails with "released-API contract violation" | Legacy DPC_EXT used unreleased ABAP API; CDS where-clause inherits the same | Replace with released equivalent (use mcp__sap-docs__search to look up modern API) |
Phase 6 — scaffold_rap_handlers returns 0 missing methods | Activation order inverted | Activate BDEF before scaffolding the behavior pool — scaffold_rap_handlers reads activated BDEF |
Phase 7 — V4 Project/$count differs from V2 ProjectSet/$count | CDS view has implicit filter (e.g. WHERE delivered_status <> 'X') you forgot | Re-read DEFINE_PROJECT for any set_filter_* calls in MPC |
Phase 7 — V4 _Tasks returns empty | Composition wrong direction or missing referential constraint | Inspect ZR_DM_PROJECT source; the composition of clause must reference the child by FK |
Phase 7 — V4 endpoint returns 403 with IWBEP/CM_V4_COS/136 … not assigned to group | Skipped Phase 6 Step 12 (V4 routing-group registration) | User runs /n/IWFND/MAINT_SERVICE Add Service for ZUI_DM_PROJECTS_O4 |
Phase 6 Step 5 — BDEF activation rejects association _X { update; delete; } | Only create is allowed in association inline block | Remove update/delete from association blocks; the child entity's own behavior block declares them |
Phase 6 Step 5 — BDEF rejects draft action Prepare | Must be draft determine action Prepare (the determine keyword is mandatory) | Fix to draft determine action Prepare |
Phase 6 Step 5 — BDEF rejects child entity Edit/Activate/Discard/Resume declarations | Draft lifecycle actions belong on the root only (the entity with lock master) | Move them to root; children only need composition associations + update;/delete; |
Phase 6 Step 5 — BDEF rejects with draft without total etag | RAP requires a total etag field when draft is on | Add total etag CreationTimeStamp (or your timestamp field) to the root behavior |
Phase 6 Step 5 — BDEF rejects child lock dependent by _Task if _Task itself has no lock master | Lock chain must terminate at a lock master entity | Add cross-BO association to ZR_DM_<root> as _Project in the grandchild root view, and use lock dependent by _Project in the BDEF |
Phase 6 Step 5 — BDEF rejects child entity ProjectId updates when used in lock dependent by _Project | Lock-by reference fields must be readonly | Add field ( readonly : update ) <field> to the child behavior block |
Phase 6 Step 5 — BDEF rejects strict ( 2 ) without authorization on every entity | strict ( 2 ) enforces explicit auth declarations | Add authorization master ( instance ) on root, authorization dependent by _Project on children. Step 7 (generate_behavior_implementation) auto-creates the matching lhc_<alias> skeletons and injects the get_instance_authorizations stub — no manual pre-create required. Do NOT drop strict ( 2 ) as the workaround. |
Phase 6 Step 7 — generate_behavior_implementation returns activation: { success: false, hint: <stale-active recovery> } | The well-known stale-active CCDEF/CCIMP coupling: active includes are SAP placeholder comments while inactive includes now contain real handlers, and RAP refuses the inactive→active transition. | Two recovery options in the hint: (a) activate the class once via Eclipse "Generate Behavior Implementation" wizard (it bypasses the coupling), or (b) SAPManage(action="delete", type="CLAS") + SAPWrite(action="create") + rerun generate_behavior_implementation against the freshly created class. The just-written CCDEF/CCIMP source is correct in either path. |
Phase 6 — CDS rejects composition [0..*] of <child> as <name> on … | Managed compositions don't take an on clause; key linking is implicit / via with foreign key in the child | Remove the on clause; do NOT switch to association to |
Phase 6 — CDS rejects provider contract transactional_interface on root view | The contract is only valid on projection views (ZC_DM_*) | This error means you're missing the projection layer — create the ZC_DM_* projection (Step 3) and put the contract there |
Phase 6 — CDS rejects tstmp_from_dat_tim(...) | Function name wrong | Use dats_tims_to_tstmp(...) (note the order: dats first, tims second) |
Phase 6 — Batch activate emits ED 064 — "no next/previous object found" warning | Benign batch-activate quirk in 7.58 | Ignore the warning; if all objects show active, batch succeeded. If a real error mixed in, retry with single-object activate. |
Phase 6a — SAPWrite create returns "object exists" but Phase 6a reset showed empty package | Object exists in a different transport, not the target package | Query TADIR for the name across all packages: SAPQuery sql="SELECT * FROM tadir WHERE obj_name = '<name>'". If found, delete it first using its actual transport, then retry create. |
Phase 6 Step 2/3 — CDS genuinely rejects @Semantics.systemDateTime.localInstanceLastChangedAt or @Semantics.businessDate.* as "unknown annotation" | The system is pre-7.55 (before draft support). On 7.58 these are valid — verified live on S/4HANA 2023; SAP's own generator emits localInstanceLastChangedAt on 758 | On 7.58 keep them. Only on a genuinely older release (< 7.55) fall back to createdAt/lastChangedAt and omit the local-instance / businessDate annotations. |
Phase 6 Step 3 — CDS rejects "inappropriate provider contract on ZC_DM_<child>" | provider contract transactional_interface was put on a child projection | Remove the contract from ZC_DM_TASK / ZC_DM_TIMEENTRY. The contract goes only on the root projection (ZC_DM_PROJECT). Children declare bare as projection on … and use redirected to parent for upward navigation. |
Phase 6 Step 5 — BDEF activation rejects with "key field PROJECTID expected at position 2, found PROJECT_ID" (or similar field-name mismatch on a draft table) | Draft table was written with snake_case field names (project_id, start_date) instead of the BO-alias-normalized names (projectid, startdate) | Rewrite the draft TABL with field names matching the BO aliases: drop underscores. Active table can keep snake_case (BDEF mapping bridges); draft table cannot. Use update + activate via SAPWrite — Run 2 confirmed this works. |
Phase 6a — SAPQuery returns HTTP 400 on a long WHERE obj_name IN ('a','b','c',…) filter | ARC-1 ≥ 0.10.0 auto-chunks simple long IN (…) literal lists (PR #254). Older builds and complex filters (NOT IN, multi-SELECT, subqueries) still hit 400. | Prefer SAPSearch(searchType="tadir_lookup", source="both", names=[...]) for cross-package existence checks (PR #256 + PR #270) — no SQL needed; the splitBrain warning array surfaces ADT/DB divergence in one call. As broader-sweep fallback, SAPQuery with devclass = '<package>'. |
mcp__sap-docs__search whenever the legacy DPC_EXT body contains an unfamiliar API call —
it'll tell you the released-cloud equivalent.mcp__sap-docs__abap_feature_matrix to check which RAP features are available on the
user's release (e.g. etag master, unmanaged save, lock master).generate-rap-service-researched.md is the canonical creator. This skill's job is the
discovery + translation; delegating the actual create+activate to the deeper skill keeps
this one focused.npx claudepluginhub arc-mcp/arc-1 --plugin arc-1Generates a complete RAP OData UI service stack from a natural-language business object description — table, CDS views, behavior definitions, metadata extension, service definition, and behavior pool class.
Guides RAP (RESTful ABAP Programming Model) development: behavior definitions, EML, managed/unmanaged BOs, draft, actions, validations, determinations, side effects, and business events for ABAP Cloud Fiori apps.
Guides importing data from SAP S/4HANA and BW/4HANA into Datasphere, covering Cloud Connector setup, CDS views selection, ODP extraction configuration, and real-time delta loads.