diff --git a/skills/README.md b/skills/README.md index 689acb01..3161cf74 100644 --- a/skills/README.md +++ b/skills/README.md @@ -4,6 +4,8 @@ Best-practice agent skills for common SAP development workflows with ARC-1. Each skill is a directory containing a `SKILL.md` file with YAML frontmatter — the format used by [Anthropic Agent Skills](https://code.claude.com/docs/en/skills) and consumed by the [`vercel-labs/skills`](https://github.com/vercel-labs/skills) CLI. Agents discover them by `description` and load them on demand. +> **CAP-side audit toolkit lives in a separate repo.** For audit / hardening / CI gates of CAP + Fiori Elements V4 + BTP applications consuming S/4HANA Tier-2 services, see [`Raistlin82/sap-cap-toolkit`](https://github.com/Raistlin82/sap-cap-toolkit) (8 skills: `sap-cap-clean-core-enforce`, `sap-cap-customizing-honor`, `sap-cap-security-rbac-matrix`, `sap-fiori-app-audit`, `sap-cap-text-polish`, `sap-cap-stack-audit-full`, `sap-cap-ci-gates-pattern`, `sap-cap-fiori-battle-tested-patterns`). The skills in `arc-1` focus on ABAP-side custom-code refactoring (`sap-erp-clean-core-refactor` + `modernize-abap-*` chain); they generate CAP code that the audit toolkit in the companion repo verifies. + ## Install via the `skills` CLI (recommended) The fastest way is `npx skills` — it auto-detects the agents installed in your project (Claude Code, Cursor, GitHub Copilot, OpenCode, Gemini CLI, Codex, …) and installs into the right paths. @@ -100,6 +102,24 @@ Both skills produce the same RAP artifact stack. The difference is how they get | [migrate-custom-code](migrate-custom-code/SKILL.md) | Runs ATC readiness checks, groups findings by priority, and generates replacement code | Preparing custom code for S/4HANA migration or ABAP Cloud readiness | | [sap-object-documenter](sap-object-documenter/SKILL.md) | Batch-documents many custom objects at once — purpose, style (Classic/Modern/Mixed), dependencies — as Markdown | Onboarding packages, handoffs, seeding a repo wiki (vs. explain-abap-code which is single-object interactive) | +### SAP ERP — Clean Core Return + Side-by-Side Refactor (Preview) + +Plans and executes the refactor of custom ABAP / ERP / S/4HANA code back to Clean Core compliance — discovery via ARC-1, classification via `sap-clean-core-atc`, **just-in-time documentation lookup** against authoritative SAP sources (no pre-crawled KB), and hand-off to `modernize-abap-to-btp-cap` for side-by-side extension scaffolds. + +| Skill | What it does | When to use | +|---|---|---| +| [sap-erp-clean-core-refactor](sap-erp-clean-core-refactor/SKILL.md) | Inventories Z/Y custom code via ARC-1, classifies Clean Core Level A/B/C/D, consults authoritative SAP sources just-in-time (git-clone of `abap-atc-cr-cv-s4hc` + curated `SAP-samples` + `cloud-sdk`; Apify on-demand for `api.sap.com` / `help.sap.com` / `developers.sap.com` / `cap.cloud.sap` / community / blogs), and emits a per-object refactor plan with rewrite-in-place / extract-to-side-by-side / keep-at-B decisions. Apify lookups bounded per finding (~5 pages); user pays for own Apify account; results cached locally for 30 days | Planning a Clean Core compliance program; pre / post-S/4HANA migration cleanup; quarterly governance review | + +The skill ships **four files** so it is fully self-contained: [`SKILL.md`](sap-erp-clean-core-refactor/SKILL.md) (the protocol), [`SOURCES.md`](sap-erp-clean-core-refactor/SOURCES.md) (curated catalog of 23 authoritative SAP sources organized in 4 tiers), [`PATTERNS.md`](sap-erp-clean-core-refactor/PATTERNS.md) (~70 battle-tested production patterns in 8 categories — consulted during target resolution and side-by-side scaffold), and [`INTEGRATIONS.md`](sap-erp-clean-core-refactor/INTEGRATIONS.md) (step-by-step mapping of refactor phase × ARC-1 MCP tool × arc-1 skill × [secondsky/sap-skills](https://github.com/secondsky/sap-skills) plugin × external sources). **No centralized KB is shipped**; documentation lookups happen on-demand within a bounded per-finding budget, charged to the user's own Apify account at ~€0.005-0.02 per lookup (typical refactor: €0.50-€5 total). + +**Required companion plugins** (the skill is materially less useful without them — all four enforced as MUST in [`INTEGRATIONS.md`](sap-erp-clean-core-refactor/INTEGRATIONS.md)): +- **`sap-abap`** ([secondsky/sap-skills](https://github.com/secondsky/sap-skills/tree/main/plugins/sap-abap)) — ABAP language patterns reference, required during Step 6a `rewrite_in_place` so generated ABAP is Cloud-compatible. +- **`sap-abap-cds`** ([secondsky/sap-skills](https://github.com/secondsky/sap-skills/tree/main/plugins/sap-abap-cds)) — CDS view design reference, required when rewrite introduces new CDS views. +- **`sap-cap-capire`** ([secondsky/sap-skills](https://github.com/secondsky/sap-skills/tree/main/plugins/sap-cap-capire)) — CAP framework, ships 4 dispatchable agents (`cap-cds-modeler`, `cap-service-developer`, `cap-performance-debugger`, `cap-project-architect`) invoked during Step 6b side-by-side scaffold. +- **`sap-btp-developer-guide`** ([secondsky/sap-skills](https://github.com/secondsky/sap-skills/tree/main/plugins/sap-btp-developer-guide)) — comprehensive BTP reference, required during target resolution and scaffold generation. + +The skill engages **all 12 ARC-1 MCP tools** (`SAPRead` source + VERSIONS, `SAPWrite` in-place rewrite, `SAPContext` impact analysis — the most important call, `SAPLint` ATC + formatting, `SAPDiagnose` unit tests, `SAPTransport`, …) and **9 arc-1 native skills** as a delegation chain (`bootstrap-system-context`, `setup-abap-mirror`, `sap-clean-core-atc`, `sap-unused-code`, `explain-abap-code`, `sap-object-documenter`, `generate-abap-unit-test`, `generate-cds-unit-test`, `generate-rap-logic`, `modernize-abap-to-btp-cap` chain, `convert-ui5-to-fiori-elements`, `analyze-chat-session`). See [`INTEGRATIONS.md`](sap-erp-clean-core-refactor/INTEGRATIONS.md) for the full coverage table. + ### Clean Core & Custom Code Retirement | Skill | What it does | When to use | @@ -131,6 +151,42 @@ Both run against the same V4 RAP service produced by `migrate-segw-to-rap`. The | **Skill depends on** | ARC-1 + sap-docs + ui5-mcp-server + fiori-mcp | ARC-1 (optional) + sap-docs + ui5-mcp-server + browser MCP | | **Maturity** | Driven by `@sap-ux/fiori-mcp-server` 3-step API + annotation-discovery via `mcp__sap-docs__search` | 5 documented Critical Traps from accumulated run learnings; teaches LLM to investigate via Self-help patterns | +### BTP CAP Modernization (Preview) + +End-to-end greenfield migration from classic ABAP custom code (Z* packages) to BTP-native CAP applications. Side-by-side approach — leaves the source ABAP system untouched and produces a complete target CAP project (CDS schema, services, Fiori Elements V4 app, CF deployment artifacts) for review and manual activation. + +| Skill | What it does | When to use | +|---|---|---| +| [modernize-abap-to-btp-cap](modernize-abap-to-btp-cap/SKILL.md) | End-to-end migration orchestrator: Z package → BTP CAP scaffold (CDS + service + Fiori + xs-security + mta.yaml + ADRs) | Planning ECC / on-prem S/4 → BTP CAP greenfield migration | +| [modernize-abap-cap-schema](modernize-abap-cap-schema/SKILL.md) | Z-tables (SE11) → CAP CDS entities with DDIC→CDS type mapping, FK→association inference, `cuid`/`managed` aspect auto-application | Data-model migration step; standalone for reverse-engineering Z tables to CDS | +| [modernize-abap-cap-service](modernize-abap-cap-service/SKILL.md) | Z function modules / reports / classes → CAP service definitions + TypeScript handler stubs with TODO markers + ABAP source excerpts | Service-layer migration step; produces compile-clean scaffold ready for business-logic translation | + +#### Modernization vs Cloud-Readiness skills + +- [sap-clean-core-atc](sap-clean-core-atc.md) classifies whether code **can stay in ABAP and move to S/4HANA Cloud / ABAP Cloud**. Source system stays SAP. +- [migrate-custom-code](migrate-custom-code.md) **fixes ATC findings in place** to make ABAP cloud-ready. Source system stays SAP. +- [modernize-abap-to-btp-cap](modernize-abap-to-btp-cap/SKILL.md) **rebuilds the application as BTP CAP** — leaves source ABAP untouched, produces a parallel BTP-native stack. Use when the target architecture is a CAP application, not just cloud-ready ABAP. + +#### Typical BTP modernization workflow + +``` +1. bootstrap-system-context → Capture source SID, release, features +2. sap-unused-code → Scope: skip dead code from migration +3. sap-clean-core-atc → Risk assessment per Z object +4. modernize-abap-to-btp-cap → Generate target CAP scaffold (orchestrator) + ├── modernize-abap-clean-core-gap → (sub-skill, planned) per-edition availability + ├── modernize-abap-cap-schema → db/schema.cds + ├── modernize-abap-cap-service → srv/*.cds + handlers stubs + ├── modernize-abap-fiori-elements → (sub-skill, planned) app// + ├── modernize-abap-auth-mapping → (sub-skill, planned) xs-security.json + └── modernize-abap-btp-mta → (sub-skill, planned) mta.yaml + Dockerfile +5. Manual: implement handler TODOs → Translate ABAP business logic +6. generate-cds-unit-test → Test the new CAP entities +7. mbt build + cf deploy → Ship to BTP CF +``` + +> **Status**: Preview / Work-in-Progress. Orchestrator + schema + service skills available now. Remaining sub-skills (`clean-core-gap`, `fiori-elements`, `auth-mapping`, `btp-mta`) tracked in [#TBD upstream issue]. Feedback welcome on the [PR thread]. + ### System Context & Local Workflow | Skill | What it does | When to use | @@ -191,3 +247,23 @@ For end-to-end legacy SEGW + UI5 modernization (backend + UI): ``` The three migration skills are explicitly designed as parallel paths after the backend lands. You don't run both UI skills — you pick the one whose architecture matches your legacy app's complexity and your team's preference. + +For SAP CAP enterprise audit (pre-release readiness for a CAP + Fiori Elements + S/4 Tier-2 stack on BTP): + +``` +1. sap-cap-stack-audit-full → Run the full audit stack in parallel; consolidated report + (orchestrates all the skills below) + + Composed of: + - sap-cap-clean-core-enforce → Audit Tier-2 S/4 service availability vs released-state repo + - sap-cap-customizing-honor → Bidirectional CSV↔code parameter consistency + - sap-cap-security-rbac-matrix → OWASP/ASVS/NIST + role coherence across 4 layers + - sap-fiori-app-audit (xN) → Per-app UI/UX + frontend/backend contract chain + - sap-cap-text-polish → User-visible text + PII safety + i18n bundle gaps + +2. sap-cap-ci-gates-pattern → Lock the audit findings into CI gates that prevent regression + (bidirectional, raise-coverage, availability-drift, + convention-drift, csv-lint) +``` + +The seven CAP audit skills are designed as a single toolkit. Run `sap-cap-stack-audit-full` to dispatch everything at once, or invoke individual skills for a focused investigation. Findings flow into `sap-cap-ci-gates-pattern` so the audit converts to enforced CI gates, not one-off checks. diff --git a/skills/modernize-abap-cap-schema/SKILL.md b/skills/modernize-abap-cap-schema/SKILL.md new file mode 100644 index 00000000..4c550491 --- /dev/null +++ b/skills/modernize-abap-cap-schema/SKILL.md @@ -0,0 +1,117 @@ +--- +name: modernize-abap-cap-schema +description: Generate a CAP CDS data model (`db/schema.cds`) from a Z* package's ABAP database tables. Maps DDIC types to CDS, infers Associations/Compositions from foreign keys, auto-applies `cuid` / `managed` aspects. Use when asked to "convert Z tables to CDS entities", "generate CAP schema from ABAP TABL", "reverse-engineer Z tables", or as sub-skill of `modernize-abap-to-btp-cap`. +--- + +# Modernize ABAP → CAP Schema + +Produces `/db/schema.cds` from a Z* package's tables (`TABL`). Greenfield CDS for the **CAP runtime** (`@sap/cds`), NOT ABAP CDS DDL. + +Sub-skill of [`../modernize-abap-to-btp-cap/SKILL.md`](../modernize-abap-to-btp-cap/SKILL.md). Also usable standalone for table-only reverse-engineering. + +## Input + +``` + [--namespace=com.example.foo] +``` + +## Defaults + +| Aspect | Default | +|---|---| +| Output | `/db/schema.cds` | +| Namespace | derived from package: `com.example.` | +| `cuid` aspect | auto-applied if PK is `sysuuid_x16` (RAW(16)) | +| `managed` aspect | auto-applied if table has any of `crusr` / `crdat` / `cruzt` / `chusr` / `chdat` / `chuzt` | +| `temporal` aspect | NOT auto-applied (rare 1:1 mapping; flag for user review) | +| Key | first DDIC key field | +| Currency / Quantity | `@Semantics.amount.currencyCode` / `@Semantics.quantity.unitOfMeasure` | +| Comments | DDIC short text + field labels → `@Common.Label` | + +## DDIC → CDS type mapping (24 mappings) + +| DDIC | CDS | Notes | +|---|---|---| +| CHAR(n) | `String(n)` | preserve length | +| NUMC(n) | `String(n)` | leading-zero numeric; CAP-side validation up to user | +| DEC(p,s) / CURR(p,s) / QUAN(p,s) | `Decimal(p,s)` | currency/quantity get semantic annotation | +| INT1 / INT2 / INT4 | `Integer` / `Int16` / `Int32` | | +| INT8 | `Int64` | | +| FLTP | `Double` | | +| RAW(n) / SSTRING / STRING | `Binary(n)` / `String` / `LargeString` | | +| RAW(16) | `UUID` (auto via `cuid`) | UUID convention | +| RAWSTRING | `LargeBinary` | | +| DATS | `Date` | | +| TIMS | `Time` | | +| TZNTSTMPS | `Timestamp` | with timezone | +| LCHR / LRAW | `LargeString` / `LargeBinary` | | +| UNIT (CUKY) | `Association to Currencies` | when associated with amount field | +| MEINS | `Association to Units` | when associated with quantity field | +| LANG (SPRAS) | `Association to Languages` | | +| CLNT | `String(3)` | client field — usually omitted in BTP CAP | + +## Workflow + +### Step 1 — Enumerate tables + +`SAPSearch(tadir_lookup, devclass=, object='TABL')` → list of Z tables. + +### Step 2 — Read DDIC details (per table) + +`SAPRead(type='TABL', name=)` — fields + types + keys + foreign keys + domain references. + +For complex domains / data elements: drill in only when the DDIC type alone is insufficient (e.g. fixed value lists → `@assert.range`). + +### Step 3 — Apply type mapping + aspects + +Walk each field through the table above. For each table, decide aspects: +- `cuid` if RAW(16) primary key +- `managed` if audit columns present +- `localized` if `texts` companion table detected (`
T`) + +### Step 4 — Infer Associations + +From DDIC foreign-key references: + +| FK cardinality | Heuristic | CAP relation | +|---|---|---| +| Child table with parent FK + parent owns lifecycle | parent has stronger semantics | `Composition of many ` on parent | +| Lookup / master data (Currencies, Units, Languages) | shared catalog | `Association to ` | +| Generic FK to entity not owned | independent lifecycle | `Association to ` | + +Bidirectional inference: write both sides (`.items : Composition of many ` AND `.parent : Association to `). + +### Step 5 — Emit + validate + +Write `/db/schema.cds` in CAP pretty-print format. Validate via: + +```bash +npx cds compile /db/schema.cds --to edmx > /dev/null && echo "OK" +``` + +### Step 6 — Migration notes + +Write `/docs/schema-notes.md` with: +- Tables migrated + their CAP entity name + namespace +- Aspects auto-applied +- Associations inferred + cardinality reasoning +- Manual review items: temporal candidates, ambiguous FKs, `LCHR` / `LRAW` size limits + +## Gotchas + +- **MANDT (client) field**: drop on BTP CAP (no client concept). Flag if data needs cross-client merge. +- **`@AbapCatalog` annotations** (DDIC): do NOT carry over; CAP has its own (`@cds.persistence.skip`, `@assert.range`, etc.). +- **Domain fixed values**: map to `@assert.range` if ≤10 values, otherwise emit a CodeList entity. +- **Hierarchical / parent-child Z tables**: review `Composition` choice — sometimes Association is safer (no cascade delete). + +## When NOT to use + +- Greenfield CDS design from scratch → use [`../generate-rap-service/SKILL.md`](../generate-rap-service/SKILL.md) or hand-write +- View-only entities (no underlying TABL) → not the target of this skill +- Multi-package data model with cross-package FK → split into multiple invocations, then merge manually + +## References + +- [SAP CAP — CDS Modeling](https://cap.cloud.sap/docs/cds/cdl) +- [CAP — Common Reuse Aspects](https://cap.cloud.sap/docs/cds/common) (`cuid`, `managed`, `temporal`, `localized`) +- DDIC types → CDS types: [SAP — ABAP CDS Types](https://help.sap.com/docs/abap-cloud/abap-data-types) (cross-reference for ABAP CDS vs CAP CDS) diff --git a/skills/modernize-abap-cap-service/SKILL.md b/skills/modernize-abap-cap-service/SKILL.md new file mode 100644 index 00000000..9ea1ddc0 --- /dev/null +++ b/skills/modernize-abap-cap-service/SKILL.md @@ -0,0 +1,151 @@ +--- +name: modernize-abap-cap-service +description: Generate a CAP service definition (`srv/service.cds` + handler skeletons) from a Z* package's function modules, function groups, and reports. Maps ABAP signatures to CAP service actions, classifies bound vs unbound, generates TypeScript handler stubs with ABAP source context as TODOs. Use when asked to "convert this ABAP FM to a CAP service", "generate CAP service from Z report", "expose Z business logic as OData V4", or as sub-skill of `modernize-abap-to-btp-cap`. +--- + +# Modernize ABAP → CAP Service + +Produces `/srv/service.cds` + handler skeletons from a Z* package's function modules (`FUGR`), reports (`PROG`), and class methods. Greenfield CAP service exposing equivalent business logic as OData V4 actions. + +Sub-skill of [`../modernize-abap-to-btp-cap/SKILL.md`](../modernize-abap-to-btp-cap/SKILL.md). Usable standalone when only the service layer needs migration (existing CAP schema, new service on top). + +## Input + +``` + [--service-name=MyService] [--ts|--js] +``` + +## Defaults + +| Aspect | Default | +|---|---| +| Service definition | `/srv/service.cds` | +| Handler file | `/srv/service.ts` (TypeScript; `--js` to opt out) | +| Service exposure | OData V4 | +| Bound vs unbound | bound if FM signature has a "key" parameter; unbound otherwise | +| Auth | `@requires: 'authenticated-user'` (refine in `services-auth.cds` later) | +| Logging | `cds.log('')` per handler | +| Error handling | central `rejectSafe` helper (created if absent) | + +## ABAP → CAP signature mapping + +| ABAP | CAP service | Handler | +|---|---|---| +| `FUNCTION ZFM_X IMPORTING iv_id` | `action callX(id : String) returns Result;` | `srv.on('callX', async (req) => …)` | +| `FUNCTION ZFM_X CHANGING ct_items` | `action callX(items : array of Item) returns array of Item;` | mutating param → returned shape | +| `FUNCTION ZFM_X RAISING zcx_my_error` | `action callX() returns Result;` + `req.reject(…)` | exception class → HTTP code (table below) | +| Report `ZRPT_X SUBMIT` | `function callX() returns Report;` | trigger via Kyma CronJob or BTP Job Scheduler | +| `METHOD .` | bound action `action (…)` | route via class wrapper | + +Exception class → HTTP code defaults: + +| ABAP exception | HTTP | +|---|---| +| `CX_SY_*` (system) | 500 | +| `ZCX_NOT_AUTHORIZED` | 403 | +| `ZCX_NOT_FOUND` | 404 | +| `ZCX_BUSINESS_*` (business rule) | 409 | +| `ZCX_INVALID_INPUT` | 400 | + +## Workflow + +### Step 1 — Enumerate + +`SAPSearch(tadir_lookup, devclass=, object IN ('FUGR','PROG','CLAS'))`. + +Filter out: +- Helper / utility classes already covered by `migrate-custom-code` (those rewrite in-place, not exposed). +- Pure data classes (no business method). +- Test classes (`ZCL_*_TEST`). + +### Step 2 — Read source + signatures + +Per FM / method / report: +- `SAPRead(type='FUNC', name=)` — I/O parameters + raising classes. +- `SAPRead(type='CLAS', name=)` — public methods + types. +- `SAPContext(action='impact', target=)` — caller fan-in. + +### Step 3 — Decide service shape + +| Caller pattern | Decision | +|---|---| +| Called only by UI / external system | Expose as OData action | +| Called only by other Z code | Don't expose; inline into the consuming action OR keep ABAP-side | +| Heavy read, light return | OData function (read-only) instead of action | +| Mutating + returns updated entity | Bound action | +| Pure utility (no entity context) | Unbound action under service root | + +### Step 4 — Generate `srv/service.cds` + +```cds +using { com.example. as my } from '../db/schema'; + +@path: '/odata/v4/' +service @(requires: 'authenticated-user') { + entity Items as projection on my.Items actions { + action approve(reason : String) returns Items; + action reject (reason : String not null) returns Items; + }; + + function searchVendors(query : String) returns array of Vendor; + action importBatch (items : array of Item) returns BatchResult; +} +``` + +Carry `@Common.Label` from FM short text. `@Core.LongDescription` from FM detailed documentation if present. + +### Step 5 — Generate `srv/service.ts` handler skeletons + +```typescript +import cds from '@sap/cds'; + +const LOG = cds.log(''); + +export default class Service extends cds.ApplicationService { + override async init() { + this.on('approve', this.approveItem); + this.on('reject', this.rejectItem); + this.on('searchVendors', this.searchVendors); + this.on('importBatch', this.importBatch); + return super.init(); + } + + async approveItem(req: cds.Request) { + // TODO: ported from ZFM_APPROVE_ITEM + // Original: IMPORTING iv_id TYPE … EXPORTING es_result TYPE … + // Logic notes: + LOG.info('approve called', { id: req.params[0] }); + return req.reject(501, 'Not yet implemented'); + } + // … one stub per action +} +``` + +Every stub includes a TODO with the source ABAP object name so the developer doing the port has the audit trail. + +### Step 6 — Migration notes + +Write `/docs/service-notes.md` with: +- FM / method → CAP action mapping table. +- Exception class → HTTP code mapping applied. +- Caller-pattern decisions (exposed vs inlined vs kept ABAP-side). +- Manual-port items: complex business rules requiring re-implementation in TypeScript. + +## Gotchas + +- **Modifying SAP standard tables in the FM**: CAP runtime cannot. Flag the FM as `extract_to_side_by_side_with_event_subscription` — the new CAP action subscribes to S/4 events and writes to CAP storage. +- **`SUBMIT … RETURN` reports**: refactor to action with explicit parameters. List-display reports → `function` returning `array of`. +- **AMDP / native HANA SQL inside FM**: do NOT port as embedded SQL — model as CDS view + CAP query. +- **Locks / SAP LUW**: explicit ENQUEUE → `cds.tx` + `forUpdate()`. + +## When NOT to use + +- Schema-only migration → [`../modernize-abap-cap-schema/SKILL.md`](../modernize-abap-cap-schema/SKILL.md). +- Full ABAP rewrite in source system → [`../migrate-custom-code/SKILL.md`](../migrate-custom-code/SKILL.md). +- New RAP service in the source ABAP system → [`../generate-rap-service/SKILL.md`](../generate-rap-service/SKILL.md). + +## References + +- [SAP CAP — Providing Services](https://cap.cloud.sap/docs/guides/providing-services) +- [SAP CAP — Custom Handlers (Node.js)](https://cap.cloud.sap/docs/node.js/core-services) +- [CAP capire — OData V4 Actions / Functions](https://cap.cloud.sap/docs/advanced/odata) diff --git a/skills/modernize-abap-to-btp-cap/SKILL.md b/skills/modernize-abap-to-btp-cap/SKILL.md new file mode 100644 index 00000000..84052bad --- /dev/null +++ b/skills/modernize-abap-to-btp-cap/SKILL.md @@ -0,0 +1,118 @@ +--- +name: modernize-abap-to-btp-cap +description: End-to-end migration orchestrator from classic ABAP Z* packages to a BTP-native CAP application (Node.js + Fiori Elements V4 + Cloud Foundry). Chains specialized `modernize-abap-cap-*` sub-skills to produce a target CAP project. Use when asked to "port this Z package to BTP CAP", "modernize ABAP to CAP", "generate a CAP scaffold from this Z package", or "migrate custom code to BTP greenfield". +--- + +# Modernize ABAP → BTP CAP + +Orchestrator that takes an ABAP Z* package and produces a CAP project under `/.target-cap-staging/`. Leaves the ABAP system untouched. + +Different from [`../migrate-custom-code/SKILL.md`](../migrate-custom-code/SKILL.md): that skill fixes ATC findings *inside* an ABAP system. This one produces a *side-by-side BTP target* that consumes released S/4HANA APIs instead. + +## Input + +``` + [--cap-runtime=node|java] [--skip=…] +``` + +Examples: +- `ZSALES_PKG ./out` — full default chain +- `ZSALES_PKG ./out --skip=fiori,mta` — only schema + service, no UI / deploy artifacts + +## Defaults (applied silently) + +| Aspect | Default | +|---|---| +| Target | `public_cloud` (Clean Core L-A goal) | +| CAP runtime | Node.js (Java deferred to v2) | +| OData | V4 | +| Fiori pattern | List Report + Object Page (LROP) | +| Auth | XSUAA | +| DB | HANA Cloud prod + SQLite dev | +| Output mode | Sandbox `./target-cap-staging/` (reversible) | +| MTA build | `mbt` | +| Output language | English i18n bundle | + +## Chain + +The orchestrator runs the 6 sub-steps below sequentially. Each can be re-run independently via the linked sub-skill. + +| Step | Sub-skill | Produces | +|---|---|---| +| 1 | (this orchestrator) Pre-flight + skeleton | CAP `cds init`-style structure under `/.target-cap-staging/`, package.json with `@sap/cds ^9` + `@cap-js/hana` + `@cap-js/sqlite` | +| 2 | Clean Core gap analysis | `docs/clean-core-gap.md` with per-object A/B/C/D level + replacement suggestions. **Gate**: > 30% C/D → recommend `migrate-custom-code` first | +| 3 | [`../modernize-abap-cap-schema/SKILL.md`](../modernize-abap-cap-schema/SKILL.md) | `db/schema.cds` from Z-tables / DDIC structures | +| 4 | [`../modernize-abap-cap-service/SKILL.md`](../modernize-abap-cap-service/SKILL.md) | `srv/service.cds` + handler stubs from FMs / programs | +| 5 | Fiori Elements V4 scaffold | `app//` LROP with annotations | +| 6 | Auth + MTA | `xs-security.json` + `mta.yaml` for `cf deploy` | + +## Pre-flight + +``` +SAPManage(action="probe") # verify ARC-1 + ADT + CDS/RAP availability +``` + +If CDS/RAP unavailable → stop (cannot inventory). If package in `$TMP` → warn user to use a transportable package first. + +Validate `` is empty or contains a previous staging output only. Refuse to overwrite an existing CAP project without explicit confirmation. + +## Gates + +- **Clean Core gap > 30% C/D objects** → warn, recommend `migrate-custom-code` first, then retry +- **No released equivalent found for a critical object** → flag in `clean-core-gap.md`; user decides whether to extract custom logic to CAP side-by-side or block +- **Target dir not empty / not staging** → refuse without `--apply` confirmation + +If the source project has a Clean Core CI gate (`scripts/ci/check-s4-compat-coverage.sh` or equivalent), invoke it after staging — drift detection against `SAP/abap-atc-cr-cv-s4hc`. + +## Output + +``` +/.target-cap-staging/ +├── db/schema.cds (Step 3) +├── srv/service.cds (Step 4) +├── srv/handlers/ (Step 4) +├── app// (Step 5) +├── xs-security.json (Step 6) +├── mta.yaml (Step 6) +├── package.json (Step 1) +├── docs/ +│ ├── clean-core-gap.md (Step 2) +│ └── porting-plan.md (orchestrator summary + ADRs) +└── README.md +``` + +The user reviews under the staging dir, then promotes to the real CAP project (rename / move, or invoke with `--apply` to write directly). + +## Hand-off + +Generated CAP project is **sandbox**. Manual next steps (not in this skill): + +```bash +cd /.target-cap-staging +npm install +cds deploy --to hana # or sqlite for dev +mbt build # build MTA archive +cf deploy mta_archives/.mtar +``` + +For audit / hardening / CI gates of the generated CAP project, see [`Raistlin82/sap-cap-toolkit`](https://github.com/Raistlin82/sap-cap-toolkit). + +## When NOT to use + +- ABAP-system-internal ATC fixes → use [`../migrate-custom-code/SKILL.md`](../migrate-custom-code/SKILL.md) +- Single-object refactor (not whole package) → invoke a sub-skill directly +- Multi-package coordinated migration → split per package, run orchestrator N times +- Java CAP runtime → v2 (not yet supported) +- Kyma deployment → v2 (defaults to CF for now) + +## Recommended companion plugins + +From [secondsky/sap-skills](https://github.com/secondsky/sap-skills): + +- `sap-abap` (MUST) — ABAP source patterns the sub-skills consume +- `sap-abap-cds` (MUST) — CDS view patterns for Step 3 +- `sap-cap-capire` (MUST, 4 agents) — CAP scaffolding patterns for Steps 3-4 +- `sap-btp-developer-guide` (MUST) — BTP deployment for Step 6 +- `sap-fiori-tools` (SHOULD) — Fiori Elements scaffolding for Step 5 + +Plus ARC-1 MCP (mandatory — system probe + source read in Steps 1-2). diff --git a/skills/sap-erp-clean-core-refactor/INTEGRATIONS.md b/skills/sap-erp-clean-core-refactor/INTEGRATIONS.md new file mode 100644 index 00000000..deeac67c --- /dev/null +++ b/skills/sap-erp-clean-core-refactor/INTEGRATIONS.md @@ -0,0 +1,173 @@ +# Integrations Map — Level A Refactor + +Detailed mapping of every refactor step against: +- **ARC-1 MCP tools** that perform the operation on the SAP system +- **arc-1 native skills** that the orchestrator delegates to +- **secondsky/sap-skills plugins** that provide the language / framework knowledge for the operation +- **Other external sources** (Apify, MCP servers, manual) + +Use this document to: +- Verify the skill's coverage of available tooling. +- Diagnose what's missing in an environment ("ARC-1 not configured" / "secondsky plugin X not installed" / …). +- Understand the cost/value contribution of each integration point. + +## Architectural roles + +| Layer | Role | Examples | +|---|---|---| +| **ARC-1 MCP** | the *hands* — reads/writes ABAP objects on the SAP system via ADT REST API | `SAPRead`, `SAPWrite`, `SAPActivate`, `SAPLint`, `SAPContext`, `SAPDiagnose`, `SAPTransport`, `SAPGit`, `SAPManage`, `SAPNavigate`, `SAPSearch`, `SAPQuery` | +| **arc-1 native skills** | the *playbook* — sequences of MCP operations encoded as reusable agents | `bootstrap-system-context`, `sap-clean-core-atc`, `generate-rap-logic`, `modernize-abap-to-btp-cap`, … | +| **secondsky/sap-skills plugins** | the *library of patterns* — how to write what the hands will write | `sap-abap`, `sap-abap-cds`, `sap-cap-capire` (with 4 agents), `sap-btp-developer-guide`, `sapui5`, `sap-fiori-tools`, … | +| **This skill** (`sap-erp-clean-core-refactor`) | the *orchestrator* — decides which patterns to apply using which hands following which playbook | (you are here) | +| **External knowledge** | JIT documentation lookup when local cache misses | Apify (per-page paid), MCP-sap-docs (free when installed), WebFetch (free for simple HTML) | + +## Step-by-step integration map + +### Step 1 — Pre-flight + +| Sub-step | ARC-1 MCP | arc-1 skills | secondsky plugin | External | Notes | +|---|---|---|---|---|---| +| 1a — ARC-1 connectivity probe | `SAPSearch(package_lookup, "")` | — | — | — | Cheap call to verify the server responds + auth works | +| 1b — Apify MCP availability | — | — | — | `mcp__apify__*` | Degrades to manual mode if absent | +| 1c — Resolve `$TARGET` | — | — | `sap-btp-developer-guide` (target landscape reference) | — | Default `btp-cf` | +| 1d — Init local cache | (bash) | — | — | — | `.cache/sap-clean-core/` gitignored | +| 1e — Bootstrap system context | `SAPManage(probe_features)` + `SAPRead(SKTD)` | **`bootstrap-system-context`** | — | — | One-time per system; produces `system-info.md` | + +### Step 2 — Inventory + +| Sub-step | ARC-1 MCP | arc-1 skills | secondsky plugin | External | Notes | +|---|---|---|---|---|---| +| 2a — Package enumeration | `SAPSearch(package_tree, root=)` | — | — | — | Recursive sub-package walk | +| 2b — Object enumeration | `SAPSearch(tadir_lookup, devclass=)` | — | — | — | All object types per package | +| 2c — Namespace filter | (post-processing) | — | — | — | Keep only `Z*`, `Y*`, customer namespace | +| 2d — Unused detection | `SAPQuery(SCMON / SUSG)` (requires `SAP_ALLOW_FREE_SQL=true`) | **`sap-unused-code`** | — | — | Last 6 months runtime hits | +| 2e — Impact analysis | **`SAPContext(action="impact")`** | — | — | — | ⚠️ **MOST IMPORTANT MCP CALL**. Fan-in count drives effort × risk multipliers | + +### Step 3 — Classification + +| Sub-step | ARC-1 MCP | arc-1 skills | secondsky plugin | External | Notes | +|---|---|---|---|---|---| +| 3a — ATC run | `SAPLint(action="run_atc", target_level="A")` | **`sap-clean-core-atc`** | — | — | Per-object Level A/B/C/D | +| 3b — Augment with finding categories | (post-processing) | — | — | — | non-released-api / direct-db-access / modification / enhancement-point | + +### Step 4 — JIT documentation lookup + decision + +| Sub-step | ARC-1 MCP | arc-1 skills | secondsky plugin | External | Notes | +|---|---|---|---|---|---| +| 4a — Cache-first lookup | (filesystem read) | — | — | — | 30d TTL stable, 7d community/blogs | +| 4b — Tier 1 git lookup | (filesystem grep) | — | — | git clones: `abap-atc-cr-cv-s4hc`, `SAP-samples`, `cloud-sdk` | Free, fast, authoritative for object classification | +| 4b — Tier 2 JIT Apify | — | — | — | `apify/website-content-crawler`, `apify/puppeteer-scraper` | Per-page cost ~€0.005-0.02; user pays | +| 4b — Tier 4 MCP-server lookup | — | — | — | `mcp-sap-docs`, `context7` | Preferred when installed (free) | +| 4c — Cite + cache | (filesystem write) | — | — | — | `.cache/sap-clean-core//-.md` | +| 4d — Budget exhaustion fallback | — | **`explain-abap-code`** (single-object deep dive) | — | — | Reduces human research effort ~50% | +| 4d-quater — Pattern mining | **`SAPRead(VERSIONS)`**, **`SAPRead(VERSION_SOURCE)`** | — | — | — | Mine customer's own history for refactor patterns. ⚠️ Reduces rewrite effort 30-50% on customers with established conventions | +| 4d-bis — Decision tree | (agent reasoning) | — | — | — | Per-object Start Level + flags → Target Level + Decision | +| 4e-4g — Level B escalation | — | — | — | — | `--aggressive` / `--push-to-a` / `--target-level=A` flags | + +### Step 5 — Plan emission + +| Sub-step | ARC-1 MCP | arc-1 skills | secondsky plugin | External | Notes | +|---|---|---|---|---|---| +| 5a — Generate plan markdown | (filesystem write) | — | — | — | `docs/refactor/-clean-core-plan.md` | +| 5b — Per-object decision rows | (templating) | — | — | — | Object / Start Level / Target Level / Decision / Replacement / Effort / Risk / KB evidence | + +### Step 6 — Execute (opt-in) + +| Sub-step | ARC-1 MCP | arc-1 skills | secondsky plugin | External | Notes | +|---|---|---|---|---|---| +| 6-pre — Generate regression tests (CLAS/FUGR) | — | **`generate-abap-unit-test`** | `sap-abap` (test patterns reference) | — | Capture current behaviour as baseline | +| 6-pre — Generate regression tests (DDLS) | — | **`generate-cds-unit-test`** | `sap-abap-cds` (CDS Test Double Framework patterns) | — | For CDS views | +| 6a — Rewrite in-place: read + write | `SAPRead(VERSIONS)`, `SAPWrite(action="update")`, `SAPActivate(scope="object")` | **`generate-rap-logic`** (when rewrite goes to RAP behavior pool), **`generate-rap-service-researched`** (full RAP stack, rare) | **`sap-abap`** (language patterns), **`sap-abap-cds`** (CDS views if introduced) | — | Pattern-mined via Step 4d-quater | +| 6a — Format | `SAPLint(action="format")` | — | — | — | Apply project formatter from `system-info.md` | +| 6a — ATC regression | `SAPLint(action="run_atc")` | — | — | — | Gate: blocks loop if regression | +| 6a — Unit test regression | `SAPDiagnose(action="run_unit_tests")` | — | — | — | Gate: blocks loop on test failure | +| 6a — Rollback if regression | `SAPGit(revert)` | — | — | — | Requires `SAP_ALLOW_GIT_WRITES=true` | +| 6b — Side-by-side scaffold | — | **`modernize-abap-to-btp-cap`** chain, **`convert-ui5-to-fiori-elements`** (UI) | **`sap-cap-capire`** (4 agents: cap-cds-modeler, cap-service-developer, cap-performance-debugger, cap-project-architect), **`sap-btp-developer-guide`**, **`sap-fiori-tools`** + `sapui5` (UI), **`sap-btp-cloud-platform`** (service binding) | — | Per-extension CAP project under `bs//` | +| 6c — Document Level B keeper | `SAPWrite(action="attach_sktd")` | **`sap-object-documenter`** | — | — | Markdown rationale + ATC exemption update | +| 6.5 — Transport requirement check | `SAPTransport(action="requirement_check")` | — | — | — | Ensure deps reachable | +| 6.5 — Transport create / reuse | `SAPTransport(action="create"|"reassign")` | — | — | — | One TR per phase or per cluster | +| 6.5 — gCTS / abapGit commit | `SAPGit(commit)` | — | — | — | Optional; with `SAP_ALLOW_GIT_WRITES=true` | + +### Step 7 — Verification + +| Sub-step | ARC-1 MCP | arc-1 skills | secondsky plugin | External | Notes | +|---|---|---|---|---|---| +| 7a — ATC final check | `SAPLint(action="run_atc")` (whole package) | — | — | — | Cumulative regression | +| 7b — Unit test full run | `SAPDiagnose(action="run_unit_tests")` (whole package) | — | — | — | All tests including pre-existing | +| 7c — Cross-check against CAP audit | — | [`sap-cap-clean-core-enforce`](https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-clean-core-enforce/SKILL.md) (other branch) | — | — | Verify BTP-side compliance | +| 7d — Session learnings | — | **`analyze-chat-session`** | — | — | Propose new skill traps for future runs | + +## Coverage assessment + +The table below shows **what fraction of ARC-1 MCP capabilities the skill currently engages**. + +| ARC-1 MCP tool | Engagement in this skill | +|---|---| +| `SAPRead` (source + VERSIONS + VERSION_SOURCE + SKTD) | 🟢 Step 2 (inventory), Step 4d-quater (mining), Step 6a (pre-write VERSIONS check), Step 6c (read SKTD) | +| `SAPSearch` (tadir_lookup + package_tree + where_used + full-text) | 🟢 Step 2 (enumerate), Step 6 (where_used for unused removal) | +| `SAPWrite` (update + delete + attach_sktd + batch_create) | 🟢 Step 6a (update), Step 6c (attach_sktd), Step 6 unused (delete) | +| `SAPActivate` | 🟢 Step 6a (post-write activation) | +| `SAPNavigate` (go-to-definition, find references) | 🟡 Implicit in `SAPContext`; not directly called | +| `SAPQuery` (free SQL, off by default) | 🟡 Used by `sap-unused-code` (delegate) — requires `SAP_ALLOW_FREE_SQL=true` | +| `SAPTransport` | 🟢 Step 6.5 (requirement_check + create + reassign) | +| `SAPGit` | 🟡 Step 6a rollback + Step 6.5 commit (both opt-in via `SAP_ALLOW_GIT_WRITES=true`) | +| `SAPContext` (impact + reverse-deps + CDS impact) | 🟢 Step 2e (MOST IMPORTANT call — drives risk multipliers) | +| `SAPLint` (run_atc + format + get_formatter_settings) | 🟢 Step 3 (classification), Step 6a (pre+post regression + format) | +| `SAPDiagnose` (syntax + unit tests + ATC + quickfix + dumps + profiler) | 🟢 Step 6a (run_unit_tests), Step 7 (full run) | +| `SAPManage` (capability detection) | 🟢 Step 1e (via `bootstrap-system-context`) | + +**Overall**: the skill engages all 12 ARC-1 tools at least once. `SAPNavigate` is the only one used implicitly (via `SAPContext`); explicit calls are not needed for the refactor workflow. + +## Coverage assessment — secondsky/sap-skills + +The table below shows **which secondsky plugins this skill enforces as MUST / SHOULD / OPTIONAL**. + +| Plugin | Severity | Step where invoked | +|---|---|---| +| `sap-abap` | **MUST** | Step 6a rewrite_in_place (every ABAP rewrite consults its language patterns) | +| `sap-abap-cds` | **MUST** | Step 6a when rewrite introduces CDS views | +| `sap-cap-capire` (with 4 agents) | **MUST** | Step 6b side-by-side scaffold | +| `sap-btp-developer-guide` | **MUST** | Step 1c target resolution, Step 6b scaffold | +| `sapui5` (with 4 agents) | SHOULD | Step 6b when extension has UI | +| `sap-fiori-tools` | SHOULD | Step 6b Fiori Elements UI generation | +| `sapui5-linter` | SHOULD | Step 6b post-scaffold UI quality gate | +| `sap-btp-cloud-platform` | SHOULD | Step 6b service binding | +| `sap-btp-connectivity` | SHOULD | Step 6b when extension uses destinations | +| `sap-cloud-sdk` | SHOULD | Step 6b when extension uses Cloud SDK | +| `sap-cloud-sdk-ai` | OPTIONAL | Step 6b when extension is AI-heavy | +| `sap-btp-cloud-logging` | OPTIONAL | Step 6b production observability | +| `sap-btp-job-scheduling` | OPTIONAL | Step 6b when extension has scheduled jobs | +| `sap-btp-cloud-transport-management` | OPTIONAL | Step 6.5 when customer uses cTMS | +| `sap-btp-master-data-integration` | OPTIONAL | Step 6b when extension subscribes to MDI events | +| `sap-btp-cias` | OPTIONAL | Step 6b when customer uses IAS instead of XSUAA | +| `sap-btp-business-application-studio` | OPTIONAL | Hand-off documentation | +| `sap-btp-integration-suite` | OPTIONAL | Step 6b when side-by-side uses iFlows | +| `sap-btp-build-work-zone-advanced` | OPTIONAL | Step 6b when UI surfaces in Work Zone | +| `sap-btp-intelligent-situation-automation` | OPTIONAL | Step 6b when extension includes workflow logic | + +Plugins **NOT** used by this skill (out of scope): +- `sap-sqlscript`, `sap-hana-ml`, `sap-hana-cloud-data-intelligence`, `sap-hana-cli` — HANA-native dev +- `sap-datasphere`, `sap-sac-*` — analytics +- `sap-ai-core` — AI infrastructure +- `sap-api-style` — API design guidelines + +## Cost model — by integration layer + +| Layer | Cost per refactor (50-200 objects) | Who pays | +|---|---|---| +| ARC-1 MCP | €0 (server runs on user's infra) | User (server hosting) | +| arc-1 native skills | €0 (skill execution is agent-time, not API-billed) | (agent inference cost is platform-billed) | +| secondsky/sap-skills | €0 (knowledge base; no per-call cost) | (one-time install) | +| Tier 1 git clones | €0 (~100 MB on first install) | User (bandwidth) | +| Tier 2 Apify JIT | €0.50-€5 typical | User (own Apify token) | +| Tier 4 MCP-server-backed | €0 | User (server hosting) | + +**Total**: €0.50-€5 per typical customer refactor, all charged to the user's own infrastructure / accounts. No centralized cost. + +## See also + +- [`./SKILL.md`](./SKILL.md) — main protocol; this document is the integration deep-dive. +- [`./SOURCES.md`](./SOURCES.md) — authoritative SAP source catalog (Tier 1-4). +- [`sap-cap-fiori-battle-tested-patterns`](./PATTERNS.md) — broader companion plugin map (Category 8) for the CAP-side toolkit. +- [ARC-1 README](https://github.com/marianfoo/arc-1) — full MCP capability reference. +- [secondsky/sap-skills](https://github.com/secondsky/sap-skills) — 32-plugin SAP skill catalog. diff --git a/skills/sap-erp-clean-core-refactor/PATTERNS.md b/skills/sap-erp-clean-core-refactor/PATTERNS.md new file mode 100644 index 00000000..96c36b7d --- /dev/null +++ b/skills/sap-erp-clean-core-refactor/PATTERNS.md @@ -0,0 +1,1065 @@ +# SAP CAP + Fiori Elements V4 — Battle-Tested Patterns + +> **This is a reference document**, not a runnable skill. It ships alongside [`SKILL.md`](./SKILL.md) inside the `sap-erp-clean-core-refactor` skill so the refactor skill is **self-contained** — it doesn't need an external repository to consult deployment patterns when generating side-by-side scaffolds. +> +> The same content is also published as a standalone reference skill in the companion [`Raistlin82/sap-cap-toolkit`](https://github.com/Raistlin82/sap-cap-toolkit) repository, where it's discoverable as `sap-cap-fiori-battle-tested-patterns`. If you're working on a CAP project unrelated to ABAP refactoring, install that repo instead. + +A reference catalog of patterns and gotchas that have surfaced repeatedly across production SAP CAP + Fiori Elements V4 + BTP deployments. Each entry distills a real-world failure mode into a generic pattern: **symptom** observed by users or operators, **root cause** in the framework / runtime / deployment layer, and a **remedy** that is portable across CAP projects. + +This document is **not a runner** — it never executes commands. It is a curated reference consulted by [`SKILL.md`](./SKILL.md) during refactor planning (Step 1c target resolution, Step 6b side-by-side scaffold), referenced from [`INTEGRATIONS.md`](./INTEGRATIONS.md) for the companion plugin map, and available for direct reading by humans. + +Patterns are organized into **eight categories**. Each category lists the most load-bearing patterns first; lighter-weight items follow. Where multiple frameworks expose the same gotcha (e.g. `@UI.Hidden` interaction with `@Core.OperationAvailable`), the entry points to the framework documentation rather than reproducing it. + +## How to read this skill + +- **As a checklist**: skim the section titles, identify any pattern that matches the current bug / question, jump to the remedy. +- **As a reference linked from another skill**: each pattern is internally addressable (anchor links work). Other skills cite specific patterns by anchor. +- **As a teaching aid**: patterns are written so a developer new to CAP + Fiori Elements V4 can read them as a sequence and absorb the institutional knowledge. + +The catalog is opinionated about defaults. Where SAP documentation lists multiple options, this skill picks one **proven in production** and notes the alternative briefly. + +--- + +## Category 1 — UI5 / Fiori Elements V4 Traps + +### 1.1 — Pin the UI5 minor version explicitly + +**Symptom.** A Fiori Elements V4 app that worked yesterday now renders a blank shell or throws `Component-preload.js 404` after the public CDN rolled forward. + +**Root cause.** Loader URL `https://ui5.sap.com/resources/sap-ui-core.js` (no version) follows the upstream LTS pointer; minor version bumps occasionally break MDC controls (1.136.x MDC Table requires `webapp/changes/{flexibility-bundle,changes-bundle}.json` files; absent → 404 cascade). + +**Remedy.** Pin a specific minor in `manifest.json` and the bootstrap script: +```html + +``` +Bump only after a smoke test in a non-prod environment. If MDC Table is used, ship empty `flexibility-bundle.json` and `changes-bundle.json` under `webapp/changes/` to prevent 404 cascades. + +### 1.2 — `@UI.Hidden` on a `@Core.OperationAvailable` operand silently disables the button + +**Symptom.** A header action button is permanently disabled even though the role grants it and the user is in the right state. + +**Root cause.** FE V4 with `autoExpandSelect: true` (the default) skips `@UI.Hidden` properties from the `$select` projection. If `@Core.OperationAvailable: SomeFlag` references a `Can*` flag that is also annotated `@UI.Hidden`, the property arrives `undefined` and the action evaluator falls back to "disabled". + +**Remedy.** On computed flags backing `OperationAvailable`, use only `@Core.Computed` — **never** `@UI.Hidden`. Expose them as regular fields (FE V4 won't render them in any UI because no annotation references them as visible). + +### 1.3 — Actions returning `Entity` must NOT use `.columns(...)` + +**Symptom.** After invoking a bound action that creates / promotes / completes a draft, the ObjectPage exits edit mode unexpectedly or shows stale data. + +**Root cause.** When a draft-aware bound action returns the entity but with a projection like `.columns('field1', 'field2')`, the resulting partial entity confuses the FE V4 client cache — it thinks the row was replaced by a thinner version and bails on the edit flow. + +**Remedy.** Return the **full** entity from draft-aware actions: +```javascript +return cds.tx(req).run(SELECT.one.from(EntityName).where({ id })); +``` +Use `@Common.SideEffects: { TargetProperties: […], TargetEntities: […] }` to scope the refresh client-side; don't shrink the action's return shape. + +### 1.4 — `liveMode: true` triggers a `$batch` per keystroke on large entities + +**Symptom.** The filter bar lags or the backend gets hammered when a user types in a filter field on a List Report. + +**Root cause.** `liveMode: true` on a SmartFilterBar / FilterField fires the filter on every keystroke. Acceptable for CodeLists / master data (~tens of rows); catastrophic for transactional entities (~millions). + +**Remedy.** Default to `liveMode: false` on transactional entities. Reserve `liveMode: true` for CodeList-backed dropdowns and small master-data lookups. + +### 1.5 — Composition vs Association for audit / log child entities + +**Symptom.** Saving a draft of a transactional entity fails with `*_drafts_pkey` unique-key violation on an audit / log child entity (`AuditLogEntry`, `ProcessStepExecution`, `BPResolutionCheck`, etc.). + +**Root cause.** CAP copies **Composition** children into the draft. When the parent already has many audit rows, the draft copy duplicates their primary keys → `_drafts_pkey` collision. Compositions also impose cascade-delete semantics, which audit / log tables should never inherit (legal retention). + +**Remedy.** Model audit / log children as **Association**, not Composition. They are not part of the editable graph; they are read-only references with their own retention rules. + +### 1.6 — `i18n>{model>keyPath}` dynamic binding does not work + +**Symptom.** A label or text appears blank or shows the raw key path (`{i18n>some.dynamic.key}`). + +**Root cause.** Double binding (`{i18n>{model>x}}`) is not resolvable at compile time and most UI5 binding parsers refuse it. + +**Remedy.** Resolve the key in the controller `onInit()` and populate the model with literal strings: +```typescript +const i18n = this.getView().getModel('i18n').getResourceBundle(); +this.getView().getModel('view').setProperty('/computedLabel', i18n.getText(keyFromModel)); +``` + +### 1.7 — Custom Facets must not collide with HeaderInfo auto-Facet IDs + +**Symptom.** ObjectPage edit mode shows an unexpected section called "Header" or "General", or your custom Facet is replaced by an auto-generated one. + +**Root cause.** FE V4 edit mode auto-generates a Facet from `UI.HeaderInfo`. Giving a custom Facet the same `ID` causes a silent merge. + +**Remedy.** Prefix custom Facet IDs with the entity name and the section purpose, e.g. `Invoices_Workflow_Timeline`. Never reuse `Header`, `General`, `Main`. + +### 1.8 — `@Common.ValueListWithFixedValues` duplicated crashes MDC + +**Symptom.** Filter bar refuses to render with "Invalid property definition" in MDC. + +**Root cause.** Annotating the same field with `@Common.ValueListWithFixedValues: true` from two sources (e.g. the schema and an annotation file) registers two value lists; MDC chokes on the duplicate. + +**Remedy.** Single source of truth. Decide whether the value list lives on the schema (`db/schema.cds`) or in the annotations layer (`app/annotations/*.cds`) — and stick to it. + +### 1.9 — `manifest.json` must declare the i18n model explicitly + +**Symptom.** Texts annotated `{i18n>label_*}` in CDS render blank in the UI. + +**Root cause.** FE V4 doesn't infer the i18n bundle path. The `manifest.json` must declare: +```json +"sap.ui5": { + "models": { + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { "bundleName": ".i18n.i18n" } + } + } +} +``` + +**Remedy.** Verify `bundleName` matches the actual file path under `webapp/i18n/`. Misaligned bundle paths → silent blank texts. + +### 1.10 — `@odata.draft.enabled` master data: choose per case, not per default + +**Symptom.** `lock_drafts` pollution and ETag conflicts on a CodeList that is rarely edited. + +**Root cause.** Blanket `@odata.draft.enabled: true` on pure read-mostly CodeLists adds friction without UX benefit. Conversely, transactional master data with multiple fields under governance benefits from the draft activate/cancel pattern. + +**Remedy.** Per-entity decision: +- **No draft** for read-mostly CodeLists / DocumentTypes (admin edits are rare, single-field). +- **Draft** for transactional master data with multi-field edits under governance. + +### 1.11 — Status fields: use `sap.common.CodeList`, not `String enum` + +**Symptom.** A status column in a table displays raw codes (`'PENDING_APPROVAL'`) instead of friendly labels. + +**Root cause.** `String(N) @assert.range enum { … }` does not generate a value list; FE V4 has no source to map code → label. + +**Remedy.** Model status as a `CodeList` entity: +```cds +entity ProcessingStatuses : sap.common.CodeList { + key code : String(20); +} +``` +Seed it via CSV. For tables, expose a `CASE`-derived computed field with `@Common.Text` + `@Common.TextArrangement: #TextOnly`. + +### 1.12 — Italian "Edit" appears as "Elabora" — never name custom actions "Edit" + +**Symptom.** The standard FE V4 Edit button conflicts with a custom action also called "Edit", or appears in unexpected positions. + +**Root cause.** FE V4 reserves the verb "Edit" for the draft-edit transition. Adding a custom action with the same label confuses the rendering pipeline. + +**Remedy.** Name custom actions explicitly: `Approve`, `Reject`, `Resolve`, `Cancel`. Never `Edit`, `Submit` (reserved by draft semantics), `Activate` (FE V4 internal). + +--- + +## Category 2 — CAP / TypeScript Pitfalls + +### 2.1 — `cds.tx(async tx => …)` autonomous deadlocks SQLite single-writer + +**Symptom.** Lifecycle E2E tests time out non-deterministically on SQLite; production rarely sees it but staging on a small Postgres might. + +**Root cause.** `cds.tx(async tx => …)` opens a separate transaction. On SQLite (single-writer) or under high contention, if the outer handler already holds a write lock, the inner autonomous tx waits forever. + +**Remedy.** Inside a handler, reuse the request tx: `cds.tx(req)`. Reserve autonomous tx (`cds.tx(async tx => …)`) for truly independent work (background jobs, post-commit side effects). For audit on Postgres / HANA where deadlock cost is lower, use `cds.connect.to('db')` and direct `db.run(INSERT…)`. + +### 2.2 — `forUpdate()` before lifecycle UPDATE + +**Symptom.** Concurrent lifecycle actions race; one of them silently overwrites the other's transition. + +**Root cause.** A `SELECT` that precedes an `UPDATE` in a lifecycle action does not lock the row. Under concurrent requests, both reads see `state=A`, both decide to advance to `state=B`, both write. + +**Remedy.** When the read informs the write, use `.forUpdate()`: +```javascript +const row = await tx.run(SELECT.one.from(Entity).where({ id }).forUpdate()); +// decide state transition based on `row` +await tx.run(UPDATE(Entity).set({ status: 'B' }).where({ id })); +``` + +### 2.3 — Side effects in `req.on('succeeded', …)`, not in the request handler + +**Symptom.** Action fails near the end, but the outbound notification / S/4 push has already fired. + +**Root cause.** A successful Cloud Application Programming handler may still roll back if a downstream handler errors. Side effects performed inside the handler get partially committed. + +**Remedy.** Use the request's lifecycle hook: +```javascript +req.on('succeeded', () => emitEvent(…)); +req.on('succeeded', () => sendNotification(…)); +``` +Inside the hook, the tx is already committed. Side effects are fire-and-forget; failure should `LOG.warn` only, never escalate to the user. + +### 2.4 — `cds.log('module-name')` everywhere; never `console.*` + +**Symptom.** Logs in production are noisy and unfilterable; correlation IDs are missing. + +**Root cause.** `console.log` bypasses the CAP logging facade. No log level, no module tag, no correlation. + +**Remedy.** Every module: +```typescript +const LOG = cds.log('module-name'); +LOG.info({ correlationId, action: 'foo' }, 'descriptive message'); +``` +Enables filtering by module, integrates with Cloud Logging / Loki, supports structured metadata. + +### 2.5 — Centralized reject helper (`rejectSafe`) — never expose `err.message` + +**Symptom.** A 500 response leaks internal error text including SQL fragments or stack traces. + +**Root cause.** `req.reject(500, err.message)` propagates the raw exception. SQL injection details, internal hostnames, table names all reach the client. + +**Remedy.** Build one helper: +```typescript +export function rejectSafe(req: cds.Request, code: number, userMsg: string, err: unknown, log: cds.Log) { + log.error({ err, code }, userMsg); + req.reject(code, userMsg); // userMsg is curated, no `err.message` +} +``` +All handlers route errors through this helper. Internal detail goes to the log; user gets a sanitized message. + +### 2.6 — TypeScript strict rolled out folder-by-folder, not repo-wide + +**Symptom.** A "strictify the project" PR explodes into thousands of type errors and never lands. + +**Root cause.** Strict mode is binary in `tsconfig.json`. Flipping it for the whole repo dumps decades of legacy types at once. + +**Remedy.** Per-folder strict tsconfig: +```json +// srv/tsconfig.strict.json +{ + "extends": "../tsconfig.json", + "compilerOptions": { "strict": true }, + "include": ["handlers/**/*.ts", "utils/**/*.ts"] +} +``` +Add `npm run typecheck:strict`. Promote folders one at a time, lock them in CI when ready. + +### 2.7 — `xs-security.json` tenant-mode: `dedicated` is the default; switch to shared deliberately + +**Symptom.** A multi-customer audit reveals tenant data crossing customer boundaries. + +**Root cause.** `"tenant-mode": "shared"` opens multi-tenancy at the XSUAA layer but doesn't automatically enforce tenant isolation in the data layer. Audit log writes, autonomous tx, and `cds.tx({tenant})` calls each need review. + +**Remedy.** Default to `"tenant-mode": "dedicated"` (single-tenant). Multi-customer = multi-deployment. Migration to shared multi-tenancy is a deliberate, planned exercise: schema-routed audit log, tenant-aware autonomous tx, separate review. + +### 2.8 — `cap-js` plugin matrix discipline + +**Symptom.** New plugins drift into `package.json` without ADR / matrix discussion; the runtime acquires unaudited side effects. + +**Root cause.** `cap-js/*` plugins are powerful but each one extends the runtime (e.g. `@cap-js/audit-logging` registers a BTP-emit layer; `@cap-js/change-tracking` hooks every update). Adding them silently leaves the team unaware of the new coupling. + +**Remedy.** Maintain a matrix doc (an ADR is sufficient) listing every `@cap-js/*` plugin: `Adopted` / `Deferred` / `Not-applicable`. CI gate verifies that `package.json` ↔ matrix doc match (see [`sap-cap-ci-gates-pattern`](https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-ci-gates-pattern/SKILL.md#pattern-4--convention--matrix-drift-detection) Pattern 4). + +--- + +## Category 3 — BTP / Kyma / On-Premise Deployment Lessons + +CAP + Fiori Elements V4 projects deploy across **four canonical target environments**, each with distinct constraints around authentication, persistence, UI delivery, remote service binding, and operational tooling. Patterns 3.A.* are target-specific; patterns 3.1-3.10 are cross-cutting (apply regardless of target). + +> **Always ask the deployment target first.** The auditor / orchestrator / generator skill **MUST** ask the user (or detect from project state) which of the four targets is in scope before generating manifests, recommending CDS profiles, or producing deployment artifacts. The wrong target produces unsalvageable advice (e.g. `html5-apps-repo` binding on Kyma → 401; `cds run` on a project expecting `cds watch` → blank UIs). + +### 3.0 — Deployment target decision matrix + +| Target | When to choose | CDS profile | Auth | DB | UI delivery | Remote services | +|---|---|---|---|---|---|---| +| **BTP Cloud Foundry** | Customer is fully BTP-managed, has CF entitlements, accepts BTP-managed services (Free or pay) | `production` (HANA HDI) or `production-pg` (PostgreSQL, deprecated 2026-Q4) | XSUAA | HANA HDI or BTP PostgreSQL service | `@sap/html5-app-repo` (Free plan OK) + approuter | BTP Destination service + Cloud Connector | +| **BTP Kyma** | Customer wants Kubernetes operational model, BTP-hosted but more flexible than CF, uses pay-tier or in-cluster services | `k8s` | OIDC (XSUAA or IAS via OAuth2) | PostgreSQL in-cluster (Bitnami Helm) or HANA Cloud | UI ZIPs embedded in approuter Docker image | `S4_BASE_URL` + Destination via Kyma BTP Operator | +| **On-Premise Kyma** | Customer-managed cluster (k3d local, Rancher, Gardener, OpenShift), uses customer IdP (Keycloak), needs full data sovereignty | `k8s-onprem` (Keycloak + HANA/PG) or `k8s-hana` (Kyma + HANA on-prem) | OIDC via customer IdP (Keycloak, Active Directory) | HANA on-prem or PostgreSQL on-prem | UI ZIPs embedded; ingress via NGINX or cluster-native | `*.svc.cluster.local` for in-cluster S/4 proxies + Cloud Connector for SaaS bridges | + +> **Not supported**: On-Premise Cloud Foundry. SAP Cloud Foundry On-Premise reached end of maintenance; the skill does not propose deployment plans against an EOL runtime. Customers with existing CF on-prem investment should be advised to migrate to **BTP CF** (managed) or **BTP Kyma**, not to extend the EOL footprint. + +The default recommendation for a new project is **BTP Cloud Foundry** — it is the most widely-adopted SAP-managed runtime, has the broadest service ecosystem (Free + paid tiers for `xsuaa`, `html5-apps-repo`, `destination`, `connectivity`, `event-mesh`, `audit-log`, `job-scheduling`, …), the most mature operational tooling (`mta.yaml` + `mbt build` + `cf deploy`), and the lowest operational ceremony for teams without dedicated Kubernetes expertise. Choose **BTP Kyma** when the customer wants the Kubernetes operational model (pay-tier services, more runtime flexibility, container-native eventing, CronJob CRDs). Choose **On-Premise Kyma** when the customer mandates data sovereignty or has existing Kubernetes infrastructure. + +### 3.A — BTP Cloud Foundry patterns + +#### 3.A.1 — Use `mta.yaml` for atomic deployment + +**Symptom.** Half-deployed state when a component fails: db deployer succeeded, srv failed, html5-apps-repo not yet wired. + +**Root cause.** Manual `cf push` per component doesn't atomically roll back on failure. + +**Remedy.** Wrap everything in `mta.yaml`: db deployer, srv module, approuter, destinations, XSUAA service binding. Use `mbt build` + `cf deploy .mtar`. Failures roll back the whole MTA. + +#### 3.A.2 — XSUAA scope wiring via `xs-security.json` + +**Symptom.** Role assignments work in dev but production users get 403 on actions that worked in test. + +**Root cause.** `xs-security.json` declares scopes; CAP handlers must `@(restrict: [{grant: '...', to: ''}])` to enforce. Mismatch between xs-security and `services-auth.cds` is silent. + +**Remedy.** Treat `xs-security.json` as the single source of truth for scope names. Match every `services-auth.cds` `to:` clause to a declared scope. Add a CI gate (see [`https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-ci-gates-pattern/SKILL.md#pattern-4--convention--matrix-drift-detection`](https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-ci-gates-pattern/SKILL.md) Pattern 4) that catches drift. + +#### 3.A.3 — Destination service for S/4HANA Tier-2 proxies + +**Symptom.** S/4 proxy calls fail with 401 in production despite working in dev with `.cdsrc-private.json`. + +**Root cause.** Dev profile uses inline credentials; production must use a Destination configured in BTP cockpit + bound to the srv module via destination service. + +**Remedy.** Define one BTP Destination per S/4 system. Bind the destination service to srv. Use principal propagation (SAML / JWT bearer) when end-user identity must reach S/4; use system user (BasicAuthentication or OAuth2ClientCredentials) for batch / job calls. + +#### 3.A.4 — html5-apps-repo for UI delivery (Free plan acceptable) + +**Symptom.** Approuter image is 600 MB because it ships every UI app embedded. + +**Root cause.** On CF, you can use the managed UI repo instead of embedding. + +**Remedy.** Use `@sap/html5-app-repo` Free or pay-tier. Build UI bundles via `ui5 build`, upload via `npm run upload-html5-apps`. Approuter routes `//*` to the repo. Free plan has limits (5 apps, 50 MB total) — verify against your project size before committing. + +#### 3.A.5 — Cloud Foundry health checks + +**Symptom.** App restarts often even when running fine. + +**Root cause.** Default CF health check is HTTP `/`. Many CAP services return 404 on `/` (no homepage). + +**Remedy.** In `manifest.yml`: `health-check-type: http`, `health-check-http-endpoint: /health/Live`. Pair with the `/health/Ready` endpoint for the proper liveness/readiness split (cross-cutting pattern 3.7). + +### 3.B — BTP Kyma patterns + +#### 3.B.1 — APIRule (CRD) for ingress, not Ingress YAML + +**Symptom.** Direct Ingress YAML works in dev but doesn't integrate with BTP IAM / Kyma identity. + +**Root cause.** Kyma's networking pipeline uses `APIRule` (custom CRD) that wires JWT validation, CORS, rate limiting natively. + +**Remedy.** For each public service, declare an `APIRule`: +```yaml +apiVersion: gateway.kyma-project.io/v1beta1 +kind: APIRule +metadata: + name: srv +spec: + host: srv.${KYMA_APP_URL} + service: + name: srv + port: 4004 + gateway: kyma-system/kyma-gateway + rules: + - path: /.* + methods: [GET, POST, PUT, PATCH, DELETE] + accessStrategies: + - handler: jwt + config: + jwks_urls: + - https://${XSUAA_HOST}/token_keys + trusted_issuers: + - https://${XSUAA_HOST} +``` +For approuter, add `noAuth` accessStrategy on the OAuth2 callback paths. + +#### 3.B.2 — Service Manager bindings via secrets (not env) + +**Symptom.** Refactoring credentials breaks the running pod because env vars don't reload. + +**Root cause.** Kyma's BTP Service Operator creates `Secret` resources on `ServiceBinding` apply; pod mounts them as volume + env. + +**Remedy.** Use `ServiceInstance` + `ServiceBinding` CRDs. Don't hardcode credentials in `Deployment.env`; reference the Secret by name. Pod restart picks up rotated credentials automatically (with proper `restartPolicy`). + +#### 3.B.3 — CronJob CRD for scheduled jobs, not BTP Job Scheduler + +**Symptom.** BTP Job Scheduler binding fails (or costs extra) on Kyma. + +**Root cause.** BTP Job Scheduler is a CF-friendly service; Kyma-native is `CronJob` CRD. + +**Remedy.** Declare every scheduled job as a Kyma `CronJob` pointing at a job-runner endpoint: +```yaml +apiVersion: batch/v1 +kind: CronJob +metadata: + name: +spec: + schedule: "0 */6 * * *" + jobTemplate: + spec: + template: + spec: + containers: + - name: trigger + image: curlimages/curl:8.5.0 + command: ["sh", "-c", "curl -fsS -X POST -H 'X-Job-Token: $(JOB_TOKEN)' http://srv:4004/api/jobs/"] + env: [{name: JOB_TOKEN, valueFrom: {secretKeyRef: {name: job-token, key: token}}}] + restartPolicy: OnFailure +``` +The srv module exposes job endpoints (token-protected). Cron triggers via HTTP. + +#### 3.B.4 — Bitnami PostgreSQL Helm chart for in-cluster persistence + +**Symptom.** BTP PostgreSQL Free plan refuses Kyma binding (CF-only). + +**Root cause.** Free-tier BTP services don't route into Kyma. + +**Remedy.** Install Bitnami PostgreSQL via Helm: `helm install pg bitnami/postgresql -n -f values-pg.yaml`. PVC-backed, single-instance OK for non-critical workloads; for production use Patroni Helm chart for HA. Connection details via Secret. + +#### 3.B.5 — `KYMA_APP_URL` + `KYMA_KUBECONFIG` GitHub Actions secrets for multi-region + +**Symptom.** Deploying to a second region requires forking the CI pipeline. + +**Root cause.** Hardcoded hostnames in workflow YAML and Kubernetes manifests. + +**Remedy.** Parameterize via 2 GitHub Actions secrets: `KYMA_APP_URL` (cluster-specific subdomain) + `KYMA_KUBECONFIG`. Use `envsubst` in the deployment workflow: +```yaml +- run: kubectl apply -f <(envsubst < k8s/deployment-srv.yaml) +``` +Region switch = rotate the two secrets + update DNS CNAME. Zero code change. + +#### 3.B.6 — HorizontalPodAutoscaler + PodDisruptionBudget for production + +**Symptom.** Pod evictions during cluster upgrades cause unavailability spikes. + +**Root cause.** Default `Deployment` has no HPA / PDB; cluster upgrades evict pods in parallel. + +**Remedy.** Pair every production `Deployment` with: +- `HorizontalPodAutoscaler` (min: 2, max: N, targetCPU: 70%) for capacity. +- `PodDisruptionBudget` (minAvailable: 1) so cluster upgrades drain pods serially. +- `NetworkPolicy` to restrict in-cluster traffic (default-deny + explicit allow). + +#### 3.B.7 — Approuter session stickiness via nginx cookie affinity + +**Symptom.** FE V4 batch requests fail intermittently with CSRF token mismatch (403 / 419). + +**Root cause.** Kyma's default load balancer round-robins across approuter pods; CSRF tokens are pod-local. + +**Remedy.** Cookie-based affinity on the Kyma `APIRule`: +```yaml +metadata: + annotations: + nginx.ingress.kubernetes.io/affinity: "cookie" + nginx.ingress.kubernetes.io/session-cookie-name: "route" + nginx.ingress.kubernetes.io/session-cookie-max-age: "172800" +``` +Never disable CSRF (`csrfProtection: false`) — UI5 + FE V4 depend on it. This is the same as cross-cutting Pattern 3.3 but worth re-stating in the Kyma section. + +### 3.C — On-Premise Kyma patterns + +#### 3.C.1 — Cluster flavor matters: k3d / Rancher / Gardener / OpenShift + +**Symptom.** Manifests that work on BTP Kyma fail on the customer's on-prem cluster. + +**Root cause.** Kyma CRDs (APIRule, ServiceInstance) may not be present; cluster may use Istio differently; storage class names differ. + +**Remedy.** Detect the flavor upfront: +- **k3d** (local dev): no APIRule CRD; use plain `Ingress` + nginx-ingress controller. +- **Rancher**: usually has Istio; APIRule may need install of Kyma networking module. +- **Gardener** (SAP-managed customer): Kyma module typically installed; treat like BTP Kyma. +- **OpenShift**: Routes instead of Ingress; SCC (Security Context Constraints) may block non-root containers. + +Provide two manifest sets: a "Kyma-native" (APIRule + ServiceInstance) and a "Kubernetes-vanilla" (Ingress + Secret). + +#### 3.C.2 — Customer IdP via Keycloak realm + +**Symptom.** BTP IAM (XSUAA / IAS) doesn't reach the customer's on-prem network. + +**Root cause.** On-prem deployment cannot consume BTP-managed auth services. + +**Remedy.** Provide a Keycloak realm definition (`keycloak-realm.json`) with: +- Roles matching the CAP `services-auth.cds` scopes (Viewer, Admin, etc.). +- Groups mapping to roles. +- OAuth2 client for the approuter with redirect URI = `https:///login/callback`. +- Optional: federate with customer's Active Directory / LDAP / SAML2 IdP. + +In CAP, use `OIDCAuthStrategy` (the project's custom strategy that validates JWTs from a configurable issuer): +```yaml +# k8s-onprem profile +cds.requires.auth: + kind: jwt-auth + issuer: https://keycloak.${CUSTOMER_DOMAIN}/realms/ + jwks_uri: https://keycloak.${CUSTOMER_DOMAIN}/realms//protocol/openid-connect/certs +``` + +#### 3.C.3 — `secrets.env` + `envsubst` pattern for per-customer config + +**Symptom.** Each customer needs different DB endpoint / IdP URL / S/4 hostname; copy-paste manifests are error-prone. + +**Root cause.** Kubernetes ConfigMaps / Secrets don't templatize across deployments. + +**Remedy.** Per-customer `secrets.env` file (one source of truth), checked into customer-specific repo only. Installer script does: +```bash +set -a +source secrets.env +set +a +envsubst < k8s/onprem/configmap.yaml | kubectl apply -f - +envsubst < k8s/onprem/deployment-srv.yaml | kubectl apply -f - +``` +`secrets.env` contains: `DB_HOST`, `DB_USER`, `S4_BASE_URL`, `KEYCLOAK_REALM_URL`, `CUSTOMER_DOMAIN`, etc. + +#### 3.C.4 — Sizing wizard: customer hardware tier (xs / s / m / l) + +**Symptom.** Customer with 2 GB cluster RAM crashes the pod on first request. + +**Root cause.** Default resource requests/limits sized for cloud (4-8 GB pod RAM); on-prem dev clusters may have far less. + +**Remedy.** Document four sizing tiers in the deployment manifests with `kustomize` or Helm overlay: +| Tier | Pod RAM request | Pod CPU request | Replicas | Use case | +|---|---|---|---|---| +| **xs** | 512 Mi | 250 m | 1 | dev / proof-of-concept, ≤10 concurrent users | +| **s** | 1 Gi | 500 m | 2 | small customer, ≤50 concurrent users | +| **m** | 2 Gi | 1000 m | 3 | medium customer, ≤200 concurrent users | +| **l** | 4 Gi | 2000 m | 4+ HPA | large customer, ≤1000 concurrent users | + +For dev clusters (`xs` tier) optionally enable `LOW_MEM_MODE=true` env var that disables expensive features (less aggressive caching, smaller worker pools). **Document as DEV-ONLY** — customers in production must use the appropriate tier from the wizard. + +#### 3.C.5 — Pre-flight check before install + +**Symptom.** Installer halfway in fails because Docker / RAM / DNS / kubectl version is insufficient. + +**Root cause.** Cluster pre-conditions not verified upfront. + +**Remedy.** First step of the installer script does a comprehensive pre-flight: +```bash +# Pre-flight +check_docker_running || die "Docker not running" +check_docker_ram_gb 4 || die "Need ≥4 GB Docker RAM" +check_kubectl_version "1.28" || die "Need kubectl ≥1.28" +check_dns_resolve "$KEYCLOAK_HOST" || die "Cannot resolve Keycloak host" +check_curl_works "$S4_BASE_URL" || warn "S/4 hostname unreachable (Cloud Connector down?)" +check_storage_class_exists "$STORAGE_CLASS" || die "Storage class missing" +``` +Exit cleanly with actionable error before consuming time / state. + +#### 3.C.6 — In-cluster service URLs (`*.svc.cluster.local`) + +**Symptom.** S/4 proxy host hardcoded as a public hostname doesn't reach in-cluster S/4 proxy. + +**Root cause.** On-prem clusters may run their own S/4 OData proxy as a sidecar service. + +**Remedy.** Configure `S4_BASE_URL` (or equivalent) to use Kubernetes service DNS: `http://s4-proxy.s4-namespace.svc.cluster.local:8080`. The CAP runtime resolves it cluster-internally without exiting to public DNS. Faster, more secure, no Cloud Connector needed for in-cluster paths. + +### 3.1 — `cds run` does NOT mount UI5 apps + +**Symptom.** Production deployment serves the OData service but the UI5 apps return 404. + +**Root cause.** `cds-plugin-ui5` (which mounts apps under `cds watch`) is dev-only. `cds run` (production) doesn't include it. + +**Remedy.** In production, serve UI bundles via the approuter: +- Build UI ZIPs (`ui5 build --all`). +- Embed under `approuter/static//` in the Docker image. +- Approuter routes `//*` → static files. +- OR use `@sap/html5-app-repo` (BTP CF only; not Kyma if free-tier). + +### 3.2 — BTP Free Plan is CF-only + +**Symptom.** Trying to bind `html5-apps-repo` or `postgresql-db` (free) from a Kyma deployment returns 401 or refuses to provision. + +**Root cause.** Free-tier service brokers expose private endpoints reachable only from BTP CF, not from Kyma's cluster network. + +**Remedy.** For Kyma deployments, pay-tier or in-cluster equivalents: +- UI ZIPs embedded in approuter image (instead of `html5-apps-repo`). +- Bitnami PostgreSQL Helm chart in-cluster (instead of `postgresql-db` free). +- HANA Cloud has a public endpoint, usable from Kyma directly. + +### 3.3 — Approuter on Kyma needs session stickiness + +**Symptom.** FE V4 batch requests fail intermittently with CSRF token mismatch (403 / 419). + +**Root cause.** Kyma's default load balancer round-robins requests across approuter pods. The CSRF token issued by pod A is rejected by pod B. + +**Remedy.** Cookie-based affinity on the Kyma APIRule: +```yaml +metadata: + annotations: + nginx.ingress.kubernetes.io/affinity: "cookie" + nginx.ingress.kubernetes.io/session-cookie-name: "route" +``` +Never disable CSRF (`csrfProtection: false`) — UI5 + FE V4 depend on it. + +### 3.4 — Multi-stage Dockerfile: build needs native toolchain, runtime stays slim + +**Symptom.** Build fails on `tree-sitter-java` native compilation or runtime image is bloated with 800 MB of build dependencies. + +**Root cause.** Some `@sap/eslint-plugin-cds` transitive deps need `python3 make g++` to build native modules; the production image doesn't need them. + +**Remedy.** Multi-stage build: +```Dockerfile +FROM node:22 AS builder +RUN apk add python3 make g++ # build-only tools +COPY package*.json ./ +RUN npm ci --legacy-peer-deps +COPY . . +RUN npx cds build --production +WORKDIR /app/gen/srv +RUN npm ci --production && npm install @cap-js/sqlite @sap/cds-dk tsx + +FROM node:22-slim AS runtime +COPY --from=builder /app/gen /app/gen +WORKDIR /app/gen/srv +ENV NODE_OPTIONS='--import tsx' +CMD ["node_modules/.bin/cds-serve"] +``` + +### 3.5 — `NODE_OPTIONS='--import tsx'` for TypeScript in production + +**Symptom.** Production container starts but fails on first TypeScript handler load: "Unknown file extension: .ts". + +**Root cause.** Standard Node 22 can't load `.ts` files; the project's CAP handlers are TypeScript. + +**Remedy.** Bundle `tsx` in the runtime image and load it via `NODE_OPTIONS='--import tsx'`. The `cds-serve` binary handles the rest. + +### 3.6 — `npm prune` after `npm ci --production` is harmful + +**Symptom.** Production image is missing `@cap-js/sqlite` (test profile) or `tsx` (TypeScript loader); runtime fails. + +**Root cause.** `npm prune --production` removes anything marked `devDependencies`, but the CAP runtime needs some `devDependencies` even in production (test driver to fallback, TS loader). + +**Remedy.** Do **not** run `npm prune`. Use `npm ci --production` in the runtime stage and add back what you specifically need: `npm install @cap-js/sqlite @sap/cds-dk tsx`. + +### 3.7 — Health probes: separate `/health/Live` (no-DB) from `/health/Ready` (DB check) + +**Symptom.** Kyma keeps restarting the pod even though the app is up because the DB is briefly unreachable. + +**Root cause.** A liveness probe that hits the DB conflates "app is alive" with "DB is reachable". A flapping DB → restart loop. + +**Remedy.** Two endpoints: +- `/health/Live` → always 200 if the process is responsive. No DB. +- `/health/Ready` → 200 only if DB is reachable AND model loaded. +Map `livenessProbe` → `/health/Live`, `readinessProbe` → `/health/Ready`. + +### 3.8 — Multi-region readiness via secret rotation + +**Symptom.** Deploying to a second region requires forking the CI pipeline. + +**Root cause.** Hardcoded hostnames in workflow YAML and manifests. + +**Remedy.** Parameterize the Kyma hostname via 2 GitHub Actions secrets: `KYMA_APP_URL` (e.g. `myapp.c-xxx.kyma.ondemand.com`) + `KYMA_KUBECONFIG`. Region switch = rotate the two secrets + update DNS. Zero code change. + +### 3.9 — Image registry: prefer public GHCR for OSS, private for IP + +**Symptom.** Kyma can't pull the image; private registry credentials are rotating constantly. + +**Root cause.** Mixing the image-pull-secret rotation with the deployment rhythm. + +**Remedy.** For open-source / non-sensitive code, public GHCR. For sensitive: dedicate a service account, rotate the PAT separately from deployment, surface the rotation as a runbook item. + +### 3.10 — `cds-plugin-ui5` is dev-only; ship UI separately + +**Symptom.** "UI works in `cds watch` but breaks in `cds run`." + +**Root cause.** `cds-plugin-ui5` registers a custom UI5 sandbox under `cds watch`. `cds run` (production) doesn't load plugins of that class. + +**Remedy.** Two distinct delivery paths: +- Local dev → `cds watch` + `cds-plugin-ui5` serves apps from `app//webapp/`. +- Production → approuter serves apps from `dist/` (post-`ui5 build`) bundled into the Docker image. + +### 3.11 — Clean Core Level A as **deployment gate**, not just an audit + +**Symptom.** Production deployment succeeds; weeks later a consumed S/4HANA Communication Scenario is deprecated; the next quarterly upgrade window breaks the app silently. + +**Root cause.** Clean Core compliance was audited once at design time and not re-verified pre-deployment. The audit's findings live in a markdown report that nobody reads on the day of deploy. + +**Remedy.** Make Clean Core Level A verification a **CI gate that blocks deployment**, not a passive report. Three authoritative sources MUST be consulted: + +1. **The ABAP API Release State repository** (`SAP/abap-atc-cr-cv-s4hc`): https://github.com/SAP/abap-atc-cr-cv-s4hc/blob/main/README.md. This repo's `*.json` files are the **source of truth** for whether an ABAP object (CDS view, BAPI, RFC FM) is released — and for which edition (Public Cloud / Private Cloud / On-Premise). Pin a `git submodule` or scheduled-clone snapshot of this repo into the project; re-pull weekly via cron. + +2. **The SAP API Hub** (https://api.sap.com/): authoritative for **OData service availability per edition** (Communication Scenarios, packages, lifecycle states). Cross-check every `cds.connect.to()` against the API Hub; record the edition matrix in the project's compatibility catalog. + +3. **The project's compatibility catalog** (e.g. `srv/integration/s4CompatibilityPolicy.js`): the project's *declared* edition × service matrix with `availability[]` and `probeObject` fields. The CI gate verifies this catalog matches sources 1 + 2; any drift is a HARD FAIL (cannot ship). + +Use [`https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-clean-core-enforce/SKILL.md`](https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-clean-core-enforce/SKILL.md) to discover consumption, build the matrix, detect drift. Use [`https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-ci-gates-pattern/SKILL.md#pattern-3--released-state--api-availability-drift-detection`](https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-ci-gates-pattern/SKILL.md) Pattern 3 to enforce the gate on every PR. + +Compliance contract: +- **Level A** (Released APIs only): every consumed Tier-2 S/4 service is in the released catalog for ALL deployment-target editions. Zero RFC/BAPI/SQL-direct usage. Zero modifications to standard tables. Zero user-exits. +- **Drift detected** ⇒ CI fails. Choices: (a) remove the consumption, (b) replace with the released equivalent, (c) accept and document a Level B/C/D exception (rare, needs sign-off). + +This is the **single most important** deployment gate for CAP + S/4 projects. A non-released service that lands in production becomes a multi-quarter migration debt the next time SAP revises the API surface. + +--- + +## Category 4 — Security Defense-in-Depth + +### 4.1 — Audit log append-only — three layers + +**Symptom.** A SOX / GDPR auditor demands proof that AuditLogEntry cannot be tampered with. + +**Root cause.** Single-layer enforcement (CDS `@restrict: READ only`) is bypassable by anyone with DB access. + +**Remedy.** Three layers, none of which can be the only one: +1. **CDS layer**: `grant: 'READ'` only; no `WRITE` / `UPDATE` / `DELETE` for any role except a specific anonymization job. +2. **Handler layer**: a before-handler on `AuditLogEntry` rejects any non-INSERT verb with 403 explicitly. +3. **Database layer**: DB trigger (Postgres / HANA) raises an exception on `UPDATE` or `DELETE` of `AuditLogEntry` rows, except from a designated retention process. + +Layer 1 is the contract; layer 2 enforces in the runtime; layer 3 is the safety net if a privileged code path slips past layers 1-2. + +### 4.2 — PII sanitization before audit log + +**Symptom.** A leaked DB dump exposes IBANs, fiscal codes, emails because they ended up in the audit log. + +**Root cause.** Audit log writes pass through every textual field as-is. + +**Remedy.** A central `_sanitizePII(text)` helper applied at the audit-log entry boundary. Mask IBANs (preserve last 4), fiscal codes, VAT numbers, full email addresses. Wire it into the audit logger: +```typescript +async function logAuditEntry(tx, entry) { + for (const field of TEXTUAL_FIELDS) { + entry[field] = _sanitizePII(entry[field]); + } + await tx.run(INSERT.into(AuditLogEntry).entries(entry)); +} +``` + +### 4.3 — Magic bytes verification on upload — never trust MIME header + +**Symptom.** A user uploads `evil.pdf` whose body is actually an HTML / SVG with a script payload. + +**Root cause.** The `Content-Type` header is set by the client; PDF readers / image previewers in the UI render based on the body bytes, not the header. + +**Remedy.** Read the first 8 bytes of the upload and verify the magic bytes match the claimed MIME type. For PDF: `%PDF-`. For PNG: `\x89PNG`. For JPEG: `\xFF\xD8\xFF`. Reject mismatches with 415. The check belongs in the upload handler before any persistence call. + +### 4.4 — Rate limiters per endpoint class, not global + +**Symptom.** Auth endpoint is blocked by a global rate limit while file upload is wide open. + +**Root cause.** A single global rate limit can't reflect the different risk / traffic profiles of different endpoint classes. + +**Remedy.** Per-class limits (typical baseline): +- Global: 200/min/IP. +- Write OData: 60/min/IP. +- File upload: 10/min/IP. +- Job endpoints: 30/min/IP. +- MCP / external integration: 60/min per consumer (key from token claim). +All env-tunable. Use Redis backing in multi-pod deployments so limits are global, not per-pod. + +### 4.5 — Token rotation: quarterly default; immediate on compromise + +**Symptom.** A bot token leaked into a public log six months ago is still valid. + +**Root cause.** No rotation policy means tokens live until manually rotated. + +**Remedy.** Document a quarterly rotation runbook for every long-lived token (job invocation token, MCP service token, integration token). Immediate rotation triggers: token in logs, contributor leaves the project, secret scanner alert. + +### 4.6 — OData `$expand` depth cap + per-entity `$top` cap + +**Symptom.** A malicious / careless client expands six levels deep with `$top=10000`; the DB shudders. + +**Root cause.** OData V4 defaults are permissive. + +**Remedy.** +- Enforce a global `$expand` depth limit (2-3) in a request preprocessing handler. +- Enforce per-entity `$top` defaults and caps (e.g. AuditLog: default 200, max 1000). + +### 4.7 — Multi-doc XML / multi-batch attack surface + +**Symptom.** Uploaded multi-document XML (e.g. FatturaPA lotto) brings the parser to its knees with thousands of bodies. + +**Root cause.** XML parsers happily walk every body; absent a cap, an attacker can DoS the service with a single-byte-per-body archive of millions. + +**Remedy.** Hard cap on documents per upload (`MAX_BODIES = 50` is reasonable for batch-invoice patterns). Reject excess with 413. + +--- + +## Category 5 — Customizing-Driven Patterns + +### 5.1 — SystemParameter single-source-of-truth with bounded cache + +**Symptom.** A parameter change in the admin UI takes 5-30 minutes to propagate; users complain. + +**Root cause.** Naive caching with no TTL or no invalidation bus. + +**Remedy.** Wrap reads through a `SystemParamReader.get(category, companyCode)`. Cache for 60-300 seconds (TTL). Publish an `InvalidationBus.publish(category)` event on writes that other pods listen to (Redis pub/sub or NATS). Cross-pod invalidation arrives in <1 s. + +### 5.2 — Adapter factory dual-source fallback chain + +**Symptom.** A change in the customizing UI doesn't take effect; the code is still reading from `cds.env`. + +**Root cause.** Adapter wiring reads `cds.env.X` only, bypassing the SystemParameter layer. + +**Remedy.** Canonical fallback chain inside the adapter factory: +```typescript +const params = await getSystemParamReader().get(CATEGORY, companyCode); +const value = params.MY_KEY // canonical + || cds.env.adapter?.my_key // cds.env fallback + || process.env.MY_KEY // env var fallback + || DEFAULT_VALUE; // hardcoded last-resort +``` +Mark the order in code comments. Audit ensures every adapter uses this chain (see [`https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-customizing-honor/SKILL.md`](https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-customizing-honor/SKILL.md)). + +### 5.3 — Per-tenant override pattern (catalog → company override) + +**Symptom.** A check must be active for company A and disabled for company B without forking the codebase. + +**Root cause.** Single-layer config (`ProcessStepCheck` catalog) can't express per-tenant variation. + +**Remedy.** Two-level table: +1. **Catalog** (`ProcessStepCheck`): ships with the product. Read-only at runtime. +2. **Override** (`CompanyCheckOverride`): one row per `(CompanyCode, CheckCode)`. Fields override catalog defaults (`IsDisabled`, `Severity`, `min`, `max`). +Runtime resolution: override → catalog → hardcoded default. + +### 5.4 — Master-data references must be value-list-bound + +**Symptom.** A user typed `1001 ` (trailing space) in a free-text CompanyCode field; downstream lookups fail. + +**Root cause.** Free-text input on a foreign-key field bypasses referential integrity. + +**Remedy.** Every FK field that points to a master-data entity (CompanyCode, GLAccount, BusinessPartner, etc.) must have `@Common.ValueList` annotation. On the filter bar AND on the edit form. Combined with `@Common.Text` + `@Common.TextArrangement: #TextOnly` for human-readable display. + +### 5.5 — Bidirectional CSV ↔ code consistency + +**Symptom.** Admin UI surfaces a parameter that does nothing; or a code path reads from a parameter the admin doesn't know exists. + +**Root cause.** No enforced contract between CSV seed and code consumer. + +**Remedy.** A CI gate (see [`sap-cap-ci-gates-pattern`](https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-ci-gates-pattern/SKILL.md#pattern-1--bidirectional-csv--code-consistency)) that fails the build on **inverse orphans** (code reads, CSV missing) or **forward orphans** (CSV seeds, no consumer). Allowlist for legitimate external consumers. + +--- + +## Category 6 — Lifecycle / Process Discipline + +### 6.1 — Centralized phase boundary map + +**Symptom.** Three different handlers compute "what's the next status after PENDING_APPROVAL" independently and disagree. + +**Root cause.** Status transitions implicit in each handler's logic. + +**Remedy.** Single map module (`srv/process/statusTransitions.ts`): +```typescript +export const PHASE_BOUNDARIES: Record = { + NEW: 'RECEIVED', + RECEIVED: 'VALIDATED', + // … +}; +``` +Every action queries the map; tests verify exhaustiveness. + +### 6.2 — CAS marker for idempotent state advance + +**Symptom.** A user double-clicks "Post Invoice"; two S/4 posts happen. + +**Root cause.** Handler isn't idempotent. + +**Remedy.** Compare-And-Swap on a marker column: +```javascript +const updated = await tx.run( + UPDATE(Invoices).set({ PostedAt: new Date() }) + .where({ id, PostedAt: { '=': null } }) // only if not yet posted +); +if (updated === 0) { + return { alreadyPosted: true }; // someone else already did it +} +// proceed with actual post-to-S4 call +``` + +### 6.3 — Touchless handler idempotency + +**Symptom.** A scheduled job runs the same automatic step twice on retry; downstream side effects duplicate. + +**Root cause.** Steps marked `IsAutomatic=true` are invoked by a job runner that may retry on transient failure. + +**Remedy.** Every touchless handler (resolveBP, checkDuplicate, validateFiscal, matchPO, etc.) must be idempotent: no double INSERT, no double S/4 call, no double notification. Use CAS markers (`6.2`) or guard with explicit "already processed" checks at the top. + +### 6.4 — Exception auto-dispatch ordering + +**Symptom.** Audit log shows the dispatch event before the raise event, confusing forensic review. + +**Root cause.** Auto-dispatcher fires before the audit logger commits. + +**Remedy.** Strict order in the raise handler: +1. `logAuditEntry(…)` — write audit row. +2. `autoDispatch(…)` — only after audit. +3. Return. +This way the audit log captures the pre-dispatch state. + +### 6.5 — Orphan workflow cleanup when exception closes via reclassify + +**Symptom.** A workflow item is still PENDING for an exception that the system already closed via reclassification. + +**Root cause.** Exception closure path doesn't cascade to dependent workflow items. + +**Remedy.** Helper `cancelOrphanWorkflows(tx, closedExceptionIds)` that: +1. Holds the lock acquired upstream (`forUpdate`). +2. UPDATE InvoiceWorkflowItem SET state='CANCELLED' WHERE exception_id IN closedExceptionIds AND state='PENDING'. +3. Logs each cancellation to the audit log. + +### 6.6 — Auto-release pattern (no `forUpdate`, reuses caller tx) + +**Symptom.** Auto-release of a payment block triggers a SELECT that races with the user's release. + +**Root cause.** Naive implementation opens its own tx + forUpdate, contending with the caller. + +**Remedy.** Reuse the caller's tx. Use a CAS marker (`SYSTEM_AUTO`) to ensure the auto-release fires only once. Document the exception clearly — this pattern is the **only** safe place to skip `forUpdate`, and it relies on CAS idempotency. + +--- + +## Category 7 — Events / Messaging Post-Commit Patterns + +### 7.1 — Declarative event service with typed payloads + +**Symptom.** Six different files emit "InvoiceApproved" with three different payload shapes. + +**Root cause.** No declarative contract for events. + +**Remedy.** Declare events in a service: +```cds +service BusinessEvents @(path: '/odata/v4/events') { + event InvoiceApproved : { eDocumentGuid: UUID; companyCode: String; approver: String; approvedAt: Timestamp }; + // … +} +``` +Emit via `cds.connect.to('BusinessEvents').emit('InvoiceApproved', payload)`. AsyncAPI auto-generated from the declaration. + +### 7.2 — Post-commit fire-and-forget emit + +**Symptom.** A successful approve action gets blocked because the event broker is slow. + +**Root cause.** Synchronous emit inside the handler couples user-facing latency to broker SLA. + +**Remedy.** Emit from `req.on('succeeded')`: +```javascript +req.on('succeeded', async () => { + try { await emitBusinessEvent('InvoiceApproved', payload); } + catch (err) { LOG.warn({ err }, 'emit failed'); } +}); +``` +The user-facing tx commits; emit failure logs but doesn't propagate. + +### 7.3 — Connection cache as `Promise`, not `Service` + +**Symptom.** First concurrent requests race; the service connects N times. + +**Root cause.** Cache pattern `let cached: Service | null; if (!cached) cached = await cds.connect.to('X')` allows multiple awaits to all see `null` and initialize. + +**Remedy.** Cache the promise: +```typescript +let cached: Promise | null = null; +function getEvents(): Promise { + if (!cached) cached = cds.connect.to('BusinessEvents'); + return cached; +} +``` +First call starts the connect; all concurrent calls await the same promise. + +### 7.4 — Idempotency key cross-emit + +**Symptom.** A retry emit during a transient broker outage produces a duplicate event downstream. + +**Root cause.** Consumer can't tell "this is a retry of the same event" from "this is a new event". + +**Remedy.** Compose a deterministic `idempotencyKey` per (eventType, entityId, companyCode) and include it in the payload metadata. Consumers dedupe on the key. Replay paths (e.g. outbox redelivery) reuse the same key. + +### 7.5 — Outbox replay mirror as detective control + +**Symptom.** Broker was down for 4 hours; some events never made it. + +**Root cause.** No replay path for emissions that failed silently. + +**Remedy.** Mirror emit events into `cds.outbox.Messages` (or a project-specific table). A periodic job re-emits unacknowledged entries. Distinguish first-emit from replay-emit in the metrics (`business_events_emit_total` vs `business_events_replay_total`). + +--- + +## Category 8 — Ecosystem Plugin Landscape + +This category is not gotchas — it's the map of **companion plugins / skills** that a CAP + Fiori Elements + BTP project benefits from. Each entry lists what it provides; the consuming skill's "Recommended Companion Plugins" section will reference these by name. + +### 8.1 — SAP CAP Capire (`sap-cap-capire`) + +CAP runtime knowledge base. Covers CDS modeling, service handlers, draft semantics, authentication / authorization, deployment profiles, multitenancy. Reference for any CAP-side question that goes deeper than this skill's snapshot. + +### 8.2 — SAP UI5 (`sapui5`, `sap-fiori-tools`) + +UI5 API explorer, control library reference, Fiori Tools scaffolding. Look up control APIs, manifest schema, annotation reference. Mandatory companion when working on Fiori Elements V4 apps. + +### 8.3 — SAP BTP Cloud Platform (`sap-btp-cloud-platform`) + +BTP service map (auth, persistence, connectivity, eventing). Use when designing a new BTP-side deployment or troubleshooting service bindings. + +### 8.4 — SAP BTP Connectivity (`sap-btp-connectivity`) + +Destination service, Cloud Connector, on-prem connectivity, principal propagation. Use when configuring S/4HANA destinations (BTP CF) or troubleshooting Tier-2 proxy auth. + +### 8.5 — SAP BTP Integration Suite (`sap-btp-integration-suite`) + +iFlow design, Open Connectors, API Management. Use when the project consumes Integration Suite iFlows or exposes APIs via API Management. + +### 8.6 — SAP BTP Cloud Logging (`sap-btp-cloud-logging`) + +OpenTelemetry integration, Kibana / Cloud Logging dashboards. Use when wiring up production observability. + +### 8.7 — SAP BTP Job Scheduling (`sap-btp-job-scheduling`) + +BTP Job Scheduler service for cron-style jobs. Alternative / complement to Kyma CronJob CRDs. + +### 8.8 — SAP BTP Audit Log (`sap-btp-audit-log`) + +BTP Audit Log Service. Adopt as a destination for the local AuditLogEntry events; never replace the local audit log (defense-in-depth Category 4.1). + +### 8.9 — SAP BTP Master Data Integration (`sap-btp-master-data-integration`) + +MDI for BP / Product / Cost-Object distribution across systems. Use when the project needs master-data sync to S/4 / SuccessFactors / etc. + +### 8.10 — SAP CAP Best Practices (`sap-cap-best-practices`) + +Curated CAP patterns from SAP engineering. Use as a cross-check for Category 2 patterns when in doubt. + +### 8.11 — SAP PCE Expert (`sap-pce-expert`) + +S/4HANA Private Cloud Edition / RISE specifics. Use when the project consumes S/4HANA PCE / RISE (Tier-2 proxies have different host patterns and Communication Scenarios). + +### 8.12 — SAP Fiori Tools (`sap-fiori-tools`) + +Fiori Tools MCP: manifest validation, Fiori app discovery, page-template scaffolding. Use during Fiori Elements V4 setup or migration. + +### 8.13 — SAP CAP MCP (`sap-cap-capire` MCP server / `@sap/cds-mcp`) + +CDS model search, doc lookup. Use during exploration of an unfamiliar CAP project. + +### 8.14 — SAP Docs / SAP Notes (`sap-docs`, `sap-note-search`) + +Help portal, Notes search, `sap_search_objects` for Clean Core checks. Mandatory companion for any S/4HANA Tier-2 proxy work. + +### 8.15 — Context7 (`context7`) + +Generic library documentation lookup. Use for non-SAP dependencies (Node libraries, npm packages). + +### 8.16 — Playwright MCP (`playwright`) + +Browser automation for UI tests. Use during Fiori app smoke tests or visual regression checks. + +--- + +## How to Use This Skill + +### As a direct reference + +When the user asks "what's the best practice for X in CAP / Fiori Elements?", search this catalog for the pattern, cite it by anchor, and reference the companion skill if a deeper / operational answer is needed. + +### As a cross-link target + +Other skills in this repository cross-link to specific patterns by anchor: +- [`https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-fiori-app-audit/SKILL.md`](https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-fiori-app-audit/SKILL.md) refers to Category 1 for FE V4 traps. +- [`https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-security-rbac-matrix/SKILL.md`](https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-security-rbac-matrix/SKILL.md) refers to Category 4 for defense-in-depth. +- [`https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-customizing-honor/SKILL.md`](https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-customizing-honor/SKILL.md) refers to Category 5 for customizing patterns. +- [`https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-clean-core-enforce/SKILL.md`](https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-clean-core-enforce/SKILL.md) refers to Category 3.8 for multi-region deployment. +- [`https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-stack-audit-full/SKILL.md`](https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-stack-audit-full/SKILL.md) acts as orchestrator and consults this catalog when consolidating findings. + +### As a teaching index + +A developer new to CAP + Fiori Elements V4 can read Categories 1 → 8 in order. The categories progress from frontend → backend → deployment → cross-cutting concerns, mirroring the layers of a typical CAP project. + +## When NOT to use + +- For project-specific advice that depends on domain semantics (the patterns here are deliberately generic). +- For runnable diagnostics (use the operational skills like [`https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-stack-audit-full/SKILL.md`](https://github.com/Raistlin82/sap-cap-toolkit/blob/main/skills/sap-cap-stack-audit-full/SKILL.md)). +- As a replacement for SAP official documentation — this catalog distills production lessons, but the authoritative spec lives in the SAP help portal. + +## Recommended Companion Plugins + +Install whichever of these are available in your environment. Each is referenced by Category 8 above; consult the relevant entry when working in that area. + +| Plugin / Skill | npm / vercel-labs install | Domain | +|---|---|---| +| `sap-cap-capire` | `npx skills add SAP/sap-cap-capire` | CAP runtime knowledge base | +| `sapui5` | `npx skills add SAP/sap-ui5` | UI5 API explorer | +| `sap-fiori-tools` | `npx skills add SAP/sap-fiori-tools` | Fiori Elements scaffolding & validation | +| `sap-btp-cloud-platform` | `npx skills add SAP/sap-btp-cloud-platform` | BTP services map | +| `sap-btp-connectivity` | `npx skills add SAP/sap-btp-connectivity` | Destinations, Cloud Connector | +| `sap-btp-integration-suite` | `npx skills add SAP/sap-btp-integration-suite` | iFlow, API Management | +| `sap-btp-cloud-logging` | `npx skills add SAP/sap-btp-cloud-logging` | Observability | +| `sap-btp-job-scheduling` | `npx skills add SAP/sap-btp-job-scheduling` | Job Scheduler | +| `sap-btp-audit-log` | `npx skills add SAP/sap-btp-audit-log` | BTP Audit Log Service | +| `sap-btp-master-data-integration` | `npx skills add SAP/sap-btp-mdi` | MDI | +| `sap-cap-best-practices` | `npx skills add SAP/sap-cap-best-practices` | CAP curated patterns | +| `sap-pce-expert` | `npx skills add SAP/sap-pce-expert` | S/4 PCE / RISE specifics | +| `sap-docs` | `npx skills add SAP/sap-docs` | Help portal, Notes search | + +The exact plugin namespace (`SAP/...` vs `vercel-labs/...` vs `/...`) depends on the package registry your tooling uses. Adapt the install commands to your environment. + +## References + +- [SAP CAP — Capire (Official Docs)](https://cap.cloud.sap/docs/) +- [SAP Fiori Elements V4 — Guidance](https://sapui5.hana.ondemand.com/sdk/#/topic/03265b0408e2432c9571d6b3feb6b1fd) +- [SAP BTP — Reference Architectures](https://help.sap.com/docs/btp/sap-business-technology-platform/reference-architectures) +- [SAP API Release State Repository](https://github.com/SAP/abap-atc-cr-cv-s4hc) +- [OWASP — Application Security Verification Standard (ASVS)](https://owasp.org/www-project-application-security-verification-standard/) +- [Kyma — Runtime Documentation](https://kyma-project.io/docs/) +- [SAP UI5 — `flexEnabled` and Variant Management](https://sapui5.hana.ondemand.com/sdk/#/topic/465f01dcf1cd49b08230e7d3b53b29ed) diff --git a/skills/sap-erp-clean-core-refactor/SKILL.md b/skills/sap-erp-clean-core-refactor/SKILL.md new file mode 100644 index 00000000..e624a833 --- /dev/null +++ b/skills/sap-erp-clean-core-refactor/SKILL.md @@ -0,0 +1,161 @@ +--- +name: sap-erp-clean-core-refactor +description: Plans and executes a Clean Core refactor of SAP ABAP custom code (Z*/Y*). Inventories objects via ARC-1, classifies them as Level A/B/C/D (via `sap-clean-core-atc`), and decides per object whether to rewrite-in-place, extract to a side-by-side BTP extension, keep at Level B, or remove. Documentation lookups are just-in-time (no pre-built KB). Use when asked to "refactor custom code to Clean Core", "plan side-by-side extensions", "Clean Core return on ERP", or to produce a documented migration plan. +--- + +# SAP ERP — Clean Core Refactor + +Plans and executes the refactor of ABAP custom code to Clean Core. Three modes: + +| Mode | What it does | Writes? | +|---|---|---| +| `discover` | Inventory the Z*/Y* package | No | +| `plan` (default) | Inventory + classify + decide per object → emit `docs/refactor/-clean-core-plan.md` | No | +| `execute` | Apply the plan: rewrite ABAP, scaffold BTP extensions, document Level B keepers, remove unused | Yes (with per-object confirmation) | + +## Input + +``` + [mode] [flags] +``` + +Examples: +- `ZFI plan` — typical first call (default target = `btp-cf`) +- `ZCL_INVOICE_HANDLER plan` — single-object focus +- `ZFI execute` — apply plan with per-object confirmation +- `ZFI plan --target=btp-kyma --aggressive` — Kyma side-by-side target + push Level B → A + +**Flags**: + +| Flag | Effect | +|---|---| +| `--target=btp-cf` (default) `· btp-kyma · onprem-kyma` | Side-by-side runtime when extracting to BTP | +| `--target-level=A` (synonym of `--aggressive`) | Prefer Level A everywhere; explore B→A escalation | +| `--target-level=B` | Settle for B; cheaper paths preferred | +| `--push-to-a=A,B,C` | Selective B→A for listed objects only | +| `--force-refresh` | Bypass the 30-day cache; re-query sources | +| `--budget=N` | Per-finding Apify lookup budget (default 5) | + +After plan emission, edit `docs/refactor/-clean-core-plan.md` to override any decision before `execute`. + +## Decision tree (per object) + +| Start Level | Default Target | Path | +|---|---|---| +| A | A | no_action | +| Unused | — | remove_unused (with sign-off) | +| C | **A** | `rewrite_in_place` via released API (or `extract_to_side_by_side` if no equivalent; `keep_at_level_b` if only data-access) | +| D | **B** | `rewrite_in_place` via BAdI / enhancement-point (or `extract_to_side_by_side` if BAdI not feasible or `--target-level=A`) | +| B | **B** | `keep_at_level_b` (default). Escalates to A only with `--aggressive` / `--push-to-a` / `--target-level=A` | + +**Side-by-side outcome** = Level A on the ERP side (the Z object disappears; logic lives on BTP under separate Clean Core gate). + +## Workflow + +### Step 1 — Pre-flight + +- Verify ARC-1 MCP is connected (`SAPSearch` probe). +- Verify Apify MCP is available — if not, the skill degrades to **manual mode** (emits "consult URL X" pointers; user pastes back snippets). +- Resolve `$TARGET` (`--target=…` or ask once). +- One-time per system: run [`../bootstrap-system-context/SKILL.md`](../bootstrap-system-context/SKILL.md) to capture release / ATC preset / formatter into `system-info.md`. +- Init `.cache/sap-clean-core/` (gitignored). + +### Step 2 — Inventory + impact analysis + +- Enumerate Z*/Y* objects: `SAPSearch(package_tree)` + `SAPSearch(tadir_lookup)`. +- Dead-code: delegate to [`../sap-unused-code/SKILL.md`](../sap-unused-code/SKILL.md) (requires `SAP_ALLOW_FREE_SQL=true`). +- **Impact analysis** for every non-A candidate: `SAPContext(action="impact")` → fan-in count drives effort × risk: + +| Fan-in | Risk × | Strategy | +|---|---|---| +| 0 | 0× | `remove_unused` candidate | +| 1-3 internal | 1× | low-risk `rewrite_in_place` | +| 4-10 | 2× | medium-risk; keep a thin adapter when rewriting | +| 11-50 | 4× | prefer `extract_to_side_by_side` if BTP available | +| 50+ | 8× | mandatory `research_required` (architectural review) | + +### Step 3 — Classification + +Delegate to [`../sap-clean-core-atc/SKILL.md`](../sap-clean-core-atc/SKILL.md). Receive back per-object Level A/B/C/D + ATC finding categories. + +### Step 4 — JIT lookup + decide + +For each non-A finding, consult sources in this order until evidence is sufficient (bounded by `--budget`): + +1. **Cache hit**: `.cache/sap-clean-core//-.md` (30-day TTL stable / 7-day community). +2. **Tier-1 git** (free): grep `abap-atc-cr-cv-s4hc`, curated `SAP-samples`, `cloud-sdk` (all installed as local clones). +3. **Tier-4 MCP** (free when installed): `mcp-sap-docs`, `context7`. +4. **Tier-2 Apify** (paid, ~€0.005-0.02/page): `api.sap.com`, `help.sap.com`, `developers.sap.com`, community, blogs. +5. **Pattern mining** (free, optional): `SAPRead(VERSIONS, VERSION_SOURCE)` for the customer's own history — find how similar Z objects have already been migrated. Cuts rewrite effort 30-50%. + +If budget exhausts without an answer, flag `research_required` and optionally invoke [`../explain-abap-code/SKILL.md`](../explain-abap-code/SKILL.md) for a deep dive. + +Full source catalog: [`./SOURCES.md`](./SOURCES.md). Battle-tested patterns referenced for decision-making: [`./PATTERNS.md`](./PATTERNS.md). + +### Step 5 — Emit plan + +Write `docs/refactor/-clean-core-plan.md` with one row per object: + +| Object | Start Level | Target Level | Decision | Replacement / Pattern | Effort | Risk | KB evidence | + +Plus: inventory summary, side-by-side extension catalog (per `extract` outcome), suggested sequencing (quick wins → in-place phase 1 → in-place phase 2 → side-by-side parallel), research backlog, source citations. + +**User reviews the plan and edits any decision** before `execute`. + +### Step 6 — Execute (opt-in) + +Per object, ask confirmation. Then dispatch: + +| Decision | Action | +|---|---| +| `rewrite_in_place` | (1) Generate regression test via [`../generate-abap-unit-test/SKILL.md`](../generate-abap-unit-test/SKILL.md) or [`../generate-cds-unit-test/SKILL.md`](../generate-cds-unit-test/SKILL.md). (2) `SAPWrite(action="update")` + `SAPActivate` + `SAPLint(format+run_atc)` + `SAPDiagnose(run_unit_tests)`. (3) Rollback via `SAPGit` if regression. For RAP behavior pool delegate to [`../generate-rap-logic/SKILL.md`](../generate-rap-logic/SKILL.md) | +| `extract_to_side_by_side` | Delegate to [`../modernize-abap-to-btp-cap/SKILL.md`](../modernize-abap-to-btp-cap/SKILL.md). ABAP source stays deprecated-tagged until QA confirms parity. UI side: [`../convert-ui5-to-fiori-elements/SKILL.md`](../convert-ui5-to-fiori-elements/SKILL.md) | +| `keep_at_level_b` | Delegate to [`../sap-object-documenter/SKILL.md`](../sap-object-documenter/SKILL.md) (SKTD rationale + ATC exemption) | +| `remove_unused` | Stakeholder sign-off → `SAPSearch(where_used)` → `SAPWrite(action="delete")` | + +Transport: `SAPTransport(requirement_check → create → reassign)`. Optional `SAPGit` commit if `SAP_ALLOW_GIT_WRITES=true`. + +### Step 7 — Verify + +Cumulative `SAPLint(run_atc)` + `SAPDiagnose(run_unit_tests)` on the whole package. Net ATC regression aborts the loop. Optional `analyze-chat-session` at session end for learnings. + +## Cost + +| Item | Cost | +|---|---| +| ARC-1 MCP / arc-1 native skills / Tier-1 git clones / MCP-server lookups | €0 | +| Tier-2 Apify per page | €0.005-0.02 | +| Typical refactor (50-200 objects) | **€0.50-€5 total**, user pays own Apify account | +| Re-run within 30 days (cache hits) | €0 | +| `--aggressive` mode delta | +30-50% | + +No centralized infra. No pre-built KB. Manual mode (no Apify) works at zero cost but slower. + +## Companion files + +| File | What | +|---|---| +| [`./SOURCES.md`](./SOURCES.md) | 23 authoritative SAP sources in 4 tiers (Tier-1 git / Tier-2 Apify / Tier-3 manual / Tier-4 MCP) | +| [`./PATTERNS.md`](./PATTERNS.md) | ~70 battle-tested patterns in 8 categories (UI5/FE V4, CAP/TS, BTP/Kyma deployment with 4-target matrix, security, customizing, lifecycle, events, ecosystem plugins). Consulted during Step 1 target resolution + Step 6 side-by-side scaffold | +| [`./INTEGRATIONS.md`](./INTEGRATIONS.md) | Step-by-step mapping: refactor phase × ARC-1 MCP tool × arc-1 native skill × secondsky/sap-skills plugin | + +## Recommended companion plugins + +**MUST** (from [secondsky/sap-skills](https://github.com/secondsky/sap-skills)): +- `sap-abap` — ABAP language patterns (Step 6 rewrite ABAP) +- `sap-abap-cds` — CDS view design (Step 6 when introducing CDS) +- `sap-cap-capire` — CAP framework + 4 dispatchable agents (Step 6 side-by-side) +- `sap-btp-developer-guide` — BTP reference (Step 1 target resolution) + +**Optional**: Apify MCP (JIT lookup), `mcp-sap-docs` (preferred over Apify when installed), `context7` (non-SAP libs), plus situational SHOULD plugins listed in [`./INTEGRATIONS.md`](./INTEGRATIONS.md). + +## When NOT to use + +- Single small change to one Z object → use ARC-1 directly. +- Green-field BTP development → use [`../modernize-abap-to-btp-cap/SKILL.md`](../modernize-abap-to-btp-cap/SKILL.md) directly. +- System upgrade plan → use SAP Activate methodology. +- Customer has no BTP plan AND no Customizing alternative → the toolkit can still help (no-BTP customers get `rewrite_in_place` + `keep_at_level_b` paths only, compliance score lower but progress is real). + +## Companion repository + +For audit / hardening / CI gates of the CAP applications this skill generates, see [`Raistlin82/sap-cap-toolkit`](https://github.com/Raistlin82/sap-cap-toolkit) (8 skills: clean-core-enforce, customizing-honor, security-rbac-matrix, fiori-app-audit, text-polish, stack-audit-full, ci-gates-pattern, fiori-battle-tested-patterns). diff --git a/skills/sap-erp-clean-core-refactor/SOURCES.md b/skills/sap-erp-clean-core-refactor/SOURCES.md new file mode 100644 index 00000000..6f1df22e --- /dev/null +++ b/skills/sap-erp-clean-core-refactor/SOURCES.md @@ -0,0 +1,84 @@ +# SAP Authoritative Sources — JIT lookup catalog + +This file is the **catalog of authoritative SAP documentation sources** that the [`sap-erp-clean-core-refactor`](./SKILL.md) skill consults just-in-time when it needs evidence for a specific custom-code finding. It is **NOT a pre-crawled knowledge base**; it is a **list of URLs + how to query each one**. + +The skill reads this file and decides which sources to consult per finding, within a bounded per-finding lookup budget. Results are cached locally under `.cache/sap-clean-core/` for 30 days (stable docs) or 7 days (community / blogs). + +## Tier 1 — Git-cloned sources (free, weekly refresh, no Apify cost) + +These are SAP-maintained git repositories that can be `git clone`d locally and refreshed via `git pull --ff-only`. They are the **first source consulted** for every finding because they are free, fast, and authoritative. + +| ID | Source | URL | Domain | Refresh strategy | +|---|---|---|---|---| +| `abap-atc-cr-cv-s4hc` | SAP API Release State repository | https://github.com/SAP/abap-atc-cr-cv-s4hc | Released ABAP object authority — JSON files per object class, per edition (Public Cloud / Private Cloud / On-Premise) | `git clone --depth 1`, weekly `git pull --ff-only` | +| `sap-samples-cap-sflight` | SAP-samples — CAP SFlight reference | https://github.com/SAP-samples/cap-sflight | Canonical CAP service + Fiori Elements V4 example | `git clone --depth 1`, weekly `git pull --ff-only` | +| `sap-samples-cloud-cap-samples` | SAP-samples — CAP samples (multi-domain) | https://github.com/SAP-samples/cloud-cap-samples | Multiple side-by-side patterns: orders, hello-world, sflight, … | `git clone --depth 1`, weekly `git pull --ff-only` | +| `sap-samples-btp-cap-multitenant-saas` | SAP-samples — multitenant CAP SaaS | https://github.com/SAP-samples/btp-cap-multitenant-saas | Multi-customer extension pattern | `git clone --depth 1`, weekly `git pull --ff-only` | +| `sap-samples-cap-event-handling` | SAP-samples — CAP event handling | https://github.com/SAP-samples/cap-sample-event-handling | Event Mesh / NATS subscription patterns | `git clone --depth 1`, weekly `git pull --ff-only` | +| `sap-samples-odata-v4-cds-cap-fiori` | SAP-samples — OData V4 + CDS + CAP + Fiori | https://github.com/SAP-samples/odata-v4-cds-cap-fiori | End-to-end OData V4 / CAP / FE V4 example | `git clone --depth 1`, weekly `git pull --ff-only` | +| `sap-samples-btp-typescript` | SAP-samples — BTP TypeScript app | https://github.com/SAP-samples/btp-build-business-application-with-typescript | TypeScript CAP scaffolding | `git clone --depth 1`, weekly `git pull --ff-only` | +| `sap-cloud-sdk` | SAP Cloud SDK (docs only) | https://github.com/SAP/cloud-sdk | JS/TS/Java SDK for consuming S/4 + BTP services | `git clone --depth 1 --filter=blob:none`, weekly `git pull --ff-only` | + +**Operational note**: a project that installs this skill runs a one-time setup that clones the Tier 1 list under `.cache/git/`. Total size after clone ≈ 100-200 MB. Weekly refresh is bandwidth-only (no compute cost). + +## Tier 2 — JIT Apify lookups (per-page cost, user pays at lookup time) + +These are HTTP / SPA sources that cannot be efficiently mirrored offline. They are queried on-demand via Apify only when a finding's evidence requires it, and only for the specific page that holds the answer (not a full-site crawl). + +| ID | Source | URL | Domain | Apify actor | Est. cost / page | +|---|---|---|---|---|---| +| `api-sap-com` | SAP API Hub | https://api.sap.com/ | OData service lifecycle (released / sandbox / deprecated), Communication Scenario membership, version history | `apify/puppeteer-scraper` (React SPA) | ~€0.01 | +| `help-sap-clean-core` | SAP Help Portal — Clean Core | https://help.sap.com/docs/btp/sap-business-technology-platform/clean-core | Clean Core principles + Levels A/B/C/D definitions | `apify/website-content-crawler` | ~€0.005 | +| `help-sap-btp` | SAP Help Portal — BTP | https://help.sap.com/docs/btp/sap-business-technology-platform | BTP services, runtime, eventing | `apify/website-content-crawler` | ~€0.005 | +| `help-sap-s4hana-cloud` | SAP Help Portal — S/4HANA Cloud | https://help.sap.com/docs/SAP_S4HANA_CLOUD | S/4HANA Cloud feature docs | `apify/website-content-crawler` | ~€0.005 | +| `help-sap-abap-development` | SAP Help Portal — ABAP for Cloud Development | https://help.sap.com/docs/abap-cloud | ABAP Cloud language reference, released-API catalog | `apify/website-content-crawler` | ~€0.005 | +| `help-sap-integration-suite` | SAP Help Portal — Integration Suite | https://help.sap.com/docs/integration-suite | iFlow design, API Management | `apify/website-content-crawler` | ~€0.005 | +| `help-sap-event-mesh` | SAP Help Portal — Event Mesh | https://help.sap.com/docs/event-mesh | BTP Event Mesh | `apify/website-content-crawler` | ~€0.005 | +| `developers-clean-core` | developers.sap.com — Clean Core topic | https://developers.sap.com/topics/clean-core.html | Clean Core tutorials | `apify/website-content-crawler` | ~€0.005 | +| `cap-cloud-sap` | SAP CAP — capire documentation | https://cap.cloud.sap/docs/ | CAP runtime, CDS, deployment | `apify/website-content-crawler` | ~€0.005 | +| `sapui5-sdk` | SAPUI5 SDK Reference | https://sapui5.hana.ondemand.com/sdk/ | UI5 / Fiori Elements V4 controls and annotations | `apify/website-content-crawler` | ~€0.005 | +| `community-sap-com` | SAP Community Q&A | https://community.sap.com/ | Recent symptom-specific troubleshooting | `apify/website-content-crawler` (recent-90d filter) | ~€0.01 | +| `blogs-sap-com` | SAP Blogs | https://community.sap.com/t5/technology-blogs-by-sap/bg-p/technology-blog-sap | Architecture / pattern essays from SAP engineers | `apify/website-content-crawler` (tag-filtered) | ~€0.01 | +| `fiori-design` | SAP Fiori Design Guidelines | https://experience.sap.com/fiori-design-web/ | Fiori UX guidance | `apify/website-content-crawler` | ~€0.005 | +| `discovery-center` | SAP Discovery Center | https://discovery-center.cloud.sap/ | Reference architectures | `apify/puppeteer-scraper` (SPA) | ~€0.01 | +| `learning-sap-com` | learning.sap.com / openSAP | https://learning.sap.com/ | Clean Core / extensibility courses | `apify/puppeteer-scraper` (SPA, some pages auth-gated) | ~€0.01 | + +**Operational note**: every Tier 2 lookup hits `.cache/sap-clean-core//-.md` first. Cache TTL: 30 days for stable docs, 7 days for community / blogs. Within the TTL window, repeat lookups are free. + +## Tier 3 — Manual-consultation sources (auth-gated, NOT crawled) + +These sources require S-user credentials and **cannot be automated** without the customer providing valid login. They are listed for awareness only; the skill emits "consult manually" pointers when they apply. + +| ID | Source | URL | Auth | Purpose | +|---|---|---|---|---| +| `launchpad-support` | SAP Notes | https://launchpad.support.sap.com/ | S-user required | Authoritative SAP Notes for specific code paths / corrections | +| `me-sap-com` | Software Lifecycle | https://me.sap.com/ | S-user required | Product lifecycle, release information | +| `support-sap-com` | Support Catalog | https://support.sap.com/ | S-user required | Support documentation, incident management | + +## Tier 4 — MCP-server-backed lookup (preferred when installed) + +When the consuming environment has these MCP servers configured, the skill prefers them over Apify (faster, often free, better structured): + +| MCP server | Replaces Apify for | When preferred | +|---|---|---| +| `mcp-sap-docs` | help.sap.com, abap-atc-cr-cv-s4hc queries | When `mcp__sap-docs__*` tools are available | +| `context7` | Generic library docs (non-SAP) | When the lookup is about an npm package or non-SAP library | + +## When-to-use heuristic — what source for what finding + +| Finding category | Tier 1 (git) | Tier 2 (Apify) | Tier 3 (manual) | +|---|---|---|---| +| `non-released-api` | `abap-atc-cr-cv-s4hc` (check object status) | `api-sap-com` (find released alternative service) | `launchpad-support` (SAP Note for migration path) | +| `direct-db-access` | `abap-atc-cr-cv-s4hc` (released CDS view available?) | `help-sap-abap-development` (released-CDS catalog) | — | +| `modification` (Level D) | — | `help-sap-clean-core` (BAdI / enhancement Level B pattern) | `launchpad-support` (mandatory SAP Note for the modification) | +| `enhancement-point` (Level B eligible) | — | `developers-clean-core` (tutorial for key-user extensibility) | — | +| `side-by-side candidate` | `sap-samples-*` (matching pattern repo) + `sap-cloud-sdk` | `cap-cloud-sap` + `help-sap-btp` (target framework docs) | — | +| `unused` (zero SCMON hits) | — | — | — (remove after stakeholder sign-off; no doc lookup needed) | + +## Refresh discipline + +**Tier 1 git-clones**: weekly `git pull --ff-only` for each repo. Cost: bandwidth only. + +**Tier 2 Apify cache**: per-entry TTL applies. The cache is local to the project (under `.cache/sap-clean-core/`), not shared across projects. Users with multiple projects on the same machine can symlink the cache to a shared location. + +**No centralized infrastructure**: this skill does NOT maintain a centralized KB. There is no weekly cron crawling everything; there is no shared cache for all users. Every project owns its own cache; every Apify call is on the user's own account.