Skip to content
Back to skills

H3yun Subtable Write

ASecurity

安全地把子表明细写入氚云指定记录时使用。触发场景:用户要求「更新子表」「写入/追加明细」「批量改明细」「把这些数据填到某条记录」「改报价单/订单的产品行」等,涉及 h3yun_update_bizobject 携带子表数据的操作。强制走「读取→匹配→diff 确认→写入→回读校验」流程,避免重复追加、静默丢数据和误删。

  • 417 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 8, 2026
developmentpythonbashapi

Works with

  • api
  • mcp

Security analysis

A100/100

Scanned November 8, 2026

npx -y skills add infometa/workbuddyskills --skill h3yun-subtable-write --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of H3yun Subtable Write?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for H3yun Subtable Write
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/infometa-h3yun-subtable-write/badge)](https://www.skillsdirectory.com/skills/infometa-h3yun-subtable-write)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: h3yun-subtable-write
description: 安全地把子表明细写入氚云指定记录时使用。触发场景:用户要求「更新子表」「写入/追加明细」「批量改明细」「把这些数据填到某条记录」「改报价单/订单的产品行」等,涉及 h3yun_update_bizobject 携带子表数据的操作。强制走「读取→匹配→diff 确认→写入→回读校验」流程,避免重复追加、静默丢数据和误删。
---

# 氚云子表写入

把一批明细数据安全地写进某条氚云记录的指定子表。

## 一、先看能力边界(三条硬约束)

这三条决定了「能做什么、不能做什么」,任何情况下都不得绕过:

| 约束       | 事实                                    | 后果                           |
| -------- | ------------------------------------- | ---------------------------- |
| **不能删行** | 省略行不会删除,空数组被禁止(报 `invalid_parameter`) | 批次里「少了的行」不会被删,只会静默保留         |
| **不幂等**  | 不带 `ObjectId` 的行一律追加,服务端不做值去重         | 同一批跑两次 = 明细翻倍,且多出来的行 API 删不掉 |
| **不能清空** | 禁止 `null`、空字符串、空数组、清空                 | 无法用「传空」实现重置                  |

**因此:本技能只做「合并(merge)」,绝不做「替换(replace)」。** 用户若要求「把明细覆盖成这批」,必须明确告知做不到,并给出替代方案(见 §6)。

## 二、标准流程(五步,不可跳步)

### 第 1 步:读现状

```
h3yun_get_bizobject(schemaCode, bizObjectId)
```

拿到 `values` 里子表编码对应的行数组,每行含 `ObjectId`、`ParentObjectId` 和各业务字段。

**必须原样保存 `ObjectId`**,它是后续匹配的唯一可靠依据。

### 第 2 步:确认业务唯一键

在子表字段里找能唯一标识一行的字段,优先级:

1. 编码类:产品编码、SKU、物料号、工号
2. 型号 / 规格
3. 名称
4. 组合键(如 型号 + 规格)

**若子表没有任何可作唯一键的字段**(例如整行只有一个数值或一个备注):

- 停止合并,降级为「纯追加」;
- 必须向用户明示:「该子表缺少可用于匹配的字段,只能追加新行,无法更新已有行。继续会产生重复行,确认吗?」

**不要用「整行值完全相等」当作匹配依据** —— 两行合法地拥有相同值是完全正常的(实测中同一张子表就有两行 `45789`),按值匹配会改错行。

**业务键不能选「你正想修改的那个字段」。** 按值匹配意味着键值一变就匹配不上:若子表唯一的可改字段就是那个键(例如整行只有一个数值 `F0000002`),把 `33235` 改成 `35000` 会被判成「新增一行 35000」而不是「修改原行」。

这种情形必须走**显式行 ID**:目标行里带上 `ObjectId`,字段值随便改都能正确定位。

```json
{ "ObjectId": "43405329-6004-4cdc-bcf5-9f3b5f3b415b", "F0000002": "35000" }
```

`plan_merge.py` 已支持:目标行进里带 `ObjectId` 时优先按 ID 定位,不看业务键。反过来说,**如果子表只有一个字段、且这个字段就是你要改的内容,那么「批量改值」只能靠先列出各行 ID 让用户指定**,不能靠键自动匹配。

### 工具名说明

本文档中的 `h3yun_get_bizobject` 等是逻辑工具名。实际调用需按当前连接器的前缀拼装,例如 `mcp__h3dev__h3yun_get_bizobject`(自定义连接器 `h3dev`)或 `mcp__h3yun-connector__h3yun_get_bizobject`(官方连接器)。**连接器指向哪个环境决定你操作的是哪套数据**,动手前先确认。

### 第 3 步:算出 diff(用脚本,不要心算)

把当前行和目标行落成两个 JSON 文件,跑:

```bash
python3 scripts/plan_merge.py \
  --current current.json \
  --desired desired.json \
  --key <业务键字段编码> \
  --schema-code <子表编码> \
  --output plan.json
```

脚本输出四类结果:

| 分类        | 含义          | payload 里怎么处理       |
| --------- | ----------- | ------------------- |
| `updates` | 匹配上且有字段变化   | 带 `ObjectId`,只放变化字段 |
| `adds`    | 未匹配上        | 不带 `ObjectId`,放全部字段 |
| `noops`   | 匹配上且值未变     | 不放                  |
| `orphans` | 库里有、目标批次里没有 | **不放**(放了也不会删),仅告警  |

### 第 4 步:把 diff 给用户确认(不可省略)

用 §7 的模板输出。**尤其必须点明 `orphans`**:这些行不会被删除,用户以为「覆盖了」实际没覆盖,是长期脏数据的主要来源。

用户确认后,才进入第 5 步。

### 第 5 步:写入 + 回读校验

```
h3yun_update_bizobject(schemaCode=<主表编码>, bizObjectId, bizObject={
  "<子表编码>": [ ...updates...,  ...adds... ]
})
```

写入后**必须再读一次**,校验:

- 行数变化 = `len(adds)`
- `updates` 中每行的目标字段已变为新值
- 原有行 `ObjectId` 未变
- `orphans` 行仍在(预期如此)

任一不符,向用户报告实际结果,不要自行重试。

## 三、payload 构造规则

```json
{
  "schemaCode": "<主表编码>",
  "bizObjectId": "<父记录ID>",
  "bizObject": {
    "<子表编码>": [
      { "ObjectId": "<已有行ID>", "F0000002": "新值" },
      { "F0000002": "新增行的值" }
    ]
  }
}
```

- 子表编码作为 `bizObject` 的键,值是**行对象数组**
- 已有行:`ObjectId` 必传,但**不可修改**系统定位字段(`ObjectId`、`ParentObjectId` 只用于定位)
- 新增行:**不要**传 `ObjectId`,也不要传 `ParentObjectId`,服务端会自动关联
- 未出现的字段由 Engine 从既有对象补全,不需要整行回传
- 禁止任何字段传 `null` / 空字符串 / 空数组

## 四、幂等保护

写入前先做一次「重复检测」:若 `adds` 中有行的业务键已存在于当前记录,说明可能是重复执行,**暂停并要求用户确认**。

工具明确声明「非幂等、无版本冲突保护、禁止自动重试」。收到以下情况一律**不得重试**,先回读核实:

- `h3yun.write.result_unknown`(写入结果未知)
- 请求超时 / 网络中断

回读方式:

- 核对**子表行内容**必须用 `h3yun_get_bizobject(schemaCode, bizObjectId)`——它返回全量行;
- `h3yun_filter_bizobject_list` 只用来「按名称/键定位是哪条记录」(`operator: "eq"`),**它的子表固定截断到 10 行**,不能用来数行数(详见 §5)。

## 五、错误码处置

| 错误码                                  | 含义                 | 处理                                                 |
| ------------------------------------ | ------------------ | -------------------------------------------------- |
| `h3yun.bizobject.field_not_writable` | 字段不可写,message 会点名  | 移除该字段重发(不是权限问题,是该字段本就不开放写入)                        |
| `h3yun.request.invalid_parameter`    | 参数值不合法             | 检查:是否传了 null/空串/空数组、子表是否写成裸对象而非数组、字段编码是否拼错         |
| `h3yun.write.result_unknown`         | 结果未知               | **禁止重试**,先回读核实                                     |
| `h3yun.bizobject.write_rejected`     | 行 ObjectId 不存在于该子表 | 说明传入的行 ID 不属于本记录。**工具会拒绝而非当新增行处理**,这是好事,重读行 ID 后重试 |
| `h3yun.bizobject.unavailable`        | 记录不存在或无权访问         | 核对 `bizObjectId` 与身份是否同租户                          |
| `h3yun.form.edit_permission_denied`  | 无编辑权限              | 停止,报告用户                                            |

### 回读必须用 `get_bizobject`,不能用 `filter_bizobject_list`

实测:`filter_bizobject_list` 返回的子表**固定截断到 10 行**,并在 `truncatedSubTables` 中标记该子表。用它核对写入结果会误判「只写进 10 行」。

`get_bizobject` 返回全量。实测 200 行子表完整返回,无截断。

### 已实测容量上限

| 项                     | 实测结果       |
| --------------------- | ---------- |
| 单次写入 50 行(1 改 + 49 增) | ✅ 成功       |
| 单次写入 150 行(全为新增)      | ✅ 成功       |
| 子表累计 200 行            | ✅ 完整读回,无截断 |
| 含非法行的批次               | ✅ 整批回滚,无半写 |

单次 150 行未触及上限,但未继续往上压测。超过 200 行时建议分批,并在每批后回读确认。

完整错误码表见 `references/payload-examples.md`。

## 六、用户要做「覆盖式更新」时怎么办

用户说「把明细改成这批」「以我这批为准」,正确回应:

1. 明确说明:当前 API 不支持删除子表行,所以无法实现真正的覆盖;
2. 给出三个选项:
   - **A. 合并**:有则改、无则增,多余行保留(推荐,最安全)
   - **B. 删后重建**:整条记录删掉重建 —— 会丢失 `ObjectId`、创建时间和流程历史,需明确告知
   - **C. 人工清理**:先合并,再由用户在页面上手删多余行
3. 等用户选择,不要自行决定。

## 七、给用户确认的模板

```
将要写入记录:<主表名> / <记录名或ID>
子表:<子表名>(当前 N 行 → 写入后 M 行)

【修改】k 行
- <业务键值>:字段A 旧值 → 新值
- ...

【新增】m 行
- <业务键值>:字段A=值, 字段B=值
- ...

【不处理】p 行(重要)
以下行在记录中存在,但不在你给的批次里。API 不支持删除,它们会原样保留:
- <业务键值> ...
如需删除,请写入后在页面上手动操作。

确认执行?
```

## 八、反模式(不要做)

- ❌ 按「整行值相等」匹配已有行 —— 相同值可能合法共存,会改错行
- ❌ 一上来就写,不先 `get_bizobject` 读行 ID —— 必然全部变成追加
- ❌ 把用户给的批次当成「全量」直接传 —— 不会删,但会让你误判已覆盖
- ❌ 省略 `ObjectId` 去「更新」已存在的行 —— 结果是新增重复行
- ❌ 写入失败后自动重试 —— 非幂等,可能已经写成功
- ❌ 传空数组表达「清空子表」 —— 直接报错
- ❌ 把子表写成单个对象而不是数组

## 九、参考

- `references/payload-examples.md` —— 真实请求/响应样本、语义规则、完整错误码表
- `scripts/plan_merge.py` —— diff 计算脚本(`--help` 查看参数)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in development →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.