# 工站契约调用方接入文档（S-XUbooth 铺子端 → DyardOS）

> 版本：v4.14 命名定版口径 · 适用 DyardOS P4+（bc18d20 起）
> 调用方：生态铺子端 **S-XUbooth**（原 X-Work/XSU-Booth）；被调方：DyardOS 数字神域（执行被调层）。
> 域名拓扑：主域 `https://dyardos.cewaysoft.com`（网关 47.117.73.101 Caddy 反代，LE 证书）= 推荐接入域；部署直连域 `https://8s27vvfnpj.coze.site` 等价可用。
> 公开版：凭证头组示例值已脱敏（`<REDACTED>`），联调真实头组经主窗/网关渠道单独发放。
> 红线：DyardOS 只做执行被调层，不抢业务决策；OFD/门户不存主数据；两库契约对接零直连；身份 OAS 统一，零自建用户体系。

## 1. 鉴权与凭证

### 1.1 身份模型

DyardOS 全部写接口经 OAS 统一身份 + `assertWriteAllowed`（fail-closed）裁决：

| 主体 | 口径 | 工站契约写权限 |
| --- | --- | --- |
| system（游客轨/未认领） | 只读浏览主体 | 一律 403 |
| human（XOAS 人轨，gateway/bearer/bridge 任一轨道） | 域内成员 | 可写（无需 scope） |
| org（户口法人） | 按归属域收敛 | 仅可写本归属域（XVPZ/XSPZ/XEPZ）工位，跨租户 403 |
| **m2m（铺子端服务号，推荐）** | 按 scope 收敛 | **必持 `thing:write`**，缺失 403 |

### 1.2 凭证发放（联调）

铺子端以服务号（m2m）接入，凭证 = 网关注入的身份头组，**由 XOAS 网关统一签发，DyardOS 不自建签发**：

| Header | 说明 | 示例 |
| --- | --- | --- |
| `x-oas-user-id` | 服务号主体 ID | `<REDACTED>` |
| `x-oas-user-name` | 展示名（可选） | `<REDACTED>` |
| `x-oas-org` | 归属域（可选） | `XVPZ` |
| `x-oas-auth-scope` | 授权 scope 列表（空格分隔） | `<REDACTED>`（scope 语义见下文 1.2 条目） |

- **联调预设**：DyardOS 侧已预置 `m2m-ofd`（全量 scope）与 `m2m-xofd-dispatch`（联调）两个 m2m 预设，booth 侧联调时向网关管理员申请等价头组签发即可；最小必要 scope 为 **`thing:write`**（占位/释放/回执），只读看板另需 `dyardos:read`。
- scope 语义：`thing:write` = 物码/派单域写；`xstation:write` = 工站/产能域写（建工站、移挂、产能任务）；`dyardos:read` = 真实端只读契约。
- 生产环境凭证由 XOAS 网关注入，调用方**禁止**自行伪造头组；开发环境可在请求上直接附头模拟。

### 1.3 统一响应信封

- 成功：`{ "success": true, "data": ... }`（HTTP 200/201）
- 失败：`{ "success": false, "error": "中文错误描述" }`
- 错误码语义：400 入参非法 / 403 越权（含 m2m 缺 scope）/ 404 不存在 / **409 状态冲突（撞占/重复回执）** / 422 语义非法（码不可识别等）/ 502 上游契约异常。

## 2. 端点清单

统一工站契约面（façade，推荐 booth 侧使用）：

| 动作 | 方法与路径 | 说明 |
| --- | --- | --- |
| 占位 | `POST /api/dyardos/contract/workstation/workorder.occupy` | 位码派单占用（=reserve 锁语义，一位一占） |
| 释放 | `POST /api/dyardos/contract/workstation/workorder.release` | 释放派单（未干完退回） |
| 回执 | `POST /api/dyardos/contract/workstation/workorder.complete` | 干完回执（结果/耗时/消耗快照） |

直路由（等价能力，二选一即可，勿混用同一票）：

| 动作 | 方法与路径 |
| --- | --- |
| 占位 | `POST /api/dyardos/work-orders` |
| 释放 | `POST /api/dyardos/work-orders/{id}/release` |
| 回执 | `POST /api/dyardos/work-orders/{id}/complete` |
| 回执列表（对账） | `GET /api/dyardos/work-orders/receipts?station_code=` |
| 单票回执（对账） | `GET /api/dyardos/work-orders/{id}/receipt` |

辅助只读：

| 用途 | 端点 |
| --- | --- |
| 工站全量只读 | `GET /api/xstation/contract/stations` |
| 工站按码视图 | `GET /api/xstation/contract/station-by-code/{code}` |
| 作业位+状态机分布 | `GET /api/xstation/contract/positions-by-station?station_code=` |
| 归属同步视图 | `GET /api/xstation/contract/ownership-sync?station_code=` |
| 真实端只读看板（需 `dyardos:read`） | `GET /api/dyardos/contract/readonly/{overview\|capacity\|plazs\|stations\|map\|alarms}` |

## 3. 参数与回执格式

### 3.1 workorder.occupy（占位）

