Where AI speeds up a move to S/4HANA (custom code, data, testing, documentation, cutover), what SAP offers in November 2026, and how people stay in charge.
Moving from SAP ECC to S/4HANA is mostly hard, repetitive work: checking thousands of custom programs, cleaning and mapping years of data, writing and running test cases, and planning a weekend cutover. Most of that work is reading, comparing and drafting. AI is good at drafting.
As of November 2026, SAP sells AI for most of these jobs. It has a custom code migration agent that finds and fixes code problems, and assistants for data quality, testing and configuration that early adopters are using.
The catch is that a migration is where confident mistakes are most expensive. A wrong code fix that "looks right" goes live with everything else. So the rule for leaders is simple: AI drafts, people decide, and every decision leaves a record. Measure what the AI saved, and keep the checks that existed before it.
Take the running example. The fictional Calder Pumpworks is moving its German company CP20 from ECC into CP10's S/4HANA system, with go-live planned for 5 July 2027 (Data migration strategy). Before then, the project team must:
Assess every custom program in CP20's ECC system. Some are no longer used and can be retired. Some read tables that S/4HANA hides behind new views. A few write directly into SAP tables, which must be redesigned.
Clean and map the data: customers, suppliers, products and open items, through several mock loads.
Test the processes end to end on migrated data: order-to-cash, procure-to-pay, intercompany.
Document what the old system does, because the people who wrote some of the code have left.
Plan and run the cutover, the weekend when the old system stops and the new one starts.
Each of these costs consultant days, and consultant days drive the budget. AI that drafts a code fix, a test case or a cleansing rule saves time only if checking the draft is faster than writing it. That depends on how good the draft is and how easy it is to check.
The risk side is just as real:
Confident wrong changes. An AI fix can compile and still change what the program does, for example by dropping a filter so a report sums the wrong orders.
Gaps in testing. AI-generated tests can confirm what the code does today, mistakes included, rather than what the business needs.
No audit trail. Auditors and the business owners who sign off the migration need to know who approved each change and why.
As of November 2026, SAP's AI for migration projects comes in three groups. Availability changes quickly, so check each item for your contract and release.
1. The custom code migration agent. SAP's Q2 2026 release highlights list the SAP S/4HANA custom code migration agent as generally available, delivered in Joule for developers (ABAP AI). It runs SAP's readiness checks across custom code packages, applies standard quick fixes, and proposes AI changes with a confidence score. High-confidence fixes are applied, lower-confidence ones become comments for a developer, and all changes are recorded in transport requests (SAP News, July 2026). SAP said in August 2026 that a second agent, for clean core transformation, was planned for Q3 2026 (SAP News, August 2026). We found no general availability announcement for it by 1 November 2026.
2. Migration and modernization assistants. On 28 October 2026 SAP announced four assistants available to early adopters: a Data Management Assistant (ranks data quality issues by business impact and helps correct them), a Test Management Assistant (test scope, critical scenarios, test cases and test data, with execution in SAP Test Automation by Tricentis), a Custom Code Assistant and a Business Configuration Assistant. A System Analysis, a Project Management and a Rollout Assistant are planned, without dates (SAP News, October 2026). The announcement gives no pricing.
3. Explanation and guidance. Joule for developers offers ABAP Program Explain, which explains the business purpose of legacy code, and Issue Explain, which helps resolve S/4HANA-related code check findings (sap.com). Joule for Consultants answers questions grounded in SAP Notes and knowledge base articles and explains ABAP and CDS logic (sap.com).
Under all of these sit SAP's long-standing, non-AI tools: the ABAP Test Cockpit (ATC) with its S/4HANA readiness checks, the Simplification Database, and SAP's published list of released objects and their successors. The AI works on their findings. It doesn't replace them.
Licensing is unsettled. SAP's Joule for developers page describes a free access offer for developers using ABAP Development Tools. SAP News (August 2026) says developer AI billing is moving to usage-based AI units. Get the terms for your contract in writing.
"The agent fixed it, so it's done." A fix that passes the code checks can still change what the program does. Only a comparison test and a reviewer prove it didn't.
"Migrate everything, then clean up." Unused programs cost money to fix. Retire them first.
"AI-written tests cover us." Tests drafted from the old system's behaviour confirm the old behaviour. The business decides what correct looks like.
"Explanations of old code are facts." They are drafts. Check them with someone who knows the process.
"Early adopter means ready for our project." Early adopter programmes are for a limited group of customers. Plan without them unless you are in one.
Pick one answer for each question. The explanation appears after you choose.
1What is the safest rule for AI in a migration project?
Answer: B. A migration is where confident mistakes are expensive, because they go live together. AI saves time on drafting, while people approve changes and the record shows who decided what.
2Calder finds a custom report that hasn't run in 14 months. What should happen first?
Answer: D. Unused code costs money to fix and test. Scoping comes before remediation, and a person who owns the process agrees to retire it.
3SAP's custom code migration agent applies high-confidence fixes automatically. What should a leader ask?
Answer: B. SAP says the agent records its changes in transport requests. The control point is the review and testing before those transports move on.
4Which SAP AI offering for migration was generally available as of November 2026?
Answer: B. SAP's Q2 2026 highlights list the custom code migration agent as generally available. The Project Management, System Analysis and Rollout Assistants were announced as planned, without dates.
5A partner shows AI-generated test cases for order-to-cash. What is the main risk?
Answer: C. Tests drafted from how the old system behaves prove that behaviour, not what the business needs. Functional consultants review each case against the process.
6How should a project prove that AI saved time on custom code?
Answer: A. A saving is a measured difference on comparable work. Vendor figures and impressions don't show what happened on your project, and a count of fixes ignores the review time they caused.
7A program writes directly into an SAP pricing table. What does SAP's clean core model say?
Answer: D. SAP's level D covers modifications and write access to SAP tables. Such code needs a redesign by a person, not an automatic fix.
Ask a question
Testing: only staff see this
Stuck on something in this layer? Ask it here. Questions are answered in the order they arrive, and the answer appears under My questions.
Sign in (free) to ask a question. You can ask anonymously.
A migration is a funnel of decisions, and AI changes the cost of each one.
Every custom object, data record and test case passes the same three gates: keep or drop, change or leave, accept or reject. Before AI, people did the analysis and the drafting for each gate. Now an agent can draft most of it. What stays human is the decision at each gate and the evidence behind it.
So design your use of AI around two questions:
How cheap is it to check the draft? A quick fix that swaps a table for its released successor is easy to check. A rewritten pricing routine is not.
What happens if the draft is wrong and nobody notices? A wrong explanation wastes an hour. A wrong filter in a revenue report goes live.
When checking is cheap and the damage is small, let the tool act and review in batches. When either is high, the tool drafts and a named person approves each item. Everything in this topic follows from that.
flowchart LR
U[Usage data] --> S[Scope:<br/>keep or retire]
S --> A[Analyze:<br/>ATC + rule list]
A --> R[Remediate:<br/>quick fix or AI draft]
R --> V[Verify:<br/>re-check + tests]
V --> H{Reviewer<br/>approves?}
H -->|no| R
H -->|yes| T[Transport to<br/>quality system]
T --> C[Comparison test]
SAP's conversion guidance starts with custom code scoping: find out which custom code is actually used and must move, and remove what isn't (SAP Learning). This is the cheapest saving in the whole project, and it needs no AI, only usage data and a business owner willing to say "drop it". CP20's report ZMM_OPEN_PO_AGING, with zero runs in 14 months, is the course example (Fit to standard).
The same SAP Learning page describes the analysis setup. A central check system (SAP_BASIS 7.52 or later, or S/4HANA 1809 or later) runs remote ATC against the old system with the check variant S4HANA_READINESS_REMOTE. The checks use the Simplification Database, downloaded through SAP Note 2241080 and imported with transaction SYCM. Findings point to SAP Notes that describe the fix.
A second source of rules is SAP's Cloudification Repository on GitHub. It lists objects with a state such as released, notToBeReleased or classicAPI, and a successor where one exists. The README says the ATC check that uses it can analyse custom code "on all ECC and S/4HANA releases" (GitHub). In the file for the latest Private Edition release, read on 1 November 2026, table VBAK is notToBeReleased with successor I_SALESDOCUMENT, and KNA1 points to I_CUSTOMER. The classifications file lists BAPI_SALESORDER_CHANGE as a classicAPI.
SAP's description of its custom code migration agent shows the right order. Deterministic quick fixes come first: rule-based changes that always produce the same result. AI changes come second, each with a confidence score; SAP's agent applies high-confidence changes and leaves the rest as comments for a developer (SAP News, July 2026).
The confidence score is the model's estimate, not proof. A fix can be confident, compile cleanly, pass every clean core check and still change the result. That's why verification has two parts:
Re-run the checks on the changed code. This proves the level, not the behaviour.
Compare behaviour. Run the old and new program on the same input in a test system and compare the output.
A reviewer facing fifty AI diffs a day needs a short, fixed checklist that targets what AI gets wrong in migrations: changed filters, invented field names, lost currency or unit, dropped authorization checks. A checklist also gives the audit trail something concrete to record.
The same pattern applies to the other workstreams:
Data: an assistant ranks issues and suggests merges or mappings; a data owner decides each one, and the mock load and reconciliation prove the result (Data migration strategy).
Testing: an assistant drafts scope and cases; a process owner accepts them; automated runs prove the system (Testing AI applications covers testing the AI itself, which is a separate job).
Documentation: an explanation tool drafts what a program does; someone who knows the process confirms it.
You will build a small custom code migration assistant for four of CP20's ECC programs. It scopes them by usage, classifies every object they use against SAP's rule list, lets an assistant draft a fix, checks the draft, and refuses to accept it until a person has worked through a review checklist. The point is to see where the human gate sits and why it catches what the checks miss.
flowchart LR
P[4 legacy programs] --> S[scan]
R[rules.json<br/>from SAP's repository] --> S
S --> X[propose<br/>sample, model or file]
X --> V[scan again + diff]
V --> C[review checklist]
C --> A[approve<br/>named reviewer]
A --> L[decisions.jsonl]
The course folder orchestrate-course with its .venv, from the setup topics.
For the free path (--sample): nothing else. A scripted assistant makes the proposal.
For the model path (--llm, optional): the ANTHROPIC_API_KEY in your .env from Unit 1, and the anthropic and python-dotenv libraries already in your requirements.txt. One call per proposal, a small per-request charge.
Network only for the optional refresh-rules step, which downloads about 14 MB from GitHub.
Time: about 45 minutes. Cost: free on the sample path.
All programs, usage numbers and order types are made up for Calder, a fictional company. Nothing connects to an SAP system.
#Step 1: Open your course folder and turn on the virtual environment
Open VS Code, choose File > Open Folder, and open orchestrate-course.
Open a terminal: Terminal > New Terminal.
Turn on the virtual environment:
Windows (PowerShell):
.venv\Scripts\Activate.ps1
macOS / Linux:
source .venv/bin/activate
You should see (.venv) at the start of the prompt. The unit13 folder exists from Set up for Unit 13. If it doesn't, create it with mkdir unit13 (the same command works in both terminals).
Run every command from the course folder, not from inside unit13.
In VS Code's Explorer, right-click unit13 and choose New File. Name it ccm_assist.py.
Paste in the code below and save.
"""Custom code migration assistant for Calder's CP20 move, with a human review gate.
It shows where AI helps a migration and where people must decide:
scope usage data decides what to migrate and what to retire
scan a rule list from SAP's Cloudification Repository classifies every object each program uses
propose an assistant (scripted or a real model) drafts a remediation; the scan runs again on it
approve a person ticks a review checklist; only then is the proposal accepted
Commands (run from your course folder):
python unit13/ccm_assist.py init create the demo in unit13/ccm_demo
python unit13/ccm_assist.py scan scope and clean core findings for every program
python unit13/ccm_assist.py propose ZSD_OPEN_ORDER_VALUE --sample scripted proposal (free)
python unit13/ccm_assist.py propose ZSD_OPEN_ORDER_VALUE --llm a real model proposes (small charge)
python unit13/ccm_assist.py propose ZMM_STOCK_CHECK --file my_fix.abap your own remediation
python unit13/ccm_assist.py approve ZSD_OPEN_ORDER_VALUE --reviewer "your name"
python unit13/ccm_assist.py refresh-rules rebuild rules.json from SAP's files on GitHub
Nothing here connects to an SAP system. The original programs in src/ are never changed.
"""
import argparse
import difflib
import json
import os
import re
import sys
import urllib.request
from datetime import datetime, timezone
from pathlib import Path
HERE = Path(__file__).resolve().parent
DEMO = HERE / "ccm_demo"
SRC = DEMO / "src"
REVIEW = DEMO / "review"
DONE = DEMO / "remediated"
RULES = DEMO / "rules.json"
DECISIONS = DEMO / "decisions.jsonl"
REPO = "https://raw.githubusercontent.com/SAP/abap-atc-cr-cv-s4hc/main/src/"
RELEASE_FILE = "objectReleaseInfo_PCELatest.json" # released and not-released objects, with successors
CLASSIC_FILE = "objectClassifications_SAP.json" # classic APIs (clean core level B)
# ---------- Calder's sample: four CP20 programs from ECC (fictional) ----------
PROGRAMS = {
"ZSD_OPEN_ORDER_VALUE": """REPORT zsd_open_order_value.
* Lists a customer's standard orders (type ZOR) with their net value.
PARAMETERS p_kunnr TYPE kunnr OBLIGATORY.
DATA: lt_vbak TYPE STANDARD TABLE OF vbak,
ls_vbak TYPE vbak,
ls_kna1 TYPE kna1,
lv_sum TYPE netwr.
SELECT SINGLE * FROM kna1 INTO ls_kna1 WHERE kunnr = p_kunnr.
SELECT * FROM vbak INTO TABLE lt_vbak
WHERE kunnr = p_kunnr AND auart = 'ZOR'.
LOOP AT lt_vbak INTO ls_vbak.
lv_sum = lv_sum + ls_vbak-netwr.
WRITE: / ls_vbak-vbeln, ls_vbak-netwr, ls_vbak-waerk.
ENDLOOP.
WRITE: / 'Total for', ls_kna1-name1, lv_sum.
""",
"ZMM_OPEN_PO_AGING": """REPORT zmm_open_po_aging.
* Ages open purchase order items by delivery date.
DATA lt_items TYPE STANDARD TABLE OF ekpo.
SELECT * FROM ekpo INTO TABLE lt_items WHERE elikz = space.
SELECT SINGLE * FROM ekko INTO @DATA(ls_ekko) WHERE ebeln = '4500001201'.
CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY'
TABLES t_outtab = lt_items.
""",
"ZSD_PRICE_FIX": """REPORT zsd_price_fix.
* Corrects a condition amount directly in the pricing table.
PARAMETERS: p_knumv TYPE knumv OBLIGATORY,
p_kbetr TYPE kbetr.
UPDATE konv SET kbetr = p_kbetr WHERE knumv = p_knumv AND kschl = 'ZPR0'.
COMMIT WORK.
""",
"ZSD_ORDER_REASON_UPD": """REPORT zsd_order_reason_upd.
* Sets the order reason on a sales order after a phone call.
PARAMETERS: p_vbeln TYPE vbeln_va OBLIGATORY,
p_augru TYPE augru.
DATA: ls_hdr TYPE bapisdh1,
ls_hdrx TYPE bapisdh1x,
lt_ret TYPE STANDARD TABLE OF bapiret2.
DATA lv_ok TYPE c LENGTH 1.
CALL FUNCTION 'POPUP_TO_CONFIRM'
EXPORTING text_question = 'Change the order reason?'
IMPORTING answer = lv_ok.
CHECK lv_ok = '1'.
ls_hdr-ord_reason = p_augru.
ls_hdrx-updateflag = 'U'.
ls_hdrx-ord_reason = 'X'.
CALL FUNCTION 'BAPI_SALESORDER_CHANGE'
EXPORTING salesdocument = p_vbeln
order_header_in = ls_hdr
order_header_inx = ls_hdrx
TABLES return = lt_ret.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'.
""",
}
# Runs in the last 14 months, as usage monitoring in ECC would report them (course values).
USAGE = {"ZSD_OPEN_ORDER_VALUE": 412, "ZMM_OPEN_PO_AGING": 0,
"ZSD_PRICE_FIX": 9, "ZSD_ORDER_REASON_UPD": 57}
# The entries this demo needs, copied from SAP's Cloudification Repository on 1 November 2026
# (objectReleaseInfo_PCELatest.json and objectClassifications_SAP.json). refresh-rules rebuilds them.
BUNDLED_RULES = {
"VBAK": {"state": "notToBeReleased", "successors": ["I_SALESDOCUMENT"]},
"VBAP": {"state": "notToBeReleased", "successors": ["I_SALESDOCUMENTITEM"]},
"KNA1": {"state": "notToBeReleased", "successors": ["I_CUSTOMER"]},
"KONV": {"state": "notToBeReleased", "successors": ["I_SLSPRCGCONDITIONRECORD"]},
"EKKO": {"state": "notToBeReleased", "successors": ["I_PURCHASEORDERAPI01"]},
"EKPO": {"state": "notToBeReleased", "successors": ["I_PURCHASEORDERITEMAPI01"]},
"MARD": {"state": "notToBeReleased", "successors": ["I_PRODUCTSTORAGELOCATIONBASIC"]},
"I_SALESDOCUMENT": {"state": "released", "successors": []},
"I_SALESDOCUMENTITEM": {"state": "released", "successors": []},
"I_CUSTOMER": {"state": "released", "successors": []},
"I_SLSPRCGCONDITIONRECORD": {"state": "released", "successors": []},
"I_PURCHASEORDERAPI01": {"state": "released", "successors": []},
"I_PURCHASEORDERITEMAPI01": {"state": "released", "successors": []},
"I_PRODUCTSTORAGELOCATIONBASIC": {"state": "released", "successors": []},
"BAPI_TRANSACTION_COMMIT": {"state": "released", "successors": []},
"BAPI_SALESORDER_CHANGE": {"state": "classicAPI", "successors": []},
"POPUP_TO_CONFIRM": {"state": "classicAPI", "successors": []},
"CL_SALV_TABLE": {"state": "classicAPI", "successors": []},
}
CHECKLIST = [
"Selection is the same as before: same filters, same values, same records",
"Every field used on the successor view exists there (checked in ADT, not taken from the proposal)",
"Amounts and quantities keep their currency and unit",
"Authorization checks are still in place (the old code's checks, or the view's access control)",
"Output matches the original on a test run with the same input in the quality system",
"No write to an SAP table, no modification, no implicit enhancement",
]
# ---------- scanning ----------
READ = re.compile(r"\b(?:FROM|JOIN)\s+@?(\w+)", re.IGNORECASE)
WRITE = re.compile(r"^\s*(?:UPDATE|INSERT\s+INTO|INSERT|MODIFY|DELETE\s+FROM)\s+(\w+)", re.IGNORECASE | re.MULTILINE)
DECLARED = re.compile(r"(?:DATA|PARAMETERS)[:(]?\s*(\w+)|^\s*,?\s*(\w+)\s+TYPE\b", re.IGNORECASE | re.MULTILINE)
CALL = re.compile(r"CALL\s+FUNCTION\s+'(\w+)'", re.IGNORECASE)
CLASS = re.compile(r"\b(\w+)=>|NEW\s+(\w+)\(|TYPE\s+REF\s+TO\s+(\w+)", re.IGNORECASE)
LEVEL_TEXT = {"A": "released API", "B": "classic API", "C": "reads an internal object",
"D": "writes to an SAP table", "?": "not in the rule list"}
def strip_comments(source: str) -> str:
lines = [ln for ln in source.splitlines() if not ln.lstrip().startswith("*")]
return "\n".join(re.sub(r'".*$', "", ln) for ln in lines)
def used_objects(source: str) -> list:
"""Objects a program uses, as (name, how). Simple patterns, not an ABAP parser."""
code = strip_comments(source)
found = []
local = {next(g for g in m.groups() if g).upper() for m in DECLARED.finditer(code)}
writes = {m.group(1).upper() for m in WRITE.finditer(code)} - local # internal tables are not SAP tables
for name in sorted(writes):
found.append((name, "write"))
for m in READ.finditer(code):
name = m.group(1).upper()
if name not in writes | local and (name, "read") not in found:
found.append((name, "read"))
for m in CALL.finditer(code):
if (m.group(1).upper(), "call") not in found:
found.append((m.group(1).upper(), "call"))
for m in CLASS.finditer(code):
name = next(g for g in m.groups() if g).upper()
if name not in ("ME", "SUPER") and (name, "class") not in found:
found.append((name, "class"))
return found
def classify(name: str, how: str, rules: dict) -> dict:
"""Clean core level for one use of one object, following SAP's A-D model."""
rule = rules.get(name, {})
state, successors = rule.get("state"), rule.get("successors", [])
custom = name.startswith(("Z", "Y"))
if how == "write" and not custom:
level, action = "D", "redesign with a person: never auto-fix"
elif custom:
level, action = "A", "own object: check it separately"
elif state == "released":
level, action = "A", "keep"
elif state == "classicAPI":
level, action = "B", "keep for now; allowed in Private Edition, plan a released successor"
elif state in ("notToBeReleased", "notToBeReleasedStable", "deprecated"):
level = "C"
action = ("use " + ", ".join(successors)) if successors else "no successor listed: ask SAP"
else:
level, action = "?", "check ATC and the simplification items; a person decides"
return {"object": name, "use": how, "level": level, "action": action}
def scan_source(source: str, rules: dict) -> list:
return [classify(name, how, rules) for name, how in used_objects(source)]
def worst(findings: list) -> str:
order = "AB?CD"
return max((f["level"] for f in findings), key=order.index, default="A")
# ---------- helpers ----------
def need_demo() -> dict:
if not RULES.exists():
sys.exit("No demo yet. Run first: python unit13/ccm_assist.py init")
return json.loads(RULES.read_text(encoding="utf-8"))["objects"]
def log_decision(**entry) -> None:
entry = {"time": datetime.now(timezone.utc).isoformat(timespec="seconds"), **entry}
with DECISIONS.open("a", encoding="utf-8") as f:
f.write(json.dumps(entry) + "\n")
def scope(name: str) -> str:
runs = USAGE.get(name)
if runs is None:
return "no usage data: find out before migrating"
return "retire (not used)" if runs == 0 else f"migrate ({runs} runs)"
# ---------- the assistant ----------
SAMPLE_PROPOSAL = {
"ZSD_OPEN_ORDER_VALUE": {
"explanation": "Replaced KNA1 with I_Customer and VBAK with I_SalesDocument, used inline "
"declarations, and kept the list output.",
"content": """REPORT zsd_open_order_value.
* Lists a customer's orders with their net value.
PARAMETERS p_kunnr TYPE kunnr OBLIGATORY.
SELECT SINGLE CustomerName FROM i_customer
WHERE Customer = @p_kunnr INTO @DATA(lv_name).
SELECT SalesDocument, TotalNetAmount, TransactionCurrency FROM i_salesdocument
WHERE SoldToParty = @p_kunnr
INTO TABLE @DATA(lt_orders).
DATA lv_sum TYPE netwr.
LOOP AT lt_orders INTO DATA(ls_order).
lv_sum = lv_sum + ls_order-TotalNetAmount.
WRITE: / ls_order-SalesDocument, ls_order-TotalNetAmount, ls_order-TransactionCurrency.
ENDLOOP.
WRITE: / 'Total for', lv_name, lv_sum.
"""},
}
SYSTEM = """You remediate classic ABAP custom code for a move from SAP ECC to SAP S/4HANA Cloud Private Edition.
Rules:
- Replace reads of internal objects with the successor named in the findings. Keep every filter and value exactly.
- Keep classic APIs (level B) unless the findings name a successor.
- Never write to SAP tables. Never invent fields: if unsure of a field name, keep the old code and say so.
- Change nothing else. Reply with JSON only: {"explanation": "two sentences", "content": "the complete new program"}"""
def llm_proposal(model: str, name: str, source: str, findings: list) -> dict:
try:
from anthropic import Anthropic # imported here so --sample works without the library
except ImportError:
sys.exit("The anthropic library is missing. Run: pip install -r requirements.txt")
prompt = (f"Program {name}:\n<code>\n{source}</code>\n\nFindings:\n"
+ "\n".join(f"{f['level']} {f['object']} ({f['use']}): {f['action']}" for f in findings))
try:
reply = Anthropic().messages.create(model=model, max_tokens=3000, system=SYSTEM,
messages=[{"role": "user", "content": prompt}])
except Exception as err: # wrong key, wrong model name, network blocked
sys.exit(f"The model call failed: {type(err).__name__}: {err}")
text = "".join(b.text for b in reply.content if b.type == "text")
try:
data = json.loads(text[text.index("{"):text.rindex("}") + 1])
return {"explanation": str(data["explanation"]), "content": str(data["content"])}
except (ValueError, KeyError) as err:
sys.exit(f"The model's reply could not be read ({err}). Run propose again.")
# ---------- commands ----------
def cmd_init(args) -> None:
if DEMO.exists() and not args.force:
sys.exit(f"{DEMO.name} already exists. Add --force to start over.")
for folder in (SRC, REVIEW, DONE):
folder.mkdir(parents=True, exist_ok=True)
for old in folder.glob("*"):
old.unlink()
for name, code in PROGRAMS.items():
(SRC / f"{name.lower()}.prog.abap").write_text(code, encoding="utf-8")
RULES.write_text(json.dumps({"source": "bundled copy, 2026-11-01", "objects": BUNDLED_RULES},
indent=1) + "\n", encoding="utf-8")
DECISIONS.unlink(missing_ok=True)
print(f"Wrote {len(PROGRAMS)} programs to unit13/ccm_demo/src and the rule list to unit13/ccm_demo/rules.json")
print("Next: python unit13/ccm_assist.py scan")
def cmd_scan(args) -> None:
rules = need_demo()
for path in sorted(SRC.glob("*.abap")):
name = path.name.split(".")[0].upper()
findings = scan_source(path.read_text(encoding="utf-8"), rules)
print(f"\n{name} scope: {scope(name)} worst level: {worst(findings)}")
for f in findings:
print(f" {f['level']} {f['use']:<5} {f['object']:<24} {f['action']}")
print("\nLevels: A released API, B classic API, C reads an internal object, "
"D writes to an SAP table, ? not in the rule list")
def cmd_propose(args) -> None:
rules = need_demo()
name = args.program.upper()
path = SRC / f"{name.lower()}.prog.abap"
if not path.exists():
sys.exit(f"No program {name} in unit13/ccm_demo/src.")
old = path.read_text(encoding="utf-8")
findings = scan_source(old, rules)
if USAGE.get(name) == 0:
print(f"{name} has not run in 14 months. Retire it instead of migrating it.")
log_decision(program=name, event="retire_suggested", runs=0)
return
if worst(findings) == "D":
print(f"{name} writes to an SAP table (level D). The assistant does not propose a fix.")
print("A person redesigns it, for example through a released API or a standard app.")
log_decision(program=name, event="refused_level_d")
return
if worst(findings) in ("A", "B"):
print(f"{name} is at level {worst(findings)}. No remediation needed; record the decision.")
log_decision(program=name, event="no_change", level=worst(findings))
return
if args.llm:
from dotenv import load_dotenv
load_dotenv()
if not os.getenv("ANTHROPIC_API_KEY"):
sys.exit("ANTHROPIC_API_KEY is not set in .env (see Set up your computer). Or use --sample.")
proposal = llm_proposal(args.model, name, old, findings)
elif args.file:
try:
content = Path(args.file).read_text(encoding="utf-8")
except OSError as err:
sys.exit(f"Could not read {args.file}: {err}")
proposal = {"explanation": f"Written by hand in {args.file}.", "content": content}
elif args.sample and name in SAMPLE_PROPOSAL:
proposal = SAMPLE_PROPOSAL[name]
else:
sys.exit(f"No scripted proposal for {name}. Use --llm, or --file with your own remediation.")
after = scan_source(proposal["content"], rules)
remaining = [f for f in after if f["level"] in ("C", "D", "?")]
diff = "\n".join(difflib.unified_diff(old.splitlines(), proposal["content"].splitlines(),
"before", "after", lineterm="", n=0))
lines = [f"# Review: {name}", "", f"Assistant: {proposal['explanation']}", "",
f"Scan after the change: worst level {worst(after)}"
+ ("" if not remaining else "; still open: " + ", ".join(f["object"] for f in remaining)),
"", "```diff", diff, "```", "", "## Checklist: [x] checked, [-] deferred to the quality system test, [ ] open", ""]
lines += [f"- [ ] {item}" for item in CHECKLIST]
review = REVIEW / f"{name.lower()}.md"
review.write_text("\n".join(lines) + "\n", encoding="utf-8")
(REVIEW / f"{name.lower()}.prog.abap").write_text(proposal["content"], encoding="utf-8")
log_decision(program=name, event="proposed", model=args.model if args.llm else ("file" if args.file else "sample"),
level_before=worst(findings), level_after=worst(after))
print(diff)
print(f"\nScan after the change: worst level {worst(after)} "
f"({len(remaining)} open finding(s)). Field names are not checked by this scan.")
print(f"Review file: unit13/ccm_demo/review/{review.name}")
def cmd_approve(args) -> None:
rules = need_demo()
name = args.program.upper()
review = REVIEW / f"{name.lower()}.md"
proposal = REVIEW / f"{name.lower()}.prog.abap"
if not review.exists():
sys.exit(f"No review file for {name}. Run propose first.")
after = scan_source(proposal.read_text(encoding="utf-8"), rules) # scan again: you may have edited it
if worst(after) in ("C", "D", "?"):
sys.exit(f"Not approved: the proposal is at level {worst(after)}. Fix it, then approve again.")
text = review.read_text(encoding="utf-8")
open_items = re.findall(r"^- \[ \] (.+)$", text, re.MULTILINE)
deferred = re.findall(r"^- \[-\] (.+)$", text, re.MULTILINE)
if open_items:
print(f"Not approved: {len(open_items)} checklist item(s) still open:")
for item in open_items:
print(" -", item)
log_decision(program=name, event="approval_refused", open_items=len(open_items), reviewer=args.reviewer)
sys.exit(1)
target = DONE / f"{name.lower()}.prog.abap"
target.write_text(proposal.read_text(encoding="utf-8"), encoding="utf-8")
log_decision(program=name, event="approved", reviewer=args.reviewer, level=worst(after), deferred=deferred)
print(f"Approved by {args.reviewer} at level {worst(after)}. Saved to unit13/ccm_demo/remediated/{target.name}")
if deferred:
print(f"{len(deferred)} check(s) deferred to the quality system test:")
for item in deferred:
print(" -", item)
def cmd_refresh(args) -> None:
rules = need_demo()
names = set(rules)
for path in SRC.glob("*.abap"):
names |= {n for n, _ in used_objects(path.read_text(encoding="utf-8"))}
fresh = {}
for file in (RELEASE_FILE, CLASSIC_FILE):
print("Downloading", file, "...")
try:
with urllib.request.urlopen(REPO + file, timeout=60) as resp:
data = json.load(resp)
except OSError as err:
sys.exit(f"Download failed: {err}. The bundled rule list stays in place.")
for item in data.get("objectReleaseInfo") or data.get("objectClassifications") or []:
key = item.get("objectKey", "").upper()
if key in names and key not in fresh:
fresh[key] = {"state": item.get("state"),
"successors": [s["objectKey"] for s in item.get("successors", [])]}
stamp = datetime.now(timezone.utc).date().isoformat()
RULES.write_text(json.dumps({"source": f"SAP Cloudification Repository, {stamp}", "objects": fresh},
indent=1) + "\n", encoding="utf-8")
changed = sorted(k for k in names if fresh.get(k) != rules.get(k))
print(f"rules.json now holds {len(fresh)} objects. Changed since the bundled copy: "
+ (", ".join(changed) if changed else "none"))
def main() -> None:
parser = argparse.ArgumentParser(description="Custom code migration assistant with a review gate.")
sub = parser.add_subparsers(dest="command", required=True)
p = sub.add_parser("init", help="create the demo")
p.add_argument("--force", action="store_true", help="start over")
sub.add_parser("scan", help="scope and clean core findings")
p = sub.add_parser("propose", help="draft a remediation for one program")
p.add_argument("program")
mode = p.add_mutually_exclusive_group(required=True)
mode.add_argument("--sample", action="store_true", help="scripted proposal, no key needed")
mode.add_argument("--llm", action="store_true", help="a real model proposes (small charge)")
mode.add_argument("--file", help="use a remediation you wrote yourself")
p.add_argument("--model", default="claude-haiku-5-5", help="Anthropic model ID for --llm")
p = sub.add_parser("approve", help="accept a reviewed proposal")
p.add_argument("program")
p.add_argument("--reviewer", required=True)
sub.add_parser("refresh-rules", help="rebuild rules.json from SAP's files on GitHub")
args = parser.parse_args()
{"init": cmd_init, "scan": cmd_scan, "propose": cmd_propose, "approve": cmd_approve,
"refresh-rules": cmd_refresh}[args.command](args)
if __name__ == "__main__":
main()
Wrote 4 programs to unit13/ccm_demo/src and the rule list to unit13/ccm_demo/rules.json
Next: python unit13/ccm_assist.py scan
Open unit13/ccm_demo/src/zsd_open_order_value.prog.abap. It is a classic ECC report: it reads customer master KNA1 and sales order headers VBAK directly, and sums the net value of a customer's orders of type ZOR (a Calder course label). Note the line AND auart = 'ZOR'. You'll need it in Step 6.
ZMM_OPEN_PO_AGING scope: retire (not used) worst level: C
C read EKPO use I_PURCHASEORDERITEMAPI01
C read EKKO use I_PURCHASEORDERAPI01
? call REUSE_ALV_GRID_DISPLAY check ATC and the simplification items; a person decides
ZSD_OPEN_ORDER_VALUE scope: migrate (412 runs) worst level: C
C read KNA1 use I_CUSTOMER
C read VBAK use I_SALESDOCUMENT
ZSD_ORDER_REASON_UPD scope: migrate (57 runs) worst level: B
B call POPUP_TO_CONFIRM keep for now; allowed in Private Edition, plan a released successor
B call BAPI_SALESORDER_CHANGE keep for now; allowed in Private Edition, plan a released successor
A call BAPI_TRANSACTION_COMMIT keep
ZSD_PRICE_FIX scope: migrate (9 runs) worst level: D
D write KONV redesign with a person: never auto-fix
Levels: A released API, B classic API, C reads an internal object, D writes to an SAP table, ? not in the rule list
Read it as a project lead would:
ZMM_OPEN_PO_AGING hasn't run in 14 months. Retire it; don't spend a day fixing it.
ZSD_OPEN_ORDER_VALUE is used and reads two internal tables. That is a remediation candidate.
ZSD_ORDER_REASON_UPD uses classic APIs. Level B is allowed in Private Edition, so record the decision and move on.
ZSD_PRICE_FIX writes straight into the pricing table. No tool should fix that alone.
REUSE_ALV_GRID_DISPLAY is in neither of SAP's files used here, so the tool says ?. Unknown is not the same as fine: ATC and the simplification items decide.
Scan after the change: worst level A (0 open finding(s)). Field names are not checked by this scan.
Review file: unit13/ccm_demo/review/zsd_open_order_value.md
By the checks, this is a perfect result: level C became level A. Don't stop there.
Open unit13/ccm_demo/review/zsd_open_order_value.md. It holds the assistant's explanation, the diff and a six-item checklist.
Compare the selection. The old code had AND auart = 'ZOR'. The proposal's WHERE has only SoldToParty = @p_kunnr. The order type filter is gone, and so is "(type ZOR)" from the comment. The new report would sum every sales document for the customer, not just standard orders. The explanation didn't mention it, and every check passed.
You should see Not approved: 6 checklist item(s) still open: and the six items. Nothing is accepted until you have worked through the list.
Fix the proposal. Open unit13/ccm_demo/review/zsd_open_order_value.prog.abap and change the line WHERE SoldToParty = @p_kunnr to:
WHERE SoldToParty = @p_kunnr AND SalesDocumentType = 'ZOR'
Save. The field name SalesDocumentType is the kind of name an assistant suggests. You can't confirm it without an SAP system, which is exactly what checklist item 2 is for.
Work through the checklist in the review file. Change [ ] to [x] for items you checked here: selection (item 1, now fixed), currency (item 3: the output still shows TransactionCurrency), and no table writes (item 6). Change [ ] to [-] for items 2, 4 and 5, which need a system: field names in ADT, authorization, and the comparison run in the quality system. Save.
Approved by Your Name at level A. Saved to unit13/ccm_demo/remediated/zsd_open_order_value.prog.abap
3 check(s) deferred to the quality system test:
- Every field used on the successor view exists there (checked in ADT, not taken from the proposal)
- Authorization checks are still in place (the old code's checks, or the view's access control)
- Output matches the original on a test run with the same input in the quality system
The approval scans your edited file again before accepting it, so an edit that reintroduced VBAK would be refused.
Open unit13/ccm_demo/decisions.jsonl. Each line is one event: retire_suggested, refused_level_d, proposed, approval_refused, approved. The approved line names you, the level and the deferred checks. That is the record an auditor or the business owner signing the migration asks for: what the tool proposed, what it refused, who accepted what, and what is still to be proven.
#Step 8: Try a real model (optional, small charge)
The default model is claude-haiku-5-5; add --model <id> for another model your key supports. The system prompt tells the model to keep every filter. Read the diff anyway: answers vary from run to run, and some runs will keep the filter while others won't. If the model leaves an internal table in place, the scan after the change shows level C and approve refuses.
#Step 9: Refresh the rule list from SAP (optional)
python unit13/ccm_assist.py refresh-rules
The script downloads objectReleaseInfo_PCELatest.json and objectClassifications_SAP.json from SAP's repository and keeps the entries your programs use. On 1 November 2026 it printed:
rules.json now holds 18 objects. Changed since the bundled copy: none
If SAP has changed an entry since then, the script names it. The repository also keeps files for specific Private Edition feature pack releases; a real project uses the one for its target release.
Four made-up CP20 programs and their run counts, as usage monitoring would report them
BUNDLED_RULES
The entries from SAP's Cloudification Repository that the demo needs, copied on 1 November 2026, so it runs offline
used_objects()
Finds tables read (FROM, JOIN), tables written (UPDATE, INSERT, MODIFY, DELETE FROM), function calls and classes, skipping the program's own variables. Patterns, not an ABAP parser
classify()
Maps each use to SAP's clean core levels: released A, classic API B, internal read C, SAP table write D, unknown ?
cmd_propose()
Refuses unused and level D programs, skips A and B, gets a draft from the sample, a model or your file, scans it again and writes the review file
CHECKLIST
The six things AI gets wrong in migrations: filters, invented fields, currency, authorization, output, table writes
cmd_approve()
Scans the reviewed file again, refuses while any item is [ ], records the reviewer and the deferred [-] items
cmd_refresh()
Downloads SAP's two files and rebuilds rules.json for the objects your programs use
Two design choices to notice. The tool refuses level D and unused code before any model sees it, so the assistant spends effort only where a draft helps. And approval is a separate command with a named reviewer, so "the model proposed it" and "a person accepted it" are never the same event.
As of November 2026, here is how SAP covers each step of the funnel. Check each item for your contract and release; several changed status during 2026.
ATC with S/4HANA readiness checks. Remote ATC from a central check system with S4HANA_READINESS_REMOTE, using the Simplification Database from SAP Note 2241080 (SAP Learning).
Cloudification Repository. The released and classic API lists behind the ATC checks "Usage of Released APIs" and "Usage of APIs", with release-specific files for Private Edition (GitHub).
System Analysis Assistant. Planned; SAP describes it as building a customer-specific understanding of systems, configuration and custom developments (SAP News, October 2026).
SAP S/4HANA custom code migration agent. Generally available per SAP's Q2 2026 highlights, in Joule for developers (ABAP AI). Readiness checks across packages, deterministic quick fixes, AI changes with confidence scores, transport recording (SAP News, July 2026).
Custom Code Assistant. Early adopters as of 28 October 2026. SAP says it classifies findings by complexity, applies automated fixes, recommends corrections for the rest, and helps decide between standard processes and custom extensions. SAP News (August 2026) says it depends on the ABAP MCP server, which Coding agents for SAP developers covers.
Joule for developers. Documentation Chat, ABAP Program Explain and Issue Explain for custom code migration (sap.com). SAP News (August 2026) says ABAP AI extends to S/4HANA Cloud Private Edition releases from 2021, with a side-by-side service for other releases.
Clean core transformation agent. Announced as planned for Q3 2026; no availability found by 1 November 2026.
Data Management Assistant and Business Configuration Assistant: early adopters as of 28 October 2026. SAP says the configuration assistant extracts source configuration, flags deviations from the target and keeps an audit trail (SAP News).
Test Management Assistant: early adopters. Test cases go to SAP Test Automation by Tricentis for execution.
Tricentis. SAP's support site says Cloud ALM has no SAP-built automation engine: customers provision a Tricentis tenant from Cloud ALM, and Enterprise Support customers are entitled to Tricentis Test Automation for SAP as a term license (SAP Support Portal). Tricentis's own page, modified 21 October 2026, describes generating test cases from natural language descriptions "using SAP AI units" (Tricentis).
Joule for developers: SAP's product page describes a free access offer for ADT users after accepting the SAP AI Terms, and a separate SKU for the ABAP environment. SAP News (August 2026) describes a move to usage-based AI units. Ask which applies to your contract.
The migration and modernization assistants: the October 2026 announcement gives no pricing.
ATC, the Simplification Database and the Cloudification Repository come with the platform or are public.
Where it sits. For Calder, the source is CP20's ECC system (ECD, ECQ, ECP). The target is CP10's S/4HANA Cloud Private Edition, release 2023, with the route S4D to S4Q to S4P (Landscape and deployment options). Analysis reads the source; remediation writes only in S4D.
flowchart LR
E[ECC CP20<br/>source code + usage] -->|remote ATC, RFC| C[Central check system]
C --> F[Findings]
F --> D[S4D development<br/>ADT + agent]
M[Model: Joule ABAP AI<br/>or generative AI hub] <--> D
D -->|transport after review| Q[S4Q quality<br/>comparison tests]
Q --> P[S4P production]
A[Cloud ALM<br/>+ Tricentis] --> Q
Non-functional requirements that decide the design:
Requirement
Typical target
What breaks if missed
Source code confidentiality
Code goes only to model providers under the customer's AI terms and region
Contract or data protection breach
Auditability
Every AI change linked to a transport, a reviewer and a test result
Migration sign-off and audits stall
Volume
Thousands of objects analysed in batches; review throughput planned per developer
Review queue becomes the bottleneck
Reversibility
Remediation in its own transports, separate from functional changes
A bad fix can't be backed out alone
Cost
AI units or tokens per object tracked against days saved
Savings claimed but never shown
Integration and identity. Remote ATC reads the old system over RFC with a technical connection set up by Basis. In S4D, the agent acts through the developer's own ADT session, so every change carries a person's user (Agent permissions and SAP authorizations). Transports carry code to S4Q; Cloud ALM and Tricentis run tests there.
Context:CP20's custom code moves into CP10's system by July 2027. SAP's agent can apply high-confidence fixes automatically.
Options: A, allow automatic fixes in S4D and review at transport release. B, allow only deterministic quick fixes automatically; every AI change waits for a named reviewer. C, no AI changes; AI only explains.
Decision: B. Deterministic fixes are checkable in bulk; AI changes are reviewed one by one with the checklist.
Consequences: slower than A on simple objects; every AI change has a reviewer on record; review capacity must be planned.
What a design review will ask:
Which objects did usage data retire, and who signed that?
What can the agent change without a person, and where is that configured?
How is behaviour compared before and after, for which objects?
Where does source code go, and under which terms?
How are level D objects redesigned, and who owns each?
Development only. Agents and assistants that change code work in the development system with the developer's own user. Transport release stays with people, under the project's normal change process.
Source code leaves the building. Custom code can contain business rules, names and sometimes credentials. Scan for secrets before sending code to any model, and check the provider terms as in Data security and PII.
Prompt injection through code. Comments in legacy code reach the model. A comment can carry instructions, as Prompt injection and tool poisoning explains. The review checklist and the re-scan are the defence.
Authorization gaps. Legacy reports often have no authorization checks, like the demo. Released CDS views can come with access controls; confirm what applies to each successor rather than assume.
Three meters: SAP AI units or licences for ABAP AI and the assistants, tokens if you call models yourself, and review time. Review time is the one projects forget. Plan it per developer per day, and stop sending objects to the AI when the review queue grows faster than it shrinks.
A migration is the cheapest moment to raise clean core levels: the code is being touched anyway. Aim for level A or B on everything that moves, and track level D objects by name until each one is redesigned. Don't let "the agent made it compile" become the definition of done.
Add a fifth CP20 program, remediate it with your own fix, and record the decision. The decision log you produce feeds the portfolio work in Unit 14.
Before you start: finish Build it yourself above.
Open unit13/ccm_assist.py. In PROGRAMS, after the ZSD_ORDER_REASON_UPD entry and before the closing }, add:
"ZMM_STOCK_CHECK": """REPORT zmm_stock_check.
* Shows Augsburg's unrestricted stock of one material.
PARAMETERS p_matnr TYPE matnr DEFAULT 'CP-IMP200'.
SELECT * FROM mard INTO TABLE @DATA(lt_stock)
WHERE matnr = @p_matnr AND werks = 'CP21'.
cl_salv_table=>factory( IMPORTING r_salv_table = DATA(lo_alv)
CHANGING t_table = lt_stock ).
lo_alv->display( ).
""",
In USAGE, add "ZMM_STOCK_CHECK": 120 (put a comma after the entry before it). Save.
Run python unit13/ccm_assist.py init --force, then python unit13/ccm_assist.py scan. Find ZMM_STOCK_CHECK: it should show worst level: C, with MARD (successor I_PRODUCTSTORAGELOCATIONBASIC) at C and CL_SALV_TABLE at B.
Create unit13/zmm_stock_check_fix.abap with the same program, but replace the SELECT lines with:
SELECT * FROM i_productstoragelocationbasic INTO TABLE @DATA(lt_stock)
WHERE Product = @p_matnr AND Plant = 'CP21'.
Run python unit13/ccm_assist.py propose ZMM_STOCK_CHECK --file unit13/zmm_stock_check_fix.abap. The scan after the change should show worst level B.
Open unit13/ccm_demo/review/zmm_stock_check.md. Tick [x] for items 1, 3 and 6 after checking them; mark [-] for items 2, 4 and 5.
Run python unit13/ccm_assist.py approve ZMM_STOCK_CHECK --reviewer "Your Name".
Write three sentences in unit13/ccm_demo/NOTES.md: why CL_SALV_TABLE may stay for now, which field names you must confirm in ADT, and what the comparison test in the quality system should show.
Pick one answer for each question. The explanation appears after you choose.
1In the decision funnel, when may a tool apply a change without a named reviewer for each item?
Answer: A. The mental model weighs how cheap a draft is to check against what happens if it's wrong. Confidence scores and passing checks prove neither behaviour nor harmlessness.
2In the script, why does propose refuse ZSD_PRICE_FIX before any model sees it?
Answer: B. The program updates KONV directly. SAP's level D covers write access to SAP tables, so the tool leaves the redesign to a person instead of drafting a fix.
3The sample proposal for ZSD_OPEN_ORDER_VALUE scans at level A. What did the review still catch?
Answer: C. The proposal removed AND auart = 'ZOR', so the report would sum every sales document. Clean core checks prove the level, not the behaviour; the checklist's first item exists for this.
4REUSE_ALV_GRID_DISPLAY shows ? in the scan. How should you treat it?
Answer: D. The object is in neither SAP file the demo uses. Unknown is not the same as clean, so the tool routes it to ATC, the simplification items and a person.
5How does SAP's custom code migration agent handle its changes, according to SAP's Q2 2026 highlights?
Answer: B. SAP describes deterministic quick fixes plus AI changes with confidence scores; high-confidence ones are applied, lower-confidence ones become comments, and updates are recorded in transport requests.
6Why does approve scan the proposal file again?
Answer: A. The reviewer fixes proposals by hand, as in Step 6. Re-scanning at approval means an edit that reintroduced an internal table can't slip through.
7Your project wants to use AI-remediated code but has no comparison tests. What do you do?
Answer: D. Checks prove the clean core level; only running old and new code on the same input proves behaviour. That evidence belongs in the quality system before transports move on.
8In the ADR for Calder, why was option B chosen over automatic AI fixes?
Answer: B. Rule-based quick fixes always produce the same result, so they can be reviewed in batches. AI changes vary and can alter behaviour, so each one gets a named reviewer, at the cost of planning review time.
Ask a question
Testing: only staff see this
Stuck on something in this layer? Ask it here. Questions are answered in the order they arrive, and the answer appears under My questions.
Sign in (free) to ask a question. You can ask anonymously.
Related topics
Picked from the terms, links and tags this topic shares with others.
SAP Business AI: Release Highlights Q2 2026 (SAP News, 20 July 2026)— SAP S/4HANA custom code migration agent generally available in SAP Joule for Developers (ABAP AI); ATC readiness checks across packages; deterministic quick fixes and AI changes with a confidence score; high-confidence fixes applied, lower-confidence ones as comments; updates recorded in transport requests
With Agentic AI, ABAP Takes Evolution to the Next Level (SAP News, 18 August 2026)— first custom code migration agent released in June 2026; seven agent families across migration phases; clean core transformation agent planned for Q3 2026; Custom Code Assistant depends on the ABAP MCP server; ABAP AI extended to Private Edition releases from 2021; move to usage-based billing in AI units
Joule for developers (sap.com product page)— custom code migration with Documentation Chat, ABAP Program Explain and Issue Explain (S/4HANA-related ATC findings, simplification items, cookbooks); free access offer for ADT users after accepting the SAP AI Terms; ABAP environment licence through SKU 8019124
What is SAP Joule for Consultants? (sap.com product page)— answers grounded in SAP knowledge including SAP Notes and KBAs; explains ABAP and CDS logic and dependencies; configuration and design guidance; works with Public and Private Edition
Cloudification Repository (SAP/abap-atc-cr-cv-s4hc on GitHub)— files read 1 November 2026: objectReleaseInfo_PCELatest.json (VBAK, KNA1, KONV, EKKO, EKPO, MARD notToBeReleased with successors I_SALESDOCUMENT, I_CUSTOMER, I_SLSPRCGCONDITIONRECORD, I_PURCHASEORDERAPI01, I_PURCHASEORDERITEMAPI01, I_PRODUCTSTORAGELOCATIONBASIC); objectClassifications_SAP.json (BAPI_SALESORDER_CHANGE, POPUP_TO_CONFIRM, CL_SALV_TABLE as classicAPI); used by ATC checks on ECC and S/4HANA