@mcp-abap-adt/adt-clients 23.0.2 → 23.0.4

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 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
- - **[Workarounds](docs/usage/WORKAROUNDS.md)** – SAP-side behaviour a consumer has to work around, with the evidence for each
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/WORKAROUNDS.md. Over RFC the create counts as that save.
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/WORKAROUNDS.md. Opening a new session is the caller's.
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/WORKAROUNDS.md. A delete from a session that saved the package
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/WORKAROUNDS.md. Over RFC the create counts as that save.
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/WORKAROUNDS.md. Opening a new session is the caller's.
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/WORKAROUNDS.md. A delete from a session that saved the package
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/WORKAROUNDS.md.
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
- - [**WORKAROUNDS.md**](usage/WORKAROUNDS.md) — SAP-side behaviour a consumer has to work around (PAK/058, refusals inside a `200`, empty `200` reads, activation flags, transport search, …): symptom, cause, rule, workaround and evidence for each
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
- │ ├── WORKAROUNDS.md # SAP-side behaviour and its workarounds
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
- - [`WORKAROUNDS.md`](WORKAROUNDS.md) — the SAP-side behaviour behind several of
130
- these answers, and what a consumer does about it
131
- - [`TROUBLESHOOTING.md`](TROUBLESHOOTING.md) — answers that are accurate about
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 { analysePublication } from '@mcp-abap-adt/adt-strategies';
1070
+ import {
1071
+ analysePublication,
1072
+ analysePublicationLock,
1073
+ } from '@mcp-abap-adt/adt-strategies';
1071
1074
 
1072
1075
  const bindings = client.getServiceBinding();
1073
1076
 
1074
- const locked = await bindings.lock({ bindingName: 'ZAC_SRVB01' });
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
- # Workarounds for SAP-side behaviour
1
+ # SAP ADT errata
2
2
 
