@mcp-abap-adt/adt-clients 23.0.2 → 23.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/core/package/AdtPackage.d.ts +3 -3
- package/dist/core/package/AdtPackage.js +3 -3
- package/dist/core/package/types.d.ts +1 -1
- package/docs/README.md +2 -4
- package/docs/usage/ANSWER_SHAPES.md +3 -4
- package/docs/usage/CLIENT_API_REFERENCE.md +15 -3
- package/docs/usage/{WORKAROUNDS.md → ERRATA.md} +514 -27
- package/docs/usage/OBJECT_LIFECYCLE.md +7 -5
- package/docs/usage/RFC_CONNECTION.md +1 -1
- package/docs/usage/STATEFUL_SESSION_GUIDE.md +1 -1
- package/package.json +1 -1
- package/docs/usage/TROUBLESHOOTING.md +0 -193
package/README.md
CHANGED
|
@@ -940,7 +940,7 @@ member uses it on your behalf; `create()` and `update()` issue one request each.
|
|
|
940
940
|
- **[Documentation index](docs/README.md)**
|
|
941
941
|
- **[Migrating to 23.0.0](docs/usage/MIGRATION-23.md)** – the replacing code for every removed behaviour
|
|
942
942
|
- **[Client API reference](docs/usage/CLIENT_API_REFERENCE.md)** – every client, member and result set
|
|
943
|
-
- **[
|
|
943
|
+
- **[SAP ADT errata](docs/usage/ERRATA.md)** – what SAP answers that it does not mean, and what to do: the object tree with each branch's cases (a `403` on a service binding's LOCK that is to be ignored, among them), then every case with its evidence. Ships in this package under `docs/usage/`, and in `@mcp-abap-adt/adt-strategies`
|
|
944
944
|
- **[Architecture](docs/architecture/ARCHITECTURE.md)** – package structure and design decisions
|
|
945
945
|
- **[Decisions](docs/architecture/DECISIONS.md)** – the choices that could have gone the other way, and why
|
|
946
946
|
- **[Operation Delays](docs/usage/OPERATION_DELAYS.md)** – configurable delays for SAP operations in tests
|
|
@@ -48,7 +48,7 @@ export declare class AdtPackage<R extends IPackageResults = typeof packageDocume
|
|
|
48
48
|
* package before it is ever locked.
|
|
49
49
|
*
|
|
50
50
|
* A package can be saved only once per ABAP session: PAK/058 — see
|
|
51
|
-
* docs/usage/
|
|
51
|
+
* docs/usage/ERRATA.md. Over RFC the create counts as that save.
|
|
52
52
|
*/
|
|
53
53
|
create<E extends IAdtError = IAdtError>(config: Omit<IPackageConfig, 'source'> & {
|
|
54
54
|
source?: never;
|
|
@@ -63,7 +63,7 @@ export declare class AdtPackage<R extends IPackageResults = typeof packageDocume
|
|
|
63
63
|
* Update the package's metadata.
|
|
64
64
|
*
|
|
65
65
|
* A package can be saved only once per ABAP session: PAK/058 — see
|
|
66
|
-
* docs/usage/
|
|
66
|
+
* docs/usage/ERRATA.md. Opening a new session is the caller's.
|
|
67
67
|
*
|
|
68
68
|
* **The whole content, every time.** This is a replace, never a merge. Read
|
|
69
69
|
* what the object holds, change what you mean to change, and pass the result:
|
|
@@ -82,7 +82,7 @@ export declare class AdtPackage<R extends IPackageResults = typeof packageDocume
|
|
|
82
82
|
* Delete the package.
|
|
83
83
|
*
|
|
84
84
|
* A package can be saved only once per ABAP session: PAK/058 — see
|
|
85
|
-
* docs/usage/
|
|
85
|
+
* docs/usage/ERRATA.md. A delete from a session that saved the package
|
|
86
86
|
* is refused; from a new session it succeeds.
|
|
87
87
|
*
|
|
88
88
|
* The answer is a 200 either way, and `isDeleted="false"` is in its body: a
|
|
@@ -86,7 +86,7 @@ class AdtPackage {
|
|
|
86
86
|
* package before it is ever locked.
|
|
87
87
|
*
|
|
88
88
|
* A package can be saved only once per ABAP session: PAK/058 — see
|
|
89
|
-
* docs/usage/
|
|
89
|
+
* docs/usage/ERRATA.md. Over RFC the create counts as that save.
|
|
90
90
|
*/
|
|
91
91
|
async create(config, options) {
|
|
92
92
|
// **No guard here, unlike every other create.**
|
|
@@ -134,7 +134,7 @@ class AdtPackage {
|
|
|
134
134
|
* Update the package's metadata.
|
|
135
135
|
*
|
|
136
136
|
* A package can be saved only once per ABAP session: PAK/058 — see
|
|
137
|
-
* docs/usage/
|
|
137
|
+
* docs/usage/ERRATA.md. Opening a new session is the caller's.
|
|
138
138
|
*
|
|
139
139
|
* **The whole content, every time.** This is a replace, never a merge. Read
|
|
140
140
|
* what the object holds, change what you mean to change, and pass the result:
|
|
@@ -189,7 +189,7 @@ class AdtPackage {
|
|
|
189
189
|
* Delete the package.
|
|
190
190
|
*
|
|
191
191
|
* A package can be saved only once per ABAP session: PAK/058 — see
|
|
192
|
-
* docs/usage/
|
|
192
|
+
* docs/usage/ERRATA.md. A delete from a session that saved the package
|
|
193
193
|
* is refused; from a new session it succeeds.
|
|
194
194
|
*
|
|
195
195
|
* The answer is a 200 either way, and `isDeleted="false"` is in its body: a
|
|
@@ -43,7 +43,7 @@ export type PackageValidationResult = string;
|
|
|
43
43
|
*
|
|
44
44
|
* A refusal arrives as **200** carrying `isDeleted="false"`, so the document,
|
|
45
45
|
* not the status, is the verdict. A package can be saved only once per ABAP
|
|
46
|
-
* session: PAK/058 — see docs/usage/
|
|
46
|
+
* session: PAK/058 — see docs/usage/ERRATA.md.
|
|
47
47
|
*/
|
|
48
48
|
export type PackageDeletionResult = string;
|
|
49
49
|
/**
|
package/docs/README.md
CHANGED
|
@@ -25,8 +25,7 @@ Complete documentation for the `@mcp-abap-adt/adt-clients` package.
|
|
|
25
25
|
- [**STATEFUL_SESSION_GUIDE.md**](usage/STATEFUL_SESSION_GUIDE.md) - Guide for stateful session management
|
|
26
26
|
- [**CHECK_LOCAL_TEST_CLASS.md**](usage/CHECK_LOCAL_TEST_CLASS.md) - Guide for validating ABAP Unit test classes
|
|
27
27
|
- [**DEBUG.md**](usage/DEBUG.md) - Debugging and troubleshooting guide
|
|
28
|
-
- [**
|
|
29
|
-
- [TROUBLESHOOTING.md](usage/TROUBLESHOOTING.md) — what ADT answers when it refuses, and why the message is often accurate about the wrong thing
|
|
28
|
+
- [**ERRATA.md**](usage/ERRATA.md) — what SAP answers that it does not mean: the object tree with each branch's cases, then every case with symptom, cause, rule, workaround and evidence
|
|
30
29
|
- [**OPERATION_DELAYS.md**](usage/OPERATION_DELAYS.md) - Detailed guide on operation delays and timing
|
|
31
30
|
- [**OPERATION_DELAYS_SUMMARY.md**](usage/OPERATION_DELAYS_SUMMARY.md) - Quick reference for operation delays
|
|
32
31
|
|
|
@@ -55,8 +54,7 @@ docs/
|
|
|
55
54
|
│ ├── OBJECT_LIFECYCLE.md # The create → … → activate flow and its exceptions
|
|
56
55
|
│ ├── CLIENT_API_REFERENCE.md # Client API reference
|
|
57
56
|
│ ├── STATEFUL_SESSION_GUIDE.md # Session management
|
|
58
|
-
│ ├──
|
|
59
|
-
│ ├── TROUBLESHOOTING.md # Refusals that name the wrong cause
|
|
57
|
+
│ ├── ERRATA.md # SAP ADT errata: the object tree, then every case
|
|
60
58
|
│ ├── CHECK_LOCAL_TEST_CLASS.md # Local test class validation
|
|
61
59
|
│ ├── DEBUG.md # Debugging guide
|
|
62
60
|
│ ├── OPERATION_DELAYS.md # Operation delays (detailed)
|
|
@@ -126,8 +126,7 @@ caller who asks for those shapes rather than the document.
|
|
|
126
126
|
- [`MIGRATION-23.md`](MIGRATION-23.md) — the readings and verdicts that moved
|
|
127
127
|
to `@mcp-abap-adt/adt-strategies` in 23.0.0
|
|
128
128
|
- [`MIGRATION-19.md`](MIGRATION-19.md) — what moved to the consumer in 19.0.0
|
|
129
|
-
- [`
|
|
130
|
-
these answers, and what a consumer does about it
|
|
131
|
-
|
|
132
|
-
the wrong thing, including the `S_ABPLNGVS` language-version refusal
|
|
129
|
+
- [`ERRATA.md`](ERRATA.md) — the SAP-side behaviour behind several of
|
|
130
|
+
these answers, by object, and what a consumer does about it — including the
|
|
131
|
+
`S_ABPLNGVS` language-version refusal
|
|
133
132
|
- [`../../examples`](../../examples) — the sequences, written out
|
|
@@ -1067,11 +1067,19 @@ Unpublishing is the same, against `…/unpublishjobs`.
|
|
|
1067
1067
|
it for you:
|
|
1068
1068
|
|
|
1069
1069
|
```typescript
|
|
1070
|
-
import {
|
|
1070
|
+
import {
|
|
1071
|
+
analysePublication,
|
|
1072
|
+
analysePublicationLock,
|
|
1073
|
+
} from '@mcp-abap-adt/adt-strategies';
|
|
1071
1074
|
|
|
1072
1075
|
const bindings = client.getServiceBinding();
|
|
1073
1076
|
|
|
1074
|
-
|
|
1077
|
+
// analysePublicationLock: a 403 (an editor holds the binding) is no failure —
|
|
1078
|
+
// the job does not need our lock, and the handle then comes back as ''.
|
|
1079
|
+
const locked = await bindings.lock(
|
|
1080
|
+
{ bindingName: 'ZAC_SRVB01' },
|
|
1081
|
+
{ analyse: analysePublicationLock },
|
|
1082
|
+
);
|
|
1075
1083
|
if (!locked.ok) throw new Error(locked.getError().message);
|
|
1076
1084
|
const lockHandle = locked.getResult().value;
|
|
1077
1085
|
|
|
@@ -1091,10 +1099,14 @@ try {
|
|
|
1091
1099
|
);
|
|
1092
1100
|
if (!answer.ok) throw new Error(answer.getError().message);
|
|
1093
1101
|
} finally {
|
|
1094
|
-
await bindings.unlock({ bindingName: 'ZAC_SRVB01' }, lockHandle);
|
|
1102
|
+
if (lockHandle) await bindings.unlock({ bindingName: 'ZAC_SRVB01' }, lockHandle);
|
|
1095
1103
|
}
|
|
1096
1104
|
```
|
|
1097
1105
|
|
|
1106
|
+
A binding just created has no active version, and publishing it answers `200`
|
|
1107
|
+
with *"Service Binding … does not exist"*: `activate()` it first. See
|
|
1108
|
+
[ERRATA.md](ERRATA.md#a-service-binding-is-locked-to-publish-it).
|
|
1109
|
+
|
|
1098
1110
|
Why the library does not do that for you: **how long a lock is held is a
|
|
1099
1111
|
policy**, and it is not one policy. Eclipse holds a binding's lock for as long
|
|
1100
1112
|
as an editor is open — across several publishes, if that is what the person
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# SAP ADT errata
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Known behaviour of the SAP system and its ADT endpoints that a consumer has to
|
|
4
|
+
work around — errata in the sense a platform vendor publishes them: the defect
|
|
5
|
+
is the platform's, each entry says how to live with it. Each case is **surprising, not a defect of this
|
|
5
6
|
library**, and has a **workaround on the consumer side**. The knowledge used to
|
|
6
7
|
be scattered across guides and code comments; it lives here now, and the other
|
|
7
8
|
documents link to it.
|
|
@@ -26,9 +27,194 @@ a second session, retry, poll or judge an answer on your behalf. Where a
|
|
|
26
27
|
workaround needs a reading, `@mcp-abap-adt/adt-strategies` usually has one, and
|
|
27
28
|
each entry names it.
|
|
28
29
|
|
|
30
|
+
Start with **[the object tree](#the-object-tree)**: find the object you were
|
|
31
|
+
working on, and its branch says what SAP answers for it that it does not answer
|
|
32
|
+
for the others. The numbered entries under [Contents](#contents) carry the
|
|
33
|
+
evidence, and some of them apply to every object.
|
|
34
|
+
|
|
29
35
|
Every entry has the same sections: **Symptom** (what the caller sees),
|
|
30
36
|
**Cause**, **Rule**, **Workaround**, **Evidence** and **Where it bites**.
|
|
31
37
|
|
|
38
|
+
## The object tree
|
|
39
|
+
|
|
40
|
+
The objects this library addresses, as they hang together: a child lives in
|
|
41
|
+
its parent or is built on it. A **⚠** marks a node where SAP answers something
|
|
42
|
+
it does not answer for the others; follow the link. A node without one has shown
|
|
43
|
+
nothing beyond the entries that apply to every type — see [Contents](#contents).
|
|
44
|
+
|
|
45
|
+
- [Transport request](#transport-request) `getRequest()` ⚠
|
|
46
|
+
- [Package](#package) `getPackage()` ⚠
|
|
47
|
+
- Source code
|
|
48
|
+
- [Class](#class) `getClass()` ⚠
|
|
49
|
+
- [local test class, local types, local definitions, local macros](#class-includes) `getLocalTestClass()` `getLocalTypes()` `getLocalDefinitions()` `getLocalMacros()` ⚠
|
|
50
|
+
- ABAP Unit run `getUnitTest()`
|
|
51
|
+
- Interface `getInterface()`
|
|
52
|
+
- Program `getProgram()`
|
|
53
|
+
- Include `getInclude()`
|
|
54
|
+
- [Function group](#function-group) `getFunctionGroup()` ⚠
|
|
55
|
+
- [Function module](#function-module) `getFunctionModule()` ⚠
|
|
56
|
+
- Function include `getFunctionInclude()`
|
|
57
|
+
- Transformation `getTransformation()`
|
|
58
|
+
- Enhancement `getEnhancement()`
|
|
59
|
+
- Dictionary
|
|
60
|
+
- [Domain](#domain-and-data-element) `getDomain()` ⚠
|
|
61
|
+
- [Data element](#domain-and-data-element) `getDataElement()` ⚠
|
|
62
|
+
- Structure `getStructure()`
|
|
63
|
+
- Table `getTable()`
|
|
64
|
+
- Append structure `getAppendStructure()`
|
|
65
|
+
- [Table type](#table-type) `getTableType()` ⚠
|
|
66
|
+
- Authorization field `getAuthorizationField()`
|
|
67
|
+
- CDS and RAP
|
|
68
|
+
- [DDL source](#ddl-source) `getDdl()` ⚠
|
|
69
|
+
- Access control `getAccessControl()`
|
|
70
|
+
- Metadata extension `getMetadataExtension()`
|
|
71
|
+
- CDS unit test `getCdsUnitTest()`
|
|
72
|
+
- Behavior definition `getBehaviorDefinition()`
|
|
73
|
+
- Behavior implementation `getBehaviorImplementation()`
|
|
74
|
+
- Scalar function `getScalarFunction()`
|
|
75
|
+
- Scalar function implementation `getScalarFunctionImplementation()`
|
|
76
|
+
- Services
|
|
77
|
+
- [Service definition](#service-definition) `getServiceDefinition()` ⚠
|
|
78
|
+
- [Service binding](#service-binding) `getServiceBinding()` ⚠
|
|
79
|
+
- Other
|
|
80
|
+
- Message class `getMessageClass()`
|
|
81
|
+
- [Message](#message) `getMessageClassMessage()` ⚠
|
|
82
|
+
- Feature toggle `getFeatureToggle()`
|
|
83
|
+
- Runtime (`AdtRuntimeClient`)
|
|
84
|
+
- [ATC](#atc) `getAtc()` ⚠
|
|
85
|
+
|
|
86
|
+
## By object
|
|
87
|
+
|
|
88
|
+
In the order of the tree.
|
|
89
|
+
|
|
90
|
+
### Transport request
|
|
91
|
+
|
|
92
|
+
`getRequest()`. A list by user or status answers an empty `<tm:root/>` — the
|
|
93
|
+
list is a search over a saved configuration; the tree under a request has no
|
|
94
|
+
fixed nesting; a task made by hand refuses objects until it is typed. See
|
|
95
|
+
ERRATA on [the transport list](#the-transport-list-is-a-saved-configuration-search),
|
|
96
|
+
[the tree](#the-transport-tree-has-no-fixed-nesting) and
|
|
97
|
+
[hand-made tasks](#a-hand-made-task-is-unclassified-and-refuses-objects).
|
|
98
|
+
|
|
99
|
+
### Package
|
|
100
|
+
|
|
101
|
+
`getPackage()`.
|
|
102
|
+
|
|
103
|
+
- **"already locked" (PAK/058) on a second save in one ABAP session** — not an
|
|
104
|
+
enqueue lock. See
|
|
105
|
+
[below](#a-package-can-be-saved-only-once-per-abap-session).
|
|
106
|
+
- **A metadata read too soon after a write answers `200` with no body** — as for
|
|
107
|
+
the other document types, see [Domain and data element](#domain-and-data-element).
|
|
108
|
+
|
|
109
|
+
### Class
|
|
110
|
+
|
|
111
|
+
`getClass()`.
|
|
112
|
+
|
|
113
|
+
- **`400` "wrong input data for processing" on every read** of a class created
|
|
114
|
+
and not yet written — see
|
|
115
|
+
[below](#what-a-bare-create-leaves-depends-on-the-type).
|
|
116
|
+
- **"Class … does not have a TMDIR entry" on an activation** — the class does
|
|
117
|
+
not exist; see [below](#activationexecuted-false-is-not-a-failure).
|
|
118
|
+
|
|
119
|
+
#### Class includes
|
|
120
|
+
|
|
121
|
+
`getLocalTestClass()`, `getLocalTypes()`, `getLocalDefinitions()`,
|
|
122
|
+
`getLocalMacros()`. **Written under the class's lock**, not one of their own:
|
|
123
|
+
lock the class. See
|
|
124
|
+
[below](#a-class-include-is-written-under-the-class-lock).
|
|
125
|
+
|
|
126
|
+
### Function group
|
|
127
|
+
|
|
128
|
+
`getFunctionGroup()`.
|
|
129
|
+
|
|
130
|
+
- **`validate()` answers `200` for a taken name**, with the verdict in the body —
|
|
131
|
+
see [below](#a-validation-answers-a-taken-name-inside-a-200).
|
|
132
|
+
- **A metadata read too soon after a write answers `200` with no body** — see
|
|
133
|
+
[Domain and data element](#domain-and-data-element).
|
|
134
|
+
|
|
135
|
+
#### Function module
|
|
136
|
+
|
|
137
|
+
`getFunctionModule()`. **`500` "An exception was raised" on a source read** of a
|
|
138
|
+
module that does not exist; only the long text says *"does not exist"* (FL651).
|
|
139
|
+
Ask existence of `readMetadata()`, which answers `404`. See
|
|
140
|
+
[below](#a-function-modules-source-answers-500-for-a-module-that-does-not-exist).
|
|
141
|
+
|
|
142
|
+
### Domain and data element
|
|
143
|
+
|
|
144
|
+
`getDomain()`, `getDataElement()` — and the other document types:
|
|
145
|
+
`getPackage()`, `getTableType()`, `getFunctionGroup()`. **A metadata read too
|
|
146
|
+
soon after a write answers `200` with no body.** Before a read-modify-write,
|
|
147
|
+
reject an empty document rather than patching it. See
|
|
148
|
+
[below](#a-read-answers-200-with-an-empty-body-instead-of-404).
|
|
149
|
+
|
|
150
|
+
### Table type
|
|
151
|
+
|
|
152
|
+
`getTableType()`. A document type — see
|
|
153
|
+
[Domain and data element](#domain-and-data-element).
|
|
154
|
+
|
|
155
|
+
### DDL source
|
|
156
|
+
|
|
157
|
+
`getDdl()`. **`validate()` answers `200` for a taken name**, with the verdict in
|
|
158
|
+
the body — see [below](#a-validation-answers-a-taken-name-inside-a-200).
|
|
159
|
+
|
|
160
|
+
### Service definition
|
|
161
|
+
|
|
162
|
+
`getServiceDefinition()`.
|
|
163
|
+
|
|
164
|
+
- **A create is a POST that leaves the object empty**: its source reads `200`
|
|
165
|
+
with no body until you write it. See
|
|
166
|
+
[below](#what-a-bare-create-leaves-depends-on-the-type).
|
|
167
|
+
- **`400` "Check of condition failed" on a create** means the request carried an
|
|
168
|
+
empty responsible person — a client built without
|
|
169
|
+
`IAdtClientOptions.responsible`. A create of a name that exists answers
|
|
170
|
+
`ExceptionResourceAlreadyExists` instead. See
|
|
171
|
+
[below](#an-empty-responsible-person-is-refused-as-check-of-condition-failed).
|
|
172
|
+
|
|
173
|
+
#### Service binding
|
|
174
|
+
|
|
175
|
+
`getServiceBinding()`. Built on a service definition; it is not edited, only
|
|
176
|
+
activated, published and unpublished.
|
|
177
|
+
|
|
178
|
+
- **`403` on the LOCK before a publish or unpublish — ignore it.** It means an
|
|
179
|
+
editing session holds the binding: an open Eclipse editor keeps its lock after
|
|
180
|
+
a publication, until the editor closes. The publication job does not need
|
|
181
|
+
your lock, and Eclipse itself posts the job after its own LOCK's `403`. Pass
|
|
182
|
+
`analysePublicationLock` (adt-strategies) to `lock()`: the `403` then answers a
|
|
183
|
+
lock without a handle (`''`), you publish, and you send no UNLOCK. Every other
|
|
184
|
+
refusal stays one. If the `403` should stop you, pass `analyseException`
|
|
185
|
+
instead. See
|
|
186
|
+
[below](#a-service-binding-is-locked-to-publish-it).
|
|
187
|
+
- **"Service Binding … does not exist" on a publish** — inside a `200` — means
|
|
188
|
+
the binding is not active yet; a binding just created has only an inactive
|
|
189
|
+
version. `activate()` it, then publish. See
|
|
190
|
+
[below](#a-binding-publishes-only-once-it-is-active).
|
|
191
|
+
- **"Error while creating service interface <BINDING>_0001_G4BA" on an
|
|
192
|
+
unpublish**, within a second, means the unpublish came too soon after a
|
|
193
|
+
publish. Repeat it minutes later; do not change the request. See
|
|
194
|
+
[below](#an-unpublish-straight-after-a-publish-is-refused).
|
|
195
|
+
- **A publication takes minutes.** About 133 s on an idle system, longer on a
|
|
196
|
+
loaded one — pass a `timeout` above the 120 s default and wait for the job's
|
|
197
|
+
own answer (`analysePublication`); nothing needs polling.
|
|
198
|
+
- **"You are already editing <BINDING>" on a delete** — the lock an editor holds.
|
|
199
|
+
Close the editor that holds it.
|
|
200
|
+
|
|
201
|
+
### Message
|
|
202
|
+
|
|
203
|
+
`getMessageClassMessage()`. **`OBJECT_NOT_FOUND` for a message** is this
|
|
204
|
+
library reading the class document, not SAP reporting absence. A message class
|
|
205
|
+
is a container and its messages are rows in it: only the container has existence
|
|
206
|
+
on the wire — 404 before it is created, 404 after it is deleted — while a row has
|
|
207
|
+
none. Measured, `POST …/messages/001?_action=LOCK_MSG` answers `200` before
|
|
208
|
+
message 001 exists, because the PUT after it is what creates it. So the member
|
|
209
|
+
fetches the class document and looks for the number itself; a consumer who
|
|
210
|
+
replaces the reading replaces that verdict with it.
|
|
211
|
+
|
|
212
|
+
### ATC
|
|
213
|
+
|
|
214
|
+
`getAtc()` (runtime). **The check variant comes from customizing**, not from
|
|
215
|
+
the run request. See
|
|
216
|
+
[below](#atc-takes-its-check-variant-from-customizing).
|
|
217
|
+
|
|
32
218
|
## Contents
|
|
33
219
|
|
|
34
220
|
Sessions and locks
|
|
@@ -37,33 +223,41 @@ Sessions and locks
|
|
|
37
223
|
2. [The session-type header is per request](#the-session-type-header-is-per-request)
|
|
38
224
|
3. [A class include is written under the class lock](#a-class-include-is-written-under-the-class-lock)
|
|
39
225
|
4. [A service binding is locked to publish it](#a-service-binding-is-locked-to-publish-it)
|
|
226
|
+
5. [A binding publishes only once it is active](#a-binding-publishes-only-once-it-is-active)
|
|
227
|
+
6. [An unpublish straight after a publish is refused](#an-unpublish-straight-after-a-publish-is-refused)
|
|
40
228
|
|
|
41
229
|
Reading answers
|
|
42
230
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
231
|
+
7. [A refusal can arrive with a 2xx, and an error status names the fix](#a-refusal-can-arrive-with-a-2xx-and-an-error-status-names-the-fix)
|
|
232
|
+
8. [A read answers 200 with an empty body instead of 404](#a-read-answers-200-with-an-empty-body-instead-of-404)
|
|
233
|
+
9. [A successful delete can carry an untyped message](#a-successful-delete-can-carry-an-untyped-message)
|
|
234
|
+
10. [A function module's source answers 500 for a module that does not exist](#a-function-modules-source-answers-500-for-a-module-that-does-not-exist)
|
|
235
|
+
11. [A validation answers a taken name inside a 200](#a-validation-answers-a-taken-name-inside-a-200)
|
|
236
|
+
12. [A deletion check that says no is not a failure](#a-deletion-check-that-says-no-is-not-a-failure)
|
|
237
|
+
13. ["No URI-Mapping defined for URI" inside a 200](#no-uri-mapping-defined-for-uri-inside-a-200)
|
|
238
|
+
14. [S_ABPLNGVS refuses a create into a package that does not exist](#s_abplngvs-refuses-a-create-into-a-package-that-does-not-exist)
|
|
47
239
|
|
|
48
240
|
Creating and checking objects
|
|
49
241
|
|
|
50
|
-
|
|
51
|
-
|
|
242
|
+
15. [What a bare create leaves depends on the type](#what-a-bare-create-leaves-depends-on-the-type)
|
|
243
|
+
16. [An empty responsible person is refused as "Check of condition failed"](#an-empty-responsible-person-is-refused-as-check-of-condition-failed)
|
|
244
|
+
17. [An object created without a package cannot be deleted](#an-object-created-without-a-package-cannot-be-deleted)
|
|
245
|
+
18. [A check run compiles source for objects that do not exist](#a-check-run-compiles-source-for-objects-that-do-not-exist)
|
|
52
246
|
|
|
53
247
|
Activation
|
|
54
248
|
|
|
55
|
-
|
|
56
|
-
|
|
249
|
+
19. [activationExecuted false is not a failure](#activationexecuted-false-is-not-a-failure)
|
|
250
|
+
20. [Activation settles inside the POST](#activation-settles-inside-the-post)
|
|
57
251
|
|
|
58
252
|
Transports
|
|
59
253
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
254
|
+
21. [The transport list is a saved-configuration search](#the-transport-list-is-a-saved-configuration-search)
|
|
255
|
+
22. [The transport tree has no fixed nesting](#the-transport-tree-has-no-fixed-nesting)
|
|
256
|
+
23. [A hand-made task is Unclassified and refuses objects](#a-hand-made-task-is-unclassified-and-refuses-objects)
|
|
63
257
|
|
|
64
258
|
ATC
|
|
65
259
|
|
|
66
|
-
|
|
260
|
+
24. [ATC takes its check variant from customizing](#atc-takes-its-check-variant-from-customizing)
|
|
67
261
|
|
|
68
262
|
---
|
|
69
263
|
|
|
@@ -300,11 +494,16 @@ publish job finishes. An open Eclipse editor on the binding holds the lock for
|
|
|
300
494
|
the same user from a different session.
|
|
301
495
|
|
|
302
496
|
**Rule.** The lock spans an editing session. From elsewhere, a held lock looks
|
|
303
|
-
like a 403; it is usually someone's open Eclipse, not a leaked lock.
|
|
497
|
+
like a 403; it is usually someone's open Eclipse, not a leaked lock. Eclipse
|
|
498
|
+
itself meets a `403` on its own LOCK when its editor already holds the binding,
|
|
499
|
+
and posts the job anyway: the job does not need the caller's lock.
|
|
304
500
|
|
|
305
501
|
**Workaround.** For a library there is no editor, so the edit is the operation:
|
|
306
502
|
`lock()`, `update()` with the desired publication state, `unlock()` in a
|
|
307
|
-
`finally`.
|
|
503
|
+
`finally`. Pass `analysePublicationLock` to the `lock()`: a `403` then answers
|
|
504
|
+
a lock without a handle (`''`), the publication goes ahead, and there is
|
|
505
|
+
nothing to unlock. A caller who wants the `403` to stop them passes
|
|
506
|
+
`analyseException` instead. The publish job took about 133 seconds on the systems measured,
|
|
308
507
|
above the 120 s default — pass a larger `timeout`. Read the job's own answer
|
|
309
508
|
(`analysePublication` from `@mcp-abap-adt/adt-strategies` reads its
|
|
310
509
|
`SEVERITY`); nothing needs polling. For a binding locked elsewhere, close the
|
|
@@ -315,12 +514,74 @@ Eclipse editor that holds it. The full example is in
|
|
|
315
514
|
`LOCK` → `200` on session *"stateful, enqueue"*; `POST
|
|
316
515
|
…/odatav4/publishjobs` → `200` stateless, 133 057 ms; `UNLOCK` → `200` on
|
|
317
516
|
*"Closing editor"*; a second `LOCK` before the unpublish → `403` while the
|
|
318
|
-
first was held.
|
|
517
|
+
first was held. Eclipse, cloud system, 2026-09-27: after a publish its editor
|
|
518
|
+
kept the lock (a LOCK from another session → `403`); the unpublish that followed
|
|
519
|
+
sent its own `LOCK` → `403` on the enqueue session and then `POST
|
|
520
|
+
…/unpublishjobs` → `200`, 132 s. Between the two, the enqueue session was kept
|
|
521
|
+
alive with `GET /sap/bc/adt/core/http/sessions`.
|
|
319
522
|
|
|
320
523
|
**Where it bites.** `getServiceBinding()`: `lock`, `update`, `delete`.
|
|
321
524
|
|
|
322
525
|
---
|
|
323
526
|
|
|
527
|
+
## A binding publishes only once it is active
|
|
528
|
+
|
|
529
|
+
**Symptom.** Publishing a binding straight after creating it answers `200`
|
|
530
|
+
with `<SEVERITY>ERROR</SEVERITY>`: *"Local Publish of <SERVICE_BINDING>
|
|
531
|
+
failed"*, long text *"Service Binding <SERVICE_BINDING> does not exist."* The
|
|
532
|
+
binding is there; its metadata reads back.
|
|
533
|
+
|
|
534
|
+
**Cause.** A created binding has only an inactive version, and the publication
|
|
535
|
+
looks for the active one.
|
|
536
|
+
|
|
537
|
+
**Rule.** "Does not exist" here means "not active". Taking the lock first does
|
|
538
|
+
not change it, and neither does reading the service's information
|
|
539
|
+
(`generateServiceBinding`, a `GET` on `/businessservices/odatav4/<binding>` that
|
|
540
|
+
Eclipse sends to refresh its view, after a publication).
|
|
541
|
+
|
|
542
|
+
**Workaround.** `activate()` the binding, then publish it.
|
|
543
|
+
|
|
544
|
+
**Evidence.** Cloud system, 2026-09-27, one binding deleted and created again:
|
|
545
|
+
publish after the create → the refusal above; lock, then publish → the same;
|
|
546
|
+
the information `GET`, then publish → the same; activate, then publish →
|
|
547
|
+
*"<SERVICE_BINDING> published locally"*, 134 s.
|
|
548
|
+
|
|
549
|
+
**Where it bites.** `getServiceBinding().create()` followed by `update()` with
|
|
550
|
+
`desiredPublicationState: 'published'`.
|
|
551
|
+
|
|
552
|
+
---
|
|
553
|
+
|
|
554
|
+
## An unpublish straight after a publish is refused
|
|
555
|
+
|
|
556
|
+
**Symptom.** An unpublish answers `200` with `<SEVERITY>ERROR</SEVERITY>`
|
|
557
|
+
within a second: *"Local un-publish of <SERVICE_BINDING> failed"*, long text
|
|
558
|
+
*"Error while creating service interface <SERVICE_BINDING>_0001_G4BA"*.
|
|
559
|
+
|
|
560
|
+
**Cause.** The publication job answers before the system has finished with
|
|
561
|
+
the binding; an unpublish in that window fails. The request is the same one
|
|
562
|
+
that succeeds later.
|
|
563
|
+
|
|
564
|
+
**Rule.** A fast `ERROR` from an unpublish is not a verdict on the binding. The
|
|
565
|
+
job itself takes minutes when it runs; an answer in under a second means it did
|
|
566
|
+
not run.
|
|
567
|
+
|
|
568
|
+
**Workaround.** Leave time between a publish and an unpublish of the same
|
|
569
|
+
binding — minutes, not seconds — and repeat an unpublish that failed this way
|
|
570
|
+
later rather than changing the request.
|
|
571
|
+
|
|
572
|
+
**Evidence.** Cloud system, 2026-09-27, one binding and the same request each
|
|
573
|
+
time: publish → *"published locally"*, 133 s; unpublish straight after → the
|
|
574
|
+
refusal above, 0.6 s; unpublish seven minutes later → *"un-published
|
|
575
|
+
locally"*, 134 s. An unpublish from Eclipse several minutes after its publish
|
|
576
|
+
took 132 s and succeeded. How long the window is, was not measured more
|
|
577
|
+
closely.
|
|
578
|
+
|
|
579
|
+
**Where it bites.** `getServiceBinding().update()` with `desiredPublicationState:
|
|
580
|
+
'unpublished'` soon after a publication — a test that publishes and unpublishes
|
|
581
|
+
in one run.
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
324
585
|
## A refusal can arrive with a 2xx, and an error status names the fix
|
|
325
586
|
|
|
326
587
|
**Symptom.** A deletion check, an activation or a validation answers **`200`**
|
|
@@ -448,6 +709,117 @@ CDS sources; any deletion reading.
|
|
|
448
709
|
|
|
449
710
|
---
|
|
450
711
|
|
|
712
|
+
## A function module's source answers 500 for a module that does not exist
|
|
713
|
+
|
|
714
|
+
**Symptom.** A read of a function module's source answers **`500`**, *"An
|
|
715
|
+
exception was raised"* (`SY/530`), for a module that is simply not there. The
|
|
716
|
+
same question asked of the module's metadata answers `404`.
|
|
717
|
+
|
|
718
|
+
```
|
|
719
|
+
GET …/fmodules/<FUNCTION_MODULE>/source/main → 500 <type id="FUNCTION"/>
|
|
720
|
+
"An exception was raised"; LONGTEXT: "Function module <FUNCTION_MODULE>
|
|
721
|
+
does not exist" (FL651)
|
|
722
|
+
GET …/fmodules/<FUNCTION_MODULE> → 404 ExceptionResourceNotFound
|
|
723
|
+
"Function module <FUNCTION_MODULE> does not exist" (FL110)
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
**Cause.** The source resource raises the function builder's own exception
|
|
727
|
+
instead of answering absence; the reason travels only in the long text.
|
|
728
|
+
|
|
729
|
+
**Rule.** The status is the least of the answer — the text says "does not
|
|
730
|
+
exist", even under a `500`. And a source is the wrong place to ask about
|
|
731
|
+
existence at all: this one answers `500`, and a source that was never written
|
|
732
|
+
answers `200` and no body
|
|
733
|
+
([above](#a-read-answers-200-with-an-empty-body-instead-of-404)). Ask the
|
|
734
|
+
object's own URI.
|
|
735
|
+
|
|
736
|
+
**Workaround.** Decide existence with `readMetadata()`: a document means the
|
|
737
|
+
object is there, `ExceptionResourceNotFound` or "does not exist" means it is
|
|
738
|
+
not, and only an answer without a text is judged by its status. Where a source
|
|
739
|
+
read has already failed, read the document in `response.data` — not the
|
|
740
|
+
transport's "Request failed with status code 500".
|
|
741
|
+
|
|
742
|
+
**Evidence.** Cloud system, 2026-09-27: both reads above, for a module in a new
|
|
743
|
+
function group and for one in a long-active group; an existing module's source
|
|
744
|
+
answered `200` with its code.
|
|
745
|
+
|
|
746
|
+
**Where it bites.** `getFunctionModule().read()` used as an existence check.
|
|
747
|
+
|
|
748
|
+
---
|
|
749
|
+
|
|
750
|
+
## A validation answers a taken name inside a 200
|
|
751
|
+
|
|
752
|
+
**Symptom.** `validate()` answers `200` for a name that is taken, and the create
|
|
753
|
+
that follows is refused because the name exists.
|
|
754
|
+
|
|
755
|
+
**Cause.** A validation refuses a taken name two different ways. A domain, a
|
|
756
|
+
structure, a table, a class and a service definition answer a failing status. A
|
|
757
|
+
**function group and a DDL source answer `200`** with the verdict in the body:
|
|
758
|
+
|
|
759
|
+
```xml
|
|
760
|
+
<SEVERITY>ERROR</SEVERITY>
|
|
761
|
+
<SHORT_TEXT>Data definition <NAME> already exists</SHORT_TEXT>
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
**Rule.** A `validate()` that returns `200` has not told you the name is free —
|
|
765
|
+
the body has. And `validate()` never answers whether the object exists: a free
|
|
766
|
+
name validates fine either way, and a name is held from the POST onward whatever
|
|
767
|
+
state the object is in.
|
|
768
|
+
|
|
769
|
+
**Workaround.** Pass `analyseValidation` (adt-strategies), which reads both
|
|
770
|
+
forms, or read `<SEVERITY>` yourself.
|
|
771
|
+
|
|
772
|
+
**Evidence.** The recorded answers `refusal-validation-name-taken-ddl`,
|
|
773
|
+
`-functiongroup`, `-class`, `-domain` and `-table` in the corpus.
|
|
774
|
+
|
|
775
|
+
**Where it bites.** `getDdl().validate()`, `getFunctionGroup().validate()`.
|
|
776
|
+
|
|
777
|
+
---
|
|
778
|
+
|
|
779
|
+
## A deletion check that says no is not a failure
|
|
780
|
+
|
|
781
|
+
**Symptom.** `checkDeletion()` answers `200` with `del:isDeletable="false"` and a
|
|
782
|
+
reason.
|
|
783
|
+
|
|
784
|
+
**Cause.** That is the answer the check exists to produce.
|
|
785
|
+
|
|
786
|
+
**Rule.** A *check* that says "no" has answered. A *delete* that reports
|
|
787
|
+
`del:isDeleted="false"` is a different thing: the objects are still on the
|
|
788
|
+
system, and that is a failure.
|
|
789
|
+
|
|
790
|
+
**Workaround.** Read the check's verdict with `analyseDeletion`; treat a refused
|
|
791
|
+
delete as a failure, a refusing check as information.
|
|
792
|
+
|
|
793
|
+
**Evidence.** The recorded answers `refusal-deletion-check-refuses` and
|
|
794
|
+
`refusal-delete-refused` in the corpus.
|
|
795
|
+
|
|
796
|
+
**Where it bites.** `checkDeletion()` and `delete()` on every type;
|
|
797
|
+
`getUtils()` group deletion.
|
|
798
|
+
|
|
799
|
+
---
|
|
800
|
+
|
|
801
|
+
## "No URI-Mapping defined for URI" inside a 200
|
|
802
|
+
|
|
803
|
+
**Symptom.** A group operation — deletion check, delete, activation — does
|
|
804
|
+
nothing and reports nothing; the body of its `200` says *"No URI-Mapping defined
|
|
805
|
+
for URI"*.
|
|
806
|
+
|
|
807
|
+
**Cause.** The address named in the request does not exist on the server. It was
|
|
808
|
+
seen when an object URI was built by lowercasing the type code — `DEVC/K` became
|
|
809
|
+
`/sap/bc/adt/devc/k/<NAME>` — where the real resource is
|
|
810
|
+
`/sap/bc/adt/packages/<NAME>`.
|
|
811
|
+
|
|
812
|
+
**Rule.** A success that did nothing has its complaint in the body.
|
|
813
|
+
|
|
814
|
+
**Workaround.** Take an object's URI from the system's own answers (search,
|
|
815
|
+
node structure), not from its type code.
|
|
816
|
+
|
|
817
|
+
**Evidence.** The package URI built from `DEVC/K`, answered as above.
|
|
818
|
+
|
|
819
|
+
**Where it bites.** `getUtils()` group operations over hand-built references.
|
|
820
|
+
|
|
821
|
+
---
|
|
822
|
+
|
|
451
823
|
## S_ABPLNGVS refuses a create into a package that does not exist
|
|
452
824
|
|
|
453
825
|
**Symptom.** A create answers `403 ExceptionResourceNoAccess`, *"You are not
|
|
@@ -467,8 +839,53 @@ scenario `ABAP_LANGUAGE_VERSION` is switched on.
|
|
|
467
839
|
**Workaround.** Before going to PFCG, check that the package in the request
|
|
468
840
|
exists and that its software component allows the language version you are
|
|
469
841
|
writing; then that the user holds that value in `ABP_LNG_VS` and the `ACTVT` for
|
|
470
|
-
the step that failed.
|
|
471
|
-
|
|
842
|
+
the step that failed.
|
|
843
|
+
|
|
844
|
+
**What the object is.** `S_ABPLNGVS` is not a role — it is an authorization
|
|
845
|
+
object for the ABAP **language version**:
|
|
846
|
+
|
|
847
|
+
| field | meaning |
|
|
848
|
+
|---|---|
|
|
849
|
+
| `ABP_LNG_VS` | the language version — *ABAP for Cloud Development*, *Standard ABAP* |
|
|
850
|
+
| `ACTVT` | the permitted operations — create, change, activate, execute, ABAP Unit |
|
|
851
|
+
|
|
852
|
+
The template role `SAP_BC_ABAP_DEVELOPER_5` carries it restricted to *ABAP for
|
|
853
|
+
Cloud Development*. Allowing *Standard ABAP* as well means adding that value in
|
|
854
|
+
PFCG.
|
|
855
|
+
|
|
856
|
+
**It may be inert.** The check applies only when the SACF authorization scenario
|
|
857
|
+
`ABAP_LANGUAGE_VERSION` is switched on. Otherwise the object sitting in a role
|
|
858
|
+
restricts nothing, so finding it in a role proves nothing about behaviour.
|
|
859
|
+
|
|
860
|
+
**It is not `S_DEVELOP`.** `S_DEVELOP` governs access to development objects at
|
|
861
|
+
all; `S_ABPLNGVS` additionally governs *which language version* they may be
|
|
862
|
+
created, changed and run in.
|
|
863
|
+
|
|
864
|
+
**What "access to a language version" actually grants.**
|
|
865
|
+
|
|
866
|
+
The object is checked as a pair, and both halves have to match the request:
|
|
867
|
+
|
|
868
|
+
- **`ABP_LNG_VS` — which language.** An object is written in one ABAP language
|
|
869
|
+
version, fixed by the software component its package belongs to. Writing into a
|
|
870
|
+
package whose component is *ABAP for Cloud Development* requires that value;
|
|
871
|
+
writing a Standard ABAP object requires the Standard ABAP value. Holding one
|
|
872
|
+
does not grant the other, and this is the usual cause on BTP ABAP Environment,
|
|
873
|
+
where a developer is typically granted the cloud value only.
|
|
874
|
+
- **`ACTVT` — which operation, per language.** Access is granted per activity:
|
|
875
|
+
create, change, activate, execute, run ABAP Unit. A user can hold *change* for
|
|
876
|
+
a language version and not *activate* it, so a chain can write successfully and
|
|
877
|
+
then fail at the activation step with the same object named.
|
|
878
|
+
|
|
879
|
+
That second half is what makes the failure look intermittent: the same user, the
|
|
880
|
+
same package, refused at one step of a create chain and not at the earlier ones.
|
|
881
|
+
Read which step the failure carries — `IAdtError.request` names it — before
|
|
882
|
+
concluding the whole language version is denied.
|
|
883
|
+
|
|
884
|
+
**What to check, in order.** Whether the package in the request exists; which
|
|
885
|
+
language version its software component fixes; whether the user holds that value
|
|
886
|
+
in `ABP_LNG_VS`; and whether they hold the specific `ACTVT` for the operation
|
|
887
|
+
that failed rather than for the one that succeeded.
|
|
888
|
+
|
|
472
889
|
|
|
473
890
|
**Evidence.** Cloud trial, 2026-09-03: a class create into a package that did
|
|
474
891
|
not exist answered this refusal. The meaning of the object is SAP's
|
|
@@ -485,8 +902,7 @@ mistyped `packageName`.
|
|
|
485
902
|
`400 ExceptionResourceWrongData`, `SADT_RESOURCE/007`, *"Resource <CLASS>: wrong
|
|
486
903
|
input data for processing"* (note the double space) — for `active`, `inactive`,
|
|
487
904
|
metadata and source alike. Waiting 30 seconds does not help, and neither does
|
|
488
|
-
lock + unlock. Other types answer differently
|
|
489
|
-
create is refused outright with *"Check of condition failed"*.
|
|
905
|
+
lock + unlock. Other types answer differently.
|
|
490
906
|
|
|
491
907
|
**Cause.** What the POST leaves is a property of the type:
|
|
492
908
|
|
|
@@ -496,7 +912,7 @@ create is refused outright with *"Check of condition failed"*.
|
|
|
496
912
|
| `interface` | generated skeleton | 53 bytes |
|
|
497
913
|
| `class` | generated skeleton | **refused** `400 ExceptionResourceWrongData` |
|
|
498
914
|
| `ddl` | object, no content | `200`, empty |
|
|
499
|
-
| `serviceDefinition` |
|
|
915
|
+
| `serviceDefinition` | object, no content | `200`, empty source |
|
|
500
916
|
|
|
501
917
|
The class does have a skeleton; it becomes readable at the first source write
|
|
502
918
|
(`version=active` → SAP's `class <CLASS> definition`, `version=inactive` → what
|
|
@@ -509,16 +925,23 @@ the unreadable class), and `validate()` answers neither "name is free" nor
|
|
|
509
925
|
"object exists" — the name is held from the POST onward regardless. Absence has
|
|
510
926
|
three wordings (*"Error while importing object … from the database"*,
|
|
511
927
|
*"Resource INTERFACE … does not exist."*, *"Data definition … of version does
|
|
512
|
-
not exist"*); do not match on
|
|
928
|
+
not exist"*); do not match on one wording. The metadata read marks absence
|
|
929
|
+
with `<type id="ExceptionResourceNotFound"/>` whatever the sentence; a function
|
|
930
|
+
module's source is the exception, where only the long text says it
|
|
931
|
+
([below](#a-function-modules-source-answers-500-for-a-module-that-does-not-exist)).
|
|
513
932
|
|
|
514
933
|
**Workaround.** After creating a class, write its source; do not retry the read.
|
|
515
934
|
The first check worth making is after the write: read the version you wrote and
|
|
516
|
-
compare. A service definition is created
|
|
517
|
-
|
|
935
|
+
compare. A service definition is created empty; write its source before
|
|
936
|
+
activating it. If a sequence stops between the POST and the write, `delete()` the object
|
|
518
937
|
while it is still bound to its package.
|
|
519
938
|
|
|
520
939
|
**Evidence.** Cloud trial, 2026-09-05, `scripts/probe-unfinished-create.ts` and
|
|
521
|
-
`scripts/probe-inactive-metadata.ts` (the table above).
|
|
940
|
+
`scripts/probe-inactive-metadata.ts` (the table above). The service definition
|
|
941
|
+
row was corrected on 2026-09-27: that probe's client carried no responsible
|
|
942
|
+
person, and its "refused" was
|
|
943
|
+
[that refusal](#an-empty-responsible-person-is-refused-as-check-of-condition-failed);
|
|
944
|
+
with the user set, the POST answers `201` and the source reads empty. `program` could not be
|
|
522
945
|
measured there — an ABAP Cloud system refuses it with `S_DEVELOP`. The class, in
|
|
523
946
|
order:
|
|
524
947
|
|
|
@@ -540,6 +963,70 @@ read inactive 200 CLASS <class> DEFINITION … ← as written
|
|
|
540
963
|
|
|
541
964
|
---
|
|
542
965
|
|
|
966
|
+
## An empty responsible person is refused as "Check of condition failed"
|
|
967
|
+
|
|
968
|
+
**Symptom.** A create answers `400 ExceptionInvalidData`, *"Check of condition
|
|
969
|
+
failed"* (`00/001`), with `XML_PATH` `srvd:srvdSource(1)` and an `XML_OFFSET`
|
|
970
|
+
that points at the end of the root element's start tag. Nothing names a field.
|
|
971
|
+
|
|
972
|
+
**Cause.** The request carried `adtcore:responsible=""`. The empty attribute is
|
|
973
|
+
the only difference from a request that is accepted: with the user in it the
|
|
974
|
+
same POST answers `201`.
|
|
975
|
+
|
|
976
|
+
**Rule.** The offset is the pointer: it names the root element's attributes,
|
|
977
|
+
not its content. A create of a name that exists is a different answer —
|
|
978
|
+
`400 ExceptionResourceAlreadyExists`, *"Resource Service Definition <NAME> does
|
|
979
|
+
already exist."* — so this sentence is not "already exists" either.
|
|
980
|
+
|
|
981
|
+
**Workaround.** Give the client the responsible person: `IAdtClientOptions.responsible`
|
|
982
|
+
(on a cloud system, `userName` from `getSystemInformation()`; on-premise, the
|
|
983
|
+
logon user, upper-cased). A client built without options sends it empty.
|
|
984
|
+
|
|
985
|
+
**Evidence.** Cloud system, 2026-09-27, the same service definition POST three
|
|
986
|
+
ways: `responsible=""` → `400` "Check of condition failed", `XML_OFFSET 330`,
|
|
987
|
+
which is the end of the start tag; `responsible` set → `201`; the same again →
|
|
988
|
+
`400 ExceptionResourceAlreadyExists`. The earlier measurement that called a
|
|
989
|
+
service definition's create "refused outright" came from a client built
|
|
990
|
+
without options, so it measured this.
|
|
991
|
+
|
|
992
|
+
**Where it bites.** `create` on a client without `responsible` — most visibly
|
|
993
|
+
`getServiceDefinition().create()`.
|
|
994
|
+
|
|
995
|
+
---
|
|
996
|
+
|
|
997
|
+
## An object created without a package cannot be deleted
|
|
998
|
+
|
|
999
|
+
**Symptom.** A name is taken — every create of it is refused — and the deletion
|
|
1000
|
+
check says the object does not exist:
|
|
1001
|
+
|
|
1002
|
+
```xml
|
|
1003
|
+
<del:object del:isDeletable="false" adtcore:name="<NAME>">
|
|
1004
|
+
<del:message del:type="E"><del:text>Object does not exist</del:text></del:message>
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
**Cause.** The object was created but never bound to a package. The deletion
|
|
1008
|
+
check resolves through the package; one it can resolve names it
|
|
1009
|
+
(`adtcore:packageName="<PACKAGE>"`), and an unbound object gets the same answer
|
|
1010
|
+
as an absent one.
|
|
1011
|
+
|
|
1012
|
+
**Rule.** "Object does not exist" from the deletion check is not proof of
|
|
1013
|
+
absence. Nothing reachable through ADT removes such an object — there is no
|
|
1014
|
+
resource that re-binds it — so cleaning it up is SAP GUI territory.
|
|
1015
|
+
|
|
1016
|
+
**Workaround.** Prevention. `create` is one POST and never leaves such an object
|
|
1017
|
+
itself; a sequence that stops between the POST and the write can. Wrap the
|
|
1018
|
+
steps after a create so a failure calls `delete` while the object is still bound;
|
|
1019
|
+
treat a create as unfinished until `getUtils().search({ query: name })` answers
|
|
1020
|
+
`adtcore:packageName` for it. The one input guard this library keeps is exactly
|
|
1021
|
+
this: `create` without `packageName` throws before any request.
|
|
1022
|
+
|
|
1023
|
+
**Evidence.** The two deletion-check shapes above. Not reproduced on purpose: the
|
|
1024
|
+
experiment leaves exactly the undeletable object it describes.
|
|
1025
|
+
|
|
1026
|
+
**Where it bites.** Any `create` followed by steps that can fail.
|
|
1027
|
+
|
|
1028
|
+
---
|
|
1029
|
+
|
|
543
1030
|
## A check run compiles source for objects that do not exist
|
|
544
1031
|
|
|
545
1032
|
**Symptom.** A check of a name that does not exist answers `200` with no
|
|
@@ -289,7 +289,7 @@ to a package. The deletion check resolves an object through its package — its
|
|
|
289
289
|
answer carries `adtcore:packageName` when it found one and says "Object does not
|
|
290
290
|
exist" when it did not — so an unbound object is reported absent while its name
|
|
291
291
|
stays taken, and there is nothing for the delete to act on. See
|
|
292
|
-
[
|
|
292
|
+
[ERRATA.md](ERRATA.md#an-object-created-without-a-package-cannot-be-deleted).
|
|
293
293
|
|
|
294
294
|
### A behavior implementation needs a second write, and it is the class's
|
|
295
295
|
|
|
@@ -396,7 +396,7 @@ system gives.
|
|
|
396
396
|
Two things worth knowing while you write it: `activationExecuted="false"` with
|
|
397
397
|
no message means "nothing to do", and a locked object refuses with HTTP 403,
|
|
398
398
|
which never reaches a body-reading strategy at all — see
|
|
399
|
-
[
|
|
399
|
+
[ERRATA.md](ERRATA.md#activationexecuted-false-is-not-a-failure).
|
|
400
400
|
`analyseActivation` in `@mcp-abap-adt/adt-strategies` reads both.
|
|
401
401
|
|
|
402
402
|
## How to check a created object, and when
|
|
@@ -530,10 +530,12 @@ A POST sometimes builds a minimal working object and sometimes builds nothing,
|
|
|
530
530
|
and which one you get is a property of the type: a domain is complete, an
|
|
531
531
|
interface has a generated skeleton, a DDL source answers `200` with an empty
|
|
532
532
|
body, a class has a skeleton that **no read can see until its first source
|
|
533
|
-
write**, and a service definition
|
|
533
|
+
write**, and a service definition is created with an empty source (a create
|
|
534
|
+
that sends no responsible person is refused — a different thing, see
|
|
535
|
+
[ERRATA.md](ERRATA.md#an-empty-responsible-person-is-refused-as-check-of-condition-failed)).
|
|
534
536
|
`getVersions()` answers `ok` in every one of those states. The measured table,
|
|
535
537
|
the class's read sequence and what to do are in
|
|
536
|
-
[
|
|
538
|
+
[ERRATA.md](ERRATA.md#what-a-bare-create-leaves-depends-on-the-type).
|
|
537
539
|
|
|
538
540
|
## The name is taken from the POST onward, and `validate()` may not say so
|
|
539
541
|
|
|
@@ -572,7 +574,7 @@ Deleting an object and reading it again answers a different sentence per type
|
|
|
572
574
|
INTERFACE … does not exist."*, *"Data definition … of version does not
|
|
573
575
|
exist"*. All mean the same thing, and none is worth matching on: branch on the
|
|
574
576
|
failure your own `analyse` decided. See
|
|
575
|
-
[
|
|
577
|
+
[ERRATA.md](ERRATA.md#what-a-bare-create-leaves-depends-on-the-type).
|
|
576
578
|
|
|
577
579
|
Reproduce with `npx ts-node scripts/probe-inactive-metadata.ts` and
|
|
578
580
|
`npx ts-node scripts/probe-unfinished-create.ts`.
|
|
@@ -189,4 +189,4 @@ A package can be saved only once per ABAP session: the next update or delete
|
|
|
189
189
|
from that session is refused with `PAK/058`. Over RFC every call shares one
|
|
190
190
|
session, so an update straight after a create is already refused. The rule for
|
|
191
191
|
both transports and the workaround are in
|
|
192
|
-
[
|
|
192
|
+
[ERRATA.md](ERRATA.md#a-package-can-be-saved-only-once-per-abap-session).
|
|
@@ -97,7 +97,7 @@ only once per ABAP session — the next update or delete from that session is
|
|
|
97
97
|
refused with `PAK/058`, and the same request from any other session succeeds at
|
|
98
98
|
once. It is `CL_PACKAGE`'s session buffer, not a delay: retrying never helps.
|
|
99
99
|
The rule for HTTP and RFC, the evidence and the workaround are in
|
|
100
|
-
[
|
|
100
|
+
[ERRATA.md](ERRATA.md#a-package-can-be-saved-only-once-per-abap-session).
|
|
101
101
|
|
|
102
102
|
So when an operation refuses in a way that names editing or locking, and the
|
|
103
103
|
object is one your session has just changed, the fix is a different session —
|
package/package.json
CHANGED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
# Troubleshooting
|
|
2
|
-
|
|
3
|
-
ADT answers are often accurate about the wrong thing. Everything below was
|
|
4
|
-
observed against a running system, and each entry says what the message looks
|
|
5
|
-
like, what it actually means, and how to tell the two apart.
|
|
6
|
-
|
|
7
|
-
SAP-side behaviour with a consumer-side workaround is collected in
|
|
8
|
-
[WORKAROUNDS.md](WORKAROUNDS.md); the entries below that belong there are short
|
|
9
|
-
and link to it.
|
|
10
|
-
|
|
11
|
-
## "You are not authorized to make changes (authorization object S_ABPLNGVS)"
|
|
12
|
-
|
|
13
|
-
Arrives as a `403` with an `<exc:exception>` document, `adtType`
|
|
14
|
-
`ExceptionResourceNoAccess`. It reads as a missing role. Often it is not.
|
|
15
|
-
|
|
16
|
-
**What the object is.** `S_ABPLNGVS` is not a role — it is an authorization
|
|
17
|
-
object for the ABAP **language version**:
|
|
18
|
-
|
|
19
|
-
| field | meaning |
|
|
20
|
-
|---|---|
|
|
21
|
-
| `ABP_LNG_VS` | the language version — *ABAP for Cloud Development*, *Standard ABAP* |
|
|
22
|
-
| `ACTVT` | the permitted operations — create, change, activate, execute, ABAP Unit |
|
|
23
|
-
|
|
24
|
-
The template role `SAP_BC_ABAP_DEVELOPER_5` carries it restricted to *ABAP for
|
|
25
|
-
Cloud Development*. Allowing *Standard ABAP* as well means adding that value in
|
|
26
|
-
PFCG.
|
|
27
|
-
|
|
28
|
-
**It may be inert.** The check applies only when the SACF authorization scenario
|
|
29
|
-
`ABAP_LANGUAGE_VERSION` is switched on. Otherwise the object sitting in a role
|
|
30
|
-
restricts nothing, so finding it in a role proves nothing about behaviour.
|
|
31
|
-
|
|
32
|
-
**It is not `S_DEVELOP`.** `S_DEVELOP` governs access to development objects at
|
|
33
|
-
all; `S_ABPLNGVS` additionally governs *which language version* they may be
|
|
34
|
-
created, changed and run in.
|
|
35
|
-
|
|
36
|
-
### What "access to a language version" actually grants
|
|
37
|
-
|
|
38
|
-
The object is checked as a pair, and both halves have to match the request:
|
|
39
|
-
|
|
40
|
-
- **`ABP_LNG_VS` — which language.** An object is written in one ABAP language
|
|
41
|
-
version, fixed by the software component its package belongs to. Writing into a
|
|
42
|
-
package whose component is *ABAP for Cloud Development* requires that value;
|
|
43
|
-
writing a Standard ABAP object requires the Standard ABAP value. Holding one
|
|
44
|
-
does not grant the other, and this is the usual cause on BTP ABAP Environment,
|
|
45
|
-
where a developer is typically granted the cloud value only.
|
|
46
|
-
- **`ACTVT` — which operation, per language.** Access is granted per activity:
|
|
47
|
-
create, change, activate, execute, run ABAP Unit. A user can hold *change* for
|
|
48
|
-
a language version and not *activate* it, so a chain can write successfully and
|
|
49
|
-
then fail at the activation step with the same object named.
|
|
50
|
-
|
|
51
|
-
That second half is what makes the failure look intermittent: the same user, the
|
|
52
|
-
same package, refused at one step of a create chain and not at the earlier ones.
|
|
53
|
-
Read which step the failure carries — `IAdtError.request` names it — before
|
|
54
|
-
concluding the whole language version is denied.
|
|
55
|
-
|
|
56
|
-
**What to check, in order.** Whether the package in the request exists; which
|
|
57
|
-
language version its software component fixes; whether the user holds that value
|
|
58
|
-
in `ABP_LNG_VS`; and whether they hold the specific `ACTVT` for the operation
|
|
59
|
-
that failed rather than for the one that succeeded.
|
|
60
|
-
|
|
61
|
-
**The trap.** It is also raised for a create into a package **that does not
|
|
62
|
-
exist** — before going to PFCG, check the package. See
|
|
63
|
-
[WORKAROUNDS.md](WORKAROUNDS.md#s_abplngvs-refuses-a-create-into-a-package-that-does-not-exist).
|
|
64
|
-
|
|
65
|
-
## A read of an object that is not there returns 200 and an empty body
|
|
66
|
-
|
|
67
|
-
`source/main` never answers 404, and a not-yet-ready metadata read answers the
|
|
68
|
-
same `200` with zero bytes, so "not there" and "there and empty" are one answer.
|
|
69
|
-
A read-modify-write that trusts the status erases the object. See
|
|
70
|
-
[WORKAROUNDS.md](WORKAROUNDS.md#a-read-answers-200-with-an-empty-body-instead-of-404).
|
|
71
|
-
|
|
72
|
-
## "Resource ZCL_X: wrong input data for processing" on a read
|
|
73
|
-
|
|
74
|
-
A `400 ExceptionResourceWrongData`, `SADT_RESOURCE/007`, on every read of a
|
|
75
|
-
class that was created and has no source written yet. It is unfinished, not
|
|
76
|
-
absent or broken: write the source, do not retry. See
|
|
77
|
-
[WORKAROUNDS.md](WORKAROUNDS.md#what-a-bare-create-leaves-depends-on-the-type).
|
|
78
|
-
|
|
79
|
-
## "Class ZCL_X does not have a TMDIR entry" on an activation
|
|
80
|
-
|
|
81
|
-
A `200` carrying `<msg type="E" code="OO(045)">`: the object **does not exist**.
|
|
82
|
-
Distinguish it from the entry above — `wrong input data` is an object that
|
|
83
|
-
exists with nothing in it, `TMDIR` is an object that is not there at all. See
|
|
84
|
-
[WORKAROUNDS.md](WORKAROUNDS.md#activationexecuted-false-is-not-a-failure).
|
|
85
|
-
|
|
86
|
-
## A message class exists; its messages do not
|
|
87
|
-
|
|
88
|
-
`getMessageClassMessage().read()` answering `OBJECT_NOT_FOUND` is **this library
|
|
89
|
-
reading content**, not SAP reporting absence. A message class is a container and
|
|
90
|
-
its messages are rows in it: only the container has existence on the wire — 404
|
|
91
|
-
before it is created, 404 after it is deleted — while a row has none. Measured,
|
|
92
|
-
`POST …/messages/001?_action=LOCK_MSG` answers `200` before message 001 exists,
|
|
93
|
-
because the PUT after it is what creates it.
|
|
94
|
-
|
|
95
|
-
So there is nothing to ask about a row, and nothing refuses. The member fetches
|
|
96
|
-
the class document and looks for the number itself. A consumer who replaces the
|
|
97
|
-
reading replaces that verdict along with it.
|
|
98
|
-
|
|
99
|
-
## `validate()` passed and the create says the name is taken
|
|
100
|
-
|
|
101
|
-
Fixed in this package, and worth knowing if you are on an older version or
|
|
102
|
-
reading raw ADT traffic: a validation refuses a taken name two different ways.
|
|
103
|
-
A domain, a structure, a table, a class and a service definition answer a
|
|
104
|
-
failing status. A **function group and a DDL source answer `200`** with the
|
|
105
|
-
verdict in the body:
|
|
106
|
-
|
|
107
|
-
```xml
|
|
108
|
-
<SEVERITY>ERROR</SEVERITY>
|
|
109
|
-
<SHORT_TEXT>Data definition ZAC_X already exists</SHORT_TEXT>
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
A `validate()` that returns `200` has therefore not told you the name is free —
|
|
113
|
-
the body has. This package reads none of it: supply an `analyse` that looks for
|
|
114
|
-
`<SEVERITY>` if a rejected name should reach you as a failure, or read the
|
|
115
|
-
document yourself.
|
|
116
|
-
|
|
117
|
-
Note what `validate()` still does not answer: whether the object exists. A free
|
|
118
|
-
name validates fine either way. And note what an abandoned create leaves — the
|
|
119
|
-
name is held from the POST onward whatever state the object is in, including a
|
|
120
|
-
class that no read can see.
|
|
121
|
-
|
|
122
|
-
## An object that exists, holds its name, and cannot be deleted
|
|
123
|
-
|
|
124
|
-
`delete()` works on anything you can name — with one exception, and it is the
|
|
125
|
-
expensive one: **an object that was created but never bound to a package.** It
|
|
126
|
-
holds its name against every future create, and nothing removes it.
|
|
127
|
-
|
|
128
|
-
The mechanism shows in the deletion check's own answer. When it can resolve the
|
|
129
|
-
object it names the package:
|
|
130
|
-
|
|
131
|
-
```xml
|
|
132
|
-
<del:object del:isDeletable="true" adtcore:name="ZAC_X" adtcore:packageName="ZADT_BLD_PKG03"/>
|
|
133
|
-
<del:object del:isDeletable="false" adtcore:name="ZAC_X">
|
|
134
|
-
<del:message del:type="E"><del:text>Object does not exist</del:text></del:message>
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
The second shape is what an absent object gets — and an unbound one gets it too,
|
|
138
|
-
because the check resolves through the package. So the system reports "does not
|
|
139
|
-
exist" about something whose name is demonstrably taken, and the delete has
|
|
140
|
-
nothing to work on.
|
|
141
|
-
|
|
142
|
-
**This page does not reproduce it on purpose.** The experiment succeeds by
|
|
143
|
-
leaving exactly the undeletable object it is describing.
|
|
144
|
-
|
|
145
|
-
**Prevention is the whole remedy this library offers:**
|
|
146
|
-
|
|
147
|
-
- since 18.0.0 `create` is one POST, so it cannot leave a half-made object of
|
|
148
|
-
its own; what can is *your* sequence stopping between the POST and the write.
|
|
149
|
-
Wrap the steps that follow a create so a failure calls `delete` while the
|
|
150
|
-
object is still bound to its package;
|
|
151
|
-
- treat a create as unfinished until you have seen the object *in its package* —
|
|
152
|
-
`getUtils().search({ query: name })` answers `adtcore:packageName` for an
|
|
153
|
-
object that resolved, and that is the attribute to look for;
|
|
154
|
-
- do not abandon a create halfway on purpose.
|
|
155
|
-
|
|
156
|
-
There is no member for re-binding an object to a package, and adding one would
|
|
157
|
-
not help: the resource that would accept it is reached the same way. Cleaning up
|
|
158
|
-
an object already in this state is SAP GUI territory.
|
|
159
|
-
|
|
160
|
-
## A refusal can arrive with a 2xx
|
|
161
|
-
|
|
162
|
-
The status is the channel; the document is the verdict. ADT has at least three
|
|
163
|
-
refusal shapes, and a `200` can carry any of them. See
|
|
164
|
-
[WORKAROUNDS.md](WORKAROUNDS.md#a-refusal-can-arrive-with-a-2xx-and-an-error-status-names-the-fix).
|
|
165
|
-
|
|
166
|
-
## Activation reports `activationExecuted="false"` and nothing is wrong
|
|
167
|
-
|
|
168
|
-
The flag says whether ADT did any work, not whether it succeeded; only
|
|
169
|
-
`<msg type="E">` is the verdict. See
|
|
170
|
-
[WORKAROUNDS.md](WORKAROUNDS.md#activationexecuted-false-is-not-a-failure).
|
|
171
|
-
|
|
172
|
-
## A deletion check that says "no" is not a failure
|
|
173
|
-
|
|
174
|
-
`/deletion/check` answers **200** with `del:isDeletable="false"` and a reason.
|
|
175
|
-
That is the answer the check exists to produce, not a failure to answer.
|
|
176
|
-
|
|
177
|
-
A *delete* that reports `del:isDeleted="false"` is a different thing entirely —
|
|
178
|
-
the objects are still on the system, and that is a failure.
|
|
179
|
-
|
|
180
|
-
## 406 and 415 name the exact content type in their text
|
|
181
|
-
|
|
182
|
-
The status number says nothing actionable; the sentence names the header. See
|
|
183
|
-
[WORKAROUNDS.md](WORKAROUNDS.md#a-refusal-can-arrive-with-a-2xx-and-an-error-status-names-the-fix).
|
|
184
|
-
|
|
185
|
-
## "No URI-Mapping defined for URI", inside a 200
|
|
186
|
-
|
|
187
|
-
The address does not exist on the server. Historically this came from building an
|
|
188
|
-
object URI by lowercasing its type code — `DEVC/K` became
|
|
189
|
-
`/sap/bc/adt/devc/k/{name}` — where the real resource is
|
|
190
|
-
`/sap/bc/adt/packages/{name}`.
|
|
191
|
-
|
|
192
|
-
If a group operation (deletion check, delete, activate) does nothing and reports
|
|
193
|
-
nothing, read the body: the complaint is in there, inside a success.
|