Files
alexzandClaude Opus 5 ffd33f4ec0 ADR-004: back out of on-stack RAP; retarget to starter customizing tenant
Both tenants belong to the same starter system: my429480 is its
customizing tenant (client 100, key-user extensibility only, no ABAP)
and my431353 is its development tenant (client 080). This package had
recorded my429480 as "productive" and ADR-002 had assumed it offered
developer extensibility; both were wrong.

- ADR-004 withdraws the on-stack RAP route and moves the application to
  a side-by-side runtime against released OData APIs. ADR-002 marked
  superseded.
- Development tenant my431353 parked: all 19 ZARENA_PLM objects left
  active, sources kept in docs/handover/rap/ as reference, so the route
  stays reversible.
- Replacement host left open as OQ-11 (key-user extensibility vs CAP
  side-by-side); recommendation recorded as CAP. No build starts until
  OQ-11 is closed.
- RAP-dependent items V-2, V-3, OQ-5, OQ-8, OQ-9, OQ-10 parked rather
  than closed.
- Tenant references corrected across CLAUDE.md, KNOWLEDGE.md,
  app/README.md, the RAP handover README, the rap-developer agent and
  the reentrance-login helper README.

No SAP tenant was modified.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-29 10:33:29 -07:00

26 KiB
Raw Permalink Blame History

KNOWLEDGE — tombstones & landmines

Carried across the 2026-09-04 knowledge bankruptcy from the working Express predecessor. Filter applied: an item was kept only if it (a) cannot be re-derived from code or a live query, AND (b) is expensive or harmful to re-learn. Everything else was deliberately left behind to be re-derived from ground truth. Every entry is dated; re-verify on first POC contact. Add new entries the same way — dated and falsifiable.

Tenant note (2026-09-29). Both tenants named below are tenants of the same starter system: the customizing tenant my429480 (client 100, key-user extensibility and configuration only) and the development tenant my431353 (client 080, developer extensibility). The development tenant is parked as of 2026-09-29 and its objects are left in place, so every my431353 entry here is a historical finding from the client 080 track, not a statement about the current working target. The working target is my429480.

SAP S/4HANA (starter system customizing tenant my429480, client 100, Public Cloud, API-only)

  • [2026-09-04, verified by predecessor in live use] BOM items must be POSTed sequentially after the header POST. Parallel item POSTs fail SAP's consistency check.
  • [2026-09-04, same] AvailabilityCheckType is not exposed in the Product API V4 — requires the V2 API_PRODUCT_SRV fallback for that one field.
  • [2026-09-04, same] BOM read works only on the v1 service; API_BILL_OF_MATERIAL_SRV;v=0002 rejects guid key lookups. Writes go to ;v=0002.
  • [2026-09-04, same] OData V2 date format is /Date(ms)/; the "end of time" value (9999-12-31) is /Date(253402214400000)/.
  • [2026-09-04, same] Idempotency pattern that works: SAP is the ledger. The Arena ECO number is stamped into ReasonForChangeText; no local sync-state database is needed or wanted.
  • [2026-09-10, predecessor code read] BOM header create must use EngineeringChangeDocForEdit — EngineeringChangeDocument is sap:creatable=false and silently dropped. ECM BOM items must carry HeaderChangeDocument = the header's change number or SAP answers "Input parameters are initial". (arena-plm.js:785-846)
  • [2026-09-10, same] A_ChangeMasterObjTypeAssgmt POST requires IsActiveForChangeNumber: true — without it SAP returns 201 and silently discards the row. (arena-plm.js:914-924)
  • [2026-09-10, same] ProductDescription updates: PATCH by full key (Product=…,Language='EN'); PATCH via the nav property returns HTTP 501; fall back to POST on 404/405. (arena-plm.js:1411-1414)
  • [2026-09-10, same] ProcurementType can only be written together with MRPType in the same ProductPlantSupplyPlanning PATCH. (arena-plm.js:1623-1640)

Arena PLM API (api.arenasolutions.com/v1)

  • [2026-09-04, same] Session auth: POST /login → arenaSessionId, ~25-minute TTL. Concurrent logins race — serialize login behind a promise lock and re-login automatically on 401.
  • [2026-09-10, predecessor code read] WORKING-revision overlay is load-bearing: the seven SAP classification attributes (SAP Product Type, SAP Plant, …) are writable only on the WORKING revision (Arena error 3224), while /items?number= resolves the EFFECTIVE revision — merge those attributes from WORKING over EFFECTIVE, and treat overlay failure as fatal (the predecessor's best-effort overlay silently turned valid items into NO_PRODUCT_TYPE errors). (arena-plm.js:508-552)
  • [2026-09-10, same] /items/{guid}/changes does not exist (404) — derive the ECO from /items/{guid}/revisions, first EFFECTIVE revision with change.number. (arena-plm.js:1799-1806)
  • [2026-09-10, same] The /changes feed has no working server-side date filter — delta detection must filter client-side (the predecessor pages the whole feed, O(history)). The outbound-integration queue (/outboundintegrations/{guid}/events, reconciled flag as a cursor held by Arena) is the clean delta mechanism. (arena-plm.js:1117-1263)

Toolchain (worker)

  • [2026-09-10, npm registry] TypeScript latest is the 7.x native-compiler major (no stable 6.x; only 6.0.0-beta). typescript-eslint 8.70.0 declares peer typescript >=4.8.4 <6.1.0, silently forcing the 5.9.x line — the worker pins ~5.9.3. Falsifiable: recheck npm view typescript-eslint peerDependencies before any TS bump.
  • [2026-09-10, npm registry] @types/node latest dist-tag points at 22.x while 24.x/26.x lines exist — an unpinned install silently targets Node 22 types. Pin the major (^24) to match the runtime.

Landmines

  • [2026-09-15] abapGit is not a prerequisite for SAP's ADT for VS Code. The sapse.adt-vscode extension talks ADT/HTTP to the tenant and ships no git or abapGit commands (extension 1.1.2 package.json/readme, verified 2026-09-15). The "abapGit must be set up in the ABAP system" idea comes from the community extension murbani.vscode-abap-remote-fs (installed on this host), whose abapGit commands need the open-source ZABAPGIT report in the backend — which cannot be installed in a Public Cloud tenant. In ABAP Cloud, abapGit exists only as SAP's supported fork behind the Eclipse ADT plug-in (https://eclipse.abapgit.org/updatesite/, "abapGit Repositories" view); no VS Code variant exists [source: sap-docs sap-help loio 2002380aeda84875a5fae4adc66b3fdb; /btp-cloud-platform/30-development/ import-content-from-abapgit-repository-into-the-abap-environment-0b0d894; https://github.com/abapGit/ADT_Frontend, verified 2026-09-15]. It imports released object types only, from an internet-reachable repository (no Cloud Connector), into a chosen package/transport.

  • [2026-09-15] ADT MCP x-ui-service generator quirks (my431353, ADT VS Code 1.1.2): every field needs semanticType (else "Builtin type of field is invalid"); it prepends its own Z to your prefix and appends 000 even with an empty suffix (Z + ARENA_CHANGE_APPROVAL → ZZUI_ARENA_CHANGE_APPRVL000_O4), names SRVD and SRVB identically, and the schema's required lists a non-existent businessEntity key — omit it. get_schema session ids are single-use. It returns only the SRVB URI; other object URIs follow abap:/repotree-v1/<dest>/System Library/ZLOCAL/<pkg>/… with extensions .tabl.json, .ddls.acds, .bdef.json, .ddlx.acds, .dcls.acds, .clas.abap, .srvd.acds, .srvb.json, .ront.json, .nont.json. create_object for SRVD needs "sourceType":"S"; BDEF and SRVB creation validate the referenced object's ACTIVE version, so create and activate DDLS/SRVD sources first. ATC via MCP rejects RONT/NONT.

  • [2026-09-16] An expired VS Code ADT logon shows up in the ADT MCP only as "Your user was logged off." abap_list_destinations still lists the destination and get_object_type_details still returns field metadata, so neither proves a live session. ADT for VS Code logs on to S/4HANA Cloud with a reentrance ticket and a localhost redirect: it opens https://<tenant>/sap/bc/adt/core/http/reentranceticket?redirect-url=http://localhost:<port>/adt/redirect, and after SSO the tenant redirects to http://localhost:<port>/adt/redirect?…&reentrance-ticket=<ticket> (observed 2026-09-16). The ticket in that URL is a live logon credential: never paste it into chats, logs or files. The same mechanism is the plausible browser-free login path for abap-adt-mcp (OQ-9). Tested 2026-09-18: it does not work. With a freshly captured reentrance ticket, https://my431353.s4hana.cloud.sap/sap/bc/adt/core/discovery and an ABAP class source URL answered HTTP 200 with the same 3094-byte SAML redirect page (SAMLRequest, saml2) for all of: Cookie: MYSAPSSO2=<ticket>, header MYSAPSSO2: <ticket>, and no credential at all, with and without ?sap-client=080. A reentrance ticket is therefore neither a logon-ticket cookie nor an assertion-ticket header here; ADT exchanges it some other way. Do not spend more on abap-adt-mcp's sso2 mode without first capturing what the extension actually sends. abap_creation-run_validation is the cheapest read-only liveness check (it answers "… validation failed: Your user was logged off."). Fix it by logging on to the destination again in VS Code; the MCP port and token stay the same unless VS Code reloads (live 2026-09-16, ~1.5 h after the last successful call).

  • [2026-09-16] Ask the ADT MCP for name limits and package existence; do not guess them. abap_creation-get_object_type_details returns each type's create fields and maxLength: TABL/DT 16, while DDLS/DF, SRVD/SRV, SRVB/SVB and BDEF/BDO allow 30 (the generator's APPRVL truncation came from that 30 limit). abap_creation-run_validation is a read-only way to check that a package exists: a missing package answers "Package '' does not exist" [source: adt-mcp on 0M7DEJI, verified 2026-09-16]. The Claude Code session does not expose the adt-mcp tools unless the server was up at session start. A JSON-RPC probe over http://127.0.0.1:2236/mcp with the .mcp.json bearer token works for read-only checks in the meantime.

  • [2026-09-16] In the VS Code ADT workspace, the .json file in an object folder is header metadata, never the source. A table folder holds zchangeapproval.tabl.json (formatVersion, description, originalLanguage, abapLanguageVersion) and zchangeapproval.tabl.ddic (the Dictionary DDL source). Paste into .ddic, and activate through the .tabl.ddic URI (live, "Activation successful."). For objects created with create_object, the returned filePath names the source file: DDLS .ddls.acds, DCLS .dcls.acds, BDEF .bdef.abdl [source: adt-mcp abap_creation-create_object / abap_activate_objects on 0M7DEJI, verified 2026-09-16]. The .tabl.json / .bdef.json activation URIs recorded on 2026-09-15 were generator objects; trust the returned filePath over that table. BDEF activation through .bdef.abdl is proven (U6, live).

  • [2026-09-16] A managed strict ( 2 ) BDEF activates before its behavior pool class exists (ZR_CHANGEAPPROVAL, "Activation successful.", with no class ZBP_R_CHANGEAPPROVAL yet) [source: adt-mcp abap_activate_objects on 0M7DEJI, verified 2026-09-16]. Once the class exists, activate BDEF and class together. abap_activate_objects returns only "Activation successful." with no warnings on success. On failure it lists messages with line and column but no severity (U13 SRVD, 2026-09-16), so read severity in the editor. Service definitions report "Comments are not supported and will be deleted on save" for a leading block comment, and that message blocks activation: the same SRVD failed with a 19-line /* */ header and activated without it (live 2026-09-16; the DCLS, DDLX and BDEF headers did not block). The language allows comments [source: abap-docs /abap-docs-cloud/ABENCDS_GENERAL_SYNTAX_RULES, verified 2026-09-16], so this is the tooling. Keep SRVD sources free of comments. abap_atc_get_result returned only totals for a run with 43 infos (UI leg), but per-object findings[] (priority, check class, message id and title, quick-fix flags) for a run with a priority-3 warning (worker leg). Expect details for warnings and errors at least (both runs live 2026-09-16). A projection BDEF over a view with @AccessControl.authorizationCheck: #CHECK raises SLIN W607 (+BDL 549) "should be replaced with #MANDATORY" (priority 3; it does not block activation). An OData V4 Web API binding reports bindingCategory: "1" (UI: "0") in fetch_services.

  • [2026-09-16] OData V4 identity on a RAP UI service: the binding name is only the URL's first segment. For definition ZUI_ARENA_CHANGE_APPROVAL and binding ZUI_ARENA_CHANGE_APPR_O4, the live $metadata uses namespace com.sap.gateway.srvd.zui_arena_change_approval.v0001, from the service definition, and entity type ChangeApprovalType, with a Type suffix and no underscore (live 2026-09-16, docs/v2/zui_arena_change_appr_o4.metadata.xml). The earlier assumption that the namespace carries the binding name and a _Type suffix was wrong. RAP also emits __OperationControl with Core.OperationAvailable Path="_it/__OperationControl/<action>" for features : instance actions, SAP__Messages, Nullable="false" on non-key elements, and cast( case … as abap_boolean preserving type ) surfaces as Edm.Boolean (INT1 literals as Edm.Byte, utclong as Edm.DateTimeOffset Precision="7"). Copying $metadata from Chrome's XML tree view decodes &amp; to &; use view-source: or re-escape.

  • [2026-09-16] Live RAP $metadata works verbatim as Fiori mock metadata (@sap-ux/fe-mockserver-core 1.7.15 serves it and accepts $select on __OperationControl/approve). Mock rows must then carry __OperationControl, or the enablement paths resolve to nothing. The app's ESLint config checks only TS/JS, so run npx @ui5/linter for UI5. The fiori-mcp list_functionality for app/ offers only add/delete page and controller extension, so dataSource and annotation edits are made directly. The cached @ui5/manifest 1.89.0 schema rejects the generator's manifest _version: "2.11.0"; that is not a defect in the app.

  • [2026-09-16] A published custom UI service answers on the UI host, not the -api host, for a business user. fetch_service_information reports the service URL on my431353-api.s4hana.cloud.sap (with sap-client=080), but a browser business-user call there is refused as "Forbidden / blocked by UCON". The same path on my431353.s4hana.cloud.sap returns $metadata for the developer user, before any IAM app exists (live 2026-09-16). The -api host is the communication-user entry point (communication arrangement), so the worker's A2X service goes there through a custom communication scenario (V-2), never the UI service. After publishing, ADT for VS Code reports " published locally".

  • [2026-09-16] In ADT for VS Code, publishing a service binding is a code lens, not a button or a palette command. Open the binding's .srvb.json source and click the Publish code lens; Unpublish and Preview… work the same way [source: sap-docs sap-help loio dd6f5de63e7245a7abe7e39389841596 "ABAP RAP Tools", verified 2026-09-16]. The extension (1.1.2) contributes no publish command, and the SAP ADT MCP has no publish tool. create_object for a class returns only the global .clas.abap path, no local-types (CCIMP) path.

  • [2026-09-16] abap-adt-mcp 2.1.0 auto-detects a browser for SSO only on macOS. detectBrowser() checks SAP_BROWSER_PATH and then three /Applications/… paths; otherwise it throws "No Chrome/Edge/Brave found for SSO login" (dist/lib/browserLogin.js:38-50, read 2026-09-16). On this Linux host the server env must carry SAP_BROWSER_PATH=/usr/bin/google-chrome along with the desktop display variables. Login harvests the HttpOnly MYSAPSSO2/SAP_SESSIONID cookies over the Chrome DevTools Protocol (puppeteer-core). listSystems needs no SAP call and is the safe first probe of its policy. Its SSO does not work from a remote shell: there is no localhost redirect (SAML returns to the tenant), and the sign-in must be typed into the server-side Chrome window (headless: false, profile ~/.abap-adt-mcp/sso/<host>, --remote-debugging-port=0, 300 s timeout). Over multi-hop SSH neither the desktop nor a DevTools relay is reachable in practice. Do the first sign-in at the server's desktop; with "stay signed in" later logins should complete unattended (unverified, 2026-09-16).

  • [2026-09-16] Create fields for the remaining RAP types (get_object_type_details on 0M7DEJI): CLAS/OC: packageName, name (≤30), description, optional superclass, interfaces. DCLS/DL: packageName, name (≤30), description, optional protectedEntity. DDLX/EX: packageName, name (≤40), description, optional extendedEntity [source: adt-mcp abap_creation-get_object_type_details on 0M7DEJI, verified 2026-09-16]. Pass protectedEntity / extendedEntity so the editor starts from the right skeleton. DCLS creation does not require the protected entity to be active. ZC_CHANGEAPPROVAL_A2X access control validated and was created while its DDLS was still empty and never activated (unlike BDEF/SRVB creation). SRVB/SVB bindingType values are exactly OData V2 - UI, OData V2 - Web API, OData V4 - UI, OData V4 - Web API, whatever label the release notes use [source: adt-mcp abap_creation-run_validation / create_object / get_object_type_details on 0M7DEJI, verified 2026-09-16].

  • [2026-09-16] Service binding names: SAP Help says 26 characters, the ADT MCP allows 30. The 26-character limit appears on the ABAP Cloud, SAP Cloud Platform and ABAP Platform 2021 "Creating Service Binding" pages [source: sap-docs sap-help loio 4bad0d7280b742a68dd2d42b1441f09a, verified 2026-09-16]. get_object_type_details reports 30 for SRVB/SVB, and the x-ui-service generator itself emits 29–30-character bindings, which activated unpublished on my431353 on 2026-09-15. Whether publishing accepts more than 26 is unknown. Stay at 26 or fewer rather than find out after a whole BO leg. The generator does not keep to the documented limit, which is one more reason not to use it for names that ship.

  • [2026-09-16] In an OData V4 binding URL /sap/opu/odata4/sap/<binding>/srvd/sap/<alias>/0001/, the second segment is the binding's service name (alias), not necessarily the service definition name. It can be renamed only while the binding is unpublished [source: sap-docs community-14433266 "How to rename an OData V4 service?", verified 2026-09-16]. Check it before publishing.

  • [2026-09-16] CDS CASE results take the longest branch type, and untyped integer literals are typed INT1..INT8 by value [source: abap-docs /abap-docs-cloud/ABENCDS_CASE_EXPRESSION_V2, ABENCDS_UNTYPED_LITERAL_V2, verified 2026-09-16]. A text CASE over status codes becomes CHAR(n) of the longest literal, and a 0–5 criticality becomes INT1. Cast explicitly when the OData type matters.

  • [2026-09-16] The ui5-mcp-server linter cannot see this repo. The server is reached over SSE on mcp.alexzaw.dev, and run_ui5_linter on app/ fails with ENOENT: realpath '/home/alexz/…/arena-plm/app' (live 2026-09-16). Its doc/API tools work; its file-based tools (linter, manifest validation, project info) do not. For real UI5 code changes, lint locally (npx @ui5/linter inside app/) or run the server where the files are.

  • [2026-09-16] The Fiori mock server does not simulate bound actions. Without a per-entity webapp/localService/mainService/data/<Entity>.js exporting executeAction, @sap-ux/fe-mockserver-core 1.7.15 returns the action data unchanged (node_modules/@sap-ux/fe-mockserver-core/dist/mockdata/fileBasedMockData.js:547-549, read locally 2026-09-16). Status transitions can be shown only against the live RAP service, or with a hand-written mock handler.

  • [2026-09-16] Cast the whole CASE to a data element, never each branch. "If a data element is specified for dtype, the result … inherits the semantic properties of the data element. An exception to this is the use of the CAST expression within a case distinction." Write cast( case … end as abap_boolean preserving type ). PRESERVING TYPE requires an exact match of built-in type and length [source: abap-docs /abap-docs-cloud/ABENCDS_CAST_EXPRESSION_V2, verified 2026-09-16]. To guarantee that length, use typed literals in the branches (abap.char'X', abap.char' ' are CHAR(1)): an untyped character literal's length is only "at least as large as the actual length of the literal" [source: abap-docs /abap-docs-cloud/ABENCDS_TYPED_LITERAL_V2, ABENCDS_UNTYPED_LITERAL_V2, verified 2026-09-16]. ABAP_BOOLEAN is CHAR 1 [source: abap-docs /abap-docs-standard/ABENDATAOBJECTS_TRUE_VALUE_GUIDL, verified 2026-09-16]. The same caveat means a CASE over untyped status literals is not provably CHAR(n) of the longest literal.

  • [2026-09-16] sap-released-objects has no SRVB object type for public_cloud, the same gap as DDLS (use CDS_STOB for views). Binding release questions cannot be answered from that server.

  • [2026-09-15] Developer business catalogs: SAP_A4C_BC_* is the Public Cloud / BTP ABAP family; SAP_A4_BC_* is on-premise / Private Cloud. On my431353 the developer role carries the A4C catalogs (e.g. SAP_A4C_BC_DEV_OBJ_DIS_PC, SAP_A4C_BC_DEV_UID_PC [source: sap-docs sap-help loio f4881a9b9141430dba1c6a76e7f456dc, verified 2026-09-15]); the SAP_A4_BC_DEV_UID_PC name in the fiori-tools-samples README is the wrong family for this tenant. Split stated by the tenant owner 2026-09-15; no SAP Help page in the loadout spells it out — do not "fix" a role toward A4.

  • [2026-09-15] Write probes on the dev tenant: $TMP and ZLOCAL both fail, for different reasons. $TMP is a classic package, so creating there checks the Standard ABAP language version and a cloud-development role is denied with "not authorized to make changes (S_ABPLNGVS)"; ZLOCAL is a structure package ("cannot contain development objects"). Objects go in a sub-package of ZLOCAL (prototyping, no workbench request for sub-packages created from release 2508) or of ZCUSTOM_DEVELOPMENT (transport layer YDEV, importable into test/prod); ZLOCAL→ZCUSTOM reassignment is allowed, the reverse is not; ZLOCAL plays the $TMP role in Public Cloud [source: sap-docs sap-help loio a0d80f14fcb74cb1ac8fb6f3d9ad48cc (2608), verified 2026-09-15]. Refusals verified live via ADT MCP, 2026-09-15.

  • [2026-09-14] sap-released-objects has no DDLS type for public_cloud; CDS views are catalogued as CDS_STOB. A "not found" for I_PRODUCT under DDLS is a type-key artefact, not a release verdict — query with object_type=CDS_STOB (verified 2026-09-14).

  • [2026-09-14] A Fiori Elements app generated on hand-written mock metadata agrees with itself and hides service-shape mismatches. ABAP Cloud OData V4 services emit Schema Namespace="com.sap.gateway.<srvd|srvd_a2x>.<service-binding-name>.v0001" Alias="SAP__self" — the name in the namespace is the service binding (group), which is also the first URL segment; the service definition / alias is the second segment (/api_currencyexchangerate/srvd_a2x/sap/currencyexchangerate/0001 → …srvd_a2x.api_currencyexchangerate.v0001; /api_product/srvd_a2x/sap/product/0002 [source: sap-docs sap-help loio 5f45f125dfe84c20beb18f5de1512f5a, verified 2026-09-14]). Shown for released A2X services; for custom UI (srvd) bindings it is strongly indicated, not proven — V-2 confirms. Also, entity types suffixed _Type, un-suffixed entity sets, and vocabulary aliases SAP__common/SAP__capabilities/SAP__core [source: live tenant my429480 EDMX ~/projects/sap-apps/exchange-rates-cap/srv/external/API_CURRENCYEXCHANGERATE.edmx lines 13–31, 55, 57, 95, verified 2026-09-14]. Front-end annotation targets written against any other namespace silently match nothing at runtime (no columns, no filters, no actions). Reconcile every Annotations Target, the edmx:Reference include namespace, and every DataFieldForAction.Action against the live $metadata before the first deploy — never trust the mock.

  • [2026-09-04] The predecessor's config/procurement-type.json, catalog-to-planning-strategy.json, and arena-category-to-sap.json were loaded but never read by any code path, and procurement-type's keys (MTS/OTS) and values (E) do not match the Arena field (1–9 Item Type) or SAP values (X) the code actually used. Do not port these configs on trust — re-derive every mapping via POC.

  • [2026-09-04] Deployment/exposure target is an OPEN decision (PRD OQ-3). The old estate fronts apps through a shared mcl-apps approuter with shared XSUAA; a CF route mapped to more than one app round-robins and causes ~50% intermittent 404s. Whatever is chosen, audit routes after every push.

  • [2026-09-04] HANA Cloud compute is the dominant cost lever of the old estate; an auto-stop systemd timer (sap-apps/ops/hana-autostop) guards it. If this package binds HANA, account for the stop/start cycle in the design.

  • [2026-09-04] The predecessor kept its approval queue in a SQLite file inside the CF droplet — wiped on every restart. Durable persistence is a hard requirement of the rebuild (PRD ARN-003), not an optimization.

  • [2026-09-10] Predecessor BOM and DOCUMENTS syncs are non-idempotent (re-approval creates a second MaterialBOM header / duplicate attachments) — the rebuild's executors must check existence first (ARN-006).

  • [2026-09-10] The predecessor uses two disagreeing change-master lookup keys (startswith(ChangeNumberDescription,'ECO:') in the product link vs ReasonForChangeText eq 'ARENA <eco>' everywhere else; the 40-char truncation can split them). The rebuild uses exactly one key.

  • [2026-09-10] Document sync links only Arena files with storageMethodName === 'WEB' (URL links, no bytes) — blob-stored files are silently skipped, and the DOKAR mapping config existed but was ignored (BUS1001006/text/url hardcoded). Decide blob handling explicitly in the rebuild.

  • [2026-09-04] If .github/agents/sap-odata-*.agent.md, a root AGENTS.md about "SAP OData Workspace", or .vscode/mcp.json pointing at sap-odata-mcp.exe appear in this repo — delete them. They are scaffolded (evidence points to the harieprasad.sap-odata-mcp VS Code extension writing its workspace kit on folder open; observed 2026-09-04 14:40, removed same day). The referenced MCP binary is a Windows .exe that cannot run on this host, so those agents fabricate SAP results — the documented failure the founding kit exists to prevent. The root AGENTS.md is the dangerous part: Claude Code reads it as project instructions. [Update 2026-09-04 15:32] The suspect extension was uninstalled from all machines (verified: deregistered and marked obsolete on this host; directory purges on next VS Code reload). If the bundle STILL reappears after this, the extension hypothesis was wrong — hunt the real writer instead of re-deleting silently.