Repository navigation
Releases: DataZooDE/erpl-adt
Release list
v2026.09.27
Create RAP service objects directly through the CLI or MCP: behavior definitions (BDEF/BDO), service definitions (SRVD/SRV), and service bindings (SRVB/SVB). Previously these types were rejected as unknown before reaching SAP.
Service bindings accept explicit binding type, version, category, and service-definition metadata:
erpl-adt object create --type SRVB/SVB --name ZORDER_BIND \
--package '$TMP' --description 'Order binding' \
--binding-type ODATA --binding-version V4 --binding-category 0 \
--service-definition ZORDER_SRVThe MCP tool adt_create_object exposes the equivalent binding_type, binding_version, binding_category, and service_definition parameters. Service definitions send SAP's required source-type metadata, and all three types use their ADT XML media types.
Validated with six live a4h regression cases covering creation, source access, service-definition activation, OData V2/V4 bindings in both SAP categories, metadata read-back, and cleanup. The unit suite and all four platform CI builds passed. The broader live integration run encountered intermittent SAP lock-service failures, also reproduced with the previous release; details are recorded in the PR.
v2026.09.26.1
What's changed
- SAP ICF HTML error pages now show their
<title>in CLI and MCP diagnostics instead of a generic “Bad request”. - HTTP 400 responses saying “Service cannot be reached” are reported as temporary connection failures with a retry hint. Genuine expired ADT sessions still receive the session-specific guidance.
- Regression coverage for the reported lock and source-write failure paths, including an end-to-end CLI protocol test.
Thanks to the reporter of issue #60.
Full changelog: v2026.09.26...v2026.09.26.1
v2026.09.26
erpl-adt v2026.09.26
Fixes
- MCP response validation:
adt_search,adt_list_package, andadt_package_treenow return an object instructuredContent, so clients that require the MCP object shape accept their results. Other array-valued tool results use the same{ "result": [...] }envelope. The text content remains the original JSON array for clients that consume it. Theadt_check_syntaxoutput schema now describes the envelope. The fix was reproduced and verified against a live a4h SAP system. Thanks to @tommzn for reporting #61. - Package creation:
object create --type DEVC/Kaccepts--software-component, allowing a transportable package to use a component such asHOMEwith--transport. The default remainsLOCALfor scratch packages. (#58)
Release and CI workflows also retry transient artifact-upload failures. Daily PyPI download statistics are now sent to PostHog.
Upgrade from PyPI with pip install --upgrade erpl-adt, or download a platform archive and its SHA256 checksum from the assets below.
v2026.09.01.2
erpl-adt v2026.09.01.2
Closes the last open issue on the tracker. This release changes a default: the MCP HTTP
transport and the catalog web UI now refuse a browser request whose Host header names an
address they do not serve on. If you reach either through a DNS name, read the first section.
Prebuilt binaries are attached below; the CLI is also on PyPI (pip install erpl-adt).
DNS rebinding is now refused by default (#50)
v2026.08.31 stopped cross-origin browser requests, and v2026.09.01.1 added a Host check —
but left it opt-in, so the attack the issue is about was still reachable in the configuration
almost everyone runs. It is closed now.
The attack it prevents: an attacker owns evil.example with a one-second DNS TTL. You load
http://evil.example:8383; their DNS then starts answering 127.0.0.1. The page fetches
http://evil.example:8383/mcp — same-origin with itself, so no CORS restriction applies
and the browser both writes and reads — and the connection lands on your MCP server, which
sits in front of adt_write_source, adt_delete_object, adt_activate and
adt_release_transport against a live SAP system, with no authentication by default. Host
and Origin both say evil.example, so an Origin check passes them: the attacker wrote both
sides of that comparison. The Host header is the one thing they cannot launder, because the
browser sends the name the page used, never the IP it resolved to.
Host |
Result |
|---|---|
loopback, an IP literal, the address bound, an --allowed-hosts entry |
served |
| any other DNS name, from a browser | 403 |
| any other DNS name, from a non-browser client | served, with a note on stderr |
Does this affect you? Only if a browser reaches the server through a DNS name — a
reverse proxy, a container name, a corporate hostname. Loopback and IP literals need no
configuration, so --mcp-host 0.0.0.0 reached at http://192.168.1.5:8383 is untouched (an IP
address has no name to rebind). curl, native MCP clients and server-to-server callers are
unaffected whatever hostname they use.
If it does, name the host once — flag, environment, or config file, in that precedence:
erpl-adt mcp --http --mcp-host 0.0.0.0 --allowed-hosts mcp.internal.example
ERPL_ADT_ALLOWED_HOSTS=mcp.internal.example erpl-adt mcp --http --mcp-host 0.0.0.0
erpl-adt mcp --http --mcp-host 0.0.0.0 -c erpl.yaml# erpl.yaml — also read by `catalog webui`
http:
allowed_hosts: [mcp.internal.example, buildbox.corp]
cors_origin: [https://catalog.example]
auth_token_env: ERPL_ADT_MCP_TOKEN # the variable's name; a raw token key is not readBoth the 403 body and the stderr line name the exact flag to add, so recovering from a
surprise is one restart. --allowed-hosts '*' disables the check entirely, with a warning.
A legitimate browser at http://buildbox.corp:8383 and a rebound one are identical on the
wire, which is why this is an allowlist and not a heuristic. The check applies to
browser-originated requests only — those carrying Origin or Sec-Fetch-* — and that loses
nothing: rebinding needs a browser, /mcp accepts POST only, and a browser always sends
Origin on a POST, same-origin ones included.
Both HTTP servers now state their access-control posture
erpl-adt MCP HTTP server listening on http://127.0.0.1:8383/mcp
hosts: loopback, IP literals (add with --allowed-hosts)
origins: same-origin, loopback (add with --cors-origin)
auth: none (--auth-token to require a bearer token)
A default that refuses requests should be visible in the log of the server doing the refusing,
not only in the docs.
Also in this release
mcp --httpandcatalog webuiaccept-c/--config, reading anhttp:block for these
settings;ERPL_ADT_ALLOWED_HOSTS/ERPL_ADT_CORS_ORIGIN/ERPL_ADT_AUTH_TOKENwork as
environment variables under both theERPL_ADT_*andSAP_*prefixes, like every other
setting.scripts/ci/webui_smoke.pynow asserts a reboundHostis refused, so every CI platform
checks the shipped default rather than a unit-test approximation.
Full changelog: v2026.09.01.1...v2026.09.01.2
v2026.09.01.1
erpl-adt v2026.09.01.1
A small bugfix release closing the two open issues on the tracker: a client-side timeout
that reported long-running ABAP as failed, and a DNS-rebinding gap the previous release's
Origin validation could not see. Prebuilt binaries are attached below; the CLI is also on
PyPI (pip install erpl-adt).
Long-running ABAP no longer fails at 121 seconds (#42)
AdtSessionOptions::read_timeout defaulted to 120s while AppConfig::timeout_seconds
documented 600, and because both CLI entry points applied --timeout only when it was
passed explicitly, 120s was what every call actually got — the CLI, the deploy path and
the MCP server alike.
Anything slower failed at 121s. The ABAP behind it keeps running server-side and normally
completes, so the caller was told the work had failed when it had in fact succeeded, and
had to go query the result to find out which. There is now one constant behind both
defaults, and the documented 600s is the effective one.
The diagnostics were the other half of the problem:
before: Error: Post
HTTP request failed: Failed to read connection
Unexpected? Please report it: https://github.com/DataZooDE/erpl-adt/issues
after: Error: Post
request timed out after 5s (connecting to http://localhost:50000)
Hint: Raise it with --timeout <seconds>. Work already started on the server
may still be running and may complete — check before repeating it.
A connection timeout is now distinguished from a read timeout — it names the connect
limit and suggests checking reachability, since --timeout would not have helped. And a
timeout no longer invites a bug report: it is a limit the caller configured and can raise,
not a surprise.
Host validation for the MCP HTTP transport (#50)
v2026.08.31 removed the wildcard Access-Control-Allow-Origin and started validating
Origin. That does not stop DNS rebinding: once a page at evil.example makes
rebind.evil.example resolve to 127.0.0.1, the browser treats the call as same-origin
and sends a Host and Origin the attacker controls — so both halves of the comparison
are theirs, and the request was answered 200.
The Host header is the half they cannot launder, and it is now classified on its own,
before Origin. Loopback names and IP literals always pass (an IP address has no name to
rebind, so --mcp-host 0.0.0.0 reached at a LAN address is unaffected), as do the bound
address and anything named by the new --allowed-hosts:
erpl-adt mcp --http --mcp-host 0.0.0.0 --allowed-hosts mcp.internal.examplePassing the flag is what turns refusal on. Without it an unrecognised Host is still
served, with one warning per distinct host on stderr — so a deployment reached through a
DNS name or a reverse proxy keeps working across the upgrade. If you run the HTTP
transport anywhere a browser can reach it, pass --allowed-hosts.
--allowed-hosts is available on both erpl-adt mcp --http and erpl-adt catalog webui.
Also in this release
--timeout's help now states the default and that it is a per-request read timeout,
everywhere it is described.- An integration test that still asserted a classrun on a missing class exits 0 has been
brought in line with the contract the previous release introduced.
Full changelog: v2026.09.01...v2026.09.01.1
v2026.09.01
erpl-adt v2026.09.01
A bugfix release. Everything here came out of auditing the command surface against a live
SAP system rather than from new feature work: commands that reported success for work that
never happened, tools that were broken while the same command worked from the CLI, and a
semantic search that had stopped working because a model was retired upstream. Prebuilt
binaries are attached below; the CLI is also on PyPI (pip install erpl-adt).
Commands no longer report success for work that did not happen
Several ADT endpoints answer HTTP 200 for a target that is not there and put the real
story in prose. Read literally, each of those said the work succeeded — and "no findings"
is indistinguishable from "clean", which is the reading that matters when an agent runs a
check after every edit and mistypes a URI once. (#52)
| Command | Used to answer for a missing target | Now |
|---|---|---|
check run |
{"error_count":0,"findings":[]} |
not-found error, exit 2 |
test run |
all_passed: true, "No test methods found" |
not-found error, exit 2 |
source check |
[] — no syntax errors |
not-found error, exit 2 |
object run |
SAP's "does not exist" message, exit 0 | not-found error, exit 2 |
transport release |
"Released transport: X" | not-found error, exit 2 |
The check is a plain GET before acting, not a match on SAP's message — that message is
translated, so matching it would be fragile. It lives in the ADT layer, so the CLI and the
MCP tools both inherit it, and anything other than 200/404 is passed through unchanged: a
403 or a dropped connection never reads as "missing".
transport list --user=ADMIN never worked
--user is the connection flag, so naming anyone else re-authenticated as them with the
current password and answered HTTP 401 — including the example in our own help. The owner
filter is now --owner, defaulting to the logon user. Nobody loses a working invocation:
the only value that ever worked was the logon user. (#52)
Two MCP tools were broken while their commands worked
Calling all 77 tools over the protocol, the way an agent does rather than through the CLI,
found bw_application_log answering HTTP 400 ("Parameter username could not be found") and
bw_validate answering HTTP 500 ("Action 'validate' is not valid"). Both had been fixed
days earlier — in the CLI handler, which the tools do not go through. (#53)
The defaults now live in the ADT layer where every caller passes through, IAdtSession
exposes the logon user so that policy has somewhere correct to live, and the integration
suite gained a smoke test that calls the tools over JSON-RPC — so a CLI-only fix that
leaves a tool broken fails CI instead of waiting for the next audit.
Semantic catalog search works again
catalog search --mode vss|hybrid and catalog build --embed failed with an unexplained
HTTP 404 from the embedding provider, with a valid API key: Google retired
text-embedding-004. The replacement is gemini-embedding-001, requested at 768
dimensions so existing catalogs stay readable, and normalised — that model's truncated
vectors are not unit length, unlike the ones before them. The model name can be overridden
with ERPL_ADT_GEMINI_EMBED_MODEL, so the next retirement is a configuration change rather
than a release. A catalog built without --embed now says it holds no embeddings instead
of returning an empty result. (#54)
Known limits
bw changeability,bw adturi,bw virtualfolders,bw datavolumesand
bw transport checkanswer 404: their routes are absent from the discovery document on
SAP_BW 758.- The BW jobs routes cannot be confirmed on a system that produces no async jobs.
- abapgit and the
deployworkflow could not be verified at all — the abapGit ADT backend
is not installed on the trial system used for this work, so treat their request shapes as
unconfirmed.
Install
pip install --upgrade erpl-adtOr download a prebuilt binary for your platform from the assets below (.tar.gz for
Linux/macOS, .zip for Windows), each with a .sha256 checksum.
Full changelog: v2026.08.31...v2026.09.01
v2026.08.31
erpl-adt v2026.08.31
A release with two themes: the MCP server grew real access control and structured
output, and the BW modeling commands were measured against a live system for the first
time — which found seven of them that had never worked. Prebuilt binaries are attached
below; the CLI is also on PyPI (pip install erpl-adt).
Security
-
The MCP HTTP transport validates
Origin. It sentAccess-Control-Allow-Origin: *
on every response and validated nothing, in front of tools that write to and delete from
a live SAP system — so any page a developer visited could post writes to their system.
Binding to127.0.0.1never helped: the browser is inside the loopback boundary.
Requests with noOrigin(curl, native MCP clients), same-origin requests and loopback
origins are allowed; anything else is refused with 403. Add more with--cors-origin.
Nothing that works today stops working. (#45) -
Optional bearer authentication for
mcp --httpandcatalog webuivia
--auth-token/--auth-token-env, checked before the request is parsed and compared
in constant time./healthzstays open. Off unless configured. (#45)
BW: commands that had never worked
Each of these failed on every system, every time, because the request shape was derived
from our own code rather than measured against a backend. They are now read off
CL_RSO_RES_* over ADT and verified live on SAP_BW 758.
-
bw createanswered HTTP 415 for every object type: the media type was sent as
Content-Typeonly, so SAP saw*/*. Every BW mutating call had the same hole —
save, delete, lock, unlock, activate, jobs. Creating also needs a real body (an empty
one answers "Object MODEL … not found"), takes the package from
<adtcore:packageRef>rather than a query parameter, and leaves an implicit lock that
outlived the session and blocked every later command. (#43, #44) -
bw activateanswered HTTP 500 "Request cannot be deserialized". The body is an
Atom feed with exactly one entry, whose content is abwModel:checkProperties
element; the mode comes from the path (/activationvs/checkruns), never a query
parameter; and the type segment of the object URI must be lower case.--validatehad
also been resolving the activation endpoint, so it would have activated the object it
was asked only to check. (#44) -
bw create IOBJanswered 404 about the object it was asked to create: InfoObjects
create with PUT, ADSOs with POST. The client now retries with PUT on a 404 rather
than hard-coding a verb per type. Copy-create also renamed by substring, so copying
0CALMONTHcorrupted references to0CALMONTH2. (#44) -
bw saveanswered HTTP 400 "Parameter version could not be found": the version is
a path segment,/{tlogo}/{name}/m. It also takes--filenow, so the payload need not
come through a shell redirect. (#44) -
bw validatesent GET to a POST-only resource, with anactionvalue the backend
does not accept.bw qpropsasked for the media type discovery advertises rather
than the one the route serves, and needs an InfoProvider argument.bw applogsent
none of its three mandatory parameters.bw move listdid a GET against an endpoint
that only ever executed moves — it now says so instead of returning HTTP 405. (#49)
bw read --version also reached the wire for the first time: the version-less rel="self"
template from discovery was overriding the version-aware path while the output still
claimed the requested version. (#41)
For agents
-
Results carry
structuredContentalongside the text block, so neither the model nor
a client has to re-parse a JSON string. The six most-parsed tools —adt_run_tests,
adt_run_atc,adt_check_syntax,adt_read_table,catalog_search,catalog_lineage
— declare anoutputSchema. (#47) -
Every tool declares
annotationsand atitle, so a host can put a confirmation in
front of the eight destructive tools and not the fifty-odd read-only ones. The
classification is a reviewed table, not a name-prefix guess. (#47) -
--tools adt,bw,catalogexposes only the families a session needs, instead of all 77
tools in every prompt. Protocol version negotiation: the server speaks 2025-06-18,
2025-03-26 and 2024-11-05 and echoes what the client asks for, instead of answering
2024-11-05 to everyone. Unknown tool is now-32602; over HTTP, notifications answer
202, unimplemented methods 404, andGET/DELETEon/mcp405. (#47) -
Handle lifetime is documented in
adt_lock,bw_lock_objectandbw_job_status:
when the handle dies, and that a stale one is a recoverable error. (#47)
Connection settings from the environment
ERPL_ADT_HOST / ERPL_ADT_PASSWORD were honored nowhere, and the SAP_* names in the
docs were read by no code — so a stale .adt.creds kept working until it didn't, and the
failure looked like a server problem. All six settings now resolve flags >
--password-env > environment > .adt.creds > defaults, under both spellings. A 401
spells out that order, and transport failures name the server they tried. (#41)
Known issues
bw changeability,bw adturi,bw virtualfoldersandbw datavolumesanswer 404:
their routes are absent from the discovery document on SAP_BW 758. Left alone rather
than guessed at.- The BW jobs routes cannot be confirmed on a system that produces no async jobs.
bw create IOBJcopies of hierarchy-enabled characteristics need activation ordering
the client does not yet do.
Install
pip install --upgrade erpl-adtOr download a prebuilt binary for your platform from the assets below (.tar.gz for
Linux/macOS, .zip for Windows), each with a .sha256 checksum.
Full changelog: v2026.08.30...v2026.08.31
v2026.08.30
erpl-adt v2026.08.30
A patch release fixing the BW modeling commands: bw create never worked, and every BW
write request asked the server for the wrong content type. Prebuilt binaries are attached
below; the CLI is also on PyPI (pip install erpl-adt).
Fixes
-
bw createworks. Every form of it failed with HTTP 415 — "Requested content type
*/* does not match back-end content type application/vnd.sap.bw.modeling.adso-v1_2_0+xml".
The object's media type was sent asContent-Typeonly; cpp-httplib adds noAcceptof
its own, so SAP saw*/*. (#41, #43) -
Every BW mutating request sends
Accept. The same hole was in save, delete, lock,
unlock, activate and the job interrupt/restart/cleanup actions. The media-type catalog
now lives in one place (adt/bw_media_types.hpp); there were two copies of it that had
drifted apart. The lock/unlock action routes turn out to negotiate on the object's media
type too —application/xmlis read as version 0.0.0 and rejected with "Your BW client
is outdated". (#43) -
The rest of the create path, found once the 415 was gone and verified against a live
SAP_BW 758 system: the backend always parses a request body (an empty one answers HTTP
500 "Object MODEL <name> not found"), and thecopyFrom*query parameters do not
fill it in — a copy now reads the source definition and sends it under the new name.
--packagealone generates a minimal ADSO definition, and a type with neither fails
before the wire with a hint naming--fileand--copy-from-*. The package travels in
<adtcore:packageRef>in the body, not as a query parameter the discovery template never
advertised. (#43) -
Created objects are no longer left locked. Creating implicitly locks the new object
and that enqueue outlives the session, so every later command —bw deleteincluded —
failed with "is locked by user …". The lock is released after a successful create
unless the new--keep-lockis given. (#43) -
Lock responses parse. The live response is a full ASXML document; wrapping it in a
synthetic<root>without stripping the XML declaration made tinyxml2 reject it, so the
server took a lock the client then threw away — leaving objects stuck. The handle is also
nested underasx:abap/asx:values/DATA, where the flat child lookup never found it. (#43) -
bw read --versionreaches the URL.--version dissued a request with no version
segment at all while the output still reported the requested version. The first template
a type advertises isrel="self"and carries no{version}; templates now keep their
relation and versioned reads resolve by it. (#41, #43) -
Connection settings from the environment.
ERPL_ADT_HOST/ERPL_ADT_PASSWORDwere
honored nowhere, andSAP_HOST/SAP_PORT/SAP_USER/SAP_CLIENTwere documented but
read by no code — so a stale.adt.credskept working until it didn't, and the failure
looked like a server problem. All six settings now resolve flags >--password-env>
environment >.adt.creds> defaults, under both spellings,ERPL_ADT_*winning over
SAP_*.SAP_PASSWORDbehaves exactly as before. (#41, #43) -
Errors that point at the right cause. A 401 spells out the credential resolution
order and names.adt.creds; transport failures name the server they tried; and a BW
403 or 404 carrying a SAP message no longer blames SICF activation — a service that
explains itself is plainly running. (#43)
Known issue
bw activate fails with HTTP 500 "Request cannot be deserialized" (#44). This predates
this release — verified by rebuilding the activation module from the previous commit — and
is not fixed here: the route rejects even an empty body, so finding the right payload needs
captured BWMT traffic. Objects created with bw create stay in their inactive (m)
version until then.
Testing
test_16_bw_lifecycle.py skipped on exactly the HTTP 415 this release fixes, which is how
the defect stayed invisible; that skip is gone. New test_19_bw_create.py drives create,
copy-create and the versioned read against a live system. BW read fixtures now pick an
active object rather than whatever sorts first out of search — bw read addresses the
active version, so an inactive-only object was never a usable subject. Unit tests:
1283 passed.
Install
pip install --upgrade erpl-adtOr download a prebuilt binary for your platform from the assets below (.tar.gz for
Linux/macOS, .zip for Windows), each with a .sha256 checksum.
Full changelog: v2026.08.11...v2026.08.30
v2026.08.11
erpl-adt v2026.08.11
A patch release fixing how HTTP 403 is reported, plus a flag combination that could never succeed. Prebuilt binaries are attached below; the CLI is also on PyPI (pip install erpl-adt).
Fixes
-
A 403 is no longer blamed on CSRF. Every 403 was classified as
csrf_tokenwith the message "Forbidden — CSRF token may be invalid", so two unrelated failures both pointed at a stale token: a service that is not activated on the connected system, and a lock conflict. A 403 is now read from what it carries — one with a SAP application error is not a token problem. Only a bare 403 stayscsrf_token, which is also the shape the client already retries. A 403 raised while fetching a token is nevercsrf_token; there was no token yet. (#40) -
Lock conflicts exit 6 instead of 1. "User X is currently editing …" arrives as HTTP 403 and was therefore reported as a CSRF error with exit code 1, even though exit 6 has always been documented as "lock conflict". Scripts branching on exit codes never saw it for the most common lock failure there is. Exit codes are otherwise unchanged; the new
authorizationcategory maps to 1, asauthenticationdoes. (#40) -
BW errors carry their hint again.
AddBwHintran in the BW operation modules but never in the session layer, so a rejected CSRF fetch — the first thing an inactive ICF node breaks — arrived with no hint at all.bw discoveragainst an unactivated service now says to activate/sap/bw/modeling/in SICF instead of implying an auth problem. (#40) -
source write --activateno longer fails after writing. Combined with--handle, activation always failed: SAP refuses to activate an object while the caller holds its lock. The write had already gone through by then, leaving the source written, the object inactive and the lock still held. The combination is now rejected up front, pointing atobject unlockfollowed byerpl-adt activate. It works unchanged in auto-lock mode, where the lock is released before activation. (#40)
Documentation
README.md,docs/cli-usage.md:--responsible, the--activate/--handlerestriction,resolved_viaonpackage exists, the 403 classification and exit codes.docs/activate-bw-modeling-on-a4h.md,CLAUDE.md: corrected a factual error — both claimed the standardabap-cloud-developer-trialimage has no BW Modeling API. It does; the ICF nodes ship inactive. After activating them on that image,bw search '*'returns delivered BW content and the BW test suites pass.
Testing
The integration suite's 8 failures are fixed, all of them errors in the tests rather than the product (#38): an ABAP Unit test that never activated the class it was testing, and six BW tests that carried the bw marker but no availability fixture, so they failed instead of skipping where BW is inactive. The suite now also activates BW itself on a local throwaway trial rather than skipping. Unit tests: 1255 passed.
Install
pip install --upgrade erpl-adtOr download a prebuilt binary for your platform from the assets below (.tar.gz for Linux/macOS, .zip for Windows), each with a .sha256 checksum.
Full changelog: v2026.08.10...v2026.08.11
v2026.08.10
erpl-adt v2026.08.10
A patch release fixing package existence detection on older SAP_BASIS releases. Prebuilt binaries are attached below; the CLI is also on PyPI (pip install erpl-adt).
Fixes
-
Package existence is now determinable on SAP_BASIS 7.40.
adt_package_existsreturned{"exists": false}for every package on 7.40 — standard, customer, and non-existent alike — andadt_list_packagereported an existing but childless package as"Package <PKG> does not exist". Both read a 404 fromGET /sap/bc/adt/packages/{name}as proof of absence, but that resource does not exist on 7.40, where it 404s for every package. Existence now falls back to the repository information system search (objectType=DEVC/K, exact name match), which is available on every release, and a search that itself fails returns an error instead of a silent "does not exist". An agent checking "does my target package exist?" before creating objects no longer concludes it does not. (#35) -
package existsreports how it answered. The result now carriesresolved_via("package_resource"or"search"), so a caller can tell a real "absent" from an answer that leaned on an endpoint the connected release may not have. (#35) -
Empty packages are listed as empty, not missing.
package listdistinguishes "the package has no objects" from "the package does not exist" — SAP'snodestructureendpoint answers HTTP 200 with an empty body for both, so the two states are now separated by a real existence check. A freshly created, still-empty package is the normal starting state for new development. (#35) -
discoverno longer over-reports package support.has_packageswas set whenever any service href merely contained/packages, so/packages/settingsalone flipped it true on releases with no package resource at all. It now requires the collection itself. (#35) -
object create --type DEVC/Kworks. Package creation failed with "Enter a valid user, not , as the person responsible". The person responsible now defaults to the logon user and can be overridden with the new--responsibleflag.
Install
pip install --upgrade erpl-adtOr download a prebuilt binary for your platform from the assets below (.tar.gz for Linux/macOS, .zip for Windows), each with a .sha256 checksum.
Full changelog: v2026.08.06...v2026.08.10