Skip to content

Releases: DataZooDE/erpl-adt

v2026.09.27

Choose a tag to compare

@github-actions github-actions released this 27 Sep 05:30
bde50db

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_SRV

The 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.

Fixes #64 via #65.

Full changelog

v2026.09.26.1

Choose a tag to compare

@github-actions github-actions released this 26 Sep 12:46
526b15f

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

Choose a tag to compare

@github-actions github-actions released this 26 Sep 08:20
47cba9c

erpl-adt v2026.09.26

Fixes

  • MCP response validation: adt_search, adt_list_package, and adt_package_tree now return an object in structuredContent, 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. The adt_check_syntax output 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/K accepts --software-component, allowing a transportable package to use a component such as HOME with --transport. The default remains LOCAL for 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

Choose a tag to compare

@github-actions github-actions released this 01 Sep 09:06
3af22ff

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 read

Both 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 --http and catalog webui accept -c/--config, reading an http: block for these
    settings; ERPL_ADT_ALLOWED_HOSTS / ERPL_ADT_CORS_ORIGIN / ERPL_ADT_AUTH_TOKEN work as
    environment variables under both the ERPL_ADT_* and SAP_* prefixes, like every other
    setting.
  • scripts/ci/webui_smoke.py now asserts a rebound Host is 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

Choose a tag to compare

@github-actions github-actions released this 01 Sep 03:17
a5cb66f

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.example

Passing 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

Choose a tag to compare

@github-actions github-actions released this 31 Aug 16:32
f8a7816

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 datavolumes and
    bw transport check answer 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 deploy workflow 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-adt

Or 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

Choose a tag to compare

@github-actions github-actions released this 31 Aug 05:30
7729dab

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 sent Access-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 to 127.0.0.1 never helped: the browser is inside the loopback boundary.
    Requests with no Origin (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 --http and catalog webui via
    --auth-token / --auth-token-env, checked before the request is parsed and compared
    in constant time. /healthz stays 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 create answered HTTP 415 for every object type: the media type was sent as
    Content-Type only, 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 activate answered HTTP 500 "Request cannot be deserialized". The body is an
    Atom feed with exactly one entry, whose content is a bwModel:checkProperties
    element; the mode comes from the path (/activation vs /checkruns), never a query
    parameter; and the type segment of the object URI must be lower case. --validate had
    also been resolving the activation endpoint, so it would have activated the object it
    was asked only to check. (#44)

  • bw create IOBJ answered 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
    0CALMONTH corrupted references to 0CALMONTH2. (#44)

  • bw save answered HTTP 400 "Parameter version could not be found": the version is
    a path segment, /{tlogo}/{name}/m. It also takes --file now, so the payload need not
    come through a shell redirect. (#44)

  • bw validate sent GET to a POST-only resource, with an action value the backend
    does not accept. bw qprops asked for the media type discovery advertises rather
    than the one the route serves, and needs an InfoProvider argument. bw applog sent
    none of its three mandatory parameters. bw move list did 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 structuredContent alongside 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 an outputSchema. (#47)

  • Every tool declares annotations and a title, 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,catalog exposes 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, and GET/DELETE on /mcp 405. (#47)

  • Handle lifetime is documented in adt_lock, bw_lock_object and bw_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 virtualfolders and bw datavolumes answer 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 IOBJ copies of hierarchy-enabled characteristics need activation ordering
    the client does not yet do.

Install

pip install --upgrade erpl-adt

Or 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

Choose a tag to compare

@github-actions github-actions released this 30 Aug 10:23
1e14024

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 create works. 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 as Content-Type only; cpp-httplib adds no Accept of
    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/xml is 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 the copyFrom* query parameters do not
    fill it in — a copy now reads the source definition and sends it under the new name.
    --package alone generates a minimal ADSO definition, and a type with neither fails
    before the wire with a hint naming --file and --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 delete included —
    failed with "is locked by user …". The lock is released after a successful create
    unless the new --keep-lock is 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 under asx:abap/asx:values/DATA, where the flat child lookup never found it. (#43)

  • bw read --version reaches the URL. --version d issued a request with no version
    segment at all while the output still reported the requested version. The first template
    a type advertises is rel="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_PASSWORD were
    honored nowhere, and SAP_HOST/SAP_PORT/SAP_USER/SAP_CLIENT were documented but
    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, ERPL_ADT_* winning over
    SAP_*. SAP_PASSWORD behaves 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-adt

Or 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

Choose a tag to compare

@github-actions github-actions released this 11 Aug 08:19
611611e

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_token with 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 stays csrf_token, which is also the shape the client already retries. A 403 raised while fetching a token is never csrf_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 authorization category maps to 1, as authentication does. (#40)

  • BW errors carry their hint again. AddBwHint ran 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 discover against an unactivated service now says to activate /sap/bw/modeling/ in SICF instead of implying an auth problem. (#40)

  • source write --activate no 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 at object unlock followed by erpl-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/--handle restriction, resolved_via on package exists, the 403 classification and exit codes.
  • docs/activate-bw-modeling-on-a4h.md, CLAUDE.md: corrected a factual error — both claimed the standard abap-cloud-developer-trial image 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-adt

Or 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

Choose a tag to compare

@github-actions github-actions released this 10 Aug 19:30
45f6d51

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_exists returned {"exists": false} for every package on 7.40 — standard, customer, and non-existent alike — and adt_list_package reported an existing but childless package as "Package <PKG> does not exist". Both read a 404 from GET /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 exists reports how it answered. The result now carries resolved_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 list distinguishes "the package has no objects" from "the package does not exist" — SAP's nodestructure endpoint 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)

  • discover no longer over-reports package support. has_packages was set whenever any service href merely contained /packages, so /packages/settings alone flipped it true on releases with no package resource at all. It now requires the collection itself. (#35)

  • object create --type DEVC/K works. 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 --responsible flag.

Install

pip install --upgrade erpl-adt

Or 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