De-risking SAP S/4HANA custom-code transitions.
Every S/4HANA migration hits the same wall: the custom Z-code — how much exists, what breaks on S/4HANA, what fights the Clean Core model, what is dead weight, and what it costs to fix. caern turns that unknown into graded, evidence-backed analysis a project lead can act on.
This repo holds the whole product:
| Where | What |
|---|---|
src/core |
shared in-system ABAP extractor base — the caern.export/v0 contract + the reused ATC / where-used / usage-harvest helpers (object table below) |
src/{lens,plumb,atlas,sweep,compass,caliper} |
the six wedges — one ABAP extractor each (ZCL_CAERN_<TOOL>), each a subpackage of the caern umbrella package |
caern-cli |
the host CLI (caern <command>) that drives extraction and renders customer-ready reports — usage: caern-cli/README.md |
assets |
the single-page cockpit CSS/JS (served as MIME repository objects — see Cockpit below) |
Approach. Each wedge reads one signal from a live SAP system and emits the one normalised
contract (caern.export/v0); core owns that contract plus the shared source-harvest and stays
wedge-agnostic (the cockpit dispatches wedges by name, so core keeps no static dependency on them).
caern's main application is the in-system cockpit (ZCL_CAERN_ICF) — a single-page analysis &
reporting workbench served from the SAP ICM; the caern CLI is a companion for scripting, CI gates and
client decks. Both render the same data through the same contract.
Handling — two ways to run it, stated up front. caern's main application is an in-system product:
the ABAP classes, the DDIC tables, the ICF cockpit node, the cockpit assets (MIME objects) and the
ZCAERN authorization object install into the SAP system by transport or abapGit, and it runs there
natively — the cockpit is opened from transaction ZCAERN. Built and proven on AS ABAP 7.52 SP04; the frameworks it uses are accessed
dynamically, so it also activates on classic 7.40/7.50 ECC and reports, in-system, which checks the
release supports (Check-content tab). The repo is serialised with abapGit PREFIX folder logic, so the
package name is chosen at install time: pull into ZCAERN (or your own registered namespace) and the
subpackages follow (ZCAERN_CORE, ZCAERN_LENS, …) — the repo bakes in no fixed package prefix.
Installing:
INSTALL.md. Two steps are not optional and not automatic — activate the ICF node (the pull installs the service but leaves it inactive) and build theZCAERNrole (the authorization object ships, a PFCG role cannot, so on day 1 nobody holds it — not evenSAP_ALL). The cockpit fails closed and tells you which step is missing.
The caern host CLI is a companion, not the product: it drives the same in-system extractors over
ADT for scripting, CI gates and generating client decks off a laptop. For that mode you need X.509 cert
auth to an ADT-reachable system (ADT_BASE / ADT_CERT / ADT_KEY in a gitignored caern-cli/.env,
see caern-cli/.env.example) and Node ≥ 20.
The core package (ZCAERN_CORE under the caern umbrella) — the reuse boundary every wedge builds on (no wedge duplicates it):
| Object | Role |
|---|---|
ZIF_CAERN_EXPORT |
the normalised export contract (caern.export/v0) every wedge emits through |
ZCL_CAERN_CORE |
build_json — assembles findings + summary into the export |
ZCL_CAERN_CORE_USED |
shared source harvest: read_source + scan (SCAN ABAP-SOURCE) + resolve_tables (one batched DD02L query) — the DB tables & called FMs a code object touches. Used by Plumb + Atlas so neither re-implements the scan. |
ZCL_CAERN_CORE_SDB |
on-prem S/4 readiness content: affected(refs) joins the installed Simplification DB (SYCM_*) to the SAP objects a program touches → the simplification item(s) hitting it; target_release() = the S/4 label the content covers. Used by Lens. |
ZCL_CAERN_CORE_ATC |
runs SAP's own Code-Inspector/ATC checks headlessly: cloud_admissibility(objects) drives CL_CI_TEST_ADMISSIBLE_4_CLOUD (object-type ABAP-Cloud admissibility). Used by Lens. |
ZCL_CAERN_SDB |
S/4HANA Simplification-DB currency check (SYCM) |
ZCL_CAERN_GRADE |
the one grading + effort model — assess(dims, errors, cloud, simpl, target) → Clean-Core score/grade + an indicative person-day band. Every surface (cockpit Overview / Effort estimate / Reports, caern estimate, the deck) reads it; nothing re-weights a grade locally. |
ZCL_CAERN_ROLLUP |
estate roll-up over the stored runs (estate(scope) — findings / dims / cloud / simpl / add-ons by custom vs modified-SAP), carrying the grade + effort pre-computed per target (assess_onprem / assess_cloud) by ZCL_CAERN_GRADE. Feeds the cockpit Overview and caern estimate. |
ZCL_CAERN_CONFIG (+ table ZCAERN_CONFIG) |
the calibration behind the model — grade bands, clean prior + pseudo-count, overhead and effort multipliers, benchmark. spec holds the defaults and the form definition; effective( ) overlays the stored overrides. This is the cockpit's Settings tab, with reset-to-defaults. |
ZCL_CAERN_CAPS |
release-capability probe — what this system can actually do (JSON adapter, Simplification DB, ABAP-Cloud check, SCMON, WBCROSSGT, Fiori map) and what a fuller result would need. Rendered as the System capabilities panel so an empty check is never mistaken for a clean one. |
ZCL_CAERN_JSON |
the dynamic JSON adapter around /UI2/CL_JSON. It is called dynamically on purpose: a static reference would fail activation on a system without SAP_UI / SAP_GWFND — this adapter is what lets caern install below its 7.52 build target. available( ) reports the gap honestly instead of dumping. |
ZCL_CAERN_REPORT |
the one renderer: body(json) + css( ) = the fragment + stylesheet the cockpit embeds, tool-aware, with finding-driven next steps. A tool it does not know renders as "no interpretation applied" — never as a clean bill of health. |
ZCL_CAERN_STORE (+ ZCAERN_RUN / ZCAERN_FINDING / ZCAERN_STATUS) |
run persistence + diff_last_two, bounded (each run caps its (package, tool) history to the most-recent keep_per_group runs), plus per-finding triage — set_status keys a status (open / ack / progress / fixed / wontfix) + note by object + check, so it survives a re-run |
ZCL_CAERN_PKG |
package inventory for the cockpit — customer packages (see custom scope below), SAP-standard packages carrying logged modifications (SMODILOG), and the installed third-party add-ons, with object counts |
ZCL_CAERN_ICF |
IF_HTTP_EXTENSION — the single-page cockpit web app (see below) |
ZCL_CAERN_ASSETS |
the cockpit CSS/JS (from assets/app.{css,js}), held as MIME repository objects under /SAP/PUBLIC/caern/ and served by ZCL_CAERN_ICF as `?asset=css |
Custom scope is not just Z*/Y*. ZCL_CAERN_PKG=>custom_scope( ) returns the Z*/Y* default
namespace plus every namespace this system produces itself — TRNSPACE rows with role = 'P' and
sapflag <> 'X'. A customer whose code lives in a registered namespace (/ACME/…) is therefore in scope
without configuration; SAP-delivered namespaces are recipient-role and stay out, so the custom estate
never fills up with SAP packages. The cockpit browser and the estate roll-up share this one definition,
so both agree on what "custom" means.
Sweep's runtime-usage (SCMON) + static where-used (WBCROSSGT) reader lives in the Sweep wedge,
not here — one consumer, so it stays out of the reuse boundary until a second wedge needs it.
ZCL_CAERN_ICF (an IF_HTTP_EXTENSION handler) serves the single-page workbench directly from
the SAP ICM — caern's main application, the analysis & reporting cockpit (caern-cli is the
scripting/deck companion). The page loads once with a boot payload
(package inventory, portfolio KPIs, the latest-run matrix and recent runs, embedded as JSON) and
then never navigates away: browsing, filtering, paginating (50/page) and selecting packages,
switching tabs and reading results all happen client-side, so there are no selection→result screen
jumps and the browser Back button is never in the critical path. Only an actual analysis hits the
server, as a small HTML fragment.
- Tabs — Overview (portfolio KPIs, worst offenders, never-analyzed), Analyze (the package
browser + the six-app palette + the tool pipeline), Result matrix (packages × tools heatmap; click a
cell to open its report), Results (the result cards of the current analysis), Effort estimate (the
estate roll-up: grade + migration effort for the chosen target) and Reports (the customer-ready
deliverable — charts, method cards, print/PDF, PNG and CSV export).
Beside them, three admin views: History (every run in a table with a detail-pane + per-run
"compare to previous"), Check content (the installed Simplification DB — release coverage, item count
and SAP's readiness check set — plus the System capabilities panel; Lens result cards deep-link
here) and Settings (the model calibration,
ZCL_CAERN_CONFIG). - Package browser replaces typing a package name: a searchable, sortable, paginated list of the
ZCL_CAERN_PKGsources (custom code and modified-SAP) with checkbox multi-select, run through the selected tools as a pipeline with live progress; each result streams in as a card. A package is analysed as a tree (the package and everything below it in the hierarchy), not as a single node. - Whole-estate scan — the pipeline is interactive and suits a handful of packages; a full estate
(hundreds of packages) is analysed by the schedulable report
ZCAERN_SCANas an SM36 background job (no HTTP timeout, one commit per run, every failure logged with its reason). The cockpit then browses what it produced. SeeINSTALL.mdstep 6. - Per-package diff — "compare to previous" surfaces added/fixed findings via
diff_last_two. - Fragment API (same node, same authenticated + authorized caller):
?frag=run&tool=&pkg=runs one wedge and returns its result card;?frag=diff&tool=&pkg=the delta;?frag=view&run=<id>a stored run. A wedge is dispatched by name (CALL METHOD (|ZCL_CAERN_{ tool }|)=>run) so core keeps no static dependency on the wedges; every run is persisted throughZCL_CAERN_STORE; live and stored runs render through the sharedZCL_CAERN_REPORT.
The cockpit node (/sap/bc/caern, handler ZCL_CAERN_ICF) is served straight from the SAP ICM. It is
authenticated and authorized — the node challenges for logon and every request runs an
AUTHORITY-CHECK on the ZCAERN object (03 display / 16 run / 02 configure), granted through the
ZCAERN role; state-changing routes are POST-only with a CSRF marker. For a quick look without the ICM,
caern lens … --html > report.html produces a self-contained file you open via file://.
Each wedge reads one signal and emits the shared caern.export/v0 contract; the caern CLI and the
cockpit render the reports. Command usage + flags: caern-cli/README.md.
| Wedge | Signal | Extractor (src/…) |
Prio |
|---|---|---|---|
| Lens | S/4HANA readiness (real ATC + Simplification-DB) | src/lens — ZCL_CAERN_LENS |
P0 |
| Plumb | Clean-Core posture / direct SAP-table access | src/plumb — ZCL_CAERN_PLUMB |
P1 |
| Atlas | as-is footprint (tables + FMs a Z-object touches) | src/atlas — ZCL_CAERN_ATLAS |
P1 |
| Caliper | dictionary check — custom DDIC against the S/4 data model (hard-typed fields, appends, includes) | src/caliper — ZCL_CAERN_CALIPER |
P1 |
| Sweep | dead code (SCMON runtime + WBCROSSGT where-used) |
src/sweep — ZCL_CAERN_SWEEP |
P2 |
| Compass | transaction → Fiori-successor mapping | src/compass — ZCL_CAERN_COMPASS |
P3 |
Effort estimate is not a seventh wedge — it is the roll-up. The grade and the migration-effort band
come from one ABAP model, ZCL_CAERN_GRADE, served pre-computed per target by ZCL_CAERN_ROLLUP over
the stored runs (drivers: Lens, Plumb and Caliper findings; Sweep marks the slice of effort you can avoid
by retiring dead code instead of migrating it). It has its own cockpit tab (Effort estimate) and its own
calibration (Settings); caern estimate and the caern report deck read that model — they never
re-derive a grade or an effort figure of their own.
The failure mode of a code-analysis tool is not a wrong number — it is a confident empty result. An estate nobody looked at, or a check the release cannot run, must never render as a clean estate. Three mechanisms make that structural rather than a matter of care:
- caern refuses to grade what it could not measure.
ZCL_CAERN_GRADEhas an explicitungradedstate ('--',graded = abap_false) with the reason attached: with no analysed objects there is nothing to grade, and the surfaces show that instead of an "A". Likewise, an object whose source could not be read gets its own dimension and never counts as a graded, clean one. - The system tells you what it can check.
ZCL_CAERN_CAPSprobes the release at runtime and the cockpit's System capabilities panel (Check content) names every optional signal — the JSON adapter, the Simplification DB, the ABAP-Cloud check, the call monitor, the where-used index, the Fiori map — and what is missing without it. So when a check comes back empty you can tell whether it found nothing or could not look. - Triage reaches the deliverable. Every finding can be given a status (
open/ack/progress/fixed/wontfix) and a note; the status is keyed by object + check, not by run, so it survives a re-analysis. A rebuilt export drops what you markedfixedorwontfix— so the report and the client deck reflect the triage instead of re-reporting work you already closed.
caern is source-available: you may read, download, run, and evaluate it, and adapt it for your
own use — including running it against your own or a client's SAP system and handing over the
analysis. You do not need permission for that, and you are never in breach merely for using it.
Reselling it, redistributing it, offering it as a hosted service, or building a competing product from
it needs written permission — that is also the channel for development partnerships and reseller
arrangements. Full terms: LICENSE.
If you run caern, the author would genuinely like to hear from you — it is how partnerships and feedback channels open up. A hello at contact@caern.eu is welcome, but it is a request, not a condition: your right to use caern does not depend on it.