dsh-d365-kit 0.34.1 → 0.34.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/CHANGELOG.md +319 -308
- package/CHANGELOG.zh-CN.md +318 -307
- package/INSTALL.md +94 -94
- package/INSTALL.zh-CN.md +93 -93
- package/LICENSE +21 -21
- package/README.md +336 -327
- package/README.zh-CN.md +336 -327
- package/client.js +1818 -1818
- package/cordis.patch.yml +44 -44
- package/docs/en/architecture.md +172 -172
- package/docs/en/checks.md +184 -184
- package/docs/en/installation.md +151 -151
- package/docs/en/permissions.md +161 -161
- package/docs/en/scenarios.md +202 -202
- package/docs/en/tools.md +665 -665
- package/docs/en/troubleshooting.md +131 -131
- package/docs/zh/architecture.md +172 -172
- package/docs/zh/checks.md +184 -184
- package/docs/zh/installation.md +151 -151
- package/docs/zh/permissions.md +161 -161
- package/docs/zh/scenarios.md +202 -202
- package/docs/zh/tools.md +665 -665
- package/docs/zh/troubleshooting.md +131 -131
- package/examples/prompts.en.md +206 -206
- package/examples/prompts.zh-CN.md +206 -206
- package/examples/waivers.example.json +20 -20
- package/icon.svg +6 -6
- package/index.js +139 -139
- package/lib/auth.js +181 -181
- package/lib/client.js +496 -496
- package/lib/code-check.js +549 -549
- package/lib/config.js +323 -323
- package/lib/contract-history.js +49 -49
- package/lib/contract.js +583 -583
- package/lib/csharp-check.js +493 -493
- package/lib/data-migrate.js +1318 -1318
- package/lib/data-policy.js +78 -78
- package/lib/error-codes.js +80 -80
- package/lib/gitlab.js +161 -161
- package/lib/health.js +205 -205
- package/lib/migrate.js +732 -732
- package/lib/preflight.js +116 -116
- package/lib/refs.js +191 -191
- package/lib/resources.js +320 -320
- package/lib/retention-defaults.js +17 -17
- package/lib/retention.js +384 -384
- package/lib/runtime.js +232 -232
- package/lib/settings-http.js +512 -512
- package/lib/settings-store.js +371 -371
- package/lib/snapshot.js +149 -149
- package/lib/tool-code-check.js +133 -133
- package/lib/tool-configure.js +169 -169
- package/lib/tool-contract.js +376 -376
- package/lib/tool-data-sync.js +559 -559
- package/lib/tool-data-write.js +203 -203
- package/lib/tool-data.js +282 -282
- package/lib/tool-diff.js +484 -484
- package/lib/tool-environments.js +106 -106
- package/lib/tool-flows.js +540 -540
- package/lib/tool-health.js +127 -127
- package/lib/tool-layers.js +497 -497
- package/lib/tool-metadata.js +417 -417
- package/lib/tool-migrate.js +102 -102
- package/lib/tool-refs.js +206 -206
- package/lib/tool-reports.js +299 -299
- package/lib/tool-sync.js +430 -430
- package/lib/tool-write.js +563 -563
- package/lib/waivers.js +83 -83
- package/lib/write-actions.js +1374 -1374
- package/lib/write-policy.js +199 -199
- package/lib/write-store.js +167 -167
- package/locale/en.json +68 -68
- package/locale/zh.json +68 -68
- package/package.json +2 -2
- package/skills/d365-troubleshooting/SKILL.md +115 -115
package/docs/zh/installation.md
CHANGED
|
@@ -1,151 +1,151 @@
|
|
|
1
|
-
# 安装与配置
|
|
2
|
-
|
|
3
|
-
**[English](../en/installation.md)** | 简体中文
|
|
4
|
-
|
|
5
|
-
## 前置
|
|
6
|
-
|
|
7
|
-
| 项 | 要求 |
|
|
8
|
-
| --- | --- |
|
|
9
|
-
| DSH | 桌面版,能打开 Web GUI(设置页是 Web 客户端插件) |
|
|
10
|
-
| 网络 | 能访问环境域名(`*.crm*.dynamics.com` 或对应区域)与登录端点 |
|
|
11
|
-
| 应用用户 | Entra ID 里的应用注册 + 在 Dataverse 里建的应用用户,拿到 `ClientId` / `ClientSecret` / `TenantId` |
|
|
12
|
-
| 权限 | 读元数据、读解决方案(`prvReadSolution`)、读自定义 API(`prvReadCustomAPI`)、读层表(`prvReadmsdyn_componentlayer`) |
|
|
13
|
-
|
|
14
|
-
只读应用用户就够用;写入能力用到的 `CreateAttribute` 等消息,需要该用户在环境里有对应权限(系统定制员或自定义角色里勾相应权限)。
|
|
15
|
-
|
|
16
|
-
> 应用用户在一个环境里建好后,要在**每个**目标环境各建一次(同一个 ClientId 可以复用)。
|
|
17
|
-
|
|
18
|
-
## 安装
|
|
19
|
-
|
|
20
|
-
### 方式一:从 npm 安装(最短)
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
plugin_manager install_bundle target="dsh-d365-kit@0.34.
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
前提是包已经发到 pnpm 配置的 registry——公网 npm 或内网镜像都行。包名没有 scope,不需要配 scope 映射。要装指定版本就写 `dsh-d365-kit@<版本>`。
|
|
27
|
-
|
|
28
|
-
> **只发 GitHub 不等于发了 npm。** 仓库可以直接用第二种 `git+https://...` 装,`npm install github:<user>/<repo>#<tag>` 也能装;但 `install_bundle target="dsh-d365-kit"` 只有在包进了 registry 之后才解析得到。仓库里带 `.github/workflows/publish.yml`:配好 `NPM_TOKEN` secret
|
|
29
|
-
|
|
30
|
-
### 方式二:从 Git 安装(不需要 registry)
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
plugin_manager install_bundle target="git+https://<your-git>/<group>/dsh-d365-kit.git#v0.34.
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
DSH 用 pnpm 从 git 拉包。装完确认:
|
|
37
|
-
|
|
38
|
-
```
|
|
39
|
-
plugin_manager list_plugins
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
`include:dsh-d365-kit` 存在且 `enabled=true`、`fiberPhase=active` 即成功。
|
|
43
|
-
|
|
44
|
-
### 方式三:从本地目录安装
|
|
45
|
-
|
|
46
|
-
把整个仓库目录放到本机任意位置:
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
plugin_manager install_bundle target="D:\path\to\dsh-d365-kit"
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
这是 `link:` 安装:改代码立刻生效(但 Host 侧仍需重载),升级要自己 `git pull`。
|
|
53
|
-
|
|
54
|
-
### 方式四:zip
|
|
55
|
-
|
|
56
|
-
解压后按方式三装。
|
|
57
|
-
|
|
58
|
-
### 装完必须重启一次
|
|
59
|
-
|
|
60
|
-
Host 侧工具在安装时就能用;**设置页**属于 Web 客户端插件,DSH 对客户端插件的模块图有缓存,必须重启 DSH 才会把页面加载进来。
|
|
61
|
-
|
|
62
|
-
重启后打开 设置,应看到「D365 工具箱」。看不到时先跑 `d365_envs` 确认 Host 侧已加载,再看 `plugin_manager list_plugins` 里的条目名。
|
|
63
|
-
|
|
64
|
-
## 首次配置
|
|
65
|
-
|
|
66
|
-
1. 设置 → D365 工具箱 → **环境** → 添加环境:
|
|
67
|
-
|
|
68
|
-
| 字段 | 说明 |
|
|
69
|
-
| --- | --- |
|
|
70
|
-
| key | 工具里用的短名,例如 `dev`、`sit`。**一旦写入计划就会记进审计,建议起稳定的名字** |
|
|
71
|
-
| URL | 环境地址,例如 `https://contoso-dev.crm5.dynamics.com` |
|
|
72
|
-
| 云 | `public` / `usgovernment` / `dod` / `china`,决定登录端点 |
|
|
73
|
-
| 租户 ID | Entra 租户 GUID |
|
|
74
|
-
| ClientId | 应用注册的 Application (client) ID |
|
|
75
|
-
| 客户端密钥 | 应用注册里生成的 secret 值 |
|
|
76
|
-
|
|
77
|
-
2. 点「测试连接」,返回组织名与版本号说明凭据正确。
|
|
78
|
-
3. **保存**。密钥写进 `d365.secrets.json`,其余进 `d365.config.json`。
|
|
79
|
-
4. **权限**标签按环境分块列出能力:「读取元数据」默认开启(关掉后连表、字段、视图都查不到);要读业务记录再勾「读取业务数据」(默认关闭)。
|
|
80
|
-
5. 需要写入时,勾对应能力,并填「变更默认解决方案」(一个**已存在的未受管**解决方案唯一名)。
|
|
81
|
-
|
|
82
|
-
## 配置存在哪
|
|
83
|
-
|
|
84
|
-
环境清单与工具参数存在 `configFile`:
|
|
85
|
-
|
|
86
|
-
```
|
|
87
|
-
<工作区>/d365.config.json # 环境清单、默认环境、工具参数、权限
|
|
88
|
-
<工作区>/d365.secrets.json # 只放 clientSecret 与 gitlabToken(明文)
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
`configFile` 的解析顺序:
|
|
92
|
-
|
|
93
|
-
1. `cordis.patch.yml` 里的 `configFile`(绝对路径,安装时的机器相关配置)
|
|
94
|
-
2. 环境变量 `D365_CONFIG_FILE`
|
|
95
|
-
3. `<USERPROFILE>\Documents\deepseek-harness\default-workspace\d365.config.json`(该目录存在时)
|
|
96
|
-
4. `<当前工作目录>\d365.config.json`
|
|
97
|
-
|
|
98
|
-
仓库里的 `cordis.patch.yml` 不带 `configFile`,所以默认落到第 3 条;工作区在别处时用环境变量或 patch 指定。
|
|
99
|
-
`d365.secrets.json` 始终与 `configFile` 同目录。
|
|
100
|
-
|
|
101
|
-
**这两个文件不要提交到 Git**:仓库的 `.gitignore` 已经挡住它们与 `.d365/`。
|
|
102
|
-
|
|
103
|
-
## 凭据解析顺序
|
|
104
|
-
|
|
105
|
-
每个值(`tenantId` / `clientId` / `clientSecret`)按顺序取:
|
|
106
|
-
|
|
107
|
-
1. 环境配置里的内联值;
|
|
108
|
-
2. 设置页写进 `d365.secrets.json` 的 `clientSecret`(覆盖层);
|
|
109
|
-
3. 该环境配置里 `<kind>Env` 指定的环境变量名,如 `"clientSecretEnv": "D365_DEV_SECRET"`;
|
|
110
|
-
4. 派生名 `D365_<ENV>_<KIND>`,如 `D365_dev_CLIENT_SECRET`(key 原样,含连字符);
|
|
111
|
-
5. 共享名 `D365_TENANT_ID` / `D365_CLIENT_ID` / `D365_CLIENT_SECRET`。
|
|
112
|
-
|
|
113
|
-
生产环境更建议用环境变量,本机调试用设置页即可。
|
|
114
|
-
|
|
115
|
-
令牌在进程内存里缓存,按「URL + 租户 + ClientId + 密钥」的指纹区分——换了凭据会立即重取,不用重启。
|
|
116
|
-
|
|
117
|
-
## 验证
|
|
118
|
-
|
|
119
|
-
```
|
|
120
|
-
d365_envs check=true
|
|
121
|
-
d365_metadata resource=entities top=3
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
两次都有内容就通了。第一次 `d365_sync` 会拉全量元数据(真实环境约 125 MB、三分钟),不想占盘可以跳过,用 `d365_metadata` 按需查。
|
|
125
|
-
|
|
126
|
-
## 升级
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
|
-
# 从 Git 安装的
|
|
130
|
-
plugin_manager remove_bundle target=dsh-d365-kit
|
|
131
|
-
plugin_manager install_bundle target="git+https://<your-git>/<group>/dsh-d365-kit.git#v0.34.
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
改版本号后必须**先 remove 再 install**:link/pnpm 认为包已存在时会返回 `already-installed`,不会替换代码。
|
|
135
|
-
|
|
136
|
-
升级后重启 DSH(设置页代码要重新进模块图)。
|
|
137
|
-
|
|
138
|
-
## 卸载
|
|
139
|
-
|
|
140
|
-
```bash
|
|
141
|
-
plugin_manager remove_bundle target=dsh-d365-kit
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
本机数据(`d365.config.json`、`d365.secrets.json`、`.d365/`)不会删,需要时手动清理。
|
|
145
|
-
|
|
146
|
-
## 多人共用
|
|
147
|
-
|
|
148
|
-
- 每个人都装一份,各自填自己的 `d365.config.json` 与密钥;
|
|
149
|
-
- **权限是按机器授权的**:别人机器上的勾选不影响你,反之亦然;
|
|
150
|
-
- 报告与快照都在各自工作区,不共享;
|
|
151
|
-
- 想让全组用同一套环境定义,可把 `d365.config.json` 的 `environments` 放进内部文档,但密钥仍各自填。
|
|
1
|
+
# 安装与配置
|
|
2
|
+
|
|
3
|
+
**[English](../en/installation.md)** | 简体中文
|
|
4
|
+
|
|
5
|
+
## 前置
|
|
6
|
+
|
|
7
|
+
| 项 | 要求 |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| DSH | 桌面版,能打开 Web GUI(设置页是 Web 客户端插件) |
|
|
10
|
+
| 网络 | 能访问环境域名(`*.crm*.dynamics.com` 或对应区域)与登录端点 |
|
|
11
|
+
| 应用用户 | Entra ID 里的应用注册 + 在 Dataverse 里建的应用用户,拿到 `ClientId` / `ClientSecret` / `TenantId` |
|
|
12
|
+
| 权限 | 读元数据、读解决方案(`prvReadSolution`)、读自定义 API(`prvReadCustomAPI`)、读层表(`prvReadmsdyn_componentlayer`) |
|
|
13
|
+
|
|
14
|
+
只读应用用户就够用;写入能力用到的 `CreateAttribute` 等消息,需要该用户在环境里有对应权限(系统定制员或自定义角色里勾相应权限)。
|
|
15
|
+
|
|
16
|
+
> 应用用户在一个环境里建好后,要在**每个**目标环境各建一次(同一个 ClientId 可以复用)。
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
### 方式一:从 npm 安装(最短)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
plugin_manager install_bundle target="dsh-d365-kit@0.34.4"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
前提是包已经发到 pnpm 配置的 registry——公网 npm 或内网镜像都行。包名没有 scope,不需要配 scope 映射。要装指定版本就写 `dsh-d365-kit@<版本>`。
|
|
27
|
+
|
|
28
|
+
> **只发 GitHub 不等于发了 npm。** 仓库可以直接用第二种 `git+https://...` 装,`npm install github:<user>/<repo>#<tag>` 也能装;但 `install_bundle target="dsh-d365-kit"` 只有在包进了 registry 之后才解析得到。仓库里带 `.github/workflows/publish.yml`:配好 `NPM_TRUSTED_PUBLISHING` 变量或 `NPM_TOKEN` secret 后,每次推 `main` 且版本号是新的,就会自动发布。
|
|
29
|
+
|
|
30
|
+
### 方式二:从 Git 安装(不需要 registry)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
plugin_manager install_bundle target="git+https://<your-git>/<group>/dsh-d365-kit.git#v0.34.4"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
DSH 用 pnpm 从 git 拉包。装完确认:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
plugin_manager list_plugins
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`include:dsh-d365-kit` 存在且 `enabled=true`、`fiberPhase=active` 即成功。
|
|
43
|
+
|
|
44
|
+
### 方式三:从本地目录安装
|
|
45
|
+
|
|
46
|
+
把整个仓库目录放到本机任意位置:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
plugin_manager install_bundle target="D:\path\to\dsh-d365-kit"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
这是 `link:` 安装:改代码立刻生效(但 Host 侧仍需重载),升级要自己 `git pull`。
|
|
53
|
+
|
|
54
|
+
### 方式四:zip
|
|
55
|
+
|
|
56
|
+
解压后按方式三装。
|
|
57
|
+
|
|
58
|
+
### 装完必须重启一次
|
|
59
|
+
|
|
60
|
+
Host 侧工具在安装时就能用;**设置页**属于 Web 客户端插件,DSH 对客户端插件的模块图有缓存,必须重启 DSH 才会把页面加载进来。
|
|
61
|
+
|
|
62
|
+
重启后打开 设置,应看到「D365 工具箱」。看不到时先跑 `d365_envs` 确认 Host 侧已加载,再看 `plugin_manager list_plugins` 里的条目名。
|
|
63
|
+
|
|
64
|
+
## 首次配置
|
|
65
|
+
|
|
66
|
+
1. 设置 → D365 工具箱 → **环境** → 添加环境:
|
|
67
|
+
|
|
68
|
+
| 字段 | 说明 |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| key | 工具里用的短名,例如 `dev`、`sit`。**一旦写入计划就会记进审计,建议起稳定的名字** |
|
|
71
|
+
| URL | 环境地址,例如 `https://contoso-dev.crm5.dynamics.com` |
|
|
72
|
+
| 云 | `public` / `usgovernment` / `dod` / `china`,决定登录端点 |
|
|
73
|
+
| 租户 ID | Entra 租户 GUID |
|
|
74
|
+
| ClientId | 应用注册的 Application (client) ID |
|
|
75
|
+
| 客户端密钥 | 应用注册里生成的 secret 值 |
|
|
76
|
+
|
|
77
|
+
2. 点「测试连接」,返回组织名与版本号说明凭据正确。
|
|
78
|
+
3. **保存**。密钥写进 `d365.secrets.json`,其余进 `d365.config.json`。
|
|
79
|
+
4. **权限**标签按环境分块列出能力:「读取元数据」默认开启(关掉后连表、字段、视图都查不到);要读业务记录再勾「读取业务数据」(默认关闭)。
|
|
80
|
+
5. 需要写入时,勾对应能力,并填「变更默认解决方案」(一个**已存在的未受管**解决方案唯一名)。
|
|
81
|
+
|
|
82
|
+
## 配置存在哪
|
|
83
|
+
|
|
84
|
+
环境清单与工具参数存在 `configFile`:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
<工作区>/d365.config.json # 环境清单、默认环境、工具参数、权限
|
|
88
|
+
<工作区>/d365.secrets.json # 只放 clientSecret 与 gitlabToken(明文)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`configFile` 的解析顺序:
|
|
92
|
+
|
|
93
|
+
1. `cordis.patch.yml` 里的 `configFile`(绝对路径,安装时的机器相关配置)
|
|
94
|
+
2. 环境变量 `D365_CONFIG_FILE`
|
|
95
|
+
3. `<USERPROFILE>\Documents\deepseek-harness\default-workspace\d365.config.json`(该目录存在时)
|
|
96
|
+
4. `<当前工作目录>\d365.config.json`
|
|
97
|
+
|
|
98
|
+
仓库里的 `cordis.patch.yml` 不带 `configFile`,所以默认落到第 3 条;工作区在别处时用环境变量或 patch 指定。
|
|
99
|
+
`d365.secrets.json` 始终与 `configFile` 同目录。
|
|
100
|
+
|
|
101
|
+
**这两个文件不要提交到 Git**:仓库的 `.gitignore` 已经挡住它们与 `.d365/`。
|
|
102
|
+
|
|
103
|
+
## 凭据解析顺序
|
|
104
|
+
|
|
105
|
+
每个值(`tenantId` / `clientId` / `clientSecret`)按顺序取:
|
|
106
|
+
|
|
107
|
+
1. 环境配置里的内联值;
|
|
108
|
+
2. 设置页写进 `d365.secrets.json` 的 `clientSecret`(覆盖层);
|
|
109
|
+
3. 该环境配置里 `<kind>Env` 指定的环境变量名,如 `"clientSecretEnv": "D365_DEV_SECRET"`;
|
|
110
|
+
4. 派生名 `D365_<ENV>_<KIND>`,如 `D365_dev_CLIENT_SECRET`(key 原样,含连字符);
|
|
111
|
+
5. 共享名 `D365_TENANT_ID` / `D365_CLIENT_ID` / `D365_CLIENT_SECRET`。
|
|
112
|
+
|
|
113
|
+
生产环境更建议用环境变量,本机调试用设置页即可。
|
|
114
|
+
|
|
115
|
+
令牌在进程内存里缓存,按「URL + 租户 + ClientId + 密钥」的指纹区分——换了凭据会立即重取,不用重启。
|
|
116
|
+
|
|
117
|
+
## 验证
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
d365_envs check=true
|
|
121
|
+
d365_metadata resource=entities top=3
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
两次都有内容就通了。第一次 `d365_sync` 会拉全量元数据(真实环境约 125 MB、三分钟),不想占盘可以跳过,用 `d365_metadata` 按需查。
|
|
125
|
+
|
|
126
|
+
## 升级
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# 从 Git 安装的
|
|
130
|
+
plugin_manager remove_bundle target=dsh-d365-kit
|
|
131
|
+
plugin_manager install_bundle target="git+https://<your-git>/<group>/dsh-d365-kit.git#v0.34.4"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
改版本号后必须**先 remove 再 install**:link/pnpm 认为包已存在时会返回 `already-installed`,不会替换代码。
|
|
135
|
+
|
|
136
|
+
升级后重启 DSH(设置页代码要重新进模块图)。
|
|
137
|
+
|
|
138
|
+
## 卸载
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
plugin_manager remove_bundle target=dsh-d365-kit
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
本机数据(`d365.config.json`、`d365.secrets.json`、`.d365/`)不会删,需要时手动清理。
|
|
145
|
+
|
|
146
|
+
## 多人共用
|
|
147
|
+
|
|
148
|
+
- 每个人都装一份,各自填自己的 `d365.config.json` 与密钥;
|
|
149
|
+
- **权限是按机器授权的**:别人机器上的勾选不影响你,反之亦然;
|
|
150
|
+
- 报告与快照都在各自工作区,不共享;
|
|
151
|
+
- 想让全组用同一套环境定义,可把 `d365.config.json` 的 `environments` 放进内部文档,但密钥仍各自填。
|
package/docs/zh/permissions.md
CHANGED
|
@@ -1,161 +1,161 @@
|
|
|
1
|
-
# 权限与写入
|
|
2
|
-
|
|
3
|
-
**[English](../en/permissions.md)** | 简体中文
|
|
4
|
-
|
|
5
|
-
## 默认姿态
|
|
6
|
-
|
|
7
|
-
装好之后是**只读**:
|
|
8
|
-
|
|
9
|
-
- 元数据(实体、字段、表单、视图、插件、解决方案、层、站点地图…)由 `metadata.read` 控制,**默认开启**——没在配置里显式列出的环境视为已授权;
|
|
10
|
-
- **业务记录**默认读不到,需要按环境授权 `data.read`;
|
|
11
|
-
- 任何修改都需要按环境授权对应能力,且每次执行要人确认。
|
|
12
|
-
|
|
13
|
-
「能看什么」和「能改什么」放在同一份按环境的能力表里,因为这两件事本来就该一起管。页面上每个环境单独一块,用分隔线隔开。
|
|
14
|
-
|
|
15
|
-
## 能力表
|
|
16
|
-
|
|
17
|
-
配置在 `d365.config.json`:
|
|
18
|
-
|
|
19
|
-
```json
|
|
20
|
-
{
|
|
21
|
-
"write": {
|
|
22
|
-
"capabilityVersion": 2,
|
|
23
|
-
"environments": {
|
|
24
|
-
"dev": ["metadata.read", "data.read", "optionValue.add", "attribute.add", "view.columns"],
|
|
25
|
-
"sit": ["metadata.read", "data.read"],
|
|
26
|
-
"uat": []
|
|
27
|
-
},
|
|
28
|
-
"solutionUniqueName": "ContosoBase"
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
- 环境**没有条目** = 还没配过,保持默认(只开 `metadata.read`);
|
|
34
|
-
- 环境条目是**空数组** = 全部关掉,连元数据也读不到;
|
|
35
|
-
- `capabilityVersion` 由设置页保存时写入。旧文件(没有这个字段)里已有环境条目会被自动补上 `metadata.read`,也就是维持升级前的「元数据随时可读」行为。
|
|
36
|
-
|
|
37
|
-
| 能力 | 真实 HTTP 操作 | 分组 | 作用 |
|
|
38
|
-
| --- | --- | --- | --- |
|
|
39
|
-
| `metadata.read` | 元数据 `GET` | 元数据 | 读表/字段/表单/视图/插件/解决方案层(默认开启) |
|
|
40
|
-
| `data.read` | 业务数据 `GET` | 数据 | 读取业务记录 |
|
|
41
|
-
| `data.write` | `POST` / `PATCH` | 数据 | 新建或修改业务记录(高危,不含删除) |
|
|
42
|
-
| `optionValue.add` | `InsertOptionValue` | 选项集 | 新增选项值(全局或字段本地) |
|
|
43
|
-
| `optionValue.updateLabel` | `UpdateOptionValue` | 选项集 | 改选项标签 |
|
|
44
|
-
| `optionValue.delete` | `DeleteOptionValue` | 选项集 | **永久删除**选项值(高危) |
|
|
45
|
-
| `attribute.add` | `POST EntityDefinitions(...)/Attributes` | 字段 | 新增字段 |
|
|
46
|
-
| `attribute.update` | `PUT` 字段 URL(`MSCRM.MergeLabels`) | 字段 | 改显示名、描述、必填级别 |
|
|
47
|
-
| `attribute.delete` | `DELETE` 字段 URL | 字段 | **永久删除**字段及其数据(高危) |
|
|
48
|
-
| `entity.update` | `PUT` 实体 URL(`MSCRM.MergeLabels`) | 表 | 改表显示名与描述 |
|
|
49
|
-
| `view.columns` | `PATCH savedqueries(id)` | 视图 | 在系统视图里增删列(只改 `layoutxml`) |
|
|
50
|
-
| `layer.remove` | `RemoveActiveCustomizations` | 组件层 | **永久移除**组件最上面的未受管自定义层(高危;支持表、字段、选项集、表单、视图、流程、Web 资源、插件、自定义 API) |
|
|
51
|
-
| `message.invoke` | 任意 `POST` action | 消息 | 调一条 Dataverse 消息。消息由目标环境的插件实现,可能写数据、发通知或调外部系统(高危,每次调用都在对话里确认) |
|
|
52
|
-
|
|
53
|
-
设置页「权限」里按环境勾选,支持「复制到其他环境」。
|
|
54
|
-
|
|
55
|
-
## 三道闸,一处判定
|
|
56
|
-
|
|
57
|
-
判定收在 `lib/client.js` 一处,新增工具绕不过去——所有请求(含设置页里的「测试连接」)都从这里发出:
|
|
58
|
-
|
|
59
|
-
- **元数据读**:URL 的实体集名在元数据白名单(`EntityDefinitions`、`systemforms`、`savedqueries`、`webresourceset`、`solutions`、`msdyn_componentlayers`…)里,要求 `metadata.read`,报 `metadata-read-forbidden`;
|
|
60
|
-
- **业务数据读**:其余 `GET` 一律按业务数据拦截,要求 `data.read`,报 `data-read-forbidden`;
|
|
61
|
-
- **写**:非 `GET` 且不属于元数据消息(`PublishXml`、`InsertOptionValue`、`CreateAttribute`、`RemoveActiveCustomizations`…)的请求,先要求 `data.write`,否则报 `data-write-forbidden`;`d365_invoke` 的 `POST` 走 `message.invoke`(被拒绝时 `message-invoke-forbidden`),它的 `GET` function 仍是读,走 `data.read`;
|
|
62
|
-
|
|
63
|
-
拦截都发生在**取令牌之前**,不会把请求发到环境里。判定结果按环境缓存 2 秒,设置页改了权限后下一次工具调用就生效。
|
|
64
|
-
|
|
65
|
-
## 两步写入
|
|
66
|
-
|
|
67
|
-
### 第一步:生成计划(只读)
|
|
68
|
-
|
|
69
|
-
`plan-*` 会读现状、跳过无变化项,把操作写成计划文件(`.d365/pending-writes/`),返回操作列表。
|
|
70
|
-
|
|
71
|
-
| 计划 | 约束 |
|
|
72
|
-
| --- | --- |
|
|
73
|
-
| `plan-add-options` | 按 `\|` 前的代码去重;新值从当前最大值 +1 顺延;已存在的代码进 `skipped` |
|
|
74
|
-
| `plan-relabel` | 只把标签确实变化的项放进计划 |
|
|
75
|
-
| `plan-option-delete` | 必须 `acknowledge: true`;不存在的值进 `skipped`,已有记录引用时 Dataverse 会拒绝 |
|
|
76
|
-
| `plan-add-attribute` | 表必须存在、字段必须不存在;`SchemaName` 必须带发布者前缀 |
|
|
77
|
-
| `plan-update-attribute` | 只放真正变化的项 |
|
|
78
|
-
| `plan-delete-attribute` | 必须 `acknowledge: true`,否则直接拒绝 |
|
|
79
|
-
| `plan-update-entity` | 只改显示名 / 描述 |
|
|
80
|
-
| `plan-view-columns` | 按视图名定位系统视图(同名用 `entity` 区分);只改 `layoutxml` 的 cell 列表,不动筛选条件 |
|
|
81
|
-
|
|
82
|
-
局部选项集(字段自带的选项集)用 `entity` + `attribute` 指定,全局选项集用 `optionSet`。
|
|
83
|
-
|
|
84
|
-
### 第二步:执行(要人)
|
|
85
|
-
|
|
86
|
-
```
|
|
87
|
-
执行 mw04aul6-1ci6aa
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
agent 调 `d365_write action=execute planId=...`,会:
|
|
91
|
-
|
|
92
|
-
1. 读计划,检查状态必须是 `pending`;
|
|
93
|
-
2. 检查该环境**现在**仍持有这项能力;
|
|
94
|
-
3. 查环境是否正在导入/发布(见下);
|
|
95
|
-
4. 弹确认卡片:环境、目标、操作条数、前 10 条操作;含删除时额外提示不可恢复;
|
|
96
|
-
5. 点「批准执行」→ 逐条写入 → 后台发布 → 记审计。
|
|
97
|
-
|
|
98
|
-
也可以到设置页「计划」标签里审阅并执行(同样的二次红色确认)。两条入口共用同一套校验。
|
|
99
|
-
|
|
100
|
-
### 计划状态
|
|
101
|
-
|
|
102
|
-
`pending → running → done / failed`
|
|
103
|
-
|
|
104
|
-
第一次写入前就落 `running`,所以进程中途被杀不会留下一个「看起来还能点」的计划。非 `pending` 的计划,工具与设置页**都拒绝**执行和取消——写操作不是幂等的,重跑会重复提交已经生效的部分。要重做必须重新生成计划。
|
|
105
|
-
|
|
106
|
-
### 执行前检查
|
|
107
|
-
|
|
108
|
-
Dataverse 在导入或发布期间持有定制化锁。微软第一方更新就是以解决方案导入形式进来的,一个环境一天里很大一部分时间在做这件事——此时写入要么 `0x80071151`,要么一直等锁直到超时。
|
|
109
|
-
|
|
110
|
-
所以执行前查 `msdyn_solutionhistories` 里 `msdyn_endtime` 为空的行:
|
|
111
|
-
|
|
112
|
-
| 情况 | 行为 |
|
|
113
|
-
| --- | --- |
|
|
114
|
-
| 有记录且开始时间在 `busyWindowMinutes`(默认 120 分钟)内 | 拒绝执行,报出正在进行的操作(`environment-busy`) |
|
|
115
|
-
| 只有更早的记录(残留僵尸行) | 不阻塞,结果里提示一句 |
|
|
116
|
-
| 查不到表(没权限) | 跳过检查,结果注明没检查成 |
|
|
117
|
-
|
|
118
|
-
`force: true` 可跳过,只在确认是残留时用。
|
|
119
|
-
|
|
120
|
-
### 默认解决方案
|
|
121
|
-
|
|
122
|
-
每次元数据修改都记在某个**解决方案层**里。不指定时进活动默认层(Default Solution),导出解决方案带不走,下游表现为「字段不存在」。
|
|
123
|
-
|
|
124
|
-
所以建议填「变更默认解决方案」:一个**已存在的未受管**解决方案唯一名。计划阶段就校验——名字不存在报 `unknown-solution`,受管报 `managed-solution`,不用等到执行才失败。
|
|
125
|
-
|
|
126
|
-
### 发布范围
|
|
127
|
-
|
|
128
|
-
写完只发布**受影响的组件**:
|
|
129
|
-
|
|
130
|
-
- 选项集操作 → 对应选项集(本地选项集 → 所属表);
|
|
131
|
-
- 字段 / 表 / 视图操作 → 所属表。
|
|
132
|
-
|
|
133
|
-
负载形如:
|
|
134
|
-
|
|
135
|
-
```xml
|
|
136
|
-
<importexportxml>
|
|
137
|
-
<entities><entity>account</entity></entities>
|
|
138
|
-
<optionsets><optionset>contoso_taxcategoryos</optionset></optionsets>
|
|
139
|
-
</importexportxml>
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
一次发布是秒级到十几秒,而不是 `PublishAllXml` 把两千多张表重算一遍的分钟级操作。推不出受影响组件时才退回全量发布。发布在后台进行,结果写 `.d365/changes/publish-<planId>.json`;执行返回里会带 `publishState`(`skipped` / `running`)。
|
|
143
|
-
|
|
144
|
-
## 业务数据写入
|
|
145
|
-
|
|
146
|
-
`d365_data_write` 的三条约束:
|
|
147
|
-
|
|
148
|
-
- 需要 `data.write`,默认关闭;
|
|
149
|
-
- 只能写**一个**环境;
|
|
150
|
-
- 每次弹确认卡片,批准才发请求。
|
|
151
|
-
|
|
152
|
-
写入不需要发布(业务数据不是元数据)。审计只记字段名、不落值。没有删除。
|
|
153
|
-
|
|
154
|
-
## 安全边界
|
|
155
|
-
|
|
156
|
-
- **写接口只接受设置页**:`/write/policy`、`/write/cancel`、`/write/execute`、`/write/publish` 要求 `x-d365-meta-write: 1` 头。跨站页面要带这个头必须过 CORS 预检,而这些路由不回应预检;若请求里有 `Origin`,还必须同源。认证通道会把 URL 改写成 `http://dsh.internal` 并吞掉 `Origin`,所以不能只靠 Origin 判断。被拒的请求记 `.d365/write-guard.log`。
|
|
157
|
-
- **重试只对安全方法**:`GET`/`HEAD` 在网络错误与 5xx 上重试;`POST`/`PATCH`/`DELETE` 只在 **429**(服务端明确没执行)时重试。写入超时按「可能已生效」报错,让人去环境核对,而不是悄悄重发。
|
|
158
|
-
- **重新发布也要过门槛**:该环境必须仍持有当时那项能力,且先做一次空闲检查(忙时返回 409,而不是原始 429)。
|
|
159
|
-
- **取消与执行一样拒绝非 `pending`**:不会出现「页面说取消了、后台还在写」。
|
|
160
|
-
- **不可回滚**:元数据删除不可恢复,所以删除必须 `acknowledge: true`,卡片上还会单独提示。
|
|
161
|
-
- 写接口不提供批量脚本入口,也没有「跳过确认」的开关;`force` 只跳过空闲检查,不跳过确认。
|
|
1
|
+
# 权限与写入
|
|
2
|
+
|
|
3
|
+
**[English](../en/permissions.md)** | 简体中文
|
|
4
|
+
|
|
5
|
+
## 默认姿态
|
|
6
|
+
|
|
7
|
+
装好之后是**只读**:
|
|
8
|
+
|
|
9
|
+
- 元数据(实体、字段、表单、视图、插件、解决方案、层、站点地图…)由 `metadata.read` 控制,**默认开启**——没在配置里显式列出的环境视为已授权;
|
|
10
|
+
- **业务记录**默认读不到,需要按环境授权 `data.read`;
|
|
11
|
+
- 任何修改都需要按环境授权对应能力,且每次执行要人确认。
|
|
12
|
+
|
|
13
|
+
「能看什么」和「能改什么」放在同一份按环境的能力表里,因为这两件事本来就该一起管。页面上每个环境单独一块,用分隔线隔开。
|
|
14
|
+
|
|
15
|
+
## 能力表
|
|
16
|
+
|
|
17
|
+
配置在 `d365.config.json`:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"write": {
|
|
22
|
+
"capabilityVersion": 2,
|
|
23
|
+
"environments": {
|
|
24
|
+
"dev": ["metadata.read", "data.read", "optionValue.add", "attribute.add", "view.columns"],
|
|
25
|
+
"sit": ["metadata.read", "data.read"],
|
|
26
|
+
"uat": []
|
|
27
|
+
},
|
|
28
|
+
"solutionUniqueName": "ContosoBase"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- 环境**没有条目** = 还没配过,保持默认(只开 `metadata.read`);
|
|
34
|
+
- 环境条目是**空数组** = 全部关掉,连元数据也读不到;
|
|
35
|
+
- `capabilityVersion` 由设置页保存时写入。旧文件(没有这个字段)里已有环境条目会被自动补上 `metadata.read`,也就是维持升级前的「元数据随时可读」行为。
|
|
36
|
+
|
|
37
|
+
| 能力 | 真实 HTTP 操作 | 分组 | 作用 |
|
|
38
|
+
| --- | --- | --- | --- |
|
|
39
|
+
| `metadata.read` | 元数据 `GET` | 元数据 | 读表/字段/表单/视图/插件/解决方案层(默认开启) |
|
|
40
|
+
| `data.read` | 业务数据 `GET` | 数据 | 读取业务记录 |
|
|
41
|
+
| `data.write` | `POST` / `PATCH` | 数据 | 新建或修改业务记录(高危,不含删除) |
|
|
42
|
+
| `optionValue.add` | `InsertOptionValue` | 选项集 | 新增选项值(全局或字段本地) |
|
|
43
|
+
| `optionValue.updateLabel` | `UpdateOptionValue` | 选项集 | 改选项标签 |
|
|
44
|
+
| `optionValue.delete` | `DeleteOptionValue` | 选项集 | **永久删除**选项值(高危) |
|
|
45
|
+
| `attribute.add` | `POST EntityDefinitions(...)/Attributes` | 字段 | 新增字段 |
|
|
46
|
+
| `attribute.update` | `PUT` 字段 URL(`MSCRM.MergeLabels`) | 字段 | 改显示名、描述、必填级别 |
|
|
47
|
+
| `attribute.delete` | `DELETE` 字段 URL | 字段 | **永久删除**字段及其数据(高危) |
|
|
48
|
+
| `entity.update` | `PUT` 实体 URL(`MSCRM.MergeLabels`) | 表 | 改表显示名与描述 |
|
|
49
|
+
| `view.columns` | `PATCH savedqueries(id)` | 视图 | 在系统视图里增删列(只改 `layoutxml`) |
|
|
50
|
+
| `layer.remove` | `RemoveActiveCustomizations` | 组件层 | **永久移除**组件最上面的未受管自定义层(高危;支持表、字段、选项集、表单、视图、流程、Web 资源、插件、自定义 API) |
|
|
51
|
+
| `message.invoke` | 任意 `POST` action | 消息 | 调一条 Dataverse 消息。消息由目标环境的插件实现,可能写数据、发通知或调外部系统(高危,每次调用都在对话里确认) |
|
|
52
|
+
|
|
53
|
+
设置页「权限」里按环境勾选,支持「复制到其他环境」。
|
|
54
|
+
|
|
55
|
+
## 三道闸,一处判定
|
|
56
|
+
|
|
57
|
+
判定收在 `lib/client.js` 一处,新增工具绕不过去——所有请求(含设置页里的「测试连接」)都从这里发出:
|
|
58
|
+
|
|
59
|
+
- **元数据读**:URL 的实体集名在元数据白名单(`EntityDefinitions`、`systemforms`、`savedqueries`、`webresourceset`、`solutions`、`msdyn_componentlayers`…)里,要求 `metadata.read`,报 `metadata-read-forbidden`;
|
|
60
|
+
- **业务数据读**:其余 `GET` 一律按业务数据拦截,要求 `data.read`,报 `data-read-forbidden`;
|
|
61
|
+
- **写**:非 `GET` 且不属于元数据消息(`PublishXml`、`InsertOptionValue`、`CreateAttribute`、`RemoveActiveCustomizations`…)的请求,先要求 `data.write`,否则报 `data-write-forbidden`;`d365_invoke` 的 `POST` 走 `message.invoke`(被拒绝时 `message-invoke-forbidden`),它的 `GET` function 仍是读,走 `data.read`;
|
|
62
|
+
|
|
63
|
+
拦截都发生在**取令牌之前**,不会把请求发到环境里。判定结果按环境缓存 2 秒,设置页改了权限后下一次工具调用就生效。
|
|
64
|
+
|
|
65
|
+
## 两步写入
|
|
66
|
+
|
|
67
|
+
### 第一步:生成计划(只读)
|
|
68
|
+
|
|
69
|
+
`plan-*` 会读现状、跳过无变化项,把操作写成计划文件(`.d365/pending-writes/`),返回操作列表。
|
|
70
|
+
|
|
71
|
+
| 计划 | 约束 |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| `plan-add-options` | 按 `\|` 前的代码去重;新值从当前最大值 +1 顺延;已存在的代码进 `skipped` |
|
|
74
|
+
| `plan-relabel` | 只把标签确实变化的项放进计划 |
|
|
75
|
+
| `plan-option-delete` | 必须 `acknowledge: true`;不存在的值进 `skipped`,已有记录引用时 Dataverse 会拒绝 |
|
|
76
|
+
| `plan-add-attribute` | 表必须存在、字段必须不存在;`SchemaName` 必须带发布者前缀 |
|
|
77
|
+
| `plan-update-attribute` | 只放真正变化的项 |
|
|
78
|
+
| `plan-delete-attribute` | 必须 `acknowledge: true`,否则直接拒绝 |
|
|
79
|
+
| `plan-update-entity` | 只改显示名 / 描述 |
|
|
80
|
+
| `plan-view-columns` | 按视图名定位系统视图(同名用 `entity` 区分);只改 `layoutxml` 的 cell 列表,不动筛选条件 |
|
|
81
|
+
|
|
82
|
+
局部选项集(字段自带的选项集)用 `entity` + `attribute` 指定,全局选项集用 `optionSet`。
|
|
83
|
+
|
|
84
|
+
### 第二步:执行(要人)
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
执行 mw04aul6-1ci6aa
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
agent 调 `d365_write action=execute planId=...`,会:
|
|
91
|
+
|
|
92
|
+
1. 读计划,检查状态必须是 `pending`;
|
|
93
|
+
2. 检查该环境**现在**仍持有这项能力;
|
|
94
|
+
3. 查环境是否正在导入/发布(见下);
|
|
95
|
+
4. 弹确认卡片:环境、目标、操作条数、前 10 条操作;含删除时额外提示不可恢复;
|
|
96
|
+
5. 点「批准执行」→ 逐条写入 → 后台发布 → 记审计。
|
|
97
|
+
|
|
98
|
+
也可以到设置页「计划」标签里审阅并执行(同样的二次红色确认)。两条入口共用同一套校验。
|
|
99
|
+
|
|
100
|
+
### 计划状态
|
|
101
|
+
|
|
102
|
+
`pending → running → done / failed`
|
|
103
|
+
|
|
104
|
+
第一次写入前就落 `running`,所以进程中途被杀不会留下一个「看起来还能点」的计划。非 `pending` 的计划,工具与设置页**都拒绝**执行和取消——写操作不是幂等的,重跑会重复提交已经生效的部分。要重做必须重新生成计划。
|
|
105
|
+
|
|
106
|
+
### 执行前检查
|
|
107
|
+
|
|
108
|
+
Dataverse 在导入或发布期间持有定制化锁。微软第一方更新就是以解决方案导入形式进来的,一个环境一天里很大一部分时间在做这件事——此时写入要么 `0x80071151`,要么一直等锁直到超时。
|
|
109
|
+
|
|
110
|
+
所以执行前查 `msdyn_solutionhistories` 里 `msdyn_endtime` 为空的行:
|
|
111
|
+
|
|
112
|
+
| 情况 | 行为 |
|
|
113
|
+
| --- | --- |
|
|
114
|
+
| 有记录且开始时间在 `busyWindowMinutes`(默认 120 分钟)内 | 拒绝执行,报出正在进行的操作(`environment-busy`) |
|
|
115
|
+
| 只有更早的记录(残留僵尸行) | 不阻塞,结果里提示一句 |
|
|
116
|
+
| 查不到表(没权限) | 跳过检查,结果注明没检查成 |
|
|
117
|
+
|
|
118
|
+
`force: true` 可跳过,只在确认是残留时用。
|
|
119
|
+
|
|
120
|
+
### 默认解决方案
|
|
121
|
+
|
|
122
|
+
每次元数据修改都记在某个**解决方案层**里。不指定时进活动默认层(Default Solution),导出解决方案带不走,下游表现为「字段不存在」。
|
|
123
|
+
|
|
124
|
+
所以建议填「变更默认解决方案」:一个**已存在的未受管**解决方案唯一名。计划阶段就校验——名字不存在报 `unknown-solution`,受管报 `managed-solution`,不用等到执行才失败。
|
|
125
|
+
|
|
126
|
+
### 发布范围
|
|
127
|
+
|
|
128
|
+
写完只发布**受影响的组件**:
|
|
129
|
+
|
|
130
|
+
- 选项集操作 → 对应选项集(本地选项集 → 所属表);
|
|
131
|
+
- 字段 / 表 / 视图操作 → 所属表。
|
|
132
|
+
|
|
133
|
+
负载形如:
|
|
134
|
+
|
|
135
|
+
```xml
|
|
136
|
+
<importexportxml>
|
|
137
|
+
<entities><entity>account</entity></entities>
|
|
138
|
+
<optionsets><optionset>contoso_taxcategoryos</optionset></optionsets>
|
|
139
|
+
</importexportxml>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
一次发布是秒级到十几秒,而不是 `PublishAllXml` 把两千多张表重算一遍的分钟级操作。推不出受影响组件时才退回全量发布。发布在后台进行,结果写 `.d365/changes/publish-<planId>.json`;执行返回里会带 `publishState`(`skipped` / `running`)。
|
|
143
|
+
|
|
144
|
+
## 业务数据写入
|
|
145
|
+
|
|
146
|
+
`d365_data_write` 的三条约束:
|
|
147
|
+
|
|
148
|
+
- 需要 `data.write`,默认关闭;
|
|
149
|
+
- 只能写**一个**环境;
|
|
150
|
+
- 每次弹确认卡片,批准才发请求。
|
|
151
|
+
|
|
152
|
+
写入不需要发布(业务数据不是元数据)。审计只记字段名、不落值。没有删除。
|
|
153
|
+
|
|
154
|
+
## 安全边界
|
|
155
|
+
|
|
156
|
+
- **写接口只接受设置页**:`/write/policy`、`/write/cancel`、`/write/execute`、`/write/publish` 要求 `x-d365-meta-write: 1` 头。跨站页面要带这个头必须过 CORS 预检,而这些路由不回应预检;若请求里有 `Origin`,还必须同源。认证通道会把 URL 改写成 `http://dsh.internal` 并吞掉 `Origin`,所以不能只靠 Origin 判断。被拒的请求记 `.d365/write-guard.log`。
|
|
157
|
+
- **重试只对安全方法**:`GET`/`HEAD` 在网络错误与 5xx 上重试;`POST`/`PATCH`/`DELETE` 只在 **429**(服务端明确没执行)时重试。写入超时按「可能已生效」报错,让人去环境核对,而不是悄悄重发。
|
|
158
|
+
- **重新发布也要过门槛**:该环境必须仍持有当时那项能力,且先做一次空闲检查(忙时返回 409,而不是原始 429)。
|
|
159
|
+
- **取消与执行一样拒绝非 `pending`**:不会出现「页面说取消了、后台还在写」。
|
|
160
|
+
- **不可回滚**:元数据删除不可恢复,所以删除必须 `acknowledge: true`,卡片上还会单独提示。
|
|
161
|
+
- 写接口不提供批量脚本入口,也没有「跳过确认」的开关;`force` 只跳过空闲检查,不跳过确认。
|