複合指令
複合指令是一支已儲存的程式,能在同一個交易內跨多張自訂資料表寫入。你只需建置一次——具型別的 inputs、有序的步驟、宣告的 outputs——之後每次呼叫都是全部提交或全部回滾,呼叫端不必為了「訂單只寫了一半」再自己寫補償邏輯。
建置的門檻刻意高於呼叫。建立指令必須指定標籤,定義中觸及的每一張資料表都要掛在該標籤下,而且你必須有管理權。執行時則只需要對每一張被引用資料表的讀取權(且標籤仍涵蓋全部資料表;restricted 的指令則改為要求管理每一張被引用的資料表、或持有 execute grant),open 的 private 指令還要求你是建立者或每一張被引用資料表的管理者,再加上各步驟本身所需的權限(定義者權限的指令不需要,它以自己的 table_access ceiling 執行)——正因如此,你才能把一個範圍明確、留有稽核紀錄的寫入動作,交給沒有能力自行建置它的人。
Dependency refresh 有兩種政策。一般 column/rule/IaC/purge-repair mutation 只在 mutation 的 commit 內刷新 selected、先前 valid 且仍可編譯的 command;DBAPIError 以外的 failure 會降級或略過,mutation 優先,command 保持 stale。Multi-table purge/repair probe 依 rewritten table 自己的 scope 分組;這種分組是完整的,因為 command contract 採 exact-scope-closed:每個 closure member 都必須通過 command 的精確 scope filter,跨 scope authoring 則以 A referenced table does not exist in this scope 失敗。每個 participating scope 都會收到完整的 rewritten-table set。透過 no-dependent fast path 併發建立的新 command,仍可能依 mutation 前形狀編譯並落成 stale。Trigger write 會先驗證 candidate graph,並要求所有 selected refresh 成功;trigger、history、IaC state 與 refreshed definitions 原子提交,失敗則以 command_dependency_refresh_failed 回滾 trigger mutation。兩種政策都不修復原本已 stale 的 command。
要列出一個範圍裡的指令:做選單與指令清單時呼叫 GET …/command-summaries(相同的指令,但不含定義);要取得單一支的定義,用 GET …/commands?name=<完整名稱>;只有真的需要整頁指令的完整定義時(limit 預設 50、最多 200),才呼叫 GET …/commands。見列出與查找指令。
呼叫指令有兩條通道。/execute 是交易式寫入通道:呼叫端有送選填的 idempotency_key 時,它會保留該 key、遇到 require_approval 規則時把整份計畫暫存並回應 409 approval_required,並留下可回頭查閱的執行稽核紀錄。/query 則是給 mode: "query" 指令的唯讀通道——不上鎖、以 cursor 分頁、沒有重播 key,也不留執行紀錄。走錯通道會得到 409,不會默默執行。在範圍路由上,/execute?dry_run=true 會預覽版本 2 的 command 而不持久化任何東西(見以 dry_run 預覽)。自後端 5.22.0 起,definer 權限 command 的 execute-only 呼叫者從預覽拿到的錯誤,會和從執行拿到的一樣被遮蔽。
宣告了 effect identity 的指令還會讓每個業務事件只被記錄一次:相同驗證後 inputs 與程式的重複執行會回傳原本的 receipt 與 replayed: true 且不寫入任何東西,inputs 不同或程式改過則是終局的 409 ct.effect_identity_conflict(見 effect identity 的 replay 與 conflict)。上述 idempotency_key 的行為不變,GET .../core/capabilities 會列出 ct.effect_identity。action policy 另外加入 execute_policy、execute grant(GET 與 POST …/commands/{command_id}/grants、DELETE …/commands/{command_id}/grants/{grant_id})、definer 的 authority 與其 table_access ceiling,以及 exclude_requester;它的能力是 ct.action_policy。
version 2 的寫入型 command 也可以在 definition 帶 attachment_copies grant(自後端 5.22.0 起)。create 與 replace 會檢查它們:只有通過撰寫門檻的已登入使用者才能儲存 grant,被拒絕的 grant 會回帶 invalid_expression 的 422,或帶 attachment_copy_grant_forbidden 的 403。validate 路由回 200,並在 errors[] 內回報相同的狀態與 detail。
除了三個範圍路由之外,還有一條位於 /agent/commands/… 的 agent 通道。它沒有範圍區段:由服務 token 驗證呼叫端,再以伺服器注入的 header 綁定唯一的 principal,指令的範圍與 acting room 都由此推導。這條路由刻意不出現在 OpenAPI,也不是瀏覽器整合介面。它沒有 dry_run 預覽:agent 通道的 /execute 會忽略該參數,所以在那裡的呼叫會真正執行該 command。
Agent execute 還要求伺服器注入 X-TeamSync-Agent-Channel。選填的 X-TeamSync-Agent-Group-Scope 是 64 個小寫十六進位字元的 digest,而且只有 channel 是 line_group 或 line_room 時才必填;其他 channel 一律不得出現。具型別 header 缺漏或格式錯誤時,request validation 會回 422;格式正確但出現條件組合錯誤時,則回 400 {"detail":{"error":"invalid_agent_group_scope"}}。這些 header 會參與授權、idempotency 與 delivery identity,模型絕不可自行產生。
在人工 scoped REST 上,expected_contract_digest 是選填的 request 欄位。動態生成的 write-command 工具則把它保留為伺服器私有值,不會放進模型可見的 schema;伺服器 adapter 會在每一次生成的寫入執行時,注入從已儲存 command definition 釘選的 digest——在預設的 direct lane 上立即注入;只有聊天設定 requires_confirmation_for_ct_action(預設 false)開啟時,才會等使用者後續以明確自然語言同意後注入。Query 模式工具不使用此欄位。
綁定 Command 的機器寫入者新增範圍受限 service credential 與驗證過的 CT 輸出回呼。沿用既有 executor 與目前來源檢查,不授予原始資料表權限;回呼卡片為特權、僅供參考。
/private/module/custom_tables/{scopeWithId}/commands/{command_id}/service-credentials將有效 service account/room 綁定到一個受限、definition-authority Command。
為可信機器設定 effect identity 與目前 execute grants。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/{command_id}/service-credentials - 部門
/private/module/custom_tables/department/{department_id}/commands/{command_id}/service-credentials - 公司
/private/module/custom_tables/company/{company_id}/commands/{command_id}/service-credentials
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_credential_manager (JWT, manage every authored table) |
| 部門 | 可使用 | require_credential_manager (JWT, manage every authored table) |
| 公司 | 可使用 | require_credential_manager (JWT, manage every authored table) |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
command_id | path | uuid | 是 | 目前有權管理的 Command UUID。 |
請求結構
CommandServiceCredentialCreate| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
chatroom_id | string (uuid) | 是 | 未指定 |
expires_in_seconds | integer | 否 | 未指定 |
service_account_id | string (uuid) | 是 | 未指定 |
回應結構
CommandServiceCredentialIssued| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
chatroom_id | string (uuid) | 是 | 未指定 |
command_id | string (uuid) | 是 | 未指定 |
created_at | string (date-time) | 是 | 未指定 |
credential | string | 是 | 未指定 |
expires_at | string (date-time) | 是 | 未指定 |
id | string (uuid) | 是 | 未指定 |
revoked_at | string (date-time) | null | 否 | 未指定 |
service_account_id | string (uuid) | 是 | 未指定 |
範例
簽發一小時憑證(合成秘密,不能實際使用)
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"command_id": "22222222-2222-4222-8222-222222222222",
"credential_id": "33333333-3333-4333-8333-333333333333"
},
"body": {
"service_account_id": "44444444-4444-4444-8444-444444444444",
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"expires_in_seconds": 3600
}
}{
"id": "33333333-3333-4333-8333-333333333333",
"command_id": "22222222-2222-4222-8222-222222222222",
"service_account_id": "44444444-4444-4444-8444-444444444444",
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"created_at": "2026-10-03T00:00:00Z",
"expires_at": "2026-10-03T01:00:00Z",
"revoked_at": null,
"credential": "ctsp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 422 | 簽發要求 Command 具有 effect_identity。 | |
| 409 | Command/範圍已不符合 definition-authority/restricted 條件。 | |
| 403 | 目前帳號、room、部門、成員或 execute grant 無效。 | |
/private/module/custom_tables/{scopeWithId}/commands/{command_id}/service-credentials檢視簽發、到期/撤銷資訊,不揭露明文。
稽核受管理 Command 的機器綁定。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/{command_id}/service-credentials - 部門
/private/module/custom_tables/department/{department_id}/commands/{command_id}/service-credentials - 公司
/private/module/custom_tables/company/{company_id}/commands/{command_id}/service-credentials
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_credential_manager (JWT, manage every authored table) |
| 部門 | 可使用 | require_credential_manager (JWT, manage every authored table) |
| 公司 | 可使用 | require_credential_manager (JWT, manage every authored table) |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
command_id | path | uuid | 是 | 目前有權管理的 Command UUID。 |
回應結構
CommandServiceCredentialList| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
items | CommandServiceCredentialMetadata[] | 是 | 未指定 |
範例
只讀 metadata
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"command_id": "22222222-2222-4222-8222-222222222222",
"credential_id": "33333333-3333-4333-8333-333333333333"
}
}{
"items": [
{
"id": "33333333-3333-4333-8333-333333333333",
"command_id": "22222222-2222-4222-8222-222222222222",
"service_account_id": "44444444-4444-4444-8444-444444444444",
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"created_at": "2026-10-03T00:00:00Z",
"expires_at": "2026-10-03T01:00:00Z",
"revoked_at": null
}
]
}/private/module/custom_tables/{scopeWithId}/commands/{command_id}/service-credentials/{credential_id}/revoke設定撤銷時間;後續請求/審批釋放重載來源。
移除機器執行能力,不刪除 Command。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/{command_id}/service-credentials/{credential_id}/revoke - 部門
/private/module/custom_tables/department/{department_id}/commands/{command_id}/service-credentials/{credential_id}/revoke - 公司
/private/module/custom_tables/company/{company_id}/commands/{command_id}/service-credentials/{credential_id}/revoke
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_credential_manager (JWT, manage every authored table) |
| 部門 | 可使用 | require_credential_manager (JWT, manage every authored table) |
| 公司 | 可使用 | require_credential_manager (JWT, manage every authored table) |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
command_id | path | uuid | 是 | 目前有權管理的 Command UUID。 |
credential_id | path | uuid | 是 | 憑證 metadata UUID,不是 ctsp 明文 token。 |
回應結構
CommandServiceCredentialMetadata| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
chatroom_id | string (uuid) | 是 | 未指定 |
command_id | string (uuid) | 是 | 未指定 |
created_at | string (date-time) | 是 | 未指定 |
expires_at | string (date-time) | 是 | 未指定 |
id | string (uuid) | 是 | 未指定 |
revoked_at | string (date-time) | null | 否 | 未指定 |
service_account_id | string (uuid) | 是 | 未指定 |
範例
依 metadata ID 撤銷
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"command_id": "22222222-2222-4222-8222-222222222222",
"credential_id": "33333333-3333-4333-8333-333333333333"
}
}{
"id": "33333333-3333-4333-8333-333333333333",
"command_id": "22222222-2222-4222-8222-222222222222",
"service_account_id": "44444444-4444-4444-8444-444444444444",
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"created_at": "2026-10-03T00:00:00Z",
"expires_at": "2026-10-03T01:00:00Z",
"revoked_at": "2026-10-03T00:02:00Z"
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 此受管理 Command 下沒有這個憑證。 | |
/public/module/custom_tables/callback/command-output/{token_id}使用封閉 Run capability 與精確 inputs 呼叫一般 executor。
custom_table_command 政策的唯參考 callback;不是瀏覽器 JWT 工具。
- 完整路由
/public/module/custom_tables/callback/command-output/{token_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 完整路由 | 可使用 | Sealed Run capability (not user JWT) |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
token_id | path | uuid | 是 | 伺服器綁定的封閉 Run callback 識別碼,不是一般 API key。 |
請求結構
SandboxCommandOutputRequest| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
inputs | object | 是 | 未指定 |
回應結構
CommandExecutionResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
authorization_snapshot | object | null | 否 | Immutable command scope, tag, and referenced-table audit facts. |
command_id | string | null | 否 | 未指定 |
created_at | string (date-time) | null | 否 | 未指定 |
duration_ms | integer | null | 否 | 未指定 |
error | string | null | 否 | 未指定 |
executed_at | string (date-time) | null | 否 | Wall-clock stamp frozen at the ORIGINAL execution; an idempotent replay returns the same stamp, so callers can distinguish a replayed earlier result from a fresh write. |
execution_id | string | 是 | 未指定 |
idempotency_key | string | null | 否 | 未指定 |
input_snapshot | object | null | 否 | 未指定 |
invoker_client_id | string | null | 否 | 未指定 |
invoker_id | string | null | 否 | 未指定 |
outputs | object | null | 否 | Declared version-2 scalar or bounded relation outputs. Internal temporary relations and step rows are never exposed. |
redacted | boolean | null | 否 | G07a: true when this definition-authority execution is shown to a caller who does not manage every referenced table — a status-only receipt (no rows, outputs, result refs or response body). |
redacted_outputs | string[] | null | 否 | On a replay: declared outputs withheld because this caller cannot read everything they were computed from, or because they derive from values a replay cannot re-check (computed columns read by a write step). Omitted when none. |
replayed | boolean | null | 否 | True when this business event (the Command's effect_identity occurrence) was already recorded: the body is the ORIGINAL receipt (original execution_id and executed_at) and nothing was written. Omitted otherwise. |
response_body | object | null | 否 | 未指定 |
result_refs | object[] | null | 否 | 未指定 |
staged_change_id | string | null | 否 | 未指定 |
status | "succeeded" | "staged" | "failed" | 是 | 未指定 |
steps | (CommandExecutionV1StepResponse | CommandExecutionV2StepResponse)[] | 否 | 未指定 |
truncated | boolean | null | 否 | True when the persisted response snapshot was trimmed to the audit byte cap: declared outputs/step rows are omitted from this body. Set on an idempotent replay of a large execution so the caller can distinguish a trimmed snapshot from a genuinely empty result. |
範例
傳送政策宣告的 event_key
{
"pathParams": {
"token_id": "33333333-3333-4333-8333-333333333333"
},
"body": {
"inputs": {
"event_key": "evt-001"
}
}
}{
"execution_id": "55555555-5555-4555-8555-555555555555",
"command_id": "22222222-2222-4222-8222-222222222222",
"status": "succeeded",
"steps": []
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 422 | 輸出不符合精確宣告的 Command input schema。 | |
| 409 | 此 Run 已固定另一個有效輸入 digest。 | |
| 409 | 政策、schema 或凍結的 Command digest 已過期失效。 | |
| 404 | Callback capability 缺少/無效;後續即時 Run 狀態檢查可能以同一 code 回 409。 | |
/private/module/custom_tables/{scopeWithId}/commands/{command_id}/prepare具體化確切寫入動作與 Review 義務,不正式寫入業務資料。
先預覽版本 2 write Command,再以獨立明確動作正式寫入。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/{command_id}/prepare - 部門
/private/module/custom_tables/department/{department_id}/commands/{command_id}/prepare - 公司
/private/module/custom_tables/company/{company_id}/commands/{command_id}/prepare
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
command_id | path | uuid | 是 | Scoped 版本 2 Command UUID;公司 Command 的 scope path 須包含 company_id。 |
請求結構
CtPrepareRequest| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
command_digest | string | 是 | 未指定 |
contract_version | integer | 是 | 未指定 |
expected_versions | CtExpectedVersion[] | 否 | 未指定 |
inputs | Record<string, JsonValue> | 是 | 未指定 |
request_idempotency_key | string | 是 | 未指定 |
selection_id | string (uuid) | null | 否 | 未指定 |
回應結構
CtProposalResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
approval_assignments | ApprovalAssignment[] | 是 | 未指定 |
command_digest | string | 是 | 未指定 |
command_id | string (uuid) | 是 | 未指定 |
effect_preview | EffectPreview[] | 是 | 未指定 |
expected_versions | CtExpectedVersion[] | 是 | 未指定 |
expires_at | string (date-time) | 是 | 未指定 |
proposal_digest | string | 是 | 未指定 |
proposal_id | string (uuid) | 是 | 未指定 |
read_set_digest | string | 是 | 未指定 |
semantic_effect_hash | string | 是 | 未指定 |
status | "prepared" | "pending_approval" | "approved" | "rejected" | "stale" | 是 | 未指定 |
範例
凍結已授權更新(hash 請換成伺服器回傳 digest)
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"command_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"contract_version": 1,
"command_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"inputs": {},
"expected_versions": [
{
"table_id": "44444444-4444-4444-8444-444444444444",
"record_id": "88888888-8888-4888-8888-888888888888",
"version": 1
}
],
"request_idempotency_key": "prepare-order-1001"
}
}{
"proposal_id": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
"command_id": "22222222-2222-4222-8222-222222222222",
"proposal_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"command_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"status": "prepared",
"expires_at": "2026-10-03T00:15:00Z",
"effect_preview": [
{
"resource_ref": "table.44444444-4444-4444-8444-444444444444",
"action": "update",
"affected_count": 1,
"summary": "Update 1 record"
}
],
"approval_assignments": [],
"read_set_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"semantic_effect_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"expected_versions": [
{
"table_id": "44444444-4444-4444-8444-444444444444",
"record_id": "88888888-8888-4888-8888-888888888888",
"version": 1
}
]
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
| 403 | 目前 Command 權限拒絕;凍結服務將 body 正規化為 Not found,不揭露私有中繼資料。 | |
| 404 | Command/proposal/operation/selection 不存在、不在此範圍,或此 principal 不可見;body 為 Not found。 | |
| 409 | 提供的 Command digest 不再符合目前定義;依 reprepare 重新準備,不重播舊預覽。 | |
/private/module/custom_tables/{scopeWithId}/commands/{command_id}/operations重新檢查目前權限、定義、讀取集合、版本、約束與核准,再套用儲存動作。
正式寫入已檢視的確切提案;回應遺失時使用相同 key 重試。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/{command_id}/operations - 部門
/private/module/custom_tables/department/{department_id}/commands/{command_id}/operations - 公司
/private/module/custom_tables/company/{company_id}/commands/{command_id}/operations
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
command_id | path | uuid | 是 | Scoped 版本 2 Command UUID;公司 Command 的 scope path 須包含 company_id。 |
請求結構
CtInvokeRequest| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
contract_version | integer | 是 | 未指定 |
expected_proposal_digest | string | 是 | 未指定 |
proposal_id | string (uuid) | 是 | 未指定 |
request_idempotency_key | string | 是 | 未指定 |
回應結構
CtOperationResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
batch_status | "validating" | "ready" | "running" | "partially_applied" | "completed" | "reconciliation_required" | "failed" | "cancelled" | null | 是 | 未指定 |
command_status | "prepared" | "pending_approval" | "approved" | "in_progress" | "applied" | "rejected" | "stale" | "outcome_unknown" | 是 | 未指定 |
external_effects | ExternalEffect[] | 是 | 未指定 |
operation_id | string (uuid) | 是 | 未指定 |
proposal_id | string (uuid) | 是 | 未指定 |
receipt | CtReceipt | null | 是 | 未指定 |
replayed | boolean | 是 | 未指定 |
範例
套用已準備提案並保留收據
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"command_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"contract_version": 1,
"proposal_id": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
"expected_proposal_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"request_idempotency_key": "post-order-1001"
}
}{
"operation_id": "ffffffff-ffff-4fff-8fff-ffffffffffff",
"proposal_id": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
"command_status": "applied",
"batch_status": null,
"receipt": {
"receipt_id": "13131313-1313-4313-8313-131313131313",
"committed_at": "2026-10-03T00:01:00Z",
"command_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"proposal_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"semantic_effect_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"effects": [
{
"effect_ref": "effect.0",
"kind": "record.update",
"resource_id": "88888888-8888-4888-8888-888888888888"
}
],
"effects_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"truncated": false,
"output_artifact_id": null
},
"external_effects": [],
"replayed": false
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
| 403 | 目前 Command 權限拒絕;凍結服務將 body 正規化為 Not found,不揭露私有中繼資料。 | |
| 404 | Command/proposal/operation/selection 不存在、不在此範圍,或此 principal 不可見;body 為 Not found。 | |
| 409 | 提供的 Command digest 不再符合目前定義;依 reprepare 重新準備,不重播舊預覽。 | |
| 503 | Commit 結果未知;保留 operation_id、查詢 operation 或使用相同 request key 重試,不直接換新 key。 | |
/private/module/custom_tables/{scopeWithId}/proposals/{proposal_id}回傳目前已授權的提案狀態、到期時間、有上限的 effect 預覽與 Review assignment 參照。
檢查審批是否完成;核准本身不會正式寫入業務變更。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/proposals/{proposal_id} - 部門
/private/module/custom_tables/department/{department_id}/proposals/{proposal_id} - 公司
/private/module/custom_tables/company/{company_id}/proposals/{proposal_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
proposal_id | path | uuid | 是 | 伺服器回傳、位於此精確範圍的凍結提案 UUID。 |
回應結構
CtProposalResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
approval_assignments | ApprovalAssignment[] | 是 | 未指定 |
command_digest | string | 是 | 未指定 |
command_id | string (uuid) | 是 | 未指定 |
effect_preview | EffectPreview[] | 是 | 未指定 |
expected_versions | CtExpectedVersion[] | 是 | 未指定 |
expires_at | string (date-time) | 是 | 未指定 |
proposal_digest | string | 是 | 未指定 |
proposal_id | string (uuid) | 是 | 未指定 |
read_set_digest | string | 是 | 未指定 |
semantic_effect_hash | string | 是 | 未指定 |
status | "prepared" | "pending_approval" | "approved" | "rejected" | "stale" | 是 | 未指定 |
範例
檢視已準備提案
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"proposal_id": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee"
}
}{
"proposal_id": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
"command_id": "22222222-2222-4222-8222-222222222222",
"proposal_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"command_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"status": "prepared",
"expires_at": "2026-10-03T00:15:00Z",
"effect_preview": [
{
"resource_ref": "table.44444444-4444-4444-8444-444444444444",
"action": "update",
"affected_count": 1,
"summary": "Update 1 record"
}
],
"approval_assignments": [],
"read_set_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"semantic_effect_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"expected_versions": [
{
"table_id": "44444444-4444-4444-8444-444444444444",
"record_id": "88888888-8888-4888-8888-888888888888",
"version": 1
}
]
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
| 403 | 目前 Command 權限拒絕;凍結服務將 body 正規化為 Not found,不揭露私有中繼資料。 | |
| 404 | Command/proposal/operation/selection 不存在、不在此範圍,或此 principal 不可見;body 為 Not found。 | |
/private/module/custom_tables/{scopeWithId}/operations/{operation_id}依目前權限取回持久收據,以及獨立的外部 effect 傳遞狀態。
恢復 ct.operation_outcome_unknown,不自行產生新正式寫入 key。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/operations/{operation_id} - 部門
/private/module/custom_tables/department/{department_id}/operations/{operation_id} - 公司
/private/module/custom_tables/company/{company_id}/operations/{operation_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
operation_id | path | uuid | 是 | 伺服器回傳的持久 operation UUID;結果未知時保留此值。 |
回應結構
CtOperationResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
batch_status | "validating" | "ready" | "running" | "partially_applied" | "completed" | "reconciliation_required" | "failed" | "cancelled" | null | 是 | 未指定 |
command_status | "prepared" | "pending_approval" | "approved" | "in_progress" | "applied" | "rejected" | "stale" | "outcome_unknown" | 是 | 未指定 |
external_effects | ExternalEffect[] | 是 | 未指定 |
operation_id | string (uuid) | 是 | 未指定 |
proposal_id | string (uuid) | 是 | 未指定 |
receipt | CtReceipt | null | 是 | 未指定 |
replayed | boolean | 是 | 未指定 |
範例
讀取原始已提交操作
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"operation_id": "ffffffff-ffff-4fff-8fff-ffffffffffff"
}
}{
"operation_id": "ffffffff-ffff-4fff-8fff-ffffffffffff",
"proposal_id": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
"command_status": "applied",
"batch_status": null,
"receipt": {
"receipt_id": "13131313-1313-4313-8313-131313131313",
"committed_at": "2026-10-03T00:01:00Z",
"command_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"proposal_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"semantic_effect_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"effects": [
{
"effect_ref": "effect.0",
"kind": "record.update",
"resource_id": "88888888-8888-4888-8888-888888888888"
}
],
"effects_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"truncated": false,
"output_artifact_id": null
},
"external_effects": [],
"replayed": false
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
| 403 | 目前 Command 權限拒絕;凍結服務將 body 正規化為 Not found,不揭露私有中繼資料。 | |
| 404 | Command/proposal/operation/selection 不存在、不在此範圍,或此 principal 不可見;body 為 Not found。 | |
/private/module/custom_tables/{scopeWithId}/commands/{command_id}/selections擷取版本 2 query Command 完整實際讀取的列/版本集合;超量時拒絕,不截短。
讓後續提案綁定固定 selection,而不是持續變動的 query page。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/{command_id}/selections - 部門
/private/module/custom_tables/department/{department_id}/commands/{command_id}/selections - 公司
/private/module/custom_tables/company/{company_id}/commands/{command_id}/selections
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
command_id | path | uuid | 是 | Scoped 版本 2 Command UUID;公司 Command 的 scope path 須包含 company_id。 |
請求結構
CtSelectionRequest| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
command_digest | string | 是 | 未指定 |
inputs | Record<string, JsonValue> | 是 | 未指定 |
maximum_rows | integer | 是 | 未指定 |
request_idempotency_key | string | 是 | 未指定 |
回應結構
CtSelectionResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
content_digest | string | 是 | 未指定 |
created_at | string (date-time) | 是 | 未指定 |
expires_at | string (date-time) | 是 | 未指定 |
manifest_ref | string (uuid) | 是 | 未指定 |
next_cursor | string | null | 是 | 未指定 |
query_digest | string | 是 | 未指定 |
records | CtExpectedVersion[] | 是 | 未指定 |
row_count | integer | 是 | 未指定 |
selection_id | string (uuid) | 是 | 未指定 |
範例
凍結最多 1000 筆實際讀取列
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"command_id": "99999999-9999-4999-8999-999999999999"
},
"body": {
"command_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"inputs": {},
"maximum_rows": 1000,
"request_idempotency_key": "select-orders-1001"
}
}{
"selection_id": "12121212-1212-4212-8212-121212121212",
"content_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"query_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"row_count": 1,
"created_at": "2026-10-03T00:00:00Z",
"expires_at": "2026-10-03T00:15:00Z",
"manifest_ref": "12121212-1212-4212-8212-121212121212",
"records": [
{
"table_id": "44444444-4444-4444-8444-444444444444",
"record_id": "88888888-8888-4888-8888-888888888888",
"version": 1
}
],
"next_cursor": null
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
| 403 | 目前 Command 權限拒絕;凍結服務將 body 正規化為 Not found,不揭露私有中繼資料。 | |
| 404 | Command/proposal/operation/selection 不存在、不在此範圍,或此 principal 不可見;body 為 Not found。 | |
| 409 | 提供的 Command digest 不再符合目前定義;依 reprepare 重新準備,不重播舊預覽。 | |
/private/module/custom_tables/{scopeWithId}/selections/{selection_id}從已授權且仍有效的凍結 selection 回傳最多 100 筆列/版本參照。
用不透明 cursor 分頁讀取固定 manifest;不是重新查詢。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/selections/{selection_id} - 部門
/private/module/custom_tables/department/{department_id}/selections/{selection_id} - 公司
/private/module/custom_tables/company/{company_id}/selections/{selection_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
selection_id | path | uuid | 是 | 伺服器回傳、位於此精確範圍的凍結 selection UUID。 |
limit | query | int (1–100) | 否 | Selection 每頁筆數:1–100,預設 100。 |
cursor | query | string (1–512) | 否 | 選用的不透明 selection cursor,1–512 字元;不可跨 selection 重用。 |
回應結構
CtSelectionResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
content_digest | string | 是 | 未指定 |
created_at | string (date-time) | 是 | 未指定 |
expires_at | string (date-time) | 是 | 未指定 |
manifest_ref | string (uuid) | 是 | 未指定 |
next_cursor | string | null | 是 | 未指定 |
query_digest | string | 是 | 未指定 |
records | CtExpectedVersion[] | 是 | 未指定 |
row_count | integer | 是 | 未指定 |
selection_id | string (uuid) | 是 | 未指定 |
範例
讀取 selection 第一頁
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"selection_id": "12121212-1212-4212-8212-121212121212"
},
"query": {
"limit": "100"
}
}{
"selection_id": "12121212-1212-4212-8212-121212121212",
"content_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"query_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"row_count": 1,
"created_at": "2026-10-03T00:00:00Z",
"expires_at": "2026-10-03T00:15:00Z",
"manifest_ref": "12121212-1212-4212-8212-121212121212",
"records": [
{
"table_id": "44444444-4444-4444-8444-444444444444",
"record_id": "88888888-8888-4888-8888-888888888888",
"version": 1
}
],
"next_cursor": null
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
| 403 | 目前 Command 權限拒絕;凍結服務將 body 正規化為 Not found,不揭露私有中繼資料。 | |
| 404 | Command/proposal/operation/selection 不存在、不在此範圍,或此 principal 不可見;body 為 Not found。 | |
/private/module/custom_tables/{scopeWithId}/commands在單一範圍內存放一支具名的有序步驟程式,於同一個交易內跨多張資料表寫入。
當一個商業動作必須同時異動多張資料表、而且「只成功一半」會弄壞資料時使用,例如建立訂單同時扣庫存、建立預約同時佔用時段。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands - 部門
/private/module/custom_tables/department/{department_id}/commands - 公司
/private/module/custom_tables/company/{company_id}/commands
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
請求結構
CommandCreateRequest| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
agent_enabled | boolean | 否 | Whether the chatroom agent may see and invoke this command. When False the command stays fully usable over REST but is never loaded as an agent tool. |
definition | CommandDefinition-Input | 是 | Typed inputs and ordered multi-table write steps. |
description | string | 否 | Human-readable purpose and usage notes. |
execute_policy | "open" | "restricted" | null | 否 | G07a who may invoke. `open` (the default when omitted on create) keeps today's rule: read every referenced table and pass visibility. `restricted` admits only live managers of every referenced table and holders of an execute grant (see /commands/{id}/grants); required for authority `definition`. Omitted on PUT means unchanged, so a PUT never reopens a restricted Command. |
name | string | 是 | Command display name; unique within its scope. |
tag_id | string | 是 | Required tag from the same custom-table scope. Every referenced table must currently be assigned to this tag. |
visibility | "public" | "private" | 否 | ``public``: any principal who can read every referenced table may bind and invoke it. ``private``: the chatroom agent binds it only for its creator; REST invoke/get/list are creator-or-manager only. |
回應結構
CommandResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
agent_enabled | boolean | 否 | Whether the chatroom agent may see and invoke this command. |
authority_owner_id | string | null | 否 | G07a server-derived owner of a definition-authority revision. |
chatroom_id | string | null | 否 | Chatroom scope id |
command_digest | string | null | 否 | 未指定 |
company_id | string | null | 否 | Company scope id |
created_at | string (date-time) | 是 | 未指定 |
created_by | string | 是 | 未指定 |
definition | CommandDefinition-Output | null | 否 | Full definition. Omitted (with definition_redacted=true) for an execute-only caller of a restricted Command, who sees only its interface. |
definition_redacted | boolean | 否 | G07a: true when the caller may invoke this restricted Command but not manage its tables; `interface` then replaces `definition`. |
department_id | string | null | 否 | Department scope id |
description | string | 否 | 未指定 |
execute_policy | "open" | "restricted" | 否 | G07a invocation policy (see CommandCreateRequest). |
id | string | 是 | Command UUID |
interface | CommandInterface | null | 否 | Inputs and output names only (execute-only view). |
is_deleted | boolean | 否 | Soft-delete marker |
lifecycle_redacted | boolean | 否 | True when callback and lifecycle-trigger configuration is omitted because the caller can execute but cannot manage every target table. |
name | string | 是 | 未指定 |
tag_id | string | null | 否 | 未指定 |
updated_at | string (date-time) | 是 | 未指定 |
visibility | "public" | "private" | 否 | 未指定 |
範例
建立「下訂單並扣庫存」指令
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111"
},
"body": {
"name": "建立訂單並扣庫存",
"description": "一次寫入訂單並扣減對應品項庫存;庫存不足時整筆回滾。",
"agent_enabled": true,
"visibility": "public",
"tag_id": "33333333-3333-4333-8333-333333333333",
"definition": {
"version": 2,
"inputs": [
{
"name": "customer",
"type": "string",
"required": true,
"max_length": 100,
"description": "客戶名稱"
},
{
"name": "item",
"type": "string",
"required": true,
"max_length": 100,
"description": "品項名稱"
},
{
"name": "quantity",
"type": "integer",
"required": true,
"description": "出貨數量"
}
],
"steps": [
{
"kind": "select",
"name": "stock",
"from": {
"table": "55555555-5555-4555-8555-555555555555",
"as": "s"
},
"select": {
"id": "$row.s.id",
"on_hand": "$row.s.庫存"
},
"where": {
"$and": [
{
"$eq": [
"$row.s.品項",
"$input.item"
]
}
]
},
"max_rows": 1
},
{
"kind": "let",
"name": "on_hand",
"type": "integer",
"value": {
"$single": {
"relation": "$rel.stock",
"expr": "$item.on_hand"
}
}
},
{
"kind": "assert",
"name": "stock_ok",
"condition": {
"$gte": [
"$var.on_hand",
"$input.quantity"
]
},
"message": "庫存不足,無法建立訂單"
},
{
"kind": "insert",
"name": "order",
"table": "44444444-4444-4444-8444-444444444444",
"values": {
"客戶": "$input.customer",
"品項": "$input.item",
"數量": "$input.quantity",
"狀態": "draft",
"建立者": "$ctx.user_id"
},
"returning": {
"id": "$new.id"
}
},
{
"kind": "update",
"name": "consume",
"table": "55555555-5555-4555-8555-555555555555",
"for_each": "$rel.stock",
"set": {
"庫存": {
"$sub": [
"$item.on_hand",
"$input.quantity"
]
}
},
"match": {
"id": "$item.id"
},
"max_rows": 1,
"returning": {
"id": "$new.id"
}
}
],
"outputs": {
"order_id": {
"value": {
"$single": {
"relation": "$rel.order",
"expr": "$item.id"
}
}
},
"remaining": {
"value": {
"$sub": [
"$var.on_hand",
"$input.quantity"
]
}
}
}
}
}
}{
"id": "22222222-2222-4222-8222-222222222222",
"name": "建立訂單並扣庫存",
"description": "一次寫入訂單並扣減對應品項庫存;庫存不足時整筆回滾。",
"agent_enabled": true,
"visibility": "public",
"execute_policy": "open",
"definition": {
"version": 2,
"inputs": [
{
"name": "customer",
"type": "string",
"required": true,
"max_length": 100,
"description": "客戶名稱"
},
{
"name": "item",
"type": "string",
"required": true,
"max_length": 100,
"description": "品項名稱"
},
{
"name": "quantity",
"type": "integer",
"required": true,
"description": "出貨數量"
}
],
"steps": [
{
"kind": "select",
"name": "stock",
"from": {
"table": "55555555-5555-4555-8555-555555555555",
"as": "s"
},
"joins": [],
"where": {
"$and": [
{
"$eq": [
{
"$column": {
"id": "col_e1111111",
"source": "s"
}
},
"$input.item"
]
}
]
},
"select": {
"id": {
"$metadata": {
"name": "id",
"source": "s"
}
},
"on_hand": {
"$column": {
"id": "col_e2222222",
"source": "s"
}
}
},
"group_by": [],
"order_by": [],
"max_rows": 1
},
{
"kind": "let",
"name": "on_hand",
"type": "integer",
"value": {
"$single": {
"relation": "$rel.stock",
"expr": "$item.on_hand"
}
}
},
{
"kind": "assert",
"name": "stock_ok",
"condition": {
"$gte": [
"$var.on_hand",
"$input.quantity"
]
},
"message": "庫存不足,無法建立訂單"
},
{
"kind": "insert",
"name": "order",
"table": "44444444-4444-4444-8444-444444444444",
"values": {
"col_d1111111": "$input.customer",
"col_d2222222": "$input.item",
"col_d3333333": "$input.quantity",
"col_d4444444": "draft",
"col_d5555555": "$ctx.user_id"
},
"returning": {
"id": {
"$metadata": {
"name": "id",
"source": "new"
}
}
}
},
{
"kind": "update",
"name": "consume",
"table": "55555555-5555-4555-8555-555555555555",
"for_each": "$rel.stock",
"set": {
"col_e2222222": {
"$sub": [
"$item.on_hand",
"$input.quantity"
]
}
},
"match": {
"id": "$item.id"
},
"max_rows": 1,
"returning": {
"id": {
"$metadata": {
"name": "id",
"source": "new"
}
}
}
}
],
"rules": [],
"callbacks": [],
"triggers": [],
"outputs": {
"order_id": {
"value": {
"$single": {
"relation": "$rel.order",
"expr": "$item.id"
}
}
},
"remaining": {
"value": {
"$sub": [
"$var.on_hand",
"$input.quantity"
]
}
}
},
"dependency_contract": {
"version": 1,
"tables": [
{
"table_id": "44444444-4444-4444-8444-444444444444",
"scope": {
"kind": "chatroom",
"id": "11111111-1111-4111-8111-111111111111"
},
"tag_id": "33333333-3333-4333-8333-333333333333",
"schema_digest": "e429417415c7338bf8cbde39f66316e04a4dc0916c9fd6bd75c3c28c8955e698",
"rules_digest": "b31bdbc7c1d2420114149476d59d0f1c6332c665201c5ada47bca84a319194ca",
"approval_digest": "a5ebf4be19d2302d02905683deeab5145cc9411f4b6143fad0cacce447a49583",
"triggers_digest": "tcm1:cb884b06fd3cf2e82fd678f50cecb75470e003ab9fef8e1baec64ebc17de595d",
"columns": [
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d1111111",
"type": "string",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d2222222",
"type": "string",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d3333333",
"type": "integer",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d4444444",
"type": "select",
"nullable": true,
"writable": true,
"options": [
"draft",
"confirmed",
"shipped"
],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d5555555",
"type": "user",
"nullable": true,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
}
]
},
{
"table_id": "55555555-5555-4555-8555-555555555555",
"scope": {
"kind": "chatroom",
"id": "11111111-1111-4111-8111-111111111111"
},
"tag_id": "33333333-3333-4333-8333-333333333333",
"schema_digest": "9806c2357a786074851f1eb439e1d9388cca0a62fd5a1d6057cddd1a0af18c2b",
"rules_digest": "25baf3b5ea0f6de017b54b3c5b494cc1a3cb18b4605a5a13f5d8cc7178c97763",
"approval_digest": "65228dceb4ceac9f4f440e27f92cf367f5b3ee2dc9317bb6732edd87454dde45",
"triggers_digest": "tcm1:d58ce7f752c202e32cebf5370832484bc47ebebe7656f0e53336750a6f527faa",
"columns": [
{
"table_id": "55555555-5555-4555-8555-555555555555",
"column_id": "col_e1111111",
"type": "string",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "55555555-5555-4555-8555-555555555555",
"column_id": "col_e2222222",
"type": "integer",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
}
]
}
]
}
},
"authority_owner_id": null,
"definition_redacted": false,
"interface": null,
"tag_id": "33333333-3333-4333-8333-333333333333",
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"department_id": null,
"company_id": null,
"created_by": "77777777-7777-4777-8777-777777777777",
"created_at": "2026-07-21T02:10:00",
"updated_at": "2026-07-21T02:10:00",
"is_deleted": false,
"lifecycle_redacted": false
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 400 | definition 未通過建置驗證;errors 會列出所有問題,例如 callback URL 的主機解析到私有、loopback 或 link-local 位址。 | |
| 400 | callback 指定的 `api_tool_id` 不是本聊天室可接線的 Custom API tool。所有原因都會列在 `errors`:指令不是聊天室範圍(`api_tool_id requires a chatroom-scope command`)、找不到、已停用、method 不是 POST/PUT/PATCH、有 `query_params`、用到 agent 參數(`{{@…}}`)、用到未知的 `{{var}}`、host 是樣板、url 或 headers 含有字面 `$`、或 url 未通過 SSRF 檢查。 | |
| 400 | `definition.examples` 其中一筆沒有通過這個定義自己的 input 驗證。`index` 是位置、`example` 是名稱、`message` 是引擎原因的前 512 字元——與 `/execute` 執行的是同一套驗證。 | |
| 400 | version 2 definition 的 step kind 是 list/object。Create、update、validate 回靜態 type/loc/msg issue,不含 input 或 ctx;其他 request 驗證維持 422。 | |
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 403 | Definition 帶有 `attachment_copies`(後端 5.22.0),而儲存的使用者並非同時符合下列條件的帳號:存活、不是服務帳號,且讀得到 grant 的來源欄位並寫得到其目的欄位。這項檢查在「管理每一張被引用資料表」的門檻之後執行,所以沒有管理每一張被引用資料表的使用者會先得到一般的 `404`(建立時是 `Referenced table not found in this scope`);validate 則把同一個 `403` 放進 `errors[]`。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
| 404 | 步驟引用了此範圍內不存在的資料表,或呼叫者無權管理的資料表;兩種情況回傳相同內容。 | |
| 404 | 只會在競態時出現:tag 通過了編譯時的檢查,但在 POST/PUT 加鎖重新檢查時已不存在於此範圍。未知的 tag 通常會改回 422 author 階段的 command_tables_outside_tag 信封。 | |
| 409 | 此範圍內已有同名的活指令。自 2026-07-28 起,刪除的指令會立即釋放名字,所以只有活的持有者會撞名。 | |
| 409 | 兩筆編寫寫入爭用同一個指令範圍,InnoDB 中止了這一筆。交易已回滾,沒有任何指令被建立——直接重送請求即可。 | |
| 409 | 無法鎖定範圍根節點(聊天室、部門或公司那一列),因為它不存在或已被軟刪除,例如在請求途中被刪除。沒有寫入任何資料。建立、更新與刪除都會先取得這把鎖,才動到指令本身。 | |
| 422 | 編譯時的 tag 檢查失敗:tag_id 在此範圍內找不到對應的 tag,或編譯後程式(直接或遞移地)依賴的某張資料表沒有掛在這個 tag 上。這個 author 階段信封不帶 tag_id 或 table_ids;請修正 tag 的資料表集合或定義。 | |
| 422 | 只會在競態時出現:某張引用的資料表在編譯時檢查與加鎖重新檢查之間離開了 tag_id;table_ids 會列出不符合的資料表。編譯時的檢查本身回的是 author 階段信封,不帶 tag_id 或 table_ids。 | |
| 422 | 編譯後的定義完全沒有引用任何自訂資料表——只有 let、只有 callback,或根本沒有 step。在這道閘門之前,同樣的 body 會回 200 並存下一支讓所有權限檢查都視為無害的指令。 | |
| 422 | effect_identity 區塊違反建立規則:例如 key term 讀了 $input、欄位值或其他 metadata 名稱、key 的 select 位在第一個寫入之後,或 key 沒有針對 id 的 row term。回應是 author 階段的 program error `effect_identity_invalid`,`path` 對 term 是 effect_identity.key[i](區塊層級的規則則是 effect_identity.key 或 effect_identity);不會儲存任何東西。 | |
| 422 | Definition 宣告了保留給 kernel 擁有之審批 provenance 的 output 名稱 `approval_provenance`(backend 5.21.0)。撰寫階段回 422 與原始 program-error envelope,validate 則把同一個 body 放進 `errors[]`。宣告它的 IaC `command` 行會得到 code 為 `command_output_reserved` 的 plan 階段 validate 錯誤。先前已儲存的 definition 不受影響。 | |
| 422 | 設了 `public: true` 的 output 或 `assert` 無法證明可以安全地顯示給 execute-only 呼叫者:受限或有 `secret_ref` 的欄位、計算欄位、不透明運算子、relation(整列)output、帶 row policy 的來源資料表,或被排除的 context/metadata 來源。`command_public_value_limit_exceeded` 是同樣的 author-phase 422,表示超過公開上限(沒有 `max_items`(上限 20)的陣列、超過 512 個呈現字元或合計超過 4 KiB 的常數、單一項目背後超過 1,000 個來源、證明超過 50,000 個節點)。 | |
| 422 | 定義的編譯錯誤。Author 階段的 program error 以 422 回傳原始信封,未設定時不帶 `step`/`path` key——這裡是 `ambiguous_reference`,資料表 token 同時符合此範圍內多張資料表(請改用 `@table:<id>`)。其他錯誤碼包括 `invalid_expression`、`expression_limit_exceeded`、`join_validation_failed`、`write_validation_failed` 與 `unsupported_clause_expression`。 | |
| 422 | Callback 或生命週期 trigger 的形狀規則沒有通過 request 驗證(URL 形式、header、body 大小、url 與 api_tool_id 擇一、名稱不可重複、指向已知的 callback 名稱)。Validate 也會回同樣的 422,而不是報告。 | |
| 422 | `attachment_copies` 裡的 grant 沒有描述 definition 自己所選出的那一次複製(後端 5.22.0):缺少某個 step 或 step 種類不對、來源 `select` 有 join、分組、`distinct` 或沒有 `where`、目的值不是恰好以 `$single` 或 `$first` 取該 select 的投影、某個欄位不是附件欄位、同一目的欄位有兩個 grant,或這個 Command 是 version 1、query 模式、帶 `proposal_policy`,或宣告了 `authority: "definition"`。Body 是 author 階段的 `invalid_expression` 信封,`path` 為 `attachment_copies[<index>]`(或 `attachment_copies`),`message` 指出被違反的規則。 | |
| 422 | definition 設了 `authority: "definition"`,但這個 command 不是 `execute_policy: "restricted"`(PUT 時省略 policy 會保留目前的值)。 | |
| 422 | `table_access` 沒有恰好指名 definition 引用的資料表;`missing_table_ids` 與 `extra_table_ids` 列出差異。只有建立、更新與 `/validate` 會做這項檢查,IaC apply 不會。 | |
/private/module/custom_tables/{scopeWithId}/commands回傳此範圍內、呼叫者對每一張被引用資料表都有讀取權的指令,依建立時間新到舊排序。Private 指令只會列給建立者,或管理所有被引用資料表的呼叫者。`restricted` 指令不適用這兩條規則:它只列給管理全部被引用資料表的人,以及持有 execute grant 的主體。對可以執行、但不管理每張被引用資料表的呼叫者,這類 restricted 指令會以同一個 execute-only 檢視(`definition: null`、`definition_redacted: true`、`interface`)出現。帶 `?name=` 時,回傳名稱完全相符、且你看得到的那一支指令。
需要指令定義時使用:用 `?name=` 取單一支指令,或在把按鈕接上某支指令前,確認特定使用者實際看得到哪些複合動作。做指令選單或指令清單,請改用 command-summaries,它不帶定義。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands - 部門
/private/module/custom_tables/department/{department_id}/commands - 公司
/private/module/custom_tables/company/{company_id}/commands
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
skip | query | int >= 0 | 否 | 略過前幾筆可見指令,預設為 0。 |
limit | query | int 1..200 | 否 | 最多回傳幾筆指令,可填 1 到 200,預設為 50。 |
name | query | string 1..64 | 否 | 指令的完整名稱,1 到 64 個字元。回傳名稱相符且你看得到的那支指令,沒有就回空的 items。想取得單一支指令的定義、又不想列出整個範圍時使用。 |
回應結構
CommandListResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
items | CommandResponse[] | 否 | 未指定 |
total | integer | 是 | 未指定 |
範例
列出聊天室中第一頁指令
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111"
},
"query": {
"skip": "0",
"limit": "20"
}
}{
"items": [
{
"id": "22222222-2222-4222-8222-222222222222",
"name": "建立訂單並扣庫存",
"description": "一次寫入訂單並扣減對應品項庫存;庫存不足時整筆回滾。",
"agent_enabled": true,
"visibility": "public",
"execute_policy": "open",
"definition": {
"version": 2,
"inputs": [
{
"name": "customer",
"type": "string",
"required": true,
"max_length": 100,
"description": "客戶名稱"
},
{
"name": "item",
"type": "string",
"required": true,
"max_length": 100,
"description": "品項名稱"
},
{
"name": "quantity",
"type": "integer",
"required": true,
"description": "出貨數量"
}
],
"steps": [
{
"kind": "select",
"name": "stock",
"from": {
"table": "55555555-5555-4555-8555-555555555555",
"as": "s"
},
"joins": [],
"where": {
"$and": [
{
"$eq": [
{
"$column": {
"id": "col_e1111111",
"source": "s"
}
},
"$input.item"
]
}
]
},
"select": {
"id": {
"$metadata": {
"name": "id",
"source": "s"
}
},
"on_hand": {
"$column": {
"id": "col_e2222222",
"source": "s"
}
}
},
"group_by": [],
"order_by": [],
"max_rows": 1
},
{
"kind": "let",
"name": "on_hand",
"type": "integer",
"value": {
"$single": {
"relation": "$rel.stock",
"expr": "$item.on_hand"
}
}
},
{
"kind": "assert",
"name": "stock_ok",
"condition": {
"$gte": [
"$var.on_hand",
"$input.quantity"
]
},
"message": "庫存不足,無法建立訂單"
},
{
"kind": "insert",
"name": "order",
"table": "44444444-4444-4444-8444-444444444444",
"values": {
"col_d1111111": "$input.customer",
"col_d2222222": "$input.item",
"col_d3333333": "$input.quantity",
"col_d4444444": "draft",
"col_d5555555": "$ctx.user_id"
},
"returning": {
"id": {
"$metadata": {
"name": "id",
"source": "new"
}
}
}
},
{
"kind": "update",
"name": "consume",
"table": "55555555-5555-4555-8555-555555555555",
"for_each": "$rel.stock",
"set": {
"col_e2222222": {
"$sub": [
"$item.on_hand",
"$input.quantity"
]
}
},
"match": {
"id": "$item.id"
},
"max_rows": 1,
"returning": {
"id": {
"$metadata": {
"name": "id",
"source": "new"
}
}
}
}
],
"rules": [],
"callbacks": [],
"triggers": [],
"outputs": {
"order_id": {
"value": {
"$single": {
"relation": "$rel.order",
"expr": "$item.id"
}
}
},
"remaining": {
"value": {
"$sub": [
"$var.on_hand",
"$input.quantity"
]
}
}
},
"dependency_contract": {
"version": 1,
"tables": [
{
"table_id": "44444444-4444-4444-8444-444444444444",
"scope": {
"kind": "chatroom",
"id": "11111111-1111-4111-8111-111111111111"
},
"tag_id": "33333333-3333-4333-8333-333333333333",
"schema_digest": "e429417415c7338bf8cbde39f66316e04a4dc0916c9fd6bd75c3c28c8955e698",
"rules_digest": "b31bdbc7c1d2420114149476d59d0f1c6332c665201c5ada47bca84a319194ca",
"approval_digest": "a5ebf4be19d2302d02905683deeab5145cc9411f4b6143fad0cacce447a49583",
"triggers_digest": "tcm1:cb884b06fd3cf2e82fd678f50cecb75470e003ab9fef8e1baec64ebc17de595d",
"columns": [
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d1111111",
"type": "string",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d2222222",
"type": "string",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d3333333",
"type": "integer",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d4444444",
"type": "select",
"nullable": true,
"writable": true,
"options": [
"draft",
"confirmed",
"shipped"
],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d5555555",
"type": "user",
"nullable": true,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
}
]
},
{
"table_id": "55555555-5555-4555-8555-555555555555",
"scope": {
"kind": "chatroom",
"id": "11111111-1111-4111-8111-111111111111"
},
"tag_id": "33333333-3333-4333-8333-333333333333",
"schema_digest": "9806c2357a786074851f1eb439e1d9388cca0a62fd5a1d6057cddd1a0af18c2b",
"rules_digest": "25baf3b5ea0f6de017b54b3c5b494cc1a3cb18b4605a5a13f5d8cc7178c97763",
"approval_digest": "65228dceb4ceac9f4f440e27f92cf367f5b3ee2dc9317bb6732edd87454dde45",
"triggers_digest": "tcm1:d58ce7f752c202e32cebf5370832484bc47ebebe7656f0e53336750a6f527faa",
"columns": [
{
"table_id": "55555555-5555-4555-8555-555555555555",
"column_id": "col_e1111111",
"type": "string",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "55555555-5555-4555-8555-555555555555",
"column_id": "col_e2222222",
"type": "integer",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
}
]
}
]
}
},
"authority_owner_id": null,
"definition_redacted": false,
"interface": null,
"tag_id": "33333333-3333-4333-8333-333333333333",
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"department_id": null,
"company_id": null,
"created_by": "77777777-7777-4777-8777-777777777777",
"created_at": "2026-07-21T02:10:00",
"updated_at": "2026-07-21T02:10:00",
"is_deleted": false,
"lifecycle_redacted": false
}
],
"total": 1
}用完整名稱查詢單一支指令
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111"
},
"query": {
"name": "建立訂單並扣庫存"
}
}{
"items": [
{
"id": "22222222-2222-4222-8222-222222222222",
"name": "建立訂單並扣庫存",
"description": "一次寫入訂單並扣減對應品項庫存;庫存不足時整筆回滾。",
"agent_enabled": true,
"visibility": "public",
"execute_policy": "open",
"definition": {
"version": 2,
"inputs": [
{
"name": "customer",
"type": "string",
"required": true,
"max_length": 100,
"description": "客戶名稱"
},
{
"name": "item",
"type": "string",
"required": true,
"max_length": 100,
"description": "品項名稱"
},
{
"name": "quantity",
"type": "integer",
"required": true,
"description": "出貨數量"
}
],
"steps": [
{
"kind": "select",
"name": "stock",
"from": {
"table": "55555555-5555-4555-8555-555555555555",
"as": "s"
},
"joins": [],
"where": {
"$and": [
{
"$eq": [
{
"$column": {
"id": "col_e1111111",
"source": "s"
}
},
"$input.item"
]
}
]
},
"select": {
"id": {
"$metadata": {
"name": "id",
"source": "s"
}
},
"on_hand": {
"$column": {
"id": "col_e2222222",
"source": "s"
}
}
},
"group_by": [],
"order_by": [],
"max_rows": 1
},
{
"kind": "let",
"name": "on_hand",
"type": "integer",
"value": {
"$single": {
"relation": "$rel.stock",
"expr": "$item.on_hand"
}
}
},
{
"kind": "assert",
"name": "stock_ok",
"condition": {
"$gte": [
"$var.on_hand",
"$input.quantity"
]
},
"message": "庫存不足,無法建立訂單"
},
{
"kind": "insert",
"name": "order",
"table": "44444444-4444-4444-8444-444444444444",
"values": {
"col_d1111111": "$input.customer",
"col_d2222222": "$input.item",
"col_d3333333": "$input.quantity",
"col_d4444444": "draft",
"col_d5555555": "$ctx.user_id"
},
"returning": {
"id": {
"$metadata": {
"name": "id",
"source": "new"
}
}
}
},
{
"kind": "update",
"name": "consume",
"table": "55555555-5555-4555-8555-555555555555",
"for_each": "$rel.stock",
"set": {
"col_e2222222": {
"$sub": [
"$item.on_hand",
"$input.quantity"
]
}
},
"match": {
"id": "$item.id"
},
"max_rows": 1,
"returning": {
"id": {
"$metadata": {
"name": "id",
"source": "new"
}
}
}
}
],
"rules": [],
"callbacks": [],
"triggers": [],
"outputs": {
"order_id": {
"value": {
"$single": {
"relation": "$rel.order",
"expr": "$item.id"
}
}
},
"remaining": {
"value": {
"$sub": [
"$var.on_hand",
"$input.quantity"
]
}
}
},
"dependency_contract": {
"version": 1,
"tables": [
{
"table_id": "44444444-4444-4444-8444-444444444444",
"scope": {
"kind": "chatroom",
"id": "11111111-1111-4111-8111-111111111111"
},
"tag_id": "33333333-3333-4333-8333-333333333333",
"schema_digest": "e429417415c7338bf8cbde39f66316e04a4dc0916c9fd6bd75c3c28c8955e698",
"rules_digest": "b31bdbc7c1d2420114149476d59d0f1c6332c665201c5ada47bca84a319194ca",
"approval_digest": "a5ebf4be19d2302d02905683deeab5145cc9411f4b6143fad0cacce447a49583",
"triggers_digest": "tcm1:cb884b06fd3cf2e82fd678f50cecb75470e003ab9fef8e1baec64ebc17de595d",
"columns": [
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d1111111",
"type": "string",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d2222222",
"type": "string",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d3333333",
"type": "integer",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d4444444",
"type": "select",
"nullable": true,
"writable": true,
"options": [
"draft",
"confirmed",
"shipped"
],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d5555555",
"type": "user",
"nullable": true,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
}
]
},
{
"table_id": "55555555-5555-4555-8555-555555555555",
"scope": {
"kind": "chatroom",
"id": "11111111-1111-4111-8111-111111111111"
},
"tag_id": "33333333-3333-4333-8333-333333333333",
"schema_digest": "9806c2357a786074851f1eb439e1d9388cca0a62fd5a1d6057cddd1a0af18c2b",
"rules_digest": "25baf3b5ea0f6de017b54b3c5b494cc1a3cb18b4605a5a13f5d8cc7178c97763",
"approval_digest": "65228dceb4ceac9f4f440e27f92cf367f5b3ee2dc9317bb6732edd87454dde45",
"triggers_digest": "tcm1:d58ce7f752c202e32cebf5370832484bc47ebebe7656f0e53336750a6f527faa",
"columns": [
{
"table_id": "55555555-5555-4555-8555-555555555555",
"column_id": "col_e1111111",
"type": "string",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "55555555-5555-4555-8555-555555555555",
"column_id": "col_e2222222",
"type": "integer",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
}
]
}
]
}
},
"authority_owner_id": null,
"definition_redacted": false,
"interface": null,
"tag_id": "33333333-3333-4333-8333-333333333333",
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"department_id": null,
"company_id": null,
"created_by": "77777777-7777-4777-8777-777777777777",
"created_at": "2026-07-21T02:10:00",
"updated_at": "2026-07-21T02:10:00",
"is_deleted": false,
"lifecycle_redacted": false
}
],
"total": 1
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
/private/module/custom_tables/{scopeWithId}/command-summaries回傳和指令清單路由相同的指令:順序、篩選(skip、limit、name)與 total 都一樣,但每一列只是摘要,沒有 definition,也沒有 interface。每一列有 id、範圍 ID、name、description、agent_enabled、visibility、tag_id、execute_policy、已儲存定義的 version 與 mode、dependency_status、兩個遮蔽旗標、is_deleted、created_by 與時間戳記。
指令選單、指令清單,以及只需要名稱與狀態的場合都用它:不必載入、驗證並回傳每一支指令的定義。需要單一支的定義時,用清單路由的 ?name=,或 GET …/commands/{command_id}。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/command-summaries - 部門
/private/module/custom_tables/department/{department_id}/command-summaries - 公司
/private/module/custom_tables/company/{company_id}/command-summaries
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
skip | query | int >= 0 | 否 | 略過前幾筆可見指令,預設為 0。 |
limit | query | int 1..200 | 否 | 最多回傳幾筆指令,可填 1 到 200,預設為 50。 |
name | query | string 1..64 | 否 | 指令的完整名稱,1 到 64 個字元。回傳名稱相符且你看得到的那支指令,沒有就回空的 items。想取得單一支指令的定義、又不想列出整個範圍時使用。 |
回應結構
CommandSummaryListResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
items | CommandSummaryResponse[] | 否 | 未指定 |
total | integer | 是 | 未指定 |
範例
列出前 50 支指令的摘要
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111"
},
"query": {
"skip": "0",
"limit": "50"
}
}{
"items": [
{
"id": "22222222-2222-4222-8222-222222222222",
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"department_id": null,
"company_id": null,
"name": "建立訂單並扣庫存",
"description": "一次寫入訂單並扣減對應品項庫存;庫存不足時整筆回滾。",
"agent_enabled": true,
"visibility": "public",
"tag_id": "33333333-3333-4333-8333-333333333333",
"execute_policy": "open",
"version": 2,
"mode": null,
"definition_redacted": false,
"lifecycle_redacted": false,
"is_deleted": false,
"dependency_status": "current",
"created_by": "77777777-7777-4777-8777-777777777777",
"created_at": "2026-07-21T02:10:00",
"updated_at": "2026-07-21T02:10:00"
}
],
"total": 1
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
/private/module/custom_tables/{scopeWithId}/commands/{command_id}回傳一支已儲存的指令,含完整 definition、標籤與範圍 id。呼叫者可執行、但不管理每張被引用資料表的 `restricted` 指令,會以 execute-only 檢視回傳:`definition: null`、`definition_redacted: true`、`lifecycle_redacted: true` 與 `interface: {inputs, outputs, mode}`。
在製作呼叫表單前先讀取,才能知道每個宣告的 input 名稱、型別,以及是否必填。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/{command_id} - 部門
/private/module/custom_tables/department/{department_id}/commands/{command_id} - 公司
/private/module/custom_tables/company/{company_id}/commands/{command_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
command_id | path | string 1..36 | 是 | 要讀取的指令 UUID。 |
回應結構
CommandResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
agent_enabled | boolean | 否 | Whether the chatroom agent may see and invoke this command. |
authority_owner_id | string | null | 否 | G07a server-derived owner of a definition-authority revision. |
chatroom_id | string | null | 否 | Chatroom scope id |
command_digest | string | null | 否 | 未指定 |
company_id | string | null | 否 | Company scope id |
created_at | string (date-time) | 是 | 未指定 |
created_by | string | 是 | 未指定 |
definition | CommandDefinition-Output | null | 否 | Full definition. Omitted (with definition_redacted=true) for an execute-only caller of a restricted Command, who sees only its interface. |
definition_redacted | boolean | 否 | G07a: true when the caller may invoke this restricted Command but not manage its tables; `interface` then replaces `definition`. |
department_id | string | null | 否 | Department scope id |
description | string | 否 | 未指定 |
execute_policy | "open" | "restricted" | 否 | G07a invocation policy (see CommandCreateRequest). |
id | string | 是 | Command UUID |
interface | CommandInterface | null | 否 | Inputs and output names only (execute-only view). |
is_deleted | boolean | 否 | Soft-delete marker |
lifecycle_redacted | boolean | 否 | True when callback and lifecycle-trigger configuration is omitted because the caller can execute but cannot manage every target table. |
name | string | 是 | 未指定 |
tag_id | string | null | 否 | 未指定 |
updated_at | string (date-time) | 是 | 未指定 |
visibility | "public" | "private" | 否 | 未指定 |
範例
在呼叫前讀取指令定義
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"command_id": "22222222-2222-4222-8222-222222222222"
}
}{
"id": "22222222-2222-4222-8222-222222222222",
"name": "建立訂單並扣庫存",
"description": "一次寫入訂單並扣減對應品項庫存;庫存不足時整筆回滾。",
"agent_enabled": true,
"visibility": "public",
"execute_policy": "open",
"definition": {
"version": 2,
"inputs": [
{
"name": "customer",
"type": "string",
"required": true,
"max_length": 100,
"description": "客戶名稱"
},
{
"name": "item",
"type": "string",
"required": true,
"max_length": 100,
"description": "品項名稱"
},
{
"name": "quantity",
"type": "integer",
"required": true,
"description": "出貨數量"
}
],
"steps": [
{
"kind": "select",
"name": "stock",
"from": {
"table": "55555555-5555-4555-8555-555555555555",
"as": "s"
},
"joins": [],
"where": {
"$and": [
{
"$eq": [
{
"$column": {
"id": "col_e1111111",
"source": "s"
}
},
"$input.item"
]
}
]
},
"select": {
"id": {
"$metadata": {
"name": "id",
"source": "s"
}
},
"on_hand": {
"$column": {
"id": "col_e2222222",
"source": "s"
}
}
},
"group_by": [],
"order_by": [],
"max_rows": 1
},
{
"kind": "let",
"name": "on_hand",
"type": "integer",
"value": {
"$single": {
"relation": "$rel.stock",
"expr": "$item.on_hand"
}
}
},
{
"kind": "assert",
"name": "stock_ok",
"condition": {
"$gte": [
"$var.on_hand",
"$input.quantity"
]
},
"message": "庫存不足,無法建立訂單"
},
{
"kind": "insert",
"name": "order",
"table": "44444444-4444-4444-8444-444444444444",
"values": {
"col_d1111111": "$input.customer",
"col_d2222222": "$input.item",
"col_d3333333": "$input.quantity",
"col_d4444444": "draft",
"col_d5555555": "$ctx.user_id"
},
"returning": {
"id": {
"$metadata": {
"name": "id",
"source": "new"
}
}
}
},
{
"kind": "update",
"name": "consume",
"table": "55555555-5555-4555-8555-555555555555",
"for_each": "$rel.stock",
"set": {
"col_e2222222": {
"$sub": [
"$item.on_hand",
"$input.quantity"
]
}
},
"match": {
"id": "$item.id"
},
"max_rows": 1,
"returning": {
"id": {
"$metadata": {
"name": "id",
"source": "new"
}
}
}
}
],
"rules": [],
"callbacks": [],
"triggers": [],
"outputs": {
"order_id": {
"value": {
"$single": {
"relation": "$rel.order",
"expr": "$item.id"
}
}
},
"remaining": {
"value": {
"$sub": [
"$var.on_hand",
"$input.quantity"
]
}
}
},
"dependency_contract": {
"version": 1,
"tables": [
{
"table_id": "44444444-4444-4444-8444-444444444444",
"scope": {
"kind": "chatroom",
"id": "11111111-1111-4111-8111-111111111111"
},
"tag_id": "33333333-3333-4333-8333-333333333333",
"schema_digest": "e429417415c7338bf8cbde39f66316e04a4dc0916c9fd6bd75c3c28c8955e698",
"rules_digest": "b31bdbc7c1d2420114149476d59d0f1c6332c665201c5ada47bca84a319194ca",
"approval_digest": "a5ebf4be19d2302d02905683deeab5145cc9411f4b6143fad0cacce447a49583",
"triggers_digest": "tcm1:cb884b06fd3cf2e82fd678f50cecb75470e003ab9fef8e1baec64ebc17de595d",
"columns": [
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d1111111",
"type": "string",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d2222222",
"type": "string",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d3333333",
"type": "integer",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d4444444",
"type": "select",
"nullable": true,
"writable": true,
"options": [
"draft",
"confirmed",
"shipped"
],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d5555555",
"type": "user",
"nullable": true,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
}
]
},
{
"table_id": "55555555-5555-4555-8555-555555555555",
"scope": {
"kind": "chatroom",
"id": "11111111-1111-4111-8111-111111111111"
},
"tag_id": "33333333-3333-4333-8333-333333333333",
"schema_digest": "9806c2357a786074851f1eb439e1d9388cca0a62fd5a1d6057cddd1a0af18c2b",
"rules_digest": "25baf3b5ea0f6de017b54b3c5b494cc1a3cb18b4605a5a13f5d8cc7178c97763",
"approval_digest": "65228dceb4ceac9f4f440e27f92cf367f5b3ee2dc9317bb6732edd87454dde45",
"triggers_digest": "tcm1:d58ce7f752c202e32cebf5370832484bc47ebebe7656f0e53336750a6f527faa",
"columns": [
{
"table_id": "55555555-5555-4555-8555-555555555555",
"column_id": "col_e1111111",
"type": "string",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "55555555-5555-4555-8555-555555555555",
"column_id": "col_e2222222",
"type": "integer",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
}
]
}
]
}
},
"authority_owner_id": null,
"definition_redacted": false,
"interface": null,
"tag_id": "33333333-3333-4333-8333-333333333333",
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"department_id": null,
"company_id": null,
"created_by": "77777777-7777-4777-8777-777777777777",
"created_at": "2026-07-21T02:10:00",
"updated_at": "2026-07-21T02:10:00",
"is_deleted": false,
"lifecycle_redacted": false
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
| 404 | 指令不存在於此範圍、已被軟刪除、沒有引用任何資料表或引用了呼叫者無法讀取的資料表、它的 tag 已不再涵蓋每一張引用的資料表,或指令是 private 且呼叫者既不是建立者、也不管理全部被引用資料表。`restricted` 指令不看資料表可讀性與 `visibility`:只有既不管理全部被引用資料表、也沒有 execute grant 的呼叫者才會得到 404。 | |
/private/module/custom_tables/{scopeWithId}/commands/{command_id}整支取代指令的名稱、說明、標籤與 definition,並依即時 schema 重新驗證。
當指令背後的商業規則改變,或它寫入的資料表新增了必須填寫的欄位時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/{command_id} - 部門
/private/module/custom_tables/department/{department_id}/commands/{command_id} - 公司
/private/module/custom_tables/company/{company_id}/commands/{command_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
command_id | path | string 1..36 | 是 | 要取代的指令 UUID。 |
請求結構
CommandUpdateRequest| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
agent_enabled | boolean | 否 | Whether the chatroom agent may see and invoke this command. When False the command stays fully usable over REST but is never loaded as an agent tool. |
definition | CommandDefinition-Input | 是 | Typed inputs and ordered multi-table write steps. |
description | string | 否 | Human-readable purpose and usage notes. |
execute_policy | "open" | "restricted" | null | 否 | G07a who may invoke. `open` (the default when omitted on create) keeps today's rule: read every referenced table and pass visibility. `restricted` admits only live managers of every referenced table and holders of an execute grant (see /commands/{id}/grants); required for authority `definition`. Omitted on PUT means unchanged, so a PUT never reopens a restricted Command. |
name | string | 是 | Command display name; unique within its scope. |
tag_id | string | 是 | Required tag from the same custom-table scope. Every referenced table must currently be assigned to this tag. |
visibility | "public" | "private" | 是 | Required on update: the desired visibility after replacement. |
回應結構
CommandResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
agent_enabled | boolean | 否 | Whether the chatroom agent may see and invoke this command. |
authority_owner_id | string | null | 否 | G07a server-derived owner of a definition-authority revision. |
chatroom_id | string | null | 否 | Chatroom scope id |
command_digest | string | null | 否 | 未指定 |
company_id | string | null | 否 | Company scope id |
created_at | string (date-time) | 是 | 未指定 |
created_by | string | 是 | 未指定 |
definition | CommandDefinition-Output | null | 否 | Full definition. Omitted (with definition_redacted=true) for an execute-only caller of a restricted Command, who sees only its interface. |
definition_redacted | boolean | 否 | G07a: true when the caller may invoke this restricted Command but not manage its tables; `interface` then replaces `definition`. |
department_id | string | null | 否 | Department scope id |
description | string | 否 | 未指定 |
execute_policy | "open" | "restricted" | 否 | G07a invocation policy (see CommandCreateRequest). |
id | string | 是 | Command UUID |
interface | CommandInterface | null | 否 | Inputs and output names only (execute-only view). |
is_deleted | boolean | 否 | Soft-delete marker |
lifecycle_redacted | boolean | 否 | True when callback and lifecycle-trigger configuration is omitted because the caller can execute but cannot manage every target table. |
name | string | 是 | 未指定 |
tag_id | string | null | 否 | 未指定 |
updated_at | string (date-time) | 是 | 未指定 |
visibility | "public" | "private" | 否 | 未指定 |
範例
更新指令定義與說明
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"command_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"name": "建立訂單並扣庫存",
"description": "一次寫入訂單並扣減對應品項庫存;庫存不足時整筆回滾。改為一次最多扣 50 件。",
"agent_enabled": false,
"visibility": "private",
"tag_id": "33333333-3333-4333-8333-333333333333",
"definition": {
"version": 2,
"inputs": [
{
"name": "customer",
"type": "string",
"required": true,
"max_length": 100,
"description": "客戶名稱"
},
{
"name": "item",
"type": "string",
"required": true,
"max_length": 100,
"description": "品項名稱"
},
{
"name": "quantity",
"type": "integer",
"required": true,
"description": "出貨數量"
}
],
"steps": [
{
"kind": "select",
"name": "stock",
"from": {
"table": "55555555-5555-4555-8555-555555555555",
"as": "s"
},
"select": {
"id": "$row.s.id",
"on_hand": "$row.s.庫存"
},
"where": {
"$and": [
{
"$eq": [
"$row.s.品項",
"$input.item"
]
}
]
},
"max_rows": 1
},
{
"kind": "let",
"name": "on_hand",
"type": "integer",
"value": {
"$single": {
"relation": "$rel.stock",
"expr": "$item.on_hand"
}
}
},
{
"kind": "assert",
"name": "stock_ok",
"condition": {
"$gte": [
"$var.on_hand",
"$input.quantity"
]
},
"message": "庫存不足,無法建立訂單"
},
{
"kind": "insert",
"name": "order",
"table": "44444444-4444-4444-8444-444444444444",
"values": {
"客戶": "$input.customer",
"品項": "$input.item",
"數量": "$input.quantity",
"狀態": "draft",
"建立者": "$ctx.user_id"
},
"returning": {
"id": "$new.id"
}
},
{
"kind": "update",
"name": "consume",
"table": "55555555-5555-4555-8555-555555555555",
"for_each": "$rel.stock",
"set": {
"庫存": {
"$sub": [
"$item.on_hand",
"$input.quantity"
]
}
},
"match": {
"id": "$item.id"
},
"max_rows": 1,
"returning": {
"id": "$new.id"
}
}
],
"outputs": {
"order_id": {
"value": {
"$single": {
"relation": "$rel.order",
"expr": "$item.id"
}
}
},
"remaining": {
"value": {
"$sub": [
"$var.on_hand",
"$input.quantity"
]
}
}
}
}
}
}{
"id": "22222222-2222-4222-8222-222222222222",
"name": "建立訂單並扣庫存",
"description": "一次寫入訂單並扣減對應品項庫存;庫存不足時整筆回滾。改為一次最多扣 50 件。",
"agent_enabled": false,
"visibility": "private",
"execute_policy": "open",
"definition": {
"version": 2,
"inputs": [
{
"name": "customer",
"type": "string",
"required": true,
"max_length": 100,
"description": "客戶名稱"
},
{
"name": "item",
"type": "string",
"required": true,
"max_length": 100,
"description": "品項名稱"
},
{
"name": "quantity",
"type": "integer",
"required": true,
"description": "出貨數量"
}
],
"steps": [
{
"kind": "select",
"name": "stock",
"from": {
"table": "55555555-5555-4555-8555-555555555555",
"as": "s"
},
"joins": [],
"where": {
"$and": [
{
"$eq": [
{
"$column": {
"id": "col_e1111111",
"source": "s"
}
},
"$input.item"
]
}
]
},
"select": {
"id": {
"$metadata": {
"name": "id",
"source": "s"
}
},
"on_hand": {
"$column": {
"id": "col_e2222222",
"source": "s"
}
}
},
"group_by": [],
"order_by": [],
"max_rows": 1
},
{
"kind": "let",
"name": "on_hand",
"type": "integer",
"value": {
"$single": {
"relation": "$rel.stock",
"expr": "$item.on_hand"
}
}
},
{
"kind": "assert",
"name": "stock_ok",
"condition": {
"$gte": [
"$var.on_hand",
"$input.quantity"
]
},
"message": "庫存不足,無法建立訂單"
},
{
"kind": "insert",
"name": "order",
"table": "44444444-4444-4444-8444-444444444444",
"values": {
"col_d1111111": "$input.customer",
"col_d2222222": "$input.item",
"col_d3333333": "$input.quantity",
"col_d4444444": "draft",
"col_d5555555": "$ctx.user_id"
},
"returning": {
"id": {
"$metadata": {
"name": "id",
"source": "new"
}
}
}
},
{
"kind": "update",
"name": "consume",
"table": "55555555-5555-4555-8555-555555555555",
"for_each": "$rel.stock",
"set": {
"col_e2222222": {
"$sub": [
"$item.on_hand",
"$input.quantity"
]
}
},
"match": {
"id": "$item.id"
},
"max_rows": 1,
"returning": {
"id": {
"$metadata": {
"name": "id",
"source": "new"
}
}
}
}
],
"rules": [],
"callbacks": [],
"triggers": [],
"outputs": {
"order_id": {
"value": {
"$single": {
"relation": "$rel.order",
"expr": "$item.id"
}
}
},
"remaining": {
"value": {
"$sub": [
"$var.on_hand",
"$input.quantity"
]
}
}
},
"dependency_contract": {
"version": 1,
"tables": [
{
"table_id": "44444444-4444-4444-8444-444444444444",
"scope": {
"kind": "chatroom",
"id": "11111111-1111-4111-8111-111111111111"
},
"tag_id": "33333333-3333-4333-8333-333333333333",
"schema_digest": "e429417415c7338bf8cbde39f66316e04a4dc0916c9fd6bd75c3c28c8955e698",
"rules_digest": "b31bdbc7c1d2420114149476d59d0f1c6332c665201c5ada47bca84a319194ca",
"approval_digest": "a5ebf4be19d2302d02905683deeab5145cc9411f4b6143fad0cacce447a49583",
"triggers_digest": "tcm1:cb884b06fd3cf2e82fd678f50cecb75470e003ab9fef8e1baec64ebc17de595d",
"columns": [
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d1111111",
"type": "string",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d2222222",
"type": "string",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d3333333",
"type": "integer",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d4444444",
"type": "select",
"nullable": true,
"writable": true,
"options": [
"draft",
"confirmed",
"shipped"
],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d5555555",
"type": "user",
"nullable": true,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
}
]
},
{
"table_id": "55555555-5555-4555-8555-555555555555",
"scope": {
"kind": "chatroom",
"id": "11111111-1111-4111-8111-111111111111"
},
"tag_id": "33333333-3333-4333-8333-333333333333",
"schema_digest": "9806c2357a786074851f1eb439e1d9388cca0a62fd5a1d6057cddd1a0af18c2b",
"rules_digest": "25baf3b5ea0f6de017b54b3c5b494cc1a3cb18b4605a5a13f5d8cc7178c97763",
"approval_digest": "65228dceb4ceac9f4f440e27f92cf367f5b3ee2dc9317bb6732edd87454dde45",
"triggers_digest": "tcm1:d58ce7f752c202e32cebf5370832484bc47ebebe7656f0e53336750a6f527faa",
"columns": [
{
"table_id": "55555555-5555-4555-8555-555555555555",
"column_id": "col_e1111111",
"type": "string",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "55555555-5555-4555-8555-555555555555",
"column_id": "col_e2222222",
"type": "integer",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
}
]
}
]
}
},
"authority_owner_id": null,
"definition_redacted": false,
"interface": null,
"tag_id": "33333333-3333-4333-8333-333333333333",
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"department_id": null,
"company_id": null,
"created_by": "77777777-7777-4777-8777-777777777777",
"created_at": "2026-07-21T02:10:00",
"updated_at": "2026-07-21T06:40:00",
"is_deleted": false,
"lifecycle_redacted": false
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
| 403 | Definition 帶有 `attachment_copies`(後端 5.22.0),而儲存的使用者並非同時符合下列條件的帳號:存活、不是服務帳號,且讀得到 grant 的來源欄位並寫得到其目的欄位。這項檢查在「管理每一張被引用資料表」的門檻之後執行,所以沒有管理每一張被引用資料表的使用者會先得到一般的 `404`(建立時是 `Referenced table not found in this scope`);validate 則把同一個 `403` 放進 `errors[]`。 | |
| 404 | 指令不存在於此範圍,或呼叫者無法管理它目前引用的每一張資料表。 | |
| 400 | version 2 definition 的 step kind 是 list/object。Create、update、validate 回靜態 type/loc/msg issue,不含 input 或 ctx;其他 request 驗證維持 422。 | |
| 409 | 這支指令仍有執行被暫存待覆核;staged_change_ids 會列出這些暫存變更。 | |
| 409 | 此範圍內已有其他活指令使用這個新名稱。刪除的指令不再佔用名字。 | |
| 409 | 有資料表觸發器會呼叫這支指令,因此不能在 write 與 query 之間切換執行模式。 | |
| 409 | 有資料表觸發器會呼叫這支指令,因此新定義不得新增對需覆核資料表的引用。 | |
| 409 | 該指令已記錄過業務事件,而這次 PUT 變更或移除了它的 effect identity(namespace、kind、literal term,或各 row term 所讀的資料表與 metadata)。回應是 author 階段的 program error `effect_identity_locked`。其他編輯仍然允許。 | |
| 409 | 兩筆編寫寫入爭用同一個指令範圍,InnoDB 中止了這一筆。交易已回滾,已儲存的定義維持不變——直接重送請求即可。 | |
| 409 | 無法鎖定範圍根節點(聊天室、部門或公司那一列),因為它不存在或已被軟刪除,例如在請求途中被刪除。沒有寫入任何資料。建立、更新與刪除都會先取得這把鎖,才動到指令本身。 | |
| 422 | 編譯時的 tag 檢查失敗:tag_id 在此範圍內找不到對應的 tag,或編譯後程式(直接或遞移地)依賴的某張資料表沒有掛在這個 tag 上。這個 author 階段信封不帶 tag_id 或 table_ids;請修正 tag 的資料表集合或定義。 | |
| 422 | 只會在競態時出現:新定義引用的某張資料表在編譯時檢查與加鎖重新檢查之間離開了 tag_id;table_ids 會列出不符合的資料表。 | |
| 422 | 新的定義編譯後資料表集合是空的。這也是既有零資料表指令無法原地修好的原因——除非新定義引用了資料表,否則 PUT 對新舊定義一視同仁地拒絕。 | |
| 422 | `attachment_copies` 裡的 grant 沒有描述 definition 自己所選出的那一次複製(後端 5.22.0):缺少某個 step 或 step 種類不對、來源 `select` 有 join、分組、`distinct` 或沒有 `where`、目的值不是恰好以 `$single` 或 `$first` 取該 select 的投影、某個欄位不是附件欄位、同一目的欄位有兩個 grant,或這個 Command 是 version 1、query 模式、帶 `proposal_policy`,或宣告了 `authority: "definition"`。Body 是 author 階段的 `invalid_expression` 信封,`path` 為 `attachment_copies[<index>]`(或 `attachment_copies`),`message` 指出被違反的規則。 | |
| 422 | Definition 宣告了保留給 kernel 擁有之審批 provenance 的 output 名稱 `approval_provenance`(backend 5.21.0)。撰寫階段回 422 與原始 program-error envelope,validate 則把同一個 body 放進 `errors[]`。宣告它的 IaC `command` 行會得到 code 為 `command_output_reserved` 的 plan 階段 validate 錯誤。先前已儲存的 definition 不受影響。 | |
| 422 | 設了 `public: true` 的 output 或 `assert` 無法證明可以安全地顯示給 execute-only 呼叫者:受限或有 `secret_ref` 的欄位、計算欄位、不透明運算子、relation(整列)output、帶 row policy 的來源資料表,或被排除的 context/metadata 來源。`command_public_value_limit_exceeded` 是同樣的 author-phase 422,表示超過公開上限(沒有 `max_items`(上限 20)的陣列、超過 512 個呈現字元或合計超過 4 KiB 的常數、單一項目背後超過 1,000 個來源、證明超過 50,000 個節點)。 | |
| 422 | 定義的編譯錯誤。Author 階段的 program error 以 422 回傳原始信封,未設定時不帶 `step`/`path` key——這裡是 `ambiguous_reference`,資料表 token 同時符合此範圍內多張資料表(請改用 `@table:<id>`)。其他錯誤碼包括 `invalid_expression`、`expression_limit_exceeded`、`join_validation_failed`、`write_validation_failed` 與 `unsupported_clause_expression`。 | |
| 422 | Callback 或生命週期 trigger 的形狀規則沒有通過 request 驗證(URL 形式、header、body 大小、url 與 api_tool_id 擇一、名稱不可重複、指向已知的 callback 名稱)。Validate 也會回同樣的 422,而不是報告。 | |
| 422 | definition 設了 `authority: "definition"`,但這個 command 不是 `execute_policy: "restricted"`(PUT 時省略 policy 會保留目前的值)。 | |
| 422 | `table_access` 沒有恰好指名 definition 引用的資料表;`missing_table_ids` 與 `extra_table_ids` 列出差異。只有建立、更新與 `/validate` 會做這項檢查,IaC apply 不會。 | |
/private/module/custom_tables/{scopeWithId}/commands/{command_id}將指令軟刪除,並回傳已標記 is_deleted 的資料。
確認沒有觸發器引用、也沒有執行仍在等待覆核後,用它下架一支複合動作。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/{command_id} - 部門
/private/module/custom_tables/department/{department_id}/commands/{command_id} - 公司
/private/module/custom_tables/company/{company_id}/commands/{command_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
command_id | path | string 1..36 | 是 | 要刪除的指令 UUID。 |
回應結構
CommandResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
agent_enabled | boolean | 否 | Whether the chatroom agent may see and invoke this command. |
authority_owner_id | string | null | 否 | G07a server-derived owner of a definition-authority revision. |
chatroom_id | string | null | 否 | Chatroom scope id |
command_digest | string | null | 否 | 未指定 |
company_id | string | null | 否 | Company scope id |
created_at | string (date-time) | 是 | 未指定 |
created_by | string | 是 | 未指定 |
definition | CommandDefinition-Output | null | 否 | Full definition. Omitted (with definition_redacted=true) for an execute-only caller of a restricted Command, who sees only its interface. |
definition_redacted | boolean | 否 | G07a: true when the caller may invoke this restricted Command but not manage its tables; `interface` then replaces `definition`. |
department_id | string | null | 否 | Department scope id |
description | string | 否 | 未指定 |
execute_policy | "open" | "restricted" | 否 | G07a invocation policy (see CommandCreateRequest). |
id | string | 是 | Command UUID |
interface | CommandInterface | null | 否 | Inputs and output names only (execute-only view). |
is_deleted | boolean | 否 | Soft-delete marker |
lifecycle_redacted | boolean | 否 | True when callback and lifecycle-trigger configuration is omitted because the caller can execute but cannot manage every target table. |
name | string | 是 | 未指定 |
tag_id | string | null | 否 | 未指定 |
updated_at | string (date-time) | 是 | 未指定 |
visibility | "public" | "private" | 否 | 未指定 |
範例
刪除未被引用的指令
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"command_id": "22222222-2222-4222-8222-222222222222"
}
}{
"id": "22222222-2222-4222-8222-222222222222",
"name": "建立訂單並扣庫存",
"description": "一次寫入訂單並扣減對應品項庫存;庫存不足時整筆回滾。",
"agent_enabled": true,
"visibility": "public",
"execute_policy": "open",
"definition": {
"version": 2,
"inputs": [
{
"name": "customer",
"type": "string",
"required": true,
"max_length": 100,
"description": "客戶名稱"
},
{
"name": "item",
"type": "string",
"required": true,
"max_length": 100,
"description": "品項名稱"
},
{
"name": "quantity",
"type": "integer",
"required": true,
"description": "出貨數量"
}
],
"steps": [
{
"kind": "select",
"name": "stock",
"from": {
"table": "55555555-5555-4555-8555-555555555555",
"as": "s"
},
"joins": [],
"where": {
"$and": [
{
"$eq": [
{
"$column": {
"id": "col_e1111111",
"source": "s"
}
},
"$input.item"
]
}
]
},
"select": {
"id": {
"$metadata": {
"name": "id",
"source": "s"
}
},
"on_hand": {
"$column": {
"id": "col_e2222222",
"source": "s"
}
}
},
"group_by": [],
"order_by": [],
"max_rows": 1
},
{
"kind": "let",
"name": "on_hand",
"type": "integer",
"value": {
"$single": {
"relation": "$rel.stock",
"expr": "$item.on_hand"
}
}
},
{
"kind": "assert",
"name": "stock_ok",
"condition": {
"$gte": [
"$var.on_hand",
"$input.quantity"
]
},
"message": "庫存不足,無法建立訂單"
},
{
"kind": "insert",
"name": "order",
"table": "44444444-4444-4444-8444-444444444444",
"values": {
"col_d1111111": "$input.customer",
"col_d2222222": "$input.item",
"col_d3333333": "$input.quantity",
"col_d4444444": "draft",
"col_d5555555": "$ctx.user_id"
},
"returning": {
"id": {
"$metadata": {
"name": "id",
"source": "new"
}
}
}
},
{
"kind": "update",
"name": "consume",
"table": "55555555-5555-4555-8555-555555555555",
"for_each": "$rel.stock",
"set": {
"col_e2222222": {
"$sub": [
"$item.on_hand",
"$input.quantity"
]
}
},
"match": {
"id": "$item.id"
},
"max_rows": 1,
"returning": {
"id": {
"$metadata": {
"name": "id",
"source": "new"
}
}
}
}
],
"rules": [],
"callbacks": [],
"triggers": [],
"outputs": {
"order_id": {
"value": {
"$single": {
"relation": "$rel.order",
"expr": "$item.id"
}
}
},
"remaining": {
"value": {
"$sub": [
"$var.on_hand",
"$input.quantity"
]
}
}
},
"dependency_contract": {
"version": 1,
"tables": [
{
"table_id": "44444444-4444-4444-8444-444444444444",
"scope": {
"kind": "chatroom",
"id": "11111111-1111-4111-8111-111111111111"
},
"tag_id": "33333333-3333-4333-8333-333333333333",
"schema_digest": "e429417415c7338bf8cbde39f66316e04a4dc0916c9fd6bd75c3c28c8955e698",
"rules_digest": "b31bdbc7c1d2420114149476d59d0f1c6332c665201c5ada47bca84a319194ca",
"approval_digest": "a5ebf4be19d2302d02905683deeab5145cc9411f4b6143fad0cacce447a49583",
"triggers_digest": "tcm1:cb884b06fd3cf2e82fd678f50cecb75470e003ab9fef8e1baec64ebc17de595d",
"columns": [
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d1111111",
"type": "string",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d2222222",
"type": "string",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d3333333",
"type": "integer",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d4444444",
"type": "select",
"nullable": true,
"writable": true,
"options": [
"draft",
"confirmed",
"shipped"
],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "44444444-4444-4444-8444-444444444444",
"column_id": "col_d5555555",
"type": "user",
"nullable": true,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
}
]
},
{
"table_id": "55555555-5555-4555-8555-555555555555",
"scope": {
"kind": "chatroom",
"id": "11111111-1111-4111-8111-111111111111"
},
"tag_id": "33333333-3333-4333-8333-333333333333",
"schema_digest": "9806c2357a786074851f1eb439e1d9388cca0a62fd5a1d6057cddd1a0af18c2b",
"rules_digest": "25baf3b5ea0f6de017b54b3c5b494cc1a3cb18b4605a5a13f5d8cc7178c97763",
"approval_digest": "65228dceb4ceac9f4f440e27f92cf367f5b3ee2dc9317bb6732edd87454dde45",
"triggers_digest": "tcm1:d58ce7f752c202e32cebf5370832484bc47ebebe7656f0e53336750a6f527faa",
"columns": [
{
"table_id": "55555555-5555-4555-8555-555555555555",
"column_id": "col_e1111111",
"type": "string",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
},
{
"table_id": "55555555-5555-4555-8555-555555555555",
"column_id": "col_e2222222",
"type": "integer",
"nullable": false,
"writable": true,
"options": [],
"link_target_table_id": null,
"cardinality": null,
"definition_digest": null
}
]
}
]
}
},
"authority_owner_id": null,
"definition_redacted": false,
"interface": null,
"tag_id": "33333333-3333-4333-8333-333333333333",
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"department_id": null,
"company_id": null,
"created_by": "77777777-7777-4777-8777-777777777777",
"created_at": "2026-07-21T02:10:00",
"updated_at": "2026-07-21T07:15:00",
"is_deleted": true,
"lifecycle_redacted": false
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
| 404 | 指令不存在於此範圍,或呼叫者既無法管理它引用的每一張資料表、也不具備指令所屬範圍的權威(聊天室建立者/管理員、部門管理員或公司管理員)。這個範圍權威後備正是資料表被刪或被 purge 之後指令仍刪得掉的原因;兩項檢查都不過的人拿到的就是這顆不可枚舉的 404。 | |
| 409 | 這支指令仍有執行被暫存待覆核。 | |
| 409 | 仍有資料表觸發器帶著指向這支指令的 invoke_command 動作;references 會列出每張資料表與觸發器。 | |
| 409 | 兩筆編寫寫入爭用同一個指令範圍,InnoDB 中止了這一筆。交易已回滾,指令仍然存在——直接重送請求即可。 | |
| 409 | 無法鎖定範圍根節點(聊天室、部門或公司那一列),因為它不存在或已被軟刪除,例如在請求途中被刪除。沒有寫入任何資料。建立、更新與刪除都會先取得這把鎖,才動到指令本身。 | |
/private/module/custom_tables/{scopeWithId}/commands/{command_id}/grants回傳 `{items, total}`:對這個 command 持有 execute grant 的每個主體(使用者、部門、聊天室或 client)。
用來稽核除了資料表 manager 之外,還有誰能執行某個受限 command。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/{command_id}/grants - 部門
/private/module/custom_tables/department/{department_id}/commands/{command_id}/grants - 公司
/private/module/custom_tables/company/{company_id}/commands/{command_id}/grants
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
command_id | path | string 1..36 | 是 | 要管理其 execute grant 的受限 command 的 id。 |
回應結構
CommandExecuteGrantList| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
items | CommandExecuteGrantResponse[] | 否 | 未指定 |
total | integer | 是 | 未指定 |
範例
列出一個受限 command 的 grant
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"command_id": "22222222-2222-4222-8222-222222222222"
}
}{
"items": [
{
"id": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
"command_id": "22222222-2222-4222-8222-222222222222",
"principal_type": "department",
"principal_id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd",
"created_by": "77777777-7777-4777-8777-777777777777",
"created_at": "2026-10-01T09:00:00"
}
],
"total": 1
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
| 404 | 這個 scope 內沒有這個 command,或呼叫者沒有管理它引用的每一張資料表(統一的 command 404)。 | |
/private/module/custom_tables/{scopeWithId}/commands/{command_id}/grants為使用者、部門(依即時成員資格)、聊天室(依 acting room 或即時成員資格)或 client 建立一個 execute grant。具冪等性:已存在的 grant 會原樣回傳。
用來讓某個團隊不必持有資料表上的任何 grant,就能執行受限(也可能是 definer authority)的 command。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/{command_id}/grants - 部門
/private/module/custom_tables/department/{department_id}/commands/{command_id}/grants - 公司
/private/module/custom_tables/company/{company_id}/commands/{command_id}/grants
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
command_id | path | string 1..36 | 是 | 要管理其 execute grant 的受限 command 的 id。 |
請求結構
CommandExecuteGrantCreate| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
principal_id | string | 是 | 未指定 |
principal_type | "user" | "department" | "chatroom" | "client" | 是 | user | department (live membership) | chatroom (acting room / live member) | client |
回應結構
CommandExecuteGrantResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
command_id | string | 是 | 未指定 |
created_at | string (date-time) | null | 否 | 未指定 |
created_by | string | null | 否 | 未指定 |
id | string | 是 | 未指定 |
principal_id | string | 是 | 未指定 |
principal_type | "user" | "department" | "chatroom" | "client" | 是 | 未指定 |
範例
讓一個部門可以執行這個 command
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"command_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"principal_type": "department",
"principal_id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd"
}
}{
"id": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
"command_id": "22222222-2222-4222-8222-222222222222",
"principal_type": "department",
"principal_id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd",
"created_by": "77777777-7777-4777-8777-777777777777",
"created_at": "2026-10-01T09:00:00"
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
| 404 | 這個 scope 內沒有這個 command,或呼叫者沒有管理它引用的每一張資料表(統一的 command 404)。 | |
| 404 | 這個主體不是該 command 所屬公司中存活的使用者、部門或聊天室,也不是房間屬於該公司的既有 client。回應是統一的,所以 grant 請求絕不能用來探測別家公司的 id。 | |
| 422 | Request 驗證失敗:`principal_type` 必須是 `user`、`department`、`chatroom` 或 `client`,`principal_id` 為 1–36 個字元。 | |
/private/module/custom_tables/{scopeWithId}/commands/{command_id}/grants/{grant_id}刪除一個 execute grant 並回傳它。撤銷立即生效。
用來收回執行受限 command 的權利;每一張被引用資料表的 manager 仍保有它。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/{command_id}/grants/{grant_id} - 部門
/private/module/custom_tables/department/{department_id}/commands/{command_id}/grants/{grant_id} - 公司
/private/module/custom_tables/company/{company_id}/commands/{command_id}/grants/{grant_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
command_id | path | string 1..36 | 是 | 要管理其 execute grant 的受限 command 的 id。 |
grant_id | path | string 1..36 | 是 | 要撤銷的 execute grant 的 id。 |
回應結構
CommandExecuteGrantResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
command_id | string | 是 | 未指定 |
created_at | string (date-time) | null | 否 | 未指定 |
created_by | string | null | 否 | 未指定 |
id | string | 是 | 未指定 |
principal_id | string | 是 | 未指定 |
principal_type | "user" | "department" | "chatroom" | "client" | 是 | 未指定 |
範例
撤銷一個 grant
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"command_id": "22222222-2222-4222-8222-222222222222",
"grant_id": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee"
}
}{
"id": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
"command_id": "22222222-2222-4222-8222-222222222222",
"principal_type": "department",
"principal_id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd",
"created_by": "77777777-7777-4777-8777-777777777777",
"created_at": "2026-10-01T09:00:00"
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
| 404 | 這個 scope 內沒有這個 command,或呼叫者沒有管理它引用的每一張資料表(統一的 command 404)。 | |
| 404 | 這個 grant id 不屬於這個 command。 | |
/private/module/custom_tables/{scopeWithId}/commands/{command_id}/execute以綁定的 inputs 執行 write 模式指令,所有步驟一起提交,或一起回滾。
真正要做出商業動作時呼叫——下訂單、訂時段、結案——尤其是「只成功一半」會弄髒資料的情況。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/{command_id}/execute - 部門
/private/module/custom_tables/department/{department_id}/commands/{command_id}/execute - 公司
/private/module/custom_tables/company/{company_id}/commands/{command_id}/execute
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
command_id | path | string 1..36 | 是 | 要執行的指令 UUID。 |
db | query | int | 否 | Redis 邏輯資料庫編號,因共用 client 相依而外洩到 schema 上。它是內部旋鈕,不屬於指令契約,請不要填。 |
dry_run | query | boolean | 否 | 設為 true 可預覽版本 2 的 command:它執行與真正呼叫相同的驗證、授權與規則檢查、之後整個回滾,並以 status "preview" 回傳會產生的 outputs。不會持久化任何東西:沒有資料列、history、待審項目、trigger、callback,也沒有 idempotency 保留。 |
請求結構
CommandExecuteRequest| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
expected_contract_digest | string | null | 否 | Optional lowercase 64-hex SHA-256 of the exact current stored command definition. A trusted manual caller may send it when executing against an exact reviewed definition snapshot; omission preserves normal manual execution behavior. Generated agent write-command tools keep the digest server-private and inject their pinned value only after later unambiguous natural-language confirmation. A mismatch fails before input validation, idempotency reservation, audit creation, row locking, or mutation. |
idempotency_key | string | null | 否 | Actor-bucketed idempotency key; failed executions do not block retry. |
inputs | object | 是 | Values for the command's declared inputs; server context is excluded. |
reason | string | null | 否 | Why this execution runs, kept with the execution and in the audit of what it changed. Required (unless the change is released through an approval) when the command changes a closed period row (ct.calendars; otherwise 422 ct.scope_override_reason_required). Only a person's reason counts: Agent and MCP executions change a closed period through an approval. |
回應結構
CommandExecutionResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
authorization_snapshot | object | null | 否 | Immutable command scope, tag, and referenced-table audit facts. |
command_id | string | null | 否 | 未指定 |
created_at | string (date-time) | null | 否 | 未指定 |
duration_ms | integer | null | 否 | 未指定 |
error | string | null | 否 | 未指定 |
executed_at | string (date-time) | null | 否 | Wall-clock stamp frozen at the ORIGINAL execution; an idempotent replay returns the same stamp, so callers can distinguish a replayed earlier result from a fresh write. |
execution_id | string | 是 | 未指定 |
idempotency_key | string | null | 否 | 未指定 |
input_snapshot | object | null | 否 | 未指定 |
invoker_client_id | string | null | 否 | 未指定 |
invoker_id | string | null | 否 | 未指定 |
outputs | object | null | 否 | Declared version-2 scalar or bounded relation outputs. Internal temporary relations and step rows are never exposed. |
redacted | boolean | null | 否 | G07a: true when this definition-authority execution is shown to a caller who does not manage every referenced table — a status-only receipt (no rows, outputs, result refs or response body). |
redacted_outputs | string[] | null | 否 | On a replay: declared outputs withheld because this caller cannot read everything they were computed from, or because they derive from values a replay cannot re-check (computed columns read by a write step). Omitted when none. |
replayed | boolean | null | 否 | True when this business event (the Command's effect_identity occurrence) was already recorded: the body is the ORIGINAL receipt (original execution_id and executed_at) and nothing was written. Omitted otherwise. |
response_body | object | null | 否 | 未指定 |
result_refs | object[] | null | 否 | 未指定 |
staged_change_id | string | null | 否 | 未指定 |
status | "succeeded" | "staged" | "failed" | 是 | 未指定 |
steps | (CommandExecutionV1StepResponse | CommandExecutionV2StepResponse)[] | 否 | 未指定 |
truncated | boolean | null | 否 | True when the persisted response snapshot was trimmed to the audit byte cap: declared outputs/step rows are omitted from this body. Set on an idempotent replay of a large execution so the caller can distinguish a trimmed snapshot from a genuinely empty result. |
範例
一次完成下訂單與扣庫存
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"command_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"inputs": {
"customer": "長榮工業",
"item": "螺絲 M4",
"quantity": 12
},
"idempotency_key": "order-2026-07-21-0042"
}
}{
"execution_id": "66666666-6666-4666-8666-666666666666",
"status": "succeeded",
"executed_at": "2026-07-21T03:02:11Z",
"steps": [
{
"name": "stock",
"kind": "select",
"status": "succeeded",
"row_count": 1,
"schema": {
"id": {
"type": "string",
"nullable": false
},
"on_hand": {
"type": "integer",
"nullable": true
}
}
},
{
"name": "on_hand",
"kind": "let",
"status": "succeeded",
"row_count": 0,
"schema": {}
},
{
"name": "stock_ok",
"kind": "assert",
"status": "succeeded",
"row_count": 0,
"schema": {}
},
{
"name": "order",
"kind": "insert",
"status": "succeeded",
"row_count": 1,
"schema": {
"id": {
"type": "string",
"nullable": false
}
}
},
{
"name": "consume",
"kind": "update",
"status": "succeeded",
"row_count": 1,
"schema": {
"id": {
"type": "string",
"nullable": false
}
}
}
],
"outputs": {
"order_id": "88888888-8888-4888-8888-888888888888",
"remaining": 8
}
}重複一個已記錄的業務事件:該指令宣告了 effect identity,所以會回傳原本的 receipt 與 replayed=true,且不寫入任何東西
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"command_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"inputs": {
"customer": "長榮工業",
"item": "螺絲 M4",
"quantity": 12
},
"idempotency_key": "retry-from-another-caller"
}
}{
"execution_id": "66666666-6666-4666-8666-666666666666",
"status": "succeeded",
"executed_at": "2026-07-21T03:02:11Z",
"steps": [
{
"name": "stock",
"kind": "select",
"status": "succeeded",
"row_count": 1,
"schema": {
"id": {
"type": "string",
"nullable": false
},
"on_hand": {
"type": "integer",
"nullable": true
}
}
},
{
"name": "on_hand",
"kind": "let",
"status": "succeeded",
"row_count": 0,
"schema": {}
},
{
"name": "stock_ok",
"kind": "assert",
"status": "succeeded",
"row_count": 0,
"schema": {}
},
{
"name": "order",
"kind": "insert",
"status": "succeeded",
"row_count": 1,
"schema": {
"id": {
"type": "string",
"nullable": false
}
}
},
{
"name": "consume",
"kind": "update",
"status": "succeeded",
"row_count": 1,
"schema": {
"id": {
"type": "string",
"nullable": false
}
}
}
],
"outputs": {
"order_id": "88888888-8888-4888-8888-888888888888"
},
"replayed": true,
"redacted_outputs": [
"remaining"
]
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
| 400 | 這次執行展開後會超過 1000 筆 row action 上限;actual 是觸發上限的實際數量。 | |
| 400 | 對版本 1 的 command 使用 dry_run;預覽需要版本 2 的定義。 | |
| 400 | decimal input 不是符合宣告精度與小數位數的純十進位字串:送了 JSON 數字、位數過多、allow_negative 為 false 時送了負數,或送了 -0。 | |
| 400 | identity 型別的 input 指向的使用者或社群媒體 client 不屬於此範圍。 | |
| 400 | 版本 1 的 update 或 delete 步驟把選填 input 綁在 match 條件裡,而呼叫者沒有送這個值。現在整次執行失敗,而不是把那個 key 丟掉:丟掉會放寬選列條件,打到建置者從未鎖定的資料列。 | |
| 400 | 被省略的選填 input 出現在 $case 裡——某個分支的 then,或 else。所有分支都必須綁得出值,因為分支要更晚才會對著鎖定的前影像挑選,所以「這個分支綁不出來」只能是錯誤,不能靠猜。 | |
| 400 | 步驟中的資料列寫入沒有通過 record core 自己的 input 驗證——未知或無法存取的 attachment id,或該 scope 沒有的 principal id。detail 就是 core 的原始訊息;v5.10.11 之前這裡會被收斂成「Command execution failed」。 | |
| 413 | 某個步驟的 insert/update 會讓序列化後的 `data` 超過 `CUSTOM_TABLE_MAX_RECORD_BYTES`(預設 1 MiB)。需審核的寫入會在送進暫存前就檢查,所以過大的受審寫入會當場被拒,而不是通過審核後才失敗。 | |
| 403 | 版本 2 程式從呼叫者無法讀取的資料表 select 或 join。過去 row ACL 會把它變成永不匹配的條件,該步驟回傳空 relation,於是「先查再寫」的去重步驟查不到東西,就寫進了一筆重複資料。 | |
| 403 | 程式有 insert 步驟,而該資料表解析出的 can_insert 是 false。這道檢查與讀取閘門在 `_prepare_program_access` 的同一輪完成——在任何 action core 能 flush 之前——所以不會留下半筆資料。 | |
| 403 | 程式有 update 或 delete 步驟,而該資料表解析出的 can_edit 是 "none"。與 insert 同一輪;filtered 或 own 授權在這裡會通過,改在稍後 ACL 範圍內的 match 查詢之後逐列把關。 | |
| 403 | 程式要寫入的某張 channel 治理資料表,替你解析不出 scope。REST 不綁 acting room,floor 改為只依執行者逐表解析,因此訊息是「不帶房間」的版本——你名下沒有任何房間在那張表上宣告 scope_values,或候選房間不只一個而無法解析。`table_id` 指出被拒的資料表,`rule_id` 指出讓它進入治理的 channel 規則。 | |
| 403 | 寫入 floor 拒絕的是資料列本身:insert 或 update 的受管連結落在你的房間宣告的 scope 之外。同一個 code 也涵蓋前影像檢查(`this record is outside your channel’s scope`,update 打到你的 scope 看不見的列),以及 policy 形式(`this write does not satisfy this table’s channel policy`)。請以 `detail.error` 判別,不要看訊息字串。 | |
| 403 | B10 歸因。insert 或 update 的連結值指向一筆確實存在、也通過你的 row ACL 的目標,只有目標資料表的 channel scope 把它藏起來——所以這條通道直接說出 floor 的 code,而不是收斂成統一的 404。在這個分支存在之前,同樣的 v2 寫入會掉進一般性的 500 `command_execution_failed`。當 floor 的判定是那樣時,`scp_scope_undeclared` 與 `scp_rule_dangling` 也會從這裡冒出來;這個形狀沒有 `rule_id`。 | |
| 403 | 版本 2 的 select 在任何子句中引用了呼叫者的資料表權限所隱藏的欄位(他們的 hidden_columns)。使用 program error 信封的 403 不只它:/query 也可能以該信封回 403 command_execute_denied,/execute 還可能回 ct.effect_identity_replay_forbidden、command_execute_denied、definition_authority_invalid 與 definition_ceiling_missing。 | |
| 403 | 即時重新載入 principal 失敗:呼叫者已不是這家公司中啟用且已驗證的使用者,或已不是路徑上聊天室的成員。這個 body 刻意保持一致,不透露原因。 | |
| 403 | Principal 透過的房間——chatroom 掛載點上是路徑的聊天室,agent 通道上是工作階段的房間——在取得權限鎖之後發現已被刪除、移到其他公司或部門,或成員資格已不存在;外部 principal 則是沒有存活的 acting room,或其 client 已換了房間。沒有寫入任何資料。 | |
| 403 | 業務事件已經記錄,但該 command 的某個 update 或 delete step 所指向的資料表上,這位呼叫者的編輯權限是 own 或 filtered(不是 all,且不是管理者),或其任一寫入 step 所指向的資料表落在呼叫者的 SCP 房間範圍內,所以無法重新證明 replay。不寫入任何東西,也不揭露先前的 execution id 或結果;重試會得到相同答案。 | |
| 403 | 這個 command 是 `restricted`,而呼叫者既不管理全部被引用的資料表,也沒有有效的 execute grant。它是帶 `phase: "execute"` 的 program-error 信封;MCP 工具改回 `not_found`。 | |
| 403 | owner 已不再有效的 `definition` authority command 會 fail closed:owner 不再存活、屬於其他公司、是服務帳號,或不再是每一張 `table_access` 資料表的 manager。必須由管理每一張資料表的 manager 重新儲存這個 command。 | |
| 403 | command 引用的某張資料表在 `table_access` ceiling 中沒有項目,所以每次執行都會 fail closed,直到 manager 補上項目並重新儲存。 | |
| 404 | 指令存在於此範圍,但沒有引用任何資料表(沒有存活指令的 id 則是下方遮蔽過的 `command_failed` 404)或引用了呼叫者無法讀取的資料表、它的 tag 已不再涵蓋每一張引用的資料表,或指令是 private 且呼叫者既不是建立者、也不管理全部被引用資料表。呼叫者無權執行的 `restricted` 指令會回 403 `command_execute_denied`,不是 404。 | |
| 404 | 這個 scope 中沒有此 id 的存活指令(或它已被刪除)。沒有資料列可判斷時,status-only 投影一律 fail closed,所以回的是帶遮蔽 `command_failed` body 的 `404`,而不是 `Command not found`。對不管理每一張被引用資料表的呼叫者,`authority: definition` 指令的任何其他純字串 404(例如 input 指到不存在的 link 目標)也會換成同一個 body。自後端 5.22.0 起,`dry_run=true` 的呼叫也得到同樣的回應(預覽不存在的指令以前回的是 `Command not found`)。 | |
| 404 | 連結值指向的目標不存在、已軟刪除,或被 row ACL 對你藏起來。三種情況共用同一個 body,所以這條路由不會開出 id 列舉的破口。注意:這個 404 與同一路由另一個代表「Command not found」的 404 撞在一起——請讀 detail 字串,不要只看狀態碼。 | |
| 409 | require_approval 規則觸發,整份計畫被暫存而非提交。寫入是被扣住,不是遺失。 | |
| 409 | 已有一筆 staged change 佔住這次執行要寫入的紀錄(或同一個建立 key),所以在那筆 staged change 結案之前寫入會被擋下。這次執行沒有暫存任何東西。 | |
| 409 | 符合的 require_approval rule 指向的 Review 範本不存在、已被軟刪除或屬於其他公司,因此無法啟動審批流程。沒有暫存任何東西;請修正 rule 的範本。 | |
| 409 | 選填的 expected_contract_digest 不符合 command 的即時定義。檢查發生在 command input 驗證、idempotency 保留、建立稽核、row lock 或異動之前,因此沒有資料被改動,也不可原樣重試這份已過期的釘選 request。 | |
| 409 | 同一位執行者以相同 idempotency key 送出不同的 inputs。 | |
| 409 | 另一個持有相同 key 的請求仍在執行中,等它結束後再重試。 | |
| 409 | 這把 idempotency key 已被另一次執行用過,而那次凍結的權限情境與這次不同——origin、acting room、principal、權限集合、指令定義或相依狀態有所不同。新的情境請用新的 key。 | |
| 409 | 這支指令的 mode 是 "query",必須改用 /query 呼叫。 | |
| 409 | 兩次執行爭用同一批資料列,InnoDB 中止了這一次。交易已回滾,沒有任何部分寫入落地,retryable 為 true——請用「同一把」idempotency key 重試:失敗路徑會釋放該 key 的保留,因此重用是合法的。 | |
| 409 | 版本 2 指令儲存的 dependency contract 已與目前的資料表不符(schema、rules、審批設定、triggers、tag 或範圍有變動)。請重新儲存指令以重建 contract;原封不動重試會以同樣方式失敗。 | |
| 409 | 版本 2 通道:指令列在驗證與套用之間被刪除,或它的範圍、tag 或定義摘要改變了。沒有寫入任何資料;請重新讀取指令後再重試。 | |
| 409 | 業務事件已經記錄,而這次執行驗證後的 inputs 或作者程式不同(details.semantic_match 為 false;details.original_execution_id 指出先前那次執行)。終局:重試或改變內容都不會成功。發生在審批 release 時,封套的 phase 為 release(重複 stage 的 inputs 與程式相同時,details.semantic_match 為 true),並讓 staged 變更以 apply_failed 結束。 | |
| 409 | 排除送審者(`exclude_requester`)之後,某個必要的審批 gate 沒有任何可核准者。送出會在任何 review process 出現之前就被拒絕;`gate_order` 指出該 gate。對於不管理每一張被引用資料表的 `authority: definition` command 呼叫者,只會回傳安全的 key,所以沒有 `gate_order`。 | |
| 409 | 這筆寫入需要排除送審者,但呼叫的主體沒有已驗證的人類身分(例如 service account),無法歸屬。不會暫存任何東西。 | |
| 409 | `date_interval_exclusion` 規則拒絕此次寫入:`date_interval_exclusion_invalid_period`(建立時省略 end key、結束日為空字串或不晚於起始日,或 scope link 沒有恰好一個目標)、`date_interval_exclusion_overlap`(另一筆 live 資料列涵蓋了這段期間的一部分),或 `date_interval_exclusion_retry`(寫入等待期間資料表的規則被改變;沒有寫入任何東西,請重送)。body 是固定訊息,絕不會指出資料列、日期或 scope 值。 | |
| 422 | 版本 2 的 assert 步驟沒有得到 true。信封會指出步驟名稱與條件路徑,message 是建置者自訂的文字,而且沒有寫入任何資料。 | |
| 422 | 作者標為 `public: true` 的 `assert` step 失敗,且呼叫者取得的是 status-only 結果(definer Command、未管理每一張被引用資料表的呼叫者)。Body 只保留 `error`、作者寫的 `message` 與 `redacted: true`。其他任何 `assertion_failed` 到了這種呼叫者手上都是被遮蔽的 `command_failed`。 | |
| 422 | 版本 1 的 rule 失敗;rule 是規則名稱,message 是建置者自訂的文字。版本 2 的 `rules` 項目失敗時則回信封——{"error":"command_rule_failed","phase":"execute","message":…}——不帶規則名稱。 | |
| 422 | `inputs` 的 request 驗證:以緊湊、key 排序的 UTF-8 JSON 計算超過 1 048 576 bytes、深度超過 64 層、超過 16 384 個節點(`command inputs: JSON depth exceeds 64`、`… node count exceeds 16384`),或含 NaN/Infinity(`command value is not canonical JSON`)。發生在任何保留、稽核列或鎖之前。 | |
| 400 | 版本 1 通道:某個 action 的紀錄與連結閉包超過 2000 筆紀錄。link 圖需要超過 256 張資料表時,會回同一個錯誤碼,帶 `limit: 256` 且沒有 `actual`。 | |
| 409 | 版本 1 通道:連結的資料表已消失,或在取得鎖的過程中 link 或紀錄的連結關係發生變動。並行變動的情況是暫時性的——請重試同一個呼叫。 | |
| 422 | 版本 1:具體計畫會寫入超過一張設有 require_approval rule 的資料表,而一次執行永遠不能橫跨兩個審批對象。tables 會列出受控管的資料表。請把指令拆開。 | |
| 422 | 自後端 5.22.0 起:不含 child_collection step 的 Command,其中一次寫入觸發了一條核准規則,而該規則的 field_map 所讀取的來源資料表不在 Command 的 tag 內。table_ids 列出不在 tag 內的來源資料表,且沒有寫入任何東西。以前這是通用的 500 command_execution_failed。 | |
| 422 | 精確十進位的值超出界限——運算結果超過它的精度,或 SQL 係數超出 65 位數的範圍(此時信封會指出該 select 步驟與 `<step>.select` 路徑)。沒有寫入任何資料。 | |
| 422 | 精確十進位運算式在執行時無效,例如除以零或得到不是有限十進位數的值。path 會指向失敗的運算式。 | |
| 422 | `$first` 的 effect-identity key term 沒選到列,或它的 select step 被 when 略過。在 claim 時點就提出:沒有寫入任何東西,也沒有取得 claim。`$single` term 則維持 400 cardinality_failed。 | |
| 423 | 版本 1 通道:計畫會碰到的某張資料表正被 schema 遷移持有。它在任何寫入之前就拋出;遷移結束後即可重試。 | |
| 429 | 暫存這個計畫會超過待審批的額度:每位執行者最多 500 筆待處理的 staged change,每張資料表最多 50 筆待處理的暫存新增。沒有暫存任何東西;請等審核者處理掉一些。 | |
| 500 | 版本 2:具體計畫會寫入超過一張受審批控管的資料表。這個拒絕以 stage 階段的 command_execution_failed 拋出,而路由會把每個 command_execution_failed 都回成 500。請把指令拆開;原封不動重試會以同樣方式失敗。 | |
| 503 | Idempotency 儲存無法連線,為避免重複寫入而直接失敗。 | |
| 504 | 執行超過 30 秒的期限。details 為空、retryable 為 false;交易已回滾。 | |
| 504 | 某個 select statement 用完了單一 statement 的預算;details.limit 是該預算(單位毫秒,最多 5000),retryable 為 true。 | |
/private/module/custom_tables/{scopeWithId}/commands/{command_id}/query執行唯讀指令,回傳其宣告 relation output 的一頁資料,以及所有 scalar outputs。
用於複合讀取——join、彙總、篩選投影——把邏輯寫在伺服器一次,而不是讓前端自己拼。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/{command_id}/query - 部門
/private/module/custom_tables/department/{department_id}/commands/{command_id}/query - 公司
/private/module/custom_tables/company/{company_id}/commands/{command_id}/query
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
command_id | path | string 1..36 | 是 | 要執行的 query 模式指令 UUID。 |
請求結構
CommandQueryRequest| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
inputs | object | 是 | Values for the command's declared inputs; server context is excluded. |
page | CommandQueryPage | null | 否 | Optional pagination window; defaults to the first page of 100 rows. |
回應結構
CommandQueryResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
next_cursor | string | null | 否 | Opaque cursor for the next page, or null when exhausted. |
outputs | object | 否 | Declared scalar outputs (relation outputs are paginated via rows). |
rows | object[] | 否 | Projected rows of the paginated relation output for this page. |
total_available | integer | null | 否 | Total rows available in the paginated relation, when known. |
範例
讀取篩選後訂單投影的第一頁
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"command_id": "99999999-9999-4999-8999-999999999999"
},
"body": {
"inputs": {
"min_quantity": 10
},
"page": {
"output": "result",
"limit": 2
}
}
}{
"rows": [
{
"id": "88888888-8888-4888-8888-888888888888",
"客戶": "長榮工業",
"數量": 12
},
{
"id": "88888888-8888-4888-8888-222222222222",
"客戶": "大同機械",
"數量": 10
}
],
"outputs": {},
"next_cursor": "eyJkIjoiOWYyYzFiM2E0ZDVlNmY3MCIsIm8iOjJ9",
"total_available": 7
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
| 400 | cursor 已無法對應這支指令的 dependency contract、inputs 或 output 名稱,通常是換頁之間 schema 或 inputs 變了。 | |
| 400 | page.output 指的不是這支指令的 relation output。 | |
| 400 | 指令宣告了不只一個 relation output,卻省略了 page.output,路由無法判斷要分頁哪一個 output。 | |
| 400 | decimal input 不是符合宣告精度與小數位數的純十進位字串:送了 JSON 數字、位數過多、allow_negative 為 false 時送了負數,或送了 -0。 | |
| 422 | 要保留的 Command pass 變更已關閉期間,卻沒有人的理由或核准釋放參照。Typed calendar 拒絕會回滾交易;範例的 rule/table/image 欄位指出被拒絕的期間變更。 | |
| 400 | identity 型別的 input 指向的使用者或社群媒體 client 不屬於此範圍。 | |
| 403 | 程式的 select 來源或 join 目標解析出的 can_read 是 none。在這道閘門之前,同樣的呼叫會回 200 加一個空 relation,agent 則回報「查無符合資料」。 | |
| 403 | 版本 2 的 select 在任何子句中引用了呼叫者的資料表權限所隱藏的欄位(他們的 hidden_columns)。使用 program error 信封的 403 不只它:/query 也可能以該信封回 403 command_execute_denied,/execute 還可能回 ct.effect_identity_replay_forbidden、command_execute_denied、definition_authority_invalid 與 definition_ceiling_missing。 | |
| 403 | 即時重新載入 principal 失敗:呼叫者已不是這家公司中啟用且已驗證的使用者,或已不是路徑上聊天室的成員。這個 body 刻意保持一致,不透露原因。 | |
| 403 | Principal 透過的房間——chatroom 掛載點上是路徑的聊天室,agent 通道上是工作階段的房間——在取得權限鎖之後發現已被刪除、移到其他公司或部門,或成員資格已不存在;外部 principal 則是沒有存活的 acting room,或其 client 已換了房間。沒有寫入任何資料。 | |
| 403 | 這個 command 是 `restricted`,而呼叫者既不管理全部被引用的資料表,也沒有有效的 execute grant。它是帶 `phase: "execute"` 的 program-error 信封;MCP 工具改回 `not_found`。 | |
| 404 | 指令不存在於此範圍、已被軟刪除、沒有引用任何資料表或引用了呼叫者無法讀取的資料表、它的 tag 已不再涵蓋每一張引用的資料表,或指令是 private 且呼叫者既不是建立者、也不管理全部被引用資料表。呼叫者無權執行的 `restricted` 指令會回 403 `command_execute_denied`,不是 404。 | |
| 409 | 這是一支寫入指令,請改用 /execute 呼叫。 | |
| 409 | 版本 2 指令儲存的 dependency contract 已與目前的資料表不符(schema、rules、審批設定、triggers、tag 或範圍有變動)。請重新儲存指令以重建 contract;原封不動重試會以同樣方式失敗。 | |
| 422 | 這一頁序列化後超過 64 KiB 的輸出上限;請調小 page.limit 或縮減 output 的 select 欄位。 | |
| 422 | 精確十進位的值超出界限——運算結果超過它的精度,或 SQL 係數超出 65 位數的範圍(此時信封會指出該 select 步驟與 `<step>.select` 路徑)。沒有寫入任何資料。 | |
| 422 | 精確十進位運算式在執行時無效,例如除以零或得到不是有限十進位數的值。path 會指向失敗的運算式。 | |
| 422 | `inputs` 的 request 驗證:以緊湊、key 排序的 UTF-8 JSON 計算超過 1 048 576 bytes、深度超過 64 層、超過 16 384 個節點(`command inputs: JSON depth exceeds 64`、`… node count exceeds 16384`),或含 NaN/Infinity(`command value is not canonical JSON`)。發生在任何保留、稽核列或鎖之前。 | |
| 504 | 某個 select statement 用完了單一 statement 的預算;details.limit 是該預算(單位毫秒,最多 5000),retryable 為 true。 | |
/private/module/custom_tables/{scopeWithId}/commands/{command_id}/executions回傳單一指令的稽核紀錄——inputs、outputs、狀態、耗時,以及執行當下擷取的權限快照,新到舊排序。
用來回答「那次到底跑成功了沒、改了什麼」,而不必回頭翻每一張被影響的資料表。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/{command_id}/executions - 部門
/private/module/custom_tables/department/{department_id}/commands/{command_id}/executions - 公司
/private/module/custom_tables/company/{company_id}/commands/{command_id}/executions
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
command_id | path | string 1..36 | 是 | 要查詢執行紀錄的指令 UUID。 |
skip | query | int >= 0 | 否 | 略過前幾筆可見執行紀錄,預設為 0。 |
limit | query | int 1..200 | 否 | 最多回傳幾筆執行紀錄,可填 1 到 200,預設為 50。 |
回應結構
CommandExecutionListResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
items | CommandExecutionResponse[] | 否 | 未指定 |
total | integer | 是 | 未指定 |
範例
列出某支指令的近期執行紀錄
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"command_id": "22222222-2222-4222-8222-222222222222"
},
"query": {
"skip": "0",
"limit": "20"
}
}{
"items": [
{
"execution_id": "66666666-6666-4666-8666-666666666666",
"status": "succeeded",
"steps": [
{
"name": "stock",
"kind": "select",
"status": "succeeded",
"row_count": 1,
"schema": {
"id": {
"type": "string",
"nullable": false
},
"on_hand": {
"type": "integer",
"nullable": true
}
}
},
{
"name": "on_hand",
"kind": "let",
"status": "succeeded",
"row_count": 0,
"schema": {}
},
{
"name": "stock_ok",
"kind": "assert",
"status": "succeeded",
"row_count": 0,
"schema": {}
},
{
"name": "order",
"kind": "insert",
"status": "succeeded",
"row_count": 1,
"schema": {
"id": {
"type": "string",
"nullable": false
}
}
},
{
"name": "consume",
"kind": "update",
"status": "succeeded",
"row_count": 1,
"schema": {
"id": {
"type": "string",
"nullable": false
}
}
}
],
"outputs": {
"order_id": "88888888-8888-4888-8888-888888888888",
"remaining": 8
},
"command_id": "22222222-2222-4222-8222-222222222222",
"invoker_id": "77777777-7777-4777-8777-777777777777",
"idempotency_key": "order-2026-07-21-0042",
"duration_ms": 84,
"created_at": "2026-07-21T03:02:11",
"input_snapshot": {
"customer": "長榮工業",
"item": "螺絲 M4",
"quantity": 12
},
"result_refs": [
{
"name": "order",
"table_id": "44444444-4444-4444-8444-444444444444",
"record_ids": [
"88888888-8888-4888-8888-888888888888"
]
},
{
"name": "consume",
"table_id": "55555555-5555-4555-8555-555555555555",
"record_ids": [
"88888888-8888-4888-8888-999999999999"
]
}
],
"response_body": {
"execution_id": "66666666-6666-4666-8666-666666666666",
"status": "succeeded",
"executed_at": "2026-07-21T03:02:11Z",
"steps": [
{
"name": "stock",
"kind": "select",
"status": "succeeded",
"row_count": 1,
"schema": {
"id": {
"type": "string",
"nullable": false
},
"on_hand": {
"type": "integer",
"nullable": true
}
}
},
{
"name": "on_hand",
"kind": "let",
"status": "succeeded",
"row_count": 0,
"schema": {}
},
{
"name": "stock_ok",
"kind": "assert",
"status": "succeeded",
"row_count": 0,
"schema": {}
},
{
"name": "order",
"kind": "insert",
"status": "succeeded",
"row_count": 1,
"schema": {
"id": {
"type": "string",
"nullable": false
}
}
},
{
"name": "consume",
"kind": "update",
"status": "succeeded",
"row_count": 1,
"schema": {
"id": {
"type": "string",
"nullable": false
}
}
}
],
"outputs": {
"order_id": "88888888-8888-4888-8888-888888888888",
"remaining": 8
}
},
"authorization_snapshot": {
"version": 1,
"scope": {
"kind": "chatroom",
"id": "11111111-1111-4111-8111-111111111111"
},
"tag_id": "33333333-3333-4333-8333-333333333333",
"table_ids": [
"44444444-4444-4444-8444-444444444444",
"55555555-5555-4555-8555-555555555555"
],
"command_updated_at": "2026-07-21T02:10:00",
"audience": "internal",
"origin": "manual",
"ai": false,
"company_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"acting_chatroom_id": "11111111-1111-4111-8111-111111111111",
"department_id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd",
"idempotency_context": {
"version": 1,
"digest": "tcm1:00c68cd04623cf515134c6a861b49c9264cddff2cf6b9f33bbdb1362ea598f0f",
"context": {
"version": 1,
"principal": {
"kind": "internal",
"user_id": "77777777-7777-4777-8777-777777777777",
"social_media_client_id": null
},
"company_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"department_id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd",
"scope": {
"kind": "chatroom",
"id": "11111111-1111-4111-8111-111111111111"
},
"acting_chatroom_id": "11111111-1111-4111-8111-111111111111",
"audience": "internal",
"origin": "manual",
"ai": false,
"command": {
"id": "22222222-2222-4222-8222-222222222222",
"version": 2,
"updated_at": "2026-07-21T02:10:00",
"definition_digest": "tcm1:4c4ed1afbfdaa1e4c3bf7bbb82d730cecb7e384da91eea4f3cc093fd545524d6",
"tag_id": "33333333-3333-4333-8333-333333333333"
},
"scp_binding": {
"kind": "ScpUnroomed",
"principal_user_id": "77777777-7777-4777-8777-777777777777",
"principal_client_id": null
},
"authority_digest": "8f76fd501bb68ef71f4e276bc28f29bce1003b0c2c9d9478de81b5bfc0cde1e9",
"dependency_digest": "tcm1:f26350dafe3f19aabfd69ac463fb5daf76015c9a2763e76e2ad32fc0fcfedf31"
}
}
}
}
],
"total": 1
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
| 404 | 指令不存在於此範圍、已被軟刪除、沒有引用任何資料表或引用了呼叫者無法讀取的資料表、它的 tag 已不再涵蓋每一張引用的資料表,或指令是 private 且呼叫者既不是建立者、也不管理全部被引用資料表。`restricted` 指令不看資料表可讀性與 `visibility`:只有既不管理全部被引用資料表、也沒有 execute grant 的呼叫者才會得到 404。 | |
/private/module/custom_tables/{scopeWithId}/commands/{command_id}/executions/{execution_id}回傳一筆執行稽核紀錄,含 input 快照、寫入結果參照、儲存的回應內容與權限快照。
收到 409 approval_required 後輪詢它,觀察 staged 紀錄在覆核結束後變成 succeeded 或 failed。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/{command_id}/executions/{execution_id} - 部門
/private/module/custom_tables/department/{department_id}/commands/{command_id}/executions/{execution_id} - 公司
/private/module/custom_tables/company/{company_id}/commands/{command_id}/executions/{execution_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
command_id | path | string 1..36 | 是 | 執行紀錄所屬的指令 UUID。 |
execution_id | path | string 1..36 | 是 | 要讀取的執行紀錄 UUID。 |
回應結構
CommandExecutionResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
authorization_snapshot | object | null | 否 | Immutable command scope, tag, and referenced-table audit facts. |
command_id | string | null | 否 | 未指定 |
created_at | string (date-time) | null | 否 | 未指定 |
duration_ms | integer | null | 否 | 未指定 |
error | string | null | 否 | 未指定 |
executed_at | string (date-time) | null | 否 | Wall-clock stamp frozen at the ORIGINAL execution; an idempotent replay returns the same stamp, so callers can distinguish a replayed earlier result from a fresh write. |
execution_id | string | 是 | 未指定 |
idempotency_key | string | null | 否 | 未指定 |
input_snapshot | object | null | 否 | 未指定 |
invoker_client_id | string | null | 否 | 未指定 |
invoker_id | string | null | 否 | 未指定 |
outputs | object | null | 否 | Declared version-2 scalar or bounded relation outputs. Internal temporary relations and step rows are never exposed. |
redacted | boolean | null | 否 | G07a: true when this definition-authority execution is shown to a caller who does not manage every referenced table — a status-only receipt (no rows, outputs, result refs or response body). |
redacted_outputs | string[] | null | 否 | On a replay: declared outputs withheld because this caller cannot read everything they were computed from, or because they derive from values a replay cannot re-check (computed columns read by a write step). Omitted when none. |
replayed | boolean | null | 否 | True when this business event (the Command's effect_identity occurrence) was already recorded: the body is the ORIGINAL receipt (original execution_id and executed_at) and nothing was written. Omitted otherwise. |
response_body | object | null | 否 | 未指定 |
result_refs | object[] | null | 否 | 未指定 |
staged_change_id | string | null | 否 | 未指定 |
status | "succeeded" | "staged" | "failed" | 是 | 未指定 |
steps | (CommandExecutionV1StepResponse | CommandExecutionV2StepResponse)[] | 否 | 未指定 |
truncated | boolean | null | 否 | True when the persisted response snapshot was trimmed to the audit byte cap: declared outputs/step rows are omitted from this body. Set on an idempotent replay of a large execution so the caller can distinguish a trimmed snapshot from a genuinely empty result. |
範例
檢視等待覆核中的暫存執行
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"command_id": "22222222-2222-4222-8222-222222222222",
"execution_id": "66666666-6666-4666-8666-666666666666"
}
}{
"execution_id": "66666666-6666-4666-8666-666666666666",
"status": "staged",
"steps": [],
"command_id": "22222222-2222-4222-8222-222222222222",
"invoker_id": "77777777-7777-4777-8777-777777777777",
"idempotency_key": "order-2026-07-21-0043",
"staged_change_id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
"duration_ms": 119,
"created_at": "2026-07-21T05:44:02",
"input_snapshot": {
"customer": "大同機械",
"item": "螺絲 M4",
"quantity": 400
},
"response_body": {
"error": "approval_required",
"process_id": "cccccccc-cccc-4ccc-8ccc-cccccccccccc",
"rule_id": "rule_3f9a1c2b7d4e",
"rule_label": "高金額訂單需主管覆核",
"staged_change_id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"
},
"authorization_snapshot": {
"version": 1,
"scope": {
"kind": "chatroom",
"id": "11111111-1111-4111-8111-111111111111"
},
"tag_id": "33333333-3333-4333-8333-333333333333",
"table_ids": [
"44444444-4444-4444-8444-444444444444",
"55555555-5555-4555-8555-555555555555"
],
"command_updated_at": "2026-07-21T02:10:00",
"audience": "internal",
"origin": "manual",
"ai": false,
"company_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"acting_chatroom_id": "11111111-1111-4111-8111-111111111111",
"department_id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd",
"idempotency_context": {
"version": 1,
"digest": "tcm1:00c68cd04623cf515134c6a861b49c9264cddff2cf6b9f33bbdb1362ea598f0f",
"context": {
"version": 1,
"principal": {
"kind": "internal",
"user_id": "77777777-7777-4777-8777-777777777777",
"social_media_client_id": null
},
"company_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"department_id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd",
"scope": {
"kind": "chatroom",
"id": "11111111-1111-4111-8111-111111111111"
},
"acting_chatroom_id": "11111111-1111-4111-8111-111111111111",
"audience": "internal",
"origin": "manual",
"ai": false,
"command": {
"id": "22222222-2222-4222-8222-222222222222",
"version": 2,
"updated_at": "2026-07-21T02:10:00",
"definition_digest": "tcm1:4c4ed1afbfdaa1e4c3bf7bbb82d730cecb7e384da91eea4f3cc093fd545524d6",
"tag_id": "33333333-3333-4333-8333-333333333333"
},
"scp_binding": {
"kind": "ScpUnroomed",
"principal_user_id": "77777777-7777-4777-8777-777777777777",
"principal_client_id": null
},
"authority_digest": "8f76fd501bb68ef71f4e276bc28f29bce1003b0c2c9d9478de81b5bfc0cde1e9",
"dependency_digest": "tcm1:f26350dafe3f19aabfd69ac463fb5daf76015c9a2763e76e2ad32fc0fcfedf31"
},
"staged_attestation_digest": "d27f7834acdaeca34ae0507d71c78ca65ade5832fac012446543309bdc1d1dbf"
}
}
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
| 404 | 指令不存在於此範圍、已被軟刪除、沒有引用任何資料表或引用了呼叫者無法讀取的資料表、它的 tag 已不再涵蓋每一張引用的資料表,或指令是 private 且呼叫者既不是建立者、也不管理全部被引用資料表。`restricted` 指令不看資料表可讀性與 `visibility`:只有既不管理全部被引用資料表、也沒有 execute grant 的呼叫者才會得到 404。 | |
| 404 | 該指令下沒有這個 id 的執行紀錄,或呼叫者看不到它。 | |
/private/module/custom_tables/agent/commands/{command_id}/execute在可信的服務通道上執行指令;身分來自伺服器注入的 header,而不是路徑上的範圍區段。
當 agent runtime 的模型工具需要代表特定「使用者+聊天室」或社群媒體 client 執行複合寫入時使用。
- 完整路由
/private/module/custom_tables/agent/commands/{command_id}/execute
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 完整路由 | 可使用 | resolve_command_service_principal (X-API-Key service token) |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
command_id | path | string 1..36 | 是 | 要執行的指令 UUID;其範圍由綁定的 principal 推導。 |
db | query | int | 否 | Redis 邏輯資料庫編號,因共用 client 相依而外洩到 schema 上,請不要填。 |
請求結構
CommandExecuteRequest| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
expected_contract_digest | string | null | 否 | Optional lowercase 64-hex SHA-256 of the exact current stored command definition. A trusted manual caller may send it when executing against an exact reviewed definition snapshot; omission preserves normal manual execution behavior. Generated agent write-command tools keep the digest server-private and inject their pinned value only after later unambiguous natural-language confirmation. A mismatch fails before input validation, idempotency reservation, audit creation, row locking, or mutation. |
idempotency_key | string | null | 否 | Actor-bucketed idempotency key; failed executions do not block retry. |
inputs | object | 是 | Values for the command's declared inputs; server context is excluded. |
reason | string | null | 否 | Why this execution runs, kept with the execution and in the audit of what it changed. Required (unless the change is released through an approval) when the command changes a closed period row (ct.calendars; otherwise 422 ct.scope_override_reason_required). Only a person's reason counts: Agent and MCP executions change a closed period through an approval. |
回應結構
CommandExecutionResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
authorization_snapshot | object | null | 否 | Immutable command scope, tag, and referenced-table audit facts. |
command_id | string | null | 否 | 未指定 |
created_at | string (date-time) | null | 否 | 未指定 |
duration_ms | integer | null | 否 | 未指定 |
error | string | null | 否 | 未指定 |
executed_at | string (date-time) | null | 否 | Wall-clock stamp frozen at the ORIGINAL execution; an idempotent replay returns the same stamp, so callers can distinguish a replayed earlier result from a fresh write. |
execution_id | string | 是 | 未指定 |
idempotency_key | string | null | 否 | 未指定 |
input_snapshot | object | null | 否 | 未指定 |
invoker_client_id | string | null | 否 | 未指定 |
invoker_id | string | null | 否 | 未指定 |
outputs | object | null | 否 | Declared version-2 scalar or bounded relation outputs. Internal temporary relations and step rows are never exposed. |
redacted | boolean | null | 否 | G07a: true when this definition-authority execution is shown to a caller who does not manage every referenced table — a status-only receipt (no rows, outputs, result refs or response body). |
redacted_outputs | string[] | null | 否 | On a replay: declared outputs withheld because this caller cannot read everything they were computed from, or because they derive from values a replay cannot re-check (computed columns read by a write step). Omitted when none. |
replayed | boolean | null | 否 | True when this business event (the Command's effect_identity occurrence) was already recorded: the body is the ORIGINAL receipt (original execution_id and executed_at) and nothing was written. Omitted otherwise. |
response_body | object | null | 否 | 未指定 |
result_refs | object[] | null | 否 | 未指定 |
staged_change_id | string | null | 否 | 未指定 |
status | "succeeded" | "staged" | "failed" | 是 | 未指定 |
steps | (CommandExecutionV1StepResponse | CommandExecutionV2StepResponse)[] | 否 | 未指定 |
truncated | boolean | null | 否 | True when the persisted response snapshot was trimmed to the audit byte cap: declared outputs/step rows are omitted from this body. Set on an idempotent replay of a large execution so the caller can distinguish a trimmed snapshot from a genuinely empty result. |
範例
從 agent runtime 執行訂單指令
{
"pathParams": {
"command_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"inputs": {
"customer": "長榮工業",
"item": "螺絲 M4",
"quantity": 12
},
"idempotency_key": "agent-run-9f2c1a"
}
}{
"execution_id": "66666666-6666-4666-8666-666666666666",
"status": "succeeded",
"executed_at": "2026-07-21T03:02:11Z",
"steps": [
{
"name": "stock",
"kind": "select",
"status": "succeeded",
"row_count": 1,
"schema": {
"id": {
"type": "string",
"nullable": false
},
"on_hand": {
"type": "integer",
"nullable": true
}
}
},
{
"name": "on_hand",
"kind": "let",
"status": "succeeded",
"row_count": 0,
"schema": {}
},
{
"name": "stock_ok",
"kind": "assert",
"status": "succeeded",
"row_count": 0,
"schema": {}
},
{
"name": "order",
"kind": "insert",
"status": "succeeded",
"row_count": 1,
"schema": {
"id": {
"type": "string",
"nullable": false
}
}
},
{
"name": "consume",
"kind": "update",
"status": "succeeded",
"row_count": 1,
"schema": {
"id": {
"type": "string",
"nullable": false
}
}
}
],
"outputs": {
"order_id": "88888888-8888-4888-8888-888888888888",
"remaining": 8
}
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 400 | header 既沒有完整的 user + chatroom 組合,也沒有單獨的社群媒體 client id,或兩種形式混用。 | |
| 400 | 具型別的 channel headers 組合無效。X-TeamSync-Agent-Group-Scope 只在 line_group 與 line_room 時必填,其他 channel 一律不得出現。 | |
| 401 | X-API-Key 與伺服器設定的指令服務 token 不符。 | |
| 403 | 指定的身分不存在、已停用,或不屬於它宣稱的聊天室或租戶。 | |
| 403 | Principal 透過的房間——chatroom 掛載點上是路徑的聊天室,agent 通道上是工作階段的房間——在取得權限鎖之後發現已被刪除、移到其他公司或部門,或成員資格已不存在;外部 principal 則是沒有存活的 acting room,或其 client 已換了房間。沒有寫入任何資料。 | |
| 403 | Acting room 在 command 觸及的某張部門資料表上沒有 internal 授權。聊天室的綁定發生在解析任何資料表權限之前——對象是每一張受 channel 管控的資料表,沒有受管控的資料表時則是 id 最小的那張——所以這個回應會早於逐表的新增、編輯與讀取 403。 | |
| 403 | 客戶 principal:受 channel 管控的資料表的 external 範圍不包含客戶自己的聊天室。 | |
| 403 | 程式讀取了 acting room 未授予讀取權的資料表。這條通道的部門資料表權限釘在 acting room 上,執行者其他聊天室給的授權在這裡不算數。 | |
| 403 | insert 步驟打到 acting room 未授予 insert 權的資料表。與讀取 403 是同一道前置閘門,所以不會寫入任何資料。 | |
| 403 | update 或 delete 步驟打到的資料表,在 acting room 下 `can_edit` 解析為 "none"。filtered 或 own 授權會通過這裡,改在稍後逐列把關。 | |
| 403 | acting room 在程式要寫入的某張 channel 治理資料表上沒有宣告 scope_values。這條通道釘住房間,所以訊息會把房間點名——不綁房間的 REST 對應版本,同一個 code 會說 `no chatroom of yours declares a scope on this table`。 | |
| 403 | 寫入 floor 拒絕了這筆資料列:acting room 宣告的 scope 涵蓋不到該步驟要寫的紀錄或它要設的連結。這是終局性的政策拒絕——從同一個房間重試不可能成功,agent protocol 也要求把訊息轉達給使用者後停手,不要繞路。 | |
| 403 | 連結值指向的目標確實存在、也通過 ACL,只有目標資料表的 channel scope 對 acting room 把它藏起來。與 REST 通道相同的 B10 歸因;在它之前,這個情況會回 500 `command_execution_failed`。 | |
| 403 | 業務事件已經記錄,但該 command 的某個 update 或 delete step 所指向的資料表上,這位呼叫者的編輯權限是 own 或 filtered(不是 all,且不是管理者),或其任一寫入 step 所指向的資料表落在呼叫者的 SCP 房間範圍內,所以無法重新證明 replay。不寫入任何東西,也不揭露先前的 execution id 或結果;重試會得到相同答案。 | |
| 403 | 這個 command 是 `restricted`,而呼叫者既不管理全部被引用的資料表,也沒有有效的 execute grant。它是帶 `phase: "execute"` 的 program-error 信封;MCP 工具改回 `not_found`。 | |
| 403 | owner 已不再有效的 `definition` authority command 會 fail closed:owner 不再存活、屬於其他公司、是服務帳號,或不再是每一張 `table_access` 資料表的 manager。必須由管理每一張資料表的 manager 重新儲存這個 command。 | |
| 403 | command 引用的某張資料表在 `table_access` ceiling 中沒有項目,所以每次執行都會 fail closed,直到 manager 補上項目並重新儲存。 | |
| 404 | 從綁定 principal 的聊天室、部門或公司都看不到這個 id 的指令。 | |
| 404 | 指令存在、範圍也看得到,但 agent_enabled 是 false。回應內容與「id 不存在」完全相同,所以 agent 通道不會洩漏任何存在性訊號。 | |
| 404 | 連結目標不存在、已軟刪除,或被 row ACL 藏起來——三種情況共用同一個 body。與上面的 Command not found 404 不同,請讀 detail 字串。 | |
| 409 | 同一位 principal 以相同 idempotency key 送出不同的 inputs。 | |
| 409 | require_approval 規則觸發,計畫已被暫存待覆核。 | |
| 409 | Command 的定義在本回合的 agent 工具建立之後發生變更。請求在保留、稽核、加鎖或異動前就被拒絕;請重新讀取 command。在確認通道上,於提案與確認回合之間做的編輯會改以 proposal_mismatch 回報給 agent,所以這個 409 只涵蓋確認回合檢查之後才發生的編輯。 | |
| 409 | 這把 idempotency key 已被另一次執行用過,而那次凍結的權限情境與這次不同——origin、acting room、principal、權限集合、指令定義或相依狀態有所不同。新的情境請用新的 key。 | |
| 409 | 兩次執行爭用同一批資料列,InnoDB 中止了這一次。交易已回滾,沒有任何部分寫入落地,retryable 為 true——請用「同一把」idempotency key 重試:失敗路徑會釋放該 key 的保留,因此重用是合法的。 | |
| 409 | 業務事件已經記錄,而這次執行驗證後的 inputs 或作者程式不同(details.semantic_match 為 false;details.original_execution_id 指出先前那次執行)。終局:重試或改變內容都不會成功。發生在審批 release 時,封套的 phase 為 release(重複 stage 的 inputs 與程式相同時,details.semantic_match 為 true),並讓 staged 變更以 apply_failed 結束。 | |
| 409 | 排除送審者(`exclude_requester`)之後,某個必要的審批 gate 沒有任何可核准者。送出會在任何 review process 出現之前就被拒絕;`gate_order` 指出該 gate。對於不管理每一張被引用資料表的 `authority: definition` command 呼叫者,只會回傳安全的 key,所以沒有 `gate_order`。 | |
| 409 | 這筆寫入需要排除送審者,但呼叫的主體沒有已驗證的人類身分(例如 service account),無法歸屬。不會暫存任何東西。 | |
| 409 | `date_interval_exclusion` 規則拒絕此次寫入:`date_interval_exclusion_invalid_period`(建立時省略 end key、結束日為空字串或不晚於起始日,或 scope link 沒有恰好一個目標)、`date_interval_exclusion_overlap`(另一筆 live 資料列涵蓋了這段期間的一部分),或 `date_interval_exclusion_retry`(寫入等待期間資料表的規則被改變;沒有寫入任何東西,請重送)。body 是固定訊息,絕不會指出資料列、日期或 scope 值。 | |
| 422 | 版本 2 的 assert 步驟沒有得到 true。信封會指出步驟名稱與條件路徑,message 是建置者自訂的文字,而且沒有寫入任何資料。 | |
| 422 | `$first` 的 effect-identity key term 沒選到列,或它的 select step 被 when 略過。在 claim 時點就提出:沒有寫入任何東西,也沒有取得 claim。`$single` term 則維持 400 cardinality_failed。 | |
| 422 | `inputs` 的 request 驗證:以緊湊、key 排序的 UTF-8 JSON 計算超過 1 048 576 bytes、深度超過 64 層、超過 16 384 個節點(`command inputs: JSON depth exceeds 64`、`… node count exceeds 16384`),或含 NaN/Infinity(`command value is not canonical JSON`)。發生在任何保留、稽核列或鎖之前。 | |
| 422 | 必要的具型別 header 缺漏或格式錯誤。X-TeamSync-Agent-Channel 必須是 ChatChannelType 值;X-TeamSync-Agent-Group-Scope 一旦出現,就必須恰好是 64 個小寫十六進位字元。這些 shape 錯誤會先由 request validation 拒絕,之後才輪到 400 的組合檢查。 | |
| 503 | 伺服器未設定 CUSTOM_TABLE_COMMAND_SERVICE_TOKEN。 | |
/private/module/custom_tables/agent/commands/{command_id}/query在可信的服務通道上執行唯讀指令,回傳一頁投影後的分頁資料。
用於只讀取的 agent 工具——查詢、彙總、篩選清單——讓模型完全碰不到寫入通道。
- 完整路由
/private/module/custom_tables/agent/commands/{command_id}/query
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 完整路由 | 可使用 | resolve_command_service_principal (X-API-Key service token) |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
command_id | path | string 1..36 | 是 | 要執行的 query 模式指令 UUID;其範圍由綁定的 principal 推導。 |
請求結構
CommandQueryRequest| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
inputs | object | 是 | Values for the command's declared inputs; server context is excluded. |
page | CommandQueryPage | null | 否 | Optional pagination window; defaults to the first page of 100 rows. |
回應結構
CommandQueryResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
next_cursor | string | null | 否 | Opaque cursor for the next page, or null when exhausted. |
outputs | object | 否 | Declared scalar outputs (relation outputs are paginated via rows). |
rows | object[] | 否 | Projected rows of the paginated relation output for this page. |
total_available | integer | null | 否 | Total rows available in the paginated relation, when known. |
範例
從 agent runtime 取得投影的下一頁
{
"pathParams": {
"command_id": "99999999-9999-4999-8999-999999999999"
},
"body": {
"inputs": {
"min_quantity": 10
},
"page": {
"output": "result",
"limit": 2,
"cursor": "eyJkIjoiOWYyYzFiM2E0ZDVlNmY3MCIsIm8iOjJ9"
}
}
}{
"rows": [
{
"id": "88888888-8888-4888-8888-333333333333",
"客戶": "中鋼結構",
"數量": 10
},
{
"id": "88888888-8888-4888-8888-444444444444",
"客戶": "台塑化工",
"數量": 10
}
],
"outputs": {},
"next_cursor": "eyJkIjoiOWYyYzFiM2E0ZDVlNmY3MCIsIm8iOjR9",
"total_available": 7
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 400 | header 既沒有完整的 user + chatroom 組合,也沒有單獨的社群媒體 client id,或兩種形式混用。 | |
| 401 | X-API-Key 與伺服器設定的指令服務 token 不符。 | |
| 403 | 指定的身分不存在、已停用,或不屬於它宣稱的聊天室或租戶。 | |
| 403 | Principal 透過的房間——chatroom 掛載點上是路徑的聊天室,agent 通道上是工作階段的房間——在取得權限鎖之後發現已被刪除、移到其他公司或部門,或成員資格已不存在;外部 principal 則是沒有存活的 acting room,或其 client 已換了房間。沒有寫入任何資料。 | |
| 400 | cursor 已無法對應這支指令的 dependency contract、inputs 或 output 名稱,通常是換頁之間 schema 或 inputs 變了。 | |
| 400 | page.output 指的不是這支指令的 relation output。 | |
| 400 | 指令宣告了不只一個 relation output,卻省略了 page.output,路由無法判斷要分頁哪一個 output。 | |
| 400 | decimal input 不是符合宣告精度與小數位數的純十進位字串:送了 JSON 數字、位數過多、allow_negative 為 false 時送了負數,或送了 -0。 | |
| 400 | identity 型別的 input 指向的使用者或社群媒體 client 不屬於此範圍。 | |
| 403 | Acting room 在 command 觸及的某張部門資料表上沒有 internal 授權。聊天室的綁定發生在解析任何資料表權限之前——對象是每一張受 channel 管控的資料表,沒有受管控的資料表時則是 id 最小的那張——所以這個回應會早於逐表的新增、編輯與讀取 403。 | |
| 403 | 客戶 principal:受 channel 管控的資料表的 external 範圍不包含客戶自己的聊天室。 | |
| 403 | select 來源或 join 目標在 acting room 的授權下解析出 can_read 為 none,因此呼叫大聲失敗,而不是回一頁空資料讓模型讀成「什麼都沒有」。 | |
| 403 | 版本 2 的 select 在任何子句中引用了呼叫者的資料表權限所隱藏的欄位(他們的 hidden_columns)。使用 program error 信封的 403 不只它:/query 也可能以該信封回 403 command_execute_denied,/execute 還可能回 ct.effect_identity_replay_forbidden、command_execute_denied、definition_authority_invalid 與 definition_ceiling_missing。 | |
| 403 | 這個 command 是 `restricted`,而呼叫者既不管理全部被引用的資料表,也沒有有效的 execute grant。它是帶 `phase: "execute"` 的 program-error 信封;MCP 工具改回 `not_found`。 | |
| 404 | 從綁定 principal 的聊天室、部門或公司都看不到這個 id 的指令。 | |
| 409 | 這是一支寫入指令,必須改走 agent 的 execute 路由。 | |
| 409 | 版本 2 指令儲存的 dependency contract 已與目前的資料表不符(schema、rules、審批設定、triggers、tag 或範圍有變動)。請重新儲存指令以重建 contract;原封不動重試會以同樣方式失敗。 | |
| 422 | 這一頁序列化後超過 64 KiB 的輸出上限;請調小 page.limit 或縮減 output 的 select 欄位。 | |
| 422 | 精確十進位的值超出界限——運算結果超過它的精度,或 SQL 係數超出 65 位數的範圍(此時信封會指出該 select 步驟與 `<step>.select` 路徑)。沒有寫入任何資料。 | |
| 422 | 精確十進位運算式在執行時無效,例如除以零或得到不是有限十進位數的值。path 會指向失敗的運算式。 | |
| 422 | `inputs` 的 request 驗證:以緊湊、key 排序的 UTF-8 JSON 計算超過 1 048 576 bytes、深度超過 64 層、超過 16 384 個節點(`command inputs: JSON depth exceeds 64`、`… node count exceeds 16384`),或含 NaN/Infinity(`command value is not canonical JSON`)。發生在任何保留、稽核列或鎖之前。 | |
| 504 | 某個 select statement 用完了單一 statement 的預算;details.limit 是該預算(單位毫秒,最多 5000),retryable 為 true。 | |
/private/module/custom_tables/{scopeWithId}/commands/validate跑 `POST /commands` 的建立閘門(但不含只有建立與更新才會執行的 `execute_policy` 檢查)——定義編譯、管理權限、callbacks、tag 歸屬、名稱唯一性、內建範例——並以 `POST` 會拋出的同一組 status 與 detail 回報每一個失敗,另外附上 advisories 與編譯後的事實。不寫入任何資料;回應前會先把讀取交易 rollback。
做指令編輯器的即時「現在合法了嗎」面板,或在不建立資料列、不佔用名稱的情況下預覽草稿會編譯成什麼。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/validate - 部門
/private/module/custom_tables/department/{department_id}/commands/validate - 公司
/private/module/custom_tables/company/{company_id}/commands/validate
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 可使用 | require_jwt_user + DepartmentTenantRequired |
| 公司 | 可使用 | require_jwt_user + CompanyTenantRequired |
請求結構
CommandCreateRequest| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
agent_enabled | boolean | 否 | Whether the chatroom agent may see and invoke this command. When False the command stays fully usable over REST but is never loaded as an agent tool. |
definition | CommandDefinition-Input | 是 | Typed inputs and ordered multi-table write steps. |
description | string | 否 | Human-readable purpose and usage notes. |
execute_policy | "open" | "restricted" | null | 否 | G07a who may invoke. `open` (the default when omitted on create) keeps today's rule: read every referenced table and pass visibility. `restricted` admits only live managers of every referenced table and holders of an execute grant (see /commands/{id}/grants); required for authority `definition`. Omitted on PUT means unchanged, so a PUT never reopens a restricted Command. |
name | string | 是 | Command display name; unique within its scope. |
tag_id | string | 是 | Required tag from the same custom-table scope. Every referenced table must currently be assigned to this tag. |
visibility | "public" | "private" | 否 | ``public``: any principal who can read every referenced table may bind and invoke it. ``private``: the chatroom agent binds it only for its creator; REST invoke/get/list are creator-or-manager only. |
回應結構
CommandValidationReport| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
advisories | CommandValidationAdvisory[] | 否 | 未指定 |
compiled | CompiledCommandFacts | null | 否 | 未指定 |
errors | CommandValidationError[] | 否 | 未指定 |
valid | boolean | 是 | 未指定 |
範例
驗證一個 write 指令草稿(通過,含一則 advisory)
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111"
},
"body": {
"name": "建立訂單並扣庫存",
"description": "一次寫入訂單並扣減對應品項庫存;庫存不足時整筆回滾。",
"agent_enabled": true,
"visibility": "public",
"tag_id": "33333333-3333-4333-8333-333333333333",
"definition": {
"version": 2,
"inputs": [
{
"name": "customer",
"type": "string",
"required": true,
"max_length": 100,
"description": "客戶名稱"
},
{
"name": "item",
"type": "string",
"required": true,
"max_length": 100,
"description": "品項名稱"
},
{
"name": "quantity",
"type": "integer",
"required": true,
"description": "出貨數量"
}
],
"steps": [
{
"kind": "select",
"name": "stock",
"from": {
"table": "55555555-5555-4555-8555-555555555555",
"as": "s"
},
"select": {
"id": "$row.s.id",
"on_hand": "$row.s.庫存"
},
"where": {
"$and": [
{
"$eq": [
"$row.s.品項",
"$input.item"
]
}
]
},
"max_rows": 1
},
{
"kind": "let",
"name": "on_hand",
"type": "integer",
"value": {
"$single": {
"relation": "$rel.stock",
"expr": "$item.on_hand"
}
}
},
{
"kind": "assert",
"name": "stock_ok",
"condition": {
"$gte": [
"$var.on_hand",
"$input.quantity"
]
},
"message": "庫存不足,無法建立訂單"
},
{
"kind": "insert",
"name": "order",
"table": "44444444-4444-4444-8444-444444444444",
"values": {
"客戶": "$input.customer",
"品項": "$input.item",
"數量": "$input.quantity",
"狀態": "draft",
"建立者": "$ctx.user_id"
},
"returning": {
"id": "$new.id"
}
},
{
"kind": "update",
"name": "consume",
"table": "55555555-5555-4555-8555-555555555555",
"for_each": "$rel.stock",
"set": {
"庫存": {
"$sub": [
"$item.on_hand",
"$input.quantity"
]
}
},
"match": {
"id": "$item.id"
},
"max_rows": 1,
"returning": {
"id": "$new.id"
}
}
],
"outputs": {
"order_id": {
"value": {
"$single": {
"relation": "$rel.order",
"expr": "$item.id"
}
}
},
"remaining": {
"value": {
"$sub": [
"$var.on_hand",
"$input.quantity"
]
}
}
}
}
}
}{
"valid": true,
"errors": [],
"advisories": [
{
"code": "name_uniqueness_unlocked",
"message": "Name uniqueness was checked without the namespace lock; it is re-checked under lock on save.",
"table_ids": []
}
],
"compiled": {
"table_ids": [
"44444444-4444-4444-8444-444444444444",
"55555555-5555-4555-8555-555555555555"
],
"mode": "write",
"write_targets": [
"44444444-4444-4444-8444-444444444444",
"55555555-5555-4555-8555-555555555555"
],
"approval_gated_tables": [],
"agent_tool_name": "custom_table_command_execute_00000000",
"input_names": [
"customer",
"item",
"quantity"
]
}
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 與 command 家族其他 route 相同的 JWT 門檻:`request.state.auth_method` 必須是 `jwt`。 | |
| 400 | version 2 definition 的 step kind 是 list/object。Create、update、validate 回靜態 type/loc/msg issue,不含 input 或 ctx;其他 request 驗證維持 422。 | |
| 403 | 聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。 | |
| 404 | 聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。 | |
| 404 | 部門範圍:路徑上的部門不存在或屬於其他公司。 | |
| 404 | 公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。 | |
| 422 | request body 不是合法的 `CommandCreateRequest`。schema 層級的失敗是 FastAPI validation error,不會進到報告的 `errors` 陣列。 | |
/private/module/custom_tables/{scopeWithId}/commands/composer/chat為這個聊天室跑一次自然語言指令 composer 回合。送 `message` 與它對話,或送 `resume` 回答它正在等待的儲存審核——兩者只能擇一。回合本身在背景執行,這個 route 在同步閘門通過後就回 `202` 與一組 handle。
做「用講的把指令描述出來」的編輯介面。若同時需要即時進度,改用 WebSocket lane。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/composer/chat
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 未提供 | 不適用 |
| 公司 | 未提供 | 不適用 |
請求結構
ComposerChatRequest| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
client_id | string | 是 | Per-device/tab thread key; the server derives the thread id from company/room/user/client. |
message | string | null | 否 | 未指定 |
resume | ComposerResumeDecision | null | 否 | Answer to a pending ``save_command`` approval. |
回應結構
ComposerTurnAccepted| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
poll_after_ms | integer | 否 | Suggested delay before the first poll. |
poll_path | string | 是 | GET …/turn path with `client_id` and this `turn_id` already filled in; add `&wait_seconds=N` (≤ 60) to long-poll. |
state | string | 否 | 未指定 |
thread_id | string | 是 | 未指定 |
turn_id | string | 是 | 未指定 |
範例
請 composer 做一個 private 指令(202 handle)
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111"
},
"body": {
"client_id": "tab-1",
"message": "幫我做一個登記客訴的指令,只有我自己能用"
}
}{
"thread_id": "ctcomposer:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa:11111111-1111-4111-8111-111111111111:77777777-7777-4777-8777-777777777777:tab-1",
"turn_id": "0f4c1b7a9e2d4a6b8c0d2e4f6a8b0c1d",
"state": "running",
"poll_path": "/private/module/custom_tables/chatroom/11111111-1111-4111-8111-111111111111/commands/composer/turn?client_id=tab-1&turn_id=0f4c1b7a9e2d4a6b8c0d2e4f6a8b0c1d",
"poll_after_ms": 2000
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 呼叫者未通過聊天室的已加入成員/管理員存取檢查,或適用於 composer 類別的公司、部門、聊天室或使用者 quota 有一項已用盡。 | |
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 404 | 路徑上的聊天室不存在或已被軟刪除。 | |
| 409 | 這個部署沒有替 composer job(`custom_table_command_composer`)釘選模型,而且沒有預設值。由 operator 以 `PUT` 呼叫 `detail.pin_path`(root API,body 為 `{"model": "<provider>/<name>"}`)釘選後再重試。body 帶有 job 與 pin_path,沒有 message;WebSocket 通道會以 status 409 的 error frame 送出相同欄位,socket 保持開啟。 | |
| 409 | 這個 thread 沒有任何待審的儲存,卻送了 `resume`。 | |
| 409 | 同一個 `client_id` 還有回合在跑(per-thread lock)。請輪詢 `GET …/turn`,或用 `DELETE …/thread` 丟棄 thread 以釋放這個 id。 | |
| 422 | 釘選的模型被政策拒絕或無法建構(有 code 時會說明原因)。 | |
| 422 | body 驗證。`send exactly one of message or resume` 來自 model validator:兩個都送、或兩個都不送,都是同一個錯誤。 | |
| 503 | 這個部署尚未設定 composer 的 checkpoint 儲存。`POST …/chat`、`GET …/history` 與 `DELETE …/thread` 回 `503 composer_unavailable`,WebSocket 則以 1013 關閉;只讀 Redis 的 `GET …/turn` 與只提供文件的 `GET …/ws/docs` 仍可使用。 | |
/private/module/custom_tables/{scopeWithId}/commands/composer/turn回報某個 composer thread 最新回合的狀態,結束後帶回結果。通過 JWT 與聊天室存取 dependency 之後,它只讀 Redis 的回合紀錄與鎖——從不碰 checkpoint Postgres——所以可以安全輪詢;`wait_seconds` 會把它變成 long poll。
在 `POST …/chat` 回 `202` 之後,且你沒有使用 WebSocket lane 時。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/composer/turn
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 未提供 | 不適用 |
| 公司 | 未提供 | 不適用 |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
client_id | query | string ^[A-Za-z0-9_-]{1,64}$ | 是 | `POST …/chat` 用的 thread key。必填——它決定要回報哪一個 thread。 |
wait_seconds | query | int 0..60 | 否 | 回合仍在執行時,最多保留回應這麼多秒(0–60,預設 0)。`0` 表示立即回傳;更大的值會回 422。 |
turn_id | query | string ^[0-9a-f]{32}$ | 否 | 202 handle 裡的 `turn_id`。帶上它就只會回報那一次回合,絕不會拿別的回合紀錄頂替。 |
回應結構
ComposerTurnStatusResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
elapsed_seconds | number | null | 否 | Since the turn started (running) or its duration (done/error) |
error | ComposerTurnError | null | 否 | Set when `state == error` |
finished_at | string (date-time) | null | 否 | 未指定 |
lane | "rest" | "ws" | null | 否 | Which lane started the turn |
result | ComposerChatResponse | null | 否 | Set when `state == done` |
started_at | string (date-time) | null | 否 | 未指定 |
state | "running" | "done" | "error" | "idle" | 是 | 未指定 |
thread_id | string | 是 | 未指定 |
turn_id | string | null | 否 | 未指定 |
範例
Long-poll 一個已結束的回合(等待儲存審核)
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111"
},
"query": {
"client_id": "tab-1",
"turn_id": "0f4c1b7a9e2d4a6b8c0d2e4f6a8b0c1d",
"wait_seconds": "30"
}
}{
"thread_id": "ctcomposer:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa:11111111-1111-4111-8111-111111111111:77777777-7777-4777-8777-777777777777:tab-1",
"state": "done",
"turn_id": "0f4c1b7a9e2d4a6b8c0d2e4f6a8b0c1d",
"lane": "rest",
"started_at": "2026-09-19T10:04:11.482913Z",
"finished_at": "2026-09-19T10:05:28.883913Z",
"elapsed_seconds": 77.401,
"result": {
"thread_id": "ctcomposer:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa:11111111-1111-4111-8111-111111111111:77777777-7777-4777-8777-777777777777:tab-1",
"status": "awaiting_approval",
"assistant_message": "草稿已通過驗證,要我存成指令嗎?",
"draft": {
"command_id": null,
"name": "登記客訴",
"description": "把一筆客訴寫進客訴表,並標記負責人。",
"tag_id": "33333333-3333-4333-8333-333333333333",
"visibility": "private",
"agent_enabled": true,
"definition": {
"version": 2,
"inputs": [
{
"name": "customer",
"type": "string",
"required": true,
"max_length": 100,
"description": "客戶名稱"
},
{
"name": "item",
"type": "string",
"required": true,
"max_length": 100,
"description": "品項名稱"
},
{
"name": "quantity",
"type": "integer",
"required": true,
"description": "出貨數量"
}
],
"steps": [
{
"kind": "select",
"name": "stock",
"from": {
"table": "55555555-5555-4555-8555-555555555555",
"as": "s"
},
"select": {
"id": "$row.s.id",
"on_hand": "$row.s.庫存"
},
"where": {
"$and": [
{
"$eq": [
"$row.s.品項",
"$input.item"
]
}
]
},
"max_rows": 1
},
{
"kind": "let",
"name": "on_hand",
"type": "integer",
"value": {
"$single": {
"relation": "$rel.stock",
"expr": "$item.on_hand"
}
}
},
{
"kind": "assert",
"name": "stock_ok",
"condition": {
"$gte": [
"$var.on_hand",
"$input.quantity"
]
},
"message": "庫存不足,無法建立訂單"
},
{
"kind": "insert",
"name": "order",
"table": "44444444-4444-4444-8444-444444444444",
"values": {
"客戶": "$input.customer",
"品項": "$input.item",
"數量": "$input.quantity",
"狀態": "draft",
"建立者": "$ctx.user_id"
},
"returning": {
"id": "$new.id"
}
},
{
"kind": "update",
"name": "consume",
"table": "55555555-5555-4555-8555-555555555555",
"for_each": "$rel.stock",
"set": {
"庫存": {
"$sub": [
"$item.on_hand",
"$input.quantity"
]
}
},
"match": {
"id": "$item.id"
},
"max_rows": 1,
"returning": {
"id": "$new.id"
}
}
],
"outputs": {
"order_id": {
"value": {
"$single": {
"relation": "$rel.order",
"expr": "$item.id"
}
}
},
"remaining": {
"value": {
"$sub": [
"$var.on_hand",
"$input.quantity"
]
}
}
}
}
},
"validation": {
"valid": true,
"errors": [],
"advisories": [
{
"code": "name_uniqueness_unlocked",
"message": "Name uniqueness was checked without the namespace lock; it is re-checked under lock on save.",
"table_ids": []
}
],
"compiled": {
"table_ids": [
"44444444-4444-4444-8444-444444444444",
"55555555-5555-4555-8555-555555555555"
],
"mode": "write",
"write_targets": [
"44444444-4444-4444-8444-444444444444",
"55555555-5555-4555-8555-555555555555"
],
"approval_gated_tables": [],
"agent_tool_name": "custom_table_command_execute_00000000",
"input_names": [
"customer",
"item",
"quantity"
]
}
},
"example": null,
"pending_approval": {
"tool": "save_command",
"name": "登記客訴",
"visibility": "private",
"agent_enabled": true,
"table_ids": [
"44444444-4444-4444-8444-444444444444",
"55555555-5555-4555-8555-555555555555"
],
"agent_tool_name": "custom_table_command_execute_00000000"
},
"saved": null,
"todos": [
{
"content": "驗證草稿",
"status": "completed"
}
]
},
"error": null
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 呼叫者未通過聊天室的已加入成員/管理員存取檢查。 | |
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 404 | 路徑上的聊天室不存在或已被軟刪除。 | |
| 422 | `client_id` 缺少或不符合 `^[A-Za-z0-9_-]{1,64}$`、`turn_id` 不是 32 個小寫十六進位字元,或 `wait_seconds` 不是 0–60 的整數。 | |
/private/module/custom_tables/{scopeWithId}/commands/composer/history回傳單一 thread 的使用者/助理逐字紀錄、目前草稿、最後一份驗證報告與待審的儲存。工具往來刻意不外露。
重新整理頁面後,或 WebSocket 重連後,用來還原 composer 面板。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/composer/history
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 未提供 | 不適用 |
| 公司 | 未提供 | 不適用 |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
client_id | query | string ^[A-Za-z0-9_-]{1,64}$ | 是 | Thread key。必填——每個 `client_id` 一份逐字紀錄。 |
回應結構
ComposerHistoryResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
draft | ComposerDraft | null | 否 | The whole draft (= a CommandCreateRequest plus an optional edit target). |
messages | ComposerMessage[] | 否 | 未指定 |
pending_approval | ComposerApprovalRequest | null | 否 | What the server is asking the user to approve before it persists. |
thread_id | string | 是 | 未指定 |
validation | CommandValidationReport | null | 否 | Persist-free authoring verdict (POST /commands/validate and the composer). |
範例
還原一個正在等待儲存審核的 thread
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111"
},
"query": {
"client_id": "tab-1"
}
}{
"thread_id": "ctcomposer:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa:11111111-1111-4111-8111-111111111111:77777777-7777-4777-8777-777777777777:tab-1",
"messages": [
{
"role": "user",
"content": "幫我做一個登記客訴的指令,只有我自己能用"
},
{
"role": "assistant",
"content": "草稿已通過驗證,要我存成指令嗎?"
}
],
"draft": {
"command_id": null,
"name": "登記客訴",
"description": "把一筆客訴寫進客訴表,並標記負責人。",
"tag_id": "33333333-3333-4333-8333-333333333333",
"visibility": "private",
"agent_enabled": true,
"definition": {
"version": 2,
"inputs": [
{
"name": "customer",
"type": "string",
"required": true,
"max_length": 100,
"description": "客戶名稱"
},
{
"name": "item",
"type": "string",
"required": true,
"max_length": 100,
"description": "品項名稱"
},
{
"name": "quantity",
"type": "integer",
"required": true,
"description": "出貨數量"
}
],
"steps": [
{
"kind": "select",
"name": "stock",
"from": {
"table": "55555555-5555-4555-8555-555555555555",
"as": "s"
},
"select": {
"id": "$row.s.id",
"on_hand": "$row.s.庫存"
},
"where": {
"$and": [
{
"$eq": [
"$row.s.品項",
"$input.item"
]
}
]
},
"max_rows": 1
},
{
"kind": "let",
"name": "on_hand",
"type": "integer",
"value": {
"$single": {
"relation": "$rel.stock",
"expr": "$item.on_hand"
}
}
},
{
"kind": "assert",
"name": "stock_ok",
"condition": {
"$gte": [
"$var.on_hand",
"$input.quantity"
]
},
"message": "庫存不足,無法建立訂單"
},
{
"kind": "insert",
"name": "order",
"table": "44444444-4444-4444-8444-444444444444",
"values": {
"客戶": "$input.customer",
"品項": "$input.item",
"數量": "$input.quantity",
"狀態": "draft",
"建立者": "$ctx.user_id"
},
"returning": {
"id": "$new.id"
}
},
{
"kind": "update",
"name": "consume",
"table": "55555555-5555-4555-8555-555555555555",
"for_each": "$rel.stock",
"set": {
"庫存": {
"$sub": [
"$item.on_hand",
"$input.quantity"
]
}
},
"match": {
"id": "$item.id"
},
"max_rows": 1,
"returning": {
"id": "$new.id"
}
}
],
"outputs": {
"order_id": {
"value": {
"$single": {
"relation": "$rel.order",
"expr": "$item.id"
}
}
},
"remaining": {
"value": {
"$sub": [
"$var.on_hand",
"$input.quantity"
]
}
}
}
}
},
"validation": {
"valid": true,
"errors": [],
"advisories": [
{
"code": "name_uniqueness_unlocked",
"message": "Name uniqueness was checked without the namespace lock; it is re-checked under lock on save.",
"table_ids": []
}
],
"compiled": {
"table_ids": [
"44444444-4444-4444-8444-444444444444",
"55555555-5555-4555-8555-555555555555"
],
"mode": "write",
"write_targets": [
"44444444-4444-4444-8444-444444444444",
"55555555-5555-4555-8555-555555555555"
],
"approval_gated_tables": [],
"agent_tool_name": "custom_table_command_execute_00000000",
"input_names": [
"customer",
"item",
"quantity"
]
}
},
"pending_approval": {
"tool": "save_command",
"name": "登記客訴",
"visibility": "private",
"agent_enabled": true,
"table_ids": [
"44444444-4444-4444-8444-444444444444",
"55555555-5555-4555-8555-555555555555"
],
"agent_tool_name": "custom_table_command_execute_00000000"
}
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 呼叫者未通過聊天室的已加入成員/管理員存取檢查。 | |
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 404 | 路徑上的聊天室不存在或已被軟刪除。 | |
| 503 | 這個部署尚未設定 composer 的 checkpoint 儲存。 | |
/private/module/custom_tables/{scopeWithId}/commands/composer/thread刪除該 thread 的 checkpoints 與 thread 索引項目,再 best-effort 清除崩潰回合可能留下的 Redis turn lock 與 turn record。Redis 清除成功時同一個 `client_id` 可立刻再用;否則舊狀態會依 TTL 到期。
「重新開始」按鈕,以及 `POST …/chat` 一直回 `409 turn_in_progress` 時的逃生出口。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/composer/thread
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user + ChatRoomJoinedRequired |
| 部門 | 未提供 | 不適用 |
| 公司 | 未提供 | 不適用 |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
client_id | query | string ^[A-Za-z0-9_-]{1,64}$ | 是 | 要丟棄的 thread key。必填。 |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
重置一個回合崩潰的 thread
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111"
},
"query": {
"client_id": "tab-1"
}
}{
"message": "Success"
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 呼叫者未通過聊天室的已加入成員/管理員存取檢查。 | |
| 403 | 請求使用 UserAPIKey 而非 JWT 驗證。 | |
| 404 | 路徑上的聊天室不存在或已被軟刪除。 | |
| 503 | 這個部署尚未設定 composer 的 checkpoint 儲存。 | |
/private/module/custom_tables/{scopeWithId}/commands/composer/ws/docsWebSocket route 不會出現在 OpenAPI,所以用這個 GET 以型別化的 payload 公開 socket 契約:socket url、如何取得 signature、每一種 client frame 與 server event 及其範例、close code、預期流程,以及對應的 REST 入口。
要產生或驗證 WebSocket client,而不想從文字敘述手抄協定時。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/commands/composer/ws/docs
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | require_jwt_user |
| 部門 | 未提供 | 不適用 |
| 公司 | 未提供 | 不適用 |
回應結構
ComposerWebSocketDocsResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
client_frames | ComposerWsFrameDoc[] | 是 | 未指定 |
close_codes | ComposerWsCloseCodeDoc[] | 是 | 未指定 |
expected_flow | string[] | 是 | 未指定 |
notes | string[] | 否 | 未指定 |
rest_equivalent | string | 是 | REST lane entry point (answers 202 with a turn handle); the `result` its GET …/turn poll returns has exactly a `turn_result` frame's shape |
server_events | ComposerWsFrameDoc[] | 是 | 未指定 |
signature_endpoint | string | 是 | Endpoint that issues the short-lived WebSocket signature |
signature_hard_cap_seconds | integer | 是 | Absolute lifetime of a signature from issue |
signature_recheck_seconds | integer | 是 | How often the socket re-validates the signature and the caller's chatroom membership (idle sockets included) |
signature_request | object | 是 | Example request body for the signature endpoint |
signature_ttl_seconds | integer | 是 | Sliding TTL of a signature |
websocket_url | string | 是 | WebSocket path. Append ?signature={signature} and optionally &client_id={client_id} |
範例
取得 socket 契約(節錄)
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111"
}
}{
"websocket_url": "/ws/custom-tables/composer?signature={signature}&client_id={client_id}",
"signature_endpoint": "/private/get_signature/11111111-1111-4111-8111-111111111111",
"signature_request": {
"client_id": "tab-1",
"chat_channel_type": "chatroom"
},
"signature_ttl_seconds": 86400,
"signature_hard_cap_seconds": 604800,
"signature_recheck_seconds": 300,
"rest_equivalent": "POST /private/module/custom_tables/chatroom/11111111-1111-4111-8111-111111111111/commands/composer/chat",
"client_frames": [
{
"frame": "message",
"direction": "client_to_server",
"description": "One composer turn — the REST chat body without client_id.",
"fields": [
"message"
],
"example": {
"message": "我要一個建立訂單的指令,只有我能用"
}
}
],
"server_events": [
{
"frame": "turn_result",
"direction": "server_to_client",
"description": "Terminal frame of a turn — exactly the REST ComposerChatResponse.",
"fields": [
"status",
"assistant_message",
"draft",
"validation",
"example",
"pending_approval",
"saved",
"todos"
],
"example": {
"event": "turn_result",
"status": "awaiting_approval",
"thread_id": "ctcomposer:…",
"assistant_message": "定義如下……要儲存嗎?",
"pending_approval": {
"tool": "save_command",
"name": "登記訂單",
"visibility": "private",
"agent_enabled": true,
"table_ids": [
"…"
]
}
}
}
],
"close_codes": [
{
"code": 1008,
"error": "invalid_signature",
"reason": "Signature unknown, expired (sliding TTL / hard cap) or lapsed mid-session"
}
],
"expected_flow": [
"POST /private/get_signature/{chatroom_id} → signature"
],
"notes": [
"WebSocket routes are not listed in OpenAPI; this GET is the published contract."
]
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 這個請求不是 JWT 使用者認證。與有狀態的 composer route 不同,這個文件端點不會執行聊天室成員資格 dependency。 | |
凍結提案與操作收據 說明 prepare、獨立正式寫入、selection、Review 義務與同 key 恢復。