diff --git a/SKILL.md b/SKILL.md index 26c9e24..b12f2ef 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,332 +1,238 @@ --- name: sap-cli -description: "Use when you need to operate SAP ABAP development objects — syncing code, querying metadata, managing transports, reading tables, or any task involving SAP ADT REST API. Trigger on: ABAP objects, SAP development, transport requests, DDIC types, SE11/SE16N/SE38/SE80 equivalents, 'sync to SAP', 'download from SAP', 'activate ABAP', 'read SAP table'. NOT for developing sap-cli itself." +description: "SAP ABAP ADT CLI for AI agents. Trigger when: operating ABAP objects, syncing code to/from SAP, managing transports, querying DDIC, running ABAP programs, reading SAP tables, or any SAP development task via terminal." version: "2.3.0" author: WuRangyu license: MIT +triggers: + - "ABAP development" + - "sync to SAP" + - "download from SAP" + - "SAP transport request" + - "DDIC/SE11/SE16N/SE38/SE80" + - "SAP ADT REST API" --- -# sap-cli — SAP ABAP 开发对象管理工具 +# sap-cli — SAP ABAP Development Object Management -sap-cli 是一个 Python CLI,封装 SAP ADT REST API,让你在终端完成 ABAP 开发对象的完整生命周期管理。 +Python CLI wrapping the SAP ADT REST API. Manages the full lifecycle of ABAP development objects from the terminal. Core loop: **download → local edit → sync back to SAP**. -核心工作流:**download → 本地编辑 → sync 回 SAP** - ---- - -## 首次使用:安装与配置 - -### 第 1 步:安装 sap-cli - -如果 `python main.py --help` 无法执行,先运行安装脚本: +## Quick Start ```bash -python /scripts/setup.py install -``` - -此脚本会: -1. 安装依赖(`requests`) -2. 将 sap-cli 安装为可编辑的 Python 包 -3. 创建配置文件模板 - -安装完成后验证: -```bash -python main.py --help -``` - -### 第 2 步:配置 SAP 连接 - -编辑配置文件 `assets/config.ini`(从 `config.ini.example` 复制): - -```ini -[SAP] -host = http://your-sap-server:8000 -client = 100 -user = your_username -password = your_password -``` - -也可通过环境变量配置(优先级高于配置文件): -- `SAP_HOST` / `SAP_CLIENT` / `SAP_USER` / `SAP_PASSWORD` - -### 第 3 步:验证连接 - -```bash -python main.py config show -python main.py transport list -``` - ---- - -## 调用方式 - -```bash -cd /assets && python main.py [options] -``` - -全局参数(在命令之前): - -| 参数 | 说明 | -|------|------| -| `--profile ` | 使用指定配置 profile | -| `--verify-ssl` | 启用 SSL 验证(默认关闭) | - ---- - -## 命令速查 - -### CRUD(对象生命周期) - -```bash -# 创建新对象 -python main.py create --name ZMY_CLASS --type class --corr_nr DEVK901XXX -python main.py create --name ZMY_DOMAIN --type domain --definition domain.json --corr_nr DEVK901XXX - -# 查询元数据 -python main.py info --name ZMY_CLASS --type class - -# 下载源码 +# 1. Single object: download → edit → sync python main.py download --name ZMY_CLASS --type class --path ./src - -# 同步到 SAP(lock → write → unlock → syntax check → activate) +# edit ./src/zmy_class.abap python main.py sync --name ZMY_CLASS --type class --path ./src/zmy_class.abap --corr_nr DEVK901XXX -# 删除对象 -python main.py delete --name ZMY_CLASS --type class - -# 单独激活(sap-cli 内置 NW 7.40 double-activate) -python main.py activate --name ZMY_CLASS --type class --corr_nr DEVK901XXX - -# 单独上传(仅锁定→写入→解锁,不检查不激活) -python main.py upload --name ZMY_CLASS --type class --path ./src/zmy_class.abap --corr_nr DEVK901XXX - -# 语法检查(检查远程对象,不上传不激活) -python main.py syntax-check --name ZMY_CLASS --type class -``` - -**create 关键参数**: - -| 参数 | 说明 | -|------|------| -| `--description` | 对象描述 | -| `--source <.abap>` | 自定义源码文件 | -| `--definition <.json>` | DDIC 定义文件(domain/dataelement/table/structure/tabletype) | -| `--package ` | SAP 包(默认 `$TMP`) | -| `--corr_nr` | 传输请求号 | - -### 批量操作 - -```bash -# 初始化项目结构 -python main.py init --path ./my_project - -# 刷新对象状态 -python main.py refresh --path ./my_project - -# 批量同步(自动按依赖拓扑排序) +# 2. Batch project sync (auto dependency-ordered) +python main.py init --path ./my_project # add objects under src/ python main.py sync --all --path ./my_project --corr_nr DEVK901XXX -python main.py sync --all --path ./my_project --corr_nr DEVK901XXX --dry-run -``` -依赖排序:`domain(10) → dataelement(20) → table(30) → tabletype(40) → interface(50) → class(60) → function(70) → report(80)` - -### 数据查询 - -```bash -# 表结构(SE11 等价) +# 3. Table query: structure (SE11) + data (SE16N) python main.py show-table --name ZMY_TABLE - -# 表数据(SE16N 等价) -python main.py read-table --name ZMY_TABLE -python main.py read-table --name ZMY_TABLE --fields "FIELD1,FIELD2" --where "FIELD1 = 'X'" --max-rows 50 - -# 列出对象 -python main.py list --type class --package ZSAPILOT - -# Where-Used -python main.py whereused --name ZMY_CLASS --type class - -# 源码搜索 -python main.py search --query "CALL FUNCTION 'Z_MY_FUNC'" +python main.py read-table --name ZMY_TABLE --fields "F1,F2" --where "F1 = 'X'" --max-rows 50 ``` -### 远程执行 +## Command Reference +Global params (before command): `--profile `, `--verify-ssl`. Optional params in `[ ]`. + +| Category | Command | Key Parameters | +|----------|---------|----------------| +| CRUD | `create` | `--name --type [--description --source <.abap> --definition <.json> --package <$TMP> --corr_nr]` | +| CRUD | `info` | `--name --type` [^1] | +| CRUD | `download` | `--name --type --path` | +| CRUD | `sync` | `--name --type --path --corr_nr` **(REQUIRED)** | +| CRUD | `delete` | `--name --type` | +| CRUD | `activate` | `--name --type --corr_nr` (built-in NW 7.40 double-activate) | +| CRUD | `upload` | `--name --type --path --corr_nr` (lock→write→unlock only; no check/activate) | +| CRUD | `syntax-check` | `--name --type` (remote object; no upload/activate) | +| Batch | `init` | `--path` | +| Batch | `refresh` | `--path` | +| Batch | `sync --all` | `--path --corr_nr [--dry-run]` | +| Batch | `analyze` | `--path` (dependency analysis) | +| Query | `show-table` | `--name` (SE11 structure) | +| Query | `read-table` | `--name [--fields --where --max-rows]` (SE16N data) | +| Query | `list` | `--type [--package --prefix]` | +| Query | `whereused` | `--name --type` | +| Query | `search` | `--query` | +| Query | `diff` | `--name --type --path` (local vs SAP) | +| Transport | `transport list` | — | +| Transport | `transport info` | `--corr_nr` | +| Transport | `transport release` | `--corr_nr` | +| Transport | `transport objects` | `--corr_nr` | +| Config | `config show` | — | +| Config | `config list-profiles` | — | +| Config | `config set` | ` ` | +| Config | `auth login` / `auth status` | password stored in keyring | +| Other | `run-program` | `--name` (SA38) | +| Other | `check` | `--name --type` (ATC) | +| Other | `format` | `--name --type` (Pretty Printer) | +| Other | `package create` | `--name` | +| Other | `cds download` | `--name --path` | +| Other | `scaffold` | `--name --template ` — alv-report, bapi-wrapper, interface-class, data-model | + +[^1]: ⚠️ `info` 不返回 `package`/`packageRef` 字段。需通过 ADT API 查询:`GET /sap/bc/adt/programs/programs/` 返回 XML 中含 ``。也可用 `list --package ` 反向查找。 + +Batch sync dependency order: `domain(10) → dataelement(20) → table(30) → tabletype(40) → interface(50) → class(60) → function(70) → report(80)`. + +Step-wise sync for lock-heavy automation — split the atomic `sync` when lock conflicts are frequent; upload now, verify, activate later: ```bash -# 执行 ABAP 程序(SA38 等价) -python main.py run-program --name ZSAPILOT_SETUP - -# 本地 vs SAP 差异 -python main.py diff --name ZMY_CLASS --type class --path ./src/zmy_class.abap +python main.py upload --name ZMY_CLASS --type class --path ./src/zmy_class.abap --corr_nr DEVK901XXX # lock→write→unlock only +python main.py syntax-check --name ZDEPENDENT_REPORT --type report # verify dependents (no upload) +python main.py activate --name ZMY_CLASS --type class --corr_nr DEVK901XXX # activate once clean ``` -### 传输请求 +### Supported Object Types (16) -```bash -python main.py transport list -python main.py transport info --corr_nr DEVK901XXX -python main.py transport release --corr_nr DEVK901XXX -python main.py transport objects --corr_nr DEVK901XXX -``` - -### 配置与认证 - -```bash -python main.py config show -python main.py config list-profiles -python main.py config set host s4h.example.com -python main.py auth login # 密码保存到 keyring -python main.py auth status -``` - -### 其他 - -```bash -python main.py check --name ZMY_CLASS --type class # ATC 检查 -python main.py format --name ZMY_CLASS --type class # Pretty Printer -python main.py package create --name ZSAPILOT # 创建包 -python main.py cds download --name ZMY_CDS --path ./src # CDS View -python main.py analyze --path ./src # 依赖分析 -python main.py scaffold --name ZMY_RPT --template alv-report # 项目模板 -# 模板: alv-report, bapi-wrapper, interface-class, data-model -``` - ---- - -## 支持的对象类型(16 种) - -| 分类 | 类型 | 有源码 | -|------|------|--------| -| 程序 | `report`, `include` | ✅ | +| Category | Types | Has source | +|----------|-------|------------| +| Programs | `report`, `include` | ✅ | | OOP | `class`, `interface` | ✅ | -| 函数 | `function` | ✅ | -| DDIC 基础 | `domain`, `dataelement`, `table`, `structure` | ✅ | -| DDIC 扩展 | `cdsview`, `view` | ✅ | -| DDIC 无源码 | `tabletype`, `messageclass`, `searchhelp`, `lockobject` | ❌ | -| 程序组 | `functiongroup` | ❌ | +| Functions | `function` | ✅ | +| DDIC base | `domain`, `dataelement`, `table`, `structure` | ✅ | +| DDIC ext | `cdsview`, `view` | ✅ | +| DDIC no-source | `tabletype`, `messageclass`, `searchhelp`, `lockobject` | ❌ | +| Groups | `functiongroup` | ❌ | -**函数命名格式**:必须使用 `组名/模块名` 格式,如 `ZMY_FGROUP/Z_MY_FUNC` +**Function naming**: must use `group/module`, e.g. `ZMY_FGROUP/Z_MY_FUNC`. ---- +## ⚠️ Constraints (HARD RULES — DO NOT VIOLATE) -## 常用工作流 +Full rules: `references/sap-tool-constraints.md`. Violations are intercepted by hooks. +质检标准: `references/abap-coding-rules.md`(28条规则,6大类:安全/性能/可维护性/错误处理/NW740兼容/风格)。质检员在代码审计时以此文档为唯一标准,判定体系:✅合规 / ❌违反(必须修复) / ⚠️建议(推荐修复)。 -### 1. 单对象编辑同步 +1. **Only `python main.py ` touches SAP** — no `requests`/`urllib`, no curl to ADT/SOAP/RFC, no `python -c` inline, no importing `ADTClient` internals, no hand-built ADT XML. Exceptions: `run-program` custom reports, explicit user request, or developing sap-cli itself. +2. **Never modify SAP standard objects** (`CL_*`, `CX_*`, `IF_*`, `SAPL*`, or any non-user original system) — read-only only: download/info/list/search/whereused/show-table/read-table/diff. When unsure, ask the user. +3. **Never write system tables** (TADIR, E071, SEOCLASS, REPOSRC, D010SINF, T000, TDEVC, TSTC) — read-only SELECT is fine; any write needs explicit authorization. +4. **`sync` must carry `--corr_nr`** — otherwise it triggers an interactive transport-request prompt. +5. **Non-interactive `delete` needs piped confirm** — `echo "yes" | python main.py delete ...` (else EOFError). +6. **DDIC `sync` failure on NW 7.40 (HTTP 406) is a system limit** — do not retry; report to the user for SE09. -```bash -python main.py download --name ZMY_CLASS --type class --path ./src -# 用编辑器修改 ./src/zmy_class.abap -python main.py sync --name ZMY_CLASS --type class --path ./src/zmy_class.abap --corr_nr DEVK901XXX +## 🔄 Standard Workflow(SAP 对象操作标准流程) + +> 所有 SAP 对象修改操作必须按此顺序执行,不得跳过或调整步骤顺序。 + +``` +步骤1 info — 确认程序存在、已激活、关联请求号 + ├─ 有请求号 → 记录 + └─ 无请求号 → transport list 找老板的请求号推荐 + → 无合适的则帮老板新建(含请求描述) + +步骤2 download — 源码下载到本地 + +步骤3 五角色流水线:研究员 → 创作者 → 质检 → 老板批准 + +步骤4 建 Gitee 仓库(ADT 层级:src/包/类型/对象/)+ 同步确认请求号 + └─ 请求号写入 doc 文档 + +步骤5 工程师改代码(本地,不 commit) + +步骤6 请求号绑定对象(如 SE09 未自动绑定) + +步骤7 sync --corr_nr → 语法检查 → 迭代修复 → 激活成功 + └─ 全程带请求号,不要到编译阶段才关联 + +步骤8 激活成功 → git commit + tag → push + └─ git = 细粒度变更追溯 / 请求号 = 系统大版本管理 ``` -### 2. 批量项目同步 +**核心原则:** +- info 同时查请求号,不单独处理 +- 建 Gitee 仓库同时确认请求号,不滞后 +- sync 全程带 `--corr_nr`,不等编译阶段补 +- 只有激活成功才 git commit,不是每次本地改就提交 +- git 管变更细节,请求号管系统大版本 -```bash -python main.py init --path ./my_project -# 添加对象到 src/ 目录 -python main.py sync --all --path ./my_project --corr_nr DEVK901XXX +## 📦 Gitee仓库规范 + +### 目录结构(ADT 层级) + +每个 SAP 项目的 Gitee 仓库按 ADT 层级组织源码: + +``` +sap-/ +├── src/ +│ └── / # SAP 包名(如 $ZIDTR) +│ ├── report/ # 程序类型目录 +│ │ └── ZXXX.abap # ABAP 源码(.abap 扩展名) +│ ├── class/ +│ │ └── ZCL_XXX.abap +│ ├── interface/ +│ │ └── ZIF_XXX.abap +│ ├── function/ +│ │ └── ZGROUP_ZFUNC.abap +│ ├── domain/ +│ ├── dataelement/ +│ ├── table/ +│ └── ... +├── doc/ # 文档(请求号记录、设计说明) +│ └── transport-.md # 每次传输的请求号记录 +├── references/ # 参照文档 +└── log/ # 错误知识库 ``` -### 3. DDIC 对象创建 +### 请求号同步确认 -```bash -# 准备 JSON 定义文件 -python main.py create --name ZMY_DOMAIN --type domain --definition domain.json --corr_nr DEVK901XXX -``` +1. **创建仓库时**:确认传输请求号(`transport list` 或 `transport info --corr_nr DEVK9XXXXX`),写入 `doc/transport-.md` +2. **每次 sync 前**:确认请求号仍有效(未释放、未满),检查 `transport objects --corr_nr DEVK9XXXXX` 确保对象已绑定 +3. **激活成功后**:更新 `doc/` 记录本次变更摘要 +4. **仓库命名建议**:`sap-`,小写、hyphen 分隔,如 `sap-zidtradmin` -### 4. 数据查询 +### Git 提交时机 -```bash -python main.py show-table --name ZMY_TABLE # 结构 -python main.py read-table --name ZMY_TABLE --max-rows 100 # 数据 -``` +- **只有 `sync` 激活成功后才 git commit**,不是每次本地修改就提交 +- commit message 包含:对象名 + 请求号 + 变更摘要 +- tag 使用请求号命名:`DEVK9XXXXX` +- git 管变更细节追溯,请求号管系统大版本管理 -### 5. 远程执行 ABAP 程序 +## Error Handling (Quick Reference) -```bash -python main.py run-program --name ZSAPILOT_SETUP -``` +错误知识库: `log/` 目录(LLM-WIKI 格式),每个错误独立一页。**先查 log/,再查 `references/error-handling.md`**。新错误按 `log/SCHEMA.md` 规范入库。 -### 6. 分步操作(上传→检查→激活) +Full flow: `references/error-handling.md`. -```bash -# 适合锁定冲突频繁的自动化场景:先安全上传,稍后再激活 -python main.py upload --name ZMY_CLASS --type class --path ./src/zmy_class.abap --corr_nr DEVK901XXX +| Error Pattern | Action | +|---|---| +| HTTP 406 (DDIC Lock) | System limit — report to user → SE09 manual DDIC sync | +| HTTP 403 (Locked) | Residual enqueue lock — try `run-program` clear-locks report, then SM12 | +| HTTP 400 (SaveFailure) | Class DEFINITION mismatch — diff local vs SAP source | +| HTTP 404 (DDIC download/sync) | NW 7.40 has no `/source/main` endpoint (7.50+) — DDIC objects only create/info/delete | +| HTTP 423 (Transport lock) | Object bound to transport — report to user → SE09 | +| HTTP 500 (set_source) | Lock 成功但写入 500,传输请求中无对象 → SE09 手动添加对象到请求 → `log/adt-set-source-500.md` | +| Activate fails, still inactive | double-activate built-in; still fails → report → SE09 | +| Open SQL @ 转义不一致 | 语法检查 2 错误——一处用 `@` 则全局必须一致(`WHERE field = @lv_var` → `INTO TABLE @lt_data`) → `log/adt-nw740-open-sql-consistency.md` | +| `WITH EMPTY KEY` dump | NW 7.40 unsupported — use `WITH NON-UNIQUE KEY` / `WITH DEFAULT KEY` | +| Function name error | Missing `/` separator — use `ZGROUP/Z_FUNC` | +| EOFError on delete | Non-interactive — prepend `echo "yes" \|` | -# 修改表结构后验证关联程序语法 -python main.py syntax-check --name ZDEPENDENT_REPORT --type report +General flow: **diagnose** (params / file / transport / network / object existence) → **usage error**: fix & retry → **sap-cli limit**: report, never bypass → **SAP limit**: exhaust automation below → **confirmed un-automatable**: request SAP GUI op. -# 确认无问题后单独激活 -python main.py activate --name ZMY_CLASS --type class --corr_nr DEVK901XXX -``` +Automation fallbacks — exhaust in order before requesting any SAP GUI action: +1. **Adjust params** — different `--corr_nr`, or `--dry-run` to preview a batch before committing. +2. **`run-program` workaround** — sync + run a helper report (e.g. `ZSAPILOT_CLEAR_LOCKS`) to clear residual enqueue locks. +3. **`delete` + recreate** — only if the object needs a full rewrite; `delete` may fail with HTTP 423 (transport-bound) → then it is SE09. +4. **`activate` retry** — double-activate is built in; if it still fails, stop retrying and report — do not chase other activation strategies. ---- +- **Never bypass sap-cli** (no requests / curl / ADTClient) — report the failure and its cause. +- **Give the complete change checklist at once** — do the full comparison first (`show-table` vs code field refs); never surface issues incrementally. +- **System-table writes** — 4-step authorization: explain *** → list exact statements → state risks & alternatives → await explicit approval. -## ⚠️ 使用约束(必须遵守) +## NW 7.40 Compatibility (E2E verified — v2.3.0) -> 详细规则见 `references/sap-tool-constraints.md` 和 `references/error-handling.md` +Full matrix: `docs/NW740-COMPATIBILITY.md`. -1. **只使用 `python main.py <命令>` 操作 SAP** — 禁止用 requests、curl 直接调 ADT/SOAP -2. **禁止修改 SAP 标准对象**(`CL_*`、`SAPL*` 等)— 只允许只读操作 -3. **禁止未授权操作系统表**(TADIR、E071、SEOCLASS 等)— SELECT 诊断可以 -4. **sync 必须带 `--corr_nr`** — 否则触发交互提示 -5. **非交互 delete 用管道确认** — `echo "yes" | python main.py delete ...`(否则 EOFError) +| Availability | Types | +|---|---| +| Full (download → edit → sync closed loop) | `report`, `class`, `interface`, `function`, `functiongroup`, `include` | +| CRUD only (create/info/delete, no source edit) | `domain`, `dataelement` — DDIC stored as XML at object URI, no `/source/main` endpoint (7.50+ feature) | +| Endpoint missing (404/415) | `table`, `structure`, `tabletype`, `view`, `messageclass`, `searchhelp`, `lockobject`, `cdsview` | ---- - -## 常见问题速查 - -| 问题 | 原因 | 解决 | -|------|------|------| -| HTTP 406(DDIC sync Lock) | NW 7.40 DDIC sync 路径 lock 仍 406(delete 已修复) | 报告用户 → SE09 手动同步 DDIC | -| HTTP 403(Locked) | 残留 enqueue lock | 先试 `run-program` 清锁,不行 → SM12 | -| HTTP 400(SaveFailure) | 类 DEFINITION 与 SAP 不一致 | 对比本地 vs SAP 源码 | -| HTTP 404(DDIC download/sync) | NW 7.40 无 `/source/main` 端点(7.50+ 特性) | DDIC 对象只能 create/info/delete,不能 download/sync | -| HTTP 423(Transport lock) | 对象绑定在传输请求中 | 报告用户 → SE09 | -| 激活失败 | NW 7.40 ADT 限制 | sap-cli 内置 double-activate,仍失败 → SE09 | -| `WITH EMPTY KEY` dump | NW 7.40 不支持 | 改用 `WITH NON-UNIQUE KEY` | -| 函数名格式错误 | 缺少 `/` 分隔符 | 使用 `ZGROUP/Z_FUNC` 格式 | - -## NW 7.40 兼容性(真实系统 E2E 验证 — v2.2.1) - -> 完整矩阵见 `docs/NW740-COMPATIBILITY.md` - -### 完整可用(download → 编辑 → sync 全闭环) - -`report` · `class` · `interface` · `function` · `functiongroup` · `include` - -### 仅 CRUD 可用(create/info/delete,无源码编辑) - -`domain` · `dataelement` — NW 7.40 的 DDIC 对象以 XML 存储于对象 URI,没有独立的 source/main 端点(7.50+ 特性)。即便 Eclipse ADT 在 7.40 上也无法编辑 DDIC 源码。 - -### 端点缺失(代码已就位,待更高版本验证) - -`table` · `structure` · `tabletype` · `view` · `messageclass` · `searchhelp` · `lockobject` · `cdsview` — NW 7.40 ADT 端点层返回 404/415,请求未到达 XML 解析。 - -### 其他 NW 7.40 特性 - -- **DDIC Lock**:delete 已修复(专用 Accept 头),sync 路径仍 406 -- **激活误报**:首次激活可能报失败但实际成功,sap-cli 已内置 double-activate -- **ABAP 语法限制**:不支持字符串模板 `\|...\|`、inline 声明 `DATA(...)`、`WITH EMPTY KEY` -- **CSRF Token**:所有写操作必须携带,sap-cli 自动处理 - ---- - -## 分发与安装(团队成员) - -### 方式 1:Hermes Agent -```bash -git clone ~/AppData/Local/hermes/skills/productivity/sap-cli -python ~/AppData/Local/hermes/skills/productivity/sap-cli/scripts/setup.py all -``` - -### 方式 2:Claude Code -```bash -git clone ~/.claude/skills/sap-cli -python ~/.claude/skills/sap-cli/scripts/setup.py all -``` - -安装后验证: -```bash -cd /assets && python main.py --help -``` +NW 7.40 specifics: +- **DDIC Lock** — `delete` fixed (dedicated Accept header); `sync` still 406. +- **Activation false-negative** — first activate may report failure but actually succeed; double-activate is built in. +- **ABAP syntax limits** — no string templates `\|...\|`, no inline `DATA(...)`, no `WITH EMPTY KEY`. +- **Open SQL @ 转义一致性** — NW 7.40 要求一处用 `@` 则全 SQL 必须一致(`WHERE field = @lv_var` → `INTO TABLE @lt_data`),混用导致语法检查 2 错误。详见 `log/adt-nw740-open-sql-consistency.md`。 +- **set_source HTTP 500(传输请求无对象)** — Lock 成功但写入 500,根因:Lock API 创建锁句柄但未绑定对象到传输请求。NW 7.40 需 SE09 手动添加对象。详见 `log/adt-set-source-500.md`。 +- **CSRF token** — all writes require it; sap-cli handles automatically.