```json
{
  "station_code": "STATION-FAB-0001",   // 必填，位码
  "thing_code":  "THING-MCH-0007",      // 可选，关联物码
  "node_code":   "LINE-A",              // 可选，产能节点占位联动（节点必须隶属该工站）
  "source":      "s-xubooth",           // 可选，来源标记
  "note":        "铺子单 #123"           // 可选
}
```

- 201 返回派单：`{ id, work_order_no, station_code, node_code, status: "occupied", ... }`
- **撞占 409**：同一工位已有 occupied 派单 → 仅一票 201，其余 409（DB 部分唯一索引兜底，并发安全）。
- **产能联动回滚语义**：指定 `node_code` 时，派单落库后锁节点运行态 `idle→occupied`；锁定失败（节点撞占/维护中）→ **派单自动回滚为 released（留痕）并返回 409**，调用方重试不会产生脏占用。

### 3.2 workorder.release（释放）

```json
{ "work_order_id": "uuid" }            // 或 { "work_order_no": "WO-000123" } 二选一
```

- 幂等语义：条件更新（仅 occupied → released），重复释放返回 409。
- 释放联动归还产能节点运行态 `occupied→idle`。

### 3.3 workorder.complete（回执）

```json
{
  "work_order_no": "WO-000123",
  "result_note": "干完，良品 96",
  "consumptions": [                     // 可选，联动扣货 Unit 余量
    { "thing_code": "THING-UNT-0003", "quantity": 2, "batch_no": "B01", "purpose": "production" }
  ]
}
```

- 回执写 `completed_by/completed_at/result_note/consumption_snapshot`，状态 occupied → completed；重复回执 409。
- `consumptions` 逐项调用货 Unit 消耗（超扣 409 整票失败）；回执后产能节点运行态归还 idle，任务资源自动释放。

### 3.4 回执格式（对账视图）

`GET /api/dyardos/work-orders/{id}/receipt` 或 `/receipts` 返回：

```json
{
  "work_order_no": "WO-000123",
  "station_id": "uuid", "station_code": "STATION-FAB-0001",
  "thing_code": "THING-MCH-0007", "node_code": "LINE-A",
  "source": "s-xubooth", "note": "铺子单 #123",
  "occupied_by": "<REDACTED>", "created_at": "2026-10-17T09:00:00Z",
  "completed_by": "<REDACTED>", "completed_at": "2026-10-17T09:42:00Z",
  "duration_ms": 2520000,
  "result_note": "干完，良品 96",
  "consumptions": [{ "thing_code": "THING-UNT-0003", "quantity": 2, "batch_no": "B01", "purpose": "production" }]
}
```

未回执的派单查询单票回执返回 409。

## 4. 对账台账查询

- **批量拉取**：`GET /api/dyardos/work-orders/receipts`（可选 `?station_code=` 过滤），仅含已回执派单，按回执时间倒序——门户/铺子 Job 定时拉取对账。
- **单票核对**：`GET /api/dyardos/work-orders/{id}/receipt`。
- **货消耗台账**：场子侧 `GET /api/xplaz/plaz/{plazId}/consumptions`、货侧 `GET /api/dyardos/things/{thingId}/consumptions`（同源 `dyardos_unit_consumption` 流水，CONS 单号）。
- DyardOS 为唯一事实源；调用方本地只做缓存/对账副本，不回写。

## 5. 联调流程示例

```bash
BASE=https://dyardos.cewaysoft.com   # 推荐主域（网关 Caddy 反代）；部署直连域 https://8s27vvfnpj.coze.site 等价可用
AUTH=(-H "x-oas-user-id: <REDACTED>" -H "x-oas-auth-scope: <REDACTED>")

# 1) 占位（撞占返回 409）
curl -X POST "${AUTH[@]}" -H 'Content-Type: application/json' \
  -d '{"station_code":"STATION-FAB-0001","source":"s-xubooth","note":"铺子单 #123"}' \
  $BASE/api/dyardos/contract/workstation/workorder.occupy

# 2) 回执（联动扣货）
curl -X POST "${AUTH[@]}" -H 'Content-Type: application/json' \
  -d '{"work_order_no":"WO-000123","result_note":"干完","consumptions":[{"thing_code":"THING-UNT-0003","quantity":2}]}' \
  $BASE/api/dyardos/contract/workstation/workorder.complete

# 3) 对账拉取
curl "${AUTH[@]}" "$BASE/api/dyardos/work-orders/receipts?station_code=STATION-FAB-0001"
```

## 6. 常见错误对照

| 现象 | 原因 | 处置 |
| --- | --- | --- |
| 403「m2m 缺 scope」 | 头组未含 `thing:write` | 向网关申请补 scope 后重试 |
| 403「跨租户」 | org 主体写他域工位 | 改用 m2m 服务号或本域工位 |
| 409「该工位已被占用」 | 一位一占撞占 | 查询现有派单，释放/回执后再占位 |
| 409（occupy 指定 node_code） | 节点撞占/维护中，派单已自动回滚 released | 待节点 idle 后重试 |
| 422 | 位码/节点码不合法或不隶属 | 先经 stations 只读端点核对编码 |
| 502 | Xstation 契约暂不可达 | 退避重试；持续失败联系 DyardOS 侧 |