3
- This document collects behaviour of the SAP system and its ADT endpoints that a
4
- consumer has to work around. Each case is **surprising, not a defect of this
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,198 @@ 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
+ - **A package walk lists objects the binding's publication generated** —
201
+ `G4BA`, `SCO2`, `SUSH` — that have no ADT address; a group deletion check
202
+ over them refuses the whole group. Leave them out; they go with the binding.
203
+ See [below](#a-package-walk-lists-a-bindings-generated-objects).
204
+
205
+ ### Message
206
+
207
+ `getMessageClassMessage()`. **`OBJECT_NOT_FOUND` for a message** is this
208
+ library reading the class document, not SAP reporting absence. A message class
209
+ is a container and its messages are rows in it: only the container has existence
210
+ on the wire — 404 before it is created, 404 after it is deleted — while a row has
211
+ none. Measured, `POST …/messages/001?_action=LOCK_MSG` answers `200` before
212
+ message 001 exists, because the PUT after it is what creates it. So the member
213
+ fetches the class document and looks for the number itself; a consumer who
214
+ replaces the reading replaces that verdict with it.
215
+
216
+ ### ATC
217
+
218
+ `getAtc()` (runtime). **The check variant comes from customizing**, not from
219
+ the run request. See
220
+ [below](#atc-takes-its-check-variant-from-customizing).
221
+
32
222
  ## Contents
33
223
 
34
224
  Sessions and locks
@@ -37,33 +227,42 @@ Sessions and locks
37
227
  2. [The session-type header is per request](#the-session-type-header-is-per-request)
38
228
  3. [A class include is written under the class lock](#a-class-include-is-written-under-the-class-lock)
39
229
  4. [A service binding is locked to publish it](#a-service-binding-is-locked-to-publish-it)
230
+ 5. [A binding publishes only once it is active](#a-binding-publishes-only-once-it-is-active)
231
+ 6. [An unpublish straight after a publish is refused](#an-unpublish-straight-after-a-publish-is-refused)
40
232
 
41
233
  Reading answers
42
234
 
43
- 5. [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)
44
- 6. [A read answers 200 with an empty body instead of 404](#a-read-answers-200-with-an-empty-body-instead-of-404)
45
- 7. [A successful delete can carry an untyped message](#a-successful-delete-can-carry-an-untyped-message)
46
- 8. [S_ABPLNGVS refuses a create into a package that does not exist](#s_abplngvs-refuses-a-create-into-a-package-that-does-not-exist)
235
+ 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)
236
+ 8. [A read answers 200 with an empty body instead of 404](#a-read-answers-200-with-an-empty-body-instead-of-404)
237
+ 9. [A successful delete can carry an untyped message](#a-successful-delete-can-carry-an-untyped-message)
238
+ 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)
239
+ 11. [A validation answers a taken name inside a 200](#a-validation-answers-a-taken-name-inside-a-200)
240
+ 12. [A deletion check that says no is not a failure](#a-deletion-check-that-says-no-is-not-a-failure)
241
+ 13. ["No URI-Mapping defined for URI" inside a 200](#no-uri-mapping-defined-for-uri-inside-a-200)
242
+ 14. [A package walk lists a binding's generated objects](#a-package-walk-lists-a-bindings-generated-objects)
243
+ 15. [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
244
 
48
245
  Creating and checking objects
49
246
 
50
- 9. [What a bare create leaves depends on the type](#what-a-bare-create-leaves-depends-on-the-type)
51
- 10. [A check run compiles source for objects that do not exist](#a-check-run-compiles-source-for-objects-that-do-not-exist)
247
+ 16. [What a bare create leaves depends on the type](#what-a-bare-create-leaves-depends-on-the-type)
248
+ 17. [An empty responsible person is refused as "Check of condition failed"](#an-empty-responsible-person-is-refused-as-check-of-condition-failed)
249
+ 18. [An object created without a package cannot be deleted](#an-object-created-without-a-package-cannot-be-deleted)
250
+ 19. [A check run compiles source for objects that do not exist](#a-check-run-compiles-source-for-objects-that-do-not-exist)
52
251
 
53
252
  Activation
54
253
 
55
- 11. [activationExecuted false is not a failure](#activationexecuted-false-is-not-a-failure)
56
- 12. [Activation settles inside the POST](#activation-settles-inside-the-post)
254
+ 20. [activationExecuted false is not a failure](#activationexecuted-false-is-not-a-failure)
255
+ 21. [Activation settles inside the POST](#activation-settles-inside-the-post)
57
256
 
58
257
  Transports
59
258
 
60
- 13. [The transport list is a saved-configuration search](#the-transport-list-is-a-saved-configuration-search)
61
- 14. [The transport tree has no fixed nesting](#the-transport-tree-has-no-fixed-nesting)
62
- 15. [A hand-made task is Unclassified and refuses objects](#a-hand-made-task-is-unclassified-and-refuses-objects)
259
+ 22. [The transport list is a saved-configuration search](#the-transport-list-is-a-saved-configuration-search)
260
+ 23. [The transport tree has no fixed nesting](#the-transport-tree-has-no-fixed-nesting)
261
+ 24. [A hand-made task is Unclassified and refuses objects](#a-hand-made-task-is-unclassified-and-refuses-objects)
63
262
 
64
263
  ATC
65
264
 
66
- 16. [ATC takes its check variant from customizing](#atc-takes-its-check-variant-from-customizing)
265
+ 25. [ATC takes its check variant from customizing](#atc-takes-its-check-variant-from-customizing)
67
266
 
68
267
  ---
69
268
 
@@ -300,11 +499,16 @@ publish job finishes. An open Eclipse editor on the binding holds the lock for
300
499
  the same user from a different session.
301
500
 
302
501
  **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.
502
+ like a 403; it is usually someone's open Eclipse, not a leaked lock. Eclipse
503
+ itself meets a `403` on its own LOCK when its editor already holds the binding,
504
+ and posts the job anyway: the job does not need the caller's lock.
304
505
 
305
506
  **Workaround.** For a library there is no editor, so the edit is the operation:
306
507
  `lock()`, `update()` with the desired publication state, `unlock()` in a
307
- `finally`. The publish job took about 133 seconds on the systems measured,
508
+ `finally`. Pass `analysePublicationLock` to the `lock()`: a `403` then answers
509
+ a lock without a handle (`''`), the publication goes ahead, and there is
510
+ nothing to unlock. A caller who wants the `403` to stop them passes
511
+ `analyseException` instead. The publish job took about 133 seconds on the systems measured,
308
512
  above the 120 s default — pass a larger `timeout`. Read the job's own answer
309
513
  (`analysePublication` from `@mcp-abap-adt/adt-strategies` reads its
310
514
  `SEVERITY`); nothing needs polling. For a binding locked elsewhere, close the
@@ -315,12 +519,74 @@ Eclipse editor that holds it. The full example is in
315
519
  `LOCK` → `200` on session *"stateful, enqueue"*; `POST
316
520
  …/odatav4/publishjobs` → `200` stateless, 133 057 ms; `UNLOCK` → `200` on
317
521
  *"Closing editor"*; a second `LOCK` before the unpublish → `403` while the
318
- first was held.
522
+ first was held. Eclipse, cloud system, 2026-09-27: after a publish its editor
523
+ kept the lock (a LOCK from another session → `403`); the unpublish that followed
524
+ sent its own `LOCK` → `403` on the enqueue session and then `POST
525
+ …/unpublishjobs` → `200`, 132 s. Between the two, the enqueue session was kept
526
+ alive with `GET /sap/bc/adt/core/http/sessions`.
319
527
 
320
528
  **Where it bites.** `getServiceBinding()`: `lock`, `update`, `delete`.
321
529
 
322
530
  ---
323
531
 
532
+ ## A binding publishes only once it is active
533
+
534
+ **Symptom.** Publishing a binding straight after creating it answers `200`
535
+ with `<SEVERITY>ERROR</SEVERITY>`: *"Local Publish of <SERVICE_BINDING>
536
+ failed"*, long text *"Service Binding <SERVICE_BINDING> does not exist."* The
537
+ binding is there; its metadata reads back.
538
+
539
+ **Cause.** A created binding has only an inactive version, and the publication
540
+ looks for the active one.
541
+
542
+ **Rule.** "Does not exist" here means "not active". Taking the lock first does
543
+ not change it, and neither does reading the service's information
544
+ (`generateServiceBinding`, a `GET` on `/businessservices/odatav4/<binding>` that
545
+ Eclipse sends to refresh its view, after a publication).
546
+
547
+ **Workaround.** `activate()` the binding, then publish it.
548
+
549
+ **Evidence.** Cloud system, 2026-09-27, one binding deleted and created again:
550
+ publish after the create → the refusal above; lock, then publish → the same;
551
+ the information `GET`, then publish → the same; activate, then publish →
552
+ *"<SERVICE_BINDING> published locally"*, 134 s.
553
+
554
+ **Where it bites.** `getServiceBinding().create()` followed by `update()` with
555
+ `desiredPublicationState: 'published'`.
556
+
557
+ ---
558
+
559
+ ## An unpublish straight after a publish is refused
560
+
561
+ **Symptom.** An unpublish answers `200` with `<SEVERITY>ERROR</SEVERITY>`
562
+ within a second: *"Local un-publish of <SERVICE_BINDING> failed"*, long text
563
+ *"Error while creating service interface <SERVICE_BINDING>_0001_G4BA"*.
564
+
565
+ **Cause.** The publication job answers before the system has finished with
566
+ the binding; an unpublish in that window fails. The request is the same one
567
+ that succeeds later.
568
+
569
+ **Rule.** A fast `ERROR` from an unpublish is not a verdict on the binding. The
570
+ job itself takes minutes when it runs; an answer in under a second means it did
571
+ not run.
572
+
573
+ **Workaround.** Leave time between a publish and an unpublish of the same
574
+ binding — minutes, not seconds — and repeat an unpublish that failed this way
575
+ later rather than changing the request.
576
+
577
+ **Evidence.** Cloud system, 2026-09-27, one binding and the same request each
578
+ time: publish → *"published locally"*, 133 s; unpublish straight after → the
579
+ refusal above, 0.6 s; unpublish seven minutes later → *"un-published
580
+ locally"*, 134 s. An unpublish from Eclipse several minutes after its publish
581
+ took 132 s and succeeded. How long the window is, was not measured more
582
+ closely.
583
+
584
+ **Where it bites.** `getServiceBinding().update()` with `desiredPublicationState:
585
+ 'unpublished'` soon after a publication — a test that publishes and unpublishes
586
+ in one run.
587
+
588
+ ---
589
+
324
590
  ## A refusal can arrive with a 2xx, and an error status names the fix
325
591
 
326
592
  **Symptom.** A deletion check, an activation or a validation answers **`200`**
@@ -448,6 +714,147 @@ CDS sources; any deletion reading.
448
714
 
449
715
  ---
450
716
 
717
+ ## A function module's source answers 500 for a module that does not exist
718
+
719
+ **Symptom.** A read of a function module's source answers **`500`**, *"An
720
+ exception was raised"* (`SY/530`), for a module that is simply not there. The
721
+ same question asked of the module's metadata answers `404`.
722
+
723
+ ```
724
+ GET …/fmodules/<FUNCTION_MODULE>/source/main → 500 <type id="FUNCTION"/>
725
+ "An exception was raised"; LONGTEXT: "Function module <FUNCTION_MODULE>
726
+ does not exist" (FL651)
727
+ GET …/fmodules/<FUNCTION_MODULE> → 404 ExceptionResourceNotFound
728
+ "Function module <FUNCTION_MODULE> does not exist" (FL110)
729
+ ```
730
+
731
+ **Cause.** The source resource raises the function builder's own exception
732
+ instead of answering absence; the reason travels only in the long text.
733
+
734
+ **Rule.** The status is the least of the answer — the text says "does not
735
+ exist", even under a `500`. And a source is the wrong place to ask about
736
+ existence at all: this one answers `500`, and a source that was never written
737
+ answers `200` and no body
738
+ ([above](#a-read-answers-200-with-an-empty-body-instead-of-404)). Ask the
739
+ object's own URI.
740
+
741
+ **Workaround.** Decide existence with `readMetadata()`: a document means the
742
+ object is there, `ExceptionResourceNotFound` or "does not exist" means it is
743
+ not, and only an answer without a text is judged by its status. Where a source
744
+ read has already failed, read the document in `response.data` — not the
745
+ transport's "Request failed with status code 500".
746
+
747
+ **Evidence.** Cloud system, 2026-09-27: both reads above, for a module in a new
748
+ function group and for one in a long-active group; an existing module's source
749
+ answered `200` with its code.
750
+
751
+ **Where it bites.** `getFunctionModule().read()` used as an existence check.
752
+
753
+ ---
754
+
755
+ ## A validation answers a taken name inside a 200
756
+
757
+ **Symptom.** `validate()` answers `200` for a name that is taken, and the create
758
+ that follows is refused because the name exists.
759
+
760
+ **Cause.** A validation refuses a taken name two different ways. A domain, a
761
+ structure, a table, a class and a service definition answer a failing status. A
762
+ **function group and a DDL source answer `200`** with the verdict in the body:
763
+
764
+ ```xml
765
+ <SEVERITY>ERROR</SEVERITY>
766
+ <SHORT_TEXT>Data definition <NAME> already exists</SHORT_TEXT>
767
+ ```
768
+
769
+ **Rule.** A `validate()` that returns `200` has not told you the name is free —
770
+ the body has. And `validate()` never answers whether the object exists: a free
771
+ name validates fine either way, and a name is held from the POST onward whatever
772
+ state the object is in.
773
+
774
+ **Workaround.** Pass `analyseValidation` (adt-strategies), which reads both
775
+ forms, or read `<SEVERITY>` yourself.
776
+
777
+ **Evidence.** The recorded answers `refusal-validation-name-taken-ddl`,
778
+ `-functiongroup`, `-class`, `-domain` and `-table` in the corpus.
779
+
780
+ **Where it bites.** `getDdl().validate()`, `getFunctionGroup().validate()`.
781
+
782
+ ---
783
+
784
+ ## A deletion check that says no is not a failure
785
+
786
+ **Symptom.** `checkDeletion()` answers `200` with `del:isDeletable="false"` and a
787
+ reason.
788
+
789
+ **Cause.** That is the answer the check exists to produce.
790
+
791
+ **Rule.** A *check* that says "no" has answered. A *delete* that reports
792
+ `del:isDeleted="false"` is a different thing: the objects are still on the
793
+ system, and that is a failure.
794
+
795
+ **Workaround.** Read the check's verdict with `analyseDeletion`; treat a refused
796
+ delete as a failure, a refusing check as information.
797
+
798
+ **Evidence.** The recorded answers `refusal-deletion-check-refuses` and
799
+ `refusal-delete-refused` in the corpus.
800
+
801
+ **Where it bites.** `checkDeletion()` and `delete()` on every type;
802
+ `getUtils()` group deletion.
803
+
804
+ ---
805
+
806
+ ## "No URI-Mapping defined for URI" inside a 200
807
+
808
+ **Symptom.** A group operation — deletion check, delete, activation — does
809
+ nothing and reports nothing; the body of its `200` says *"No URI-Mapping defined
810
+ for URI"*.
811
+
812
+ **Cause.** The address named in the request does not exist on the server. It was
813
+ seen when an object URI was built by lowercasing the type code — `DEVC/K` became
814
+ `/sap/bc/adt/devc/k/<NAME>` — where the real resource is
815
+ `/sap/bc/adt/packages/<NAME>`.
816
+
817
+ **Rule.** A success that did nothing has its complaint in the body.
818
+
819
+ **Workaround.** Take an object's URI from the system's own answers (search,
820
+ node structure), not from its type code.
821
+
822
+ **Evidence.** The package URI built from `DEVC/K`, answered as above.
823
+
824
+ **Where it bites.** `getUtils()` group operations over hand-built references,
825
+ and over a package walk that includes a binding's generated objects — see
826
+ [the next entry](#a-package-walk-lists-a-bindings-generated-objects).
827
+
828
+ ---
829
+
830
+ ## A package walk lists a binding's generated objects
831
+
832
+ **Symptom.** A group deletion check over everything a package walk listed
833
+ answers `200` refusing the group, with `[E]` *"No URI-Mapping defined for
834
+ URI"* on objects that carry no name in the answer. Nothing is deleted.
835
+
836
+ **Cause.** Next to a service binding that has been published, the node
837
+ structure of its package lists what the system generated for it — a `G4BA`
838
+ named like the binding, an `SCO2` `<BINDING>_0001_G4BA`, a `SUSH` with a
839
+ generated name. They are not ADT objects: there is no resource to address, so
840
+ the deletion check cannot place them.
841
+
842
+ **Rule.** A package walk lists more than ADT can act on. What you can delete is
843
+ what you can address.
844
+
845
+ **Workaround.** Leave these types out of a group deletion.
846
+ Delete the binding; its generated objects go with it.
847
+
848
+ **Evidence.** Cloud system, 2026-09-27, cleaning a test package: with the three
849
+ included, the group check refused everything as above; without them, 26 objects
850
+ were checked and deleted, and a walk of the package afterwards listed nothing —
851
+ the generated objects had gone with the binding.
852
+
853
+ **Where it bites.** `getUtils().checkDeletionGroup()` and `deleteObjectsGroup()`
854
+ over references taken from `fetchNodeStructure()` or any package walk.
855
+
856
+ ---
857
+
451
858
  ## S_ABPLNGVS refuses a create into a package that does not exist
452
859
 
453
860
  **Symptom.** A create answers `403 ExceptionResourceNoAccess`, *"You are not
@@ -467,8 +874,53 @@ scenario `ABAP_LANGUAGE_VERSION` is switched on.
467
874
  **Workaround.** Before going to PFCG, check that the package in the request
468
875
  exists and that its software component allows the language version you are
469
876
  writing; then that the user holds that value in `ABP_LNG_VS` and the `ACTVT` for
470
- the step that failed. The authorization side is explained in
471
- [TROUBLESHOOTING.md](TROUBLESHOOTING.md#you-are-not-authorized-to-make-changes-authorization-object-s_abplngvs).
877
+ the step that failed.
878
+
879
+ **What the object is.** `S_ABPLNGVS` is not a role — it is an authorization
880
+ object for the ABAP **language version**:
881
+
882
+ | field | meaning |
883
+ |---|---|
884
+ | `ABP_LNG_VS` | the language version — *ABAP for Cloud Development*, *Standard ABAP* |
885
+ | `ACTVT` | the permitted operations — create, change, activate, execute, ABAP Unit |
886
+
887
+ The template role `SAP_BC_ABAP_DEVELOPER_5` carries it restricted to *ABAP for
888
+ Cloud Development*. Allowing *Standard ABAP* as well means adding that value in
889
+ PFCG.
890
+
891
+ **It may be inert.** The check applies only when the SACF authorization scenario
892
+ `ABAP_LANGUAGE_VERSION` is switched on. Otherwise the object sitting in a role
893
+ restricts nothing, so finding it in a role proves nothing about behaviour.
894
+
895
+ **It is not `S_DEVELOP`.** `S_DEVELOP` governs access to development objects at
896
+ all; `S_ABPLNGVS` additionally governs *which language version* they may be
897
+ created, changed and run in.
898
+
899
+ **What "access to a language version" actually grants.**
900
+
901
+ The object is checked as a pair, and both halves have to match the request:
902
+
903
+ - **`ABP_LNG_VS` — which language.** An object is written in one ABAP language
904
+ version, fixed by the software component its package belongs to. Writing into a
905
+ package whose component is *ABAP for Cloud Development* requires that value;
906
+ writing a Standard ABAP object requires the Standard ABAP value. Holding one
907
+ does not grant the other, and this is the usual cause on BTP ABAP Environment,
908
+ where a developer is typically granted the cloud value only.
909
+ - **`ACTVT` — which operation, per language.** Access is granted per activity:
910
+ create, change, activate, execute, run ABAP Unit. A user can hold *change* for
911
+ a language version and not *activate* it, so a chain can write successfully and
912
+ then fail at the activation step with the same object named.
913
+
914
+ That second half is what makes the failure look intermittent: the same user, the
915
+ same package, refused at one step of a create chain and not at the earlier ones.
916
+ Read which step the failure carries — `IAdtError.request` names it — before
917
+ concluding the whole language version is denied.
918
+
919
+ **What to check, in order.** Whether the package in the request exists; which
920
+ language version its software component fixes; whether the user holds that value
921
+ in `ABP_LNG_VS`; and whether they hold the specific `ACTVT` for the operation
922
+ that failed rather than for the one that succeeded.
923
+
472
924
 
473
925
  **Evidence.** Cloud trial, 2026-09-03: a class create into a package that did
474
926
  not exist answered this refusal. The meaning of the object is SAP's
@@ -485,8 +937,7 @@ mistyped `packageName`.
485
937
  `400 ExceptionResourceWrongData`, `SADT_RESOURCE/007`, *"Resource <CLASS>: wrong
486
938
  input data for processing"* (note the double space) — for `active`, `inactive`,
487
939
  metadata and source alike. Waiting 30 seconds does not help, and neither does
488
- lock + unlock. Other types answer differently, and a service definition's
489
- create is refused outright with *"Check of condition failed"*.
940
+ lock + unlock. Other types answer differently.
490
941
 
491
942
  **Cause.** What the POST leaves is a property of the type:
492
943
 
@@ -496,7 +947,7 @@ create is refused outright with *"Check of condition failed"*.
496
947
  | `interface` | generated skeleton | 53 bytes |
497
948
  | `class` | generated skeleton | **refused** `400 ExceptionResourceWrongData` |
498
949
  | `ddl` | object, no content | `200`, empty |
499
- | `serviceDefinition` | **nothing — the POST is refused** | — |
950
+ | `serviceDefinition` | object, no content | `200`, empty source |
500
951
 
501
952
  The class does have a skeleton; it becomes readable at the first source write
502
953
  (`version=active` → SAP's `class <CLASS> definition`, `version=inactive` → what
@@ -509,16 +960,23 @@ the unreadable class), and `validate()` answers neither "name is free" nor
509
960
  "object exists" — the name is held from the POST onward regardless. Absence has
510
961
  three wordings (*"Error while importing object … from the database"*,
511
962
  *"Resource INTERFACE … does not exist."*, *"Data definition … of version does
512
- not exist"*); do not match on the text.
963
+ not exist"*); do not match on one wording. The metadata read marks absence
964
+ with `<type id="ExceptionResourceNotFound"/>` whatever the sentence; a function
965
+ module's source is the exception, where only the long text says it
966
+ ([below](#a-function-modules-source-answers-500-for-a-module-that-does-not-exist)).
513
967
 
514
968
  **Workaround.** After creating a class, write its source; do not retry the read.
515
969
  The first check worth making is after the write: read the version you wrote and
516
- compare. A service definition is created with its source in the same flow, not
517
- bare. If a sequence stops between the POST and the write, `delete()` the object
970
+ compare. A service definition is created empty; write its source before
971
+ activating it. If a sequence stops between the POST and the write, `delete()` the object
518
972
  while it is still bound to its package.
519
973
 
520
974
  **Evidence.** Cloud trial, 2026-09-05, `scripts/probe-unfinished-create.ts` and
521
- `scripts/probe-inactive-metadata.ts` (the table above). `program` could not be
975
+ `scripts/probe-inactive-metadata.ts` (the table above). The service definition
976
+ row was corrected on 2026-09-27: that probe's client carried no responsible
977
+ person, and its "refused" was
978
+ [that refusal](#an-empty-responsible-person-is-refused-as-check-of-condition-failed);
979
+ with the user set, the POST answers `201` and the source reads empty. `program` could not be
522
980
  measured there — an ABAP Cloud system refuses it with `S_DEVELOP`. The class, in
523
981
  order:
524
982
 
@@ -540,6 +998,70 @@ read inactive 200 CLASS <class> DEFINITION … ← as written
540
998
 
541
999
  ---
542
1000
 
1001
+ ## An empty responsible person is refused as "Check of condition failed"
1002
+
1003
+ **Symptom.** A create answers `400 ExceptionInvalidData`, *"Check of condition
1004
+ failed"* (`00/001`), with `XML_PATH` `srvd:srvdSource(1)` and an `XML_OFFSET`
1005
+ that points at the end of the root element's start tag. Nothing names a field.
1006
+
1007
+ **Cause.** The request carried `adtcore:responsible=""`. The empty attribute is
1008
+ the only difference from a request that is accepted: with the user in it the
1009
+ same POST answers `201`.
1010
+
1011
+ **Rule.** The offset is the pointer: it names the root element's attributes,
1012
+ not its content. A create of a name that exists is a different answer —
1013
+ `400 ExceptionResourceAlreadyExists`, *"Resource Service Definition <NAME> does
1014
+ already exist."* — so this sentence is not "already exists" either.
1015
+
1016
+ **Workaround.** Give the client the responsible person: `IAdtClientOptions.responsible`
1017
+ (on a cloud system, `userName` from `getSystemInformation()`; on-premise, the
1018
+ logon user, upper-cased). A client built without options sends it empty.
1019
+
1020
+ **Evidence.** Cloud system, 2026-09-27, the same service definition POST three
1021
+ ways: `responsible=""` → `400` "Check of condition failed", `XML_OFFSET 330`,
1022
+ which is the end of the start tag; `responsible` set → `201`; the same again →
1023
+ `400 ExceptionResourceAlreadyExists`. The earlier measurement that called a
1024
+ service definition's create "refused outright" came from a client built
1025
+ without options, so it measured this.
1026
+
1027
+ **Where it bites.** `create` on a client without `responsible` — most visibly
1028
+ `getServiceDefinition().create()`.
1029
+
1030
+ ---
1031
+
1032
+ ## An object created without a package cannot be deleted
1033
+
1034
+ **Symptom.** A name is taken — every create of it is refused — and the deletion
1035
+ check says the object does not exist:
1036
+
1037
+ ```xml
1038
+ <del:object del:isDeletable="false" adtcore:name="<NAME>">
1039
+ <del:message del:type="E"><del:text>Object does not exist</del:text></del:message>
1040
+ ```
1041
+
1042
+ **Cause.** The object was created but never bound to a package. The deletion
1043
+ check resolves through the package; one it can resolve names it
1044
+ (`adtcore:packageName="<PACKAGE>"`), and an unbound object gets the same answer
1045
+ as an absent one.
1046
+
1047
+ **Rule.** "Object does not exist" from the deletion check is not proof of
1048
+ absence. Nothing reachable through ADT removes such an object — there is no
1049
+ resource that re-binds it — so cleaning it up is SAP GUI territory.
1050
+
1051
+ **Workaround.** Prevention. `create` is one POST and never leaves such an object
1052
+ itself; a sequence that stops between the POST and the write can. Wrap the
1053
+ steps after a create so a failure calls `delete` while the object is still bound;
1054
+ treat a create as unfinished until `getUtils().search({ query: name })` answers
1055
+ `adtcore:packageName` for it. The one input guard this library keeps is exactly
1056
+ this: `create` without `packageName` throws before any request.
1057
+
1058
+ **Evidence.** The two deletion-check shapes above. Not reproduced on purpose: the
1059
+ experiment leaves exactly the undeletable object it describes.
1060
+
1061
+ **Where it bites.** Any `create` followed by steps that can fail.
1062
+
1063
+ ---
1064
+
543
1065
  ## A check run compiles source for objects that do not exist
544
1066
 
545
1067
  **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
- [TROUBLESHOOTING.md](TROUBLESHOOTING.md#an-object-that-exists-holds-its-name-and-cannot-be-deleted).
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
- [WORKAROUNDS.md](WORKAROUNDS.md#activationexecuted-false-is-not-a-failure).
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's create is refused outright.
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
- [WORKAROUNDS.md](WORKAROUNDS.md#what-a-bare-create-leaves-depends-on-the-type).
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
- [WORKAROUNDS.md](WORKAROUNDS.md#what-a-bare-create-leaves-depends-on-the-type).
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
- [WORKAROUNDS.md](WORKAROUNDS.md#a-package-can-be-saved-only-once-per-abap-session).
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
- [WORKAROUNDS.md](WORKAROUNDS.md#a-package-can-be-saved-only-once-per-abap-session).
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/adt-clients",
3
- "version": "23.0.2",
3
+ "version": "23.0.4",
4
4
  "description": "ADT clients for SAP ABAP systems - AdtClient and AdtRuntimeClient",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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.