Skip to Content
API 參考複合指令

複合指令

複合指令是一支已儲存的程式,能在同一個交易內跨多張自訂資料表寫入。你只需建置一次——具型別的 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 與目前來源檢查,不授予原始資料表權限;回呼卡片為特權、僅供參考。

POST
路由範本/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_idpathuuid是目前有權管理的 Command UUID。

請求結構

CommandServiceCredentialCreate
欄位型別必填說明
chatroom_idstring (uuid)是未指定
expires_in_secondsinteger否未指定
service_account_idstring (uuid)是未指定

回應結構

CommandServiceCredentialIssued
欄位型別必填說明
chatroom_idstring (uuid)是未指定
command_idstring (uuid)是未指定
created_atstring (date-time)是未指定
credentialstring是未指定
expires_atstring (date-time)是未指定
idstring (uuid)是未指定
revoked_atstring (date-time) | null否未指定
service_account_idstring (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
  }
}
回應201
{
  "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。
{
  "detail": {
    "code": "service_command_identity_required"
  }
}
409Command/範圍已不符合 definition-authority/restricted 條件。
{
  "detail": {
    "code": "service_credential_binding_changed"
  }
}
403目前帳號、room、部門、成員或 execute grant 無效。
{
  "detail": {
    "code": "service_credential_scope_denied"
  }
}
GET
路由範本/private/module/custom_tables/{scopeWithId}/commands/{command_id}/service-credentials

列出機器憑證 metadata

用途

檢視簽發、到期/撤銷資訊,不揭露明文。

適用時機

稽核受管理 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_idpathuuid是目前有權管理的 Command UUID。

回應結構

CommandServiceCredentialList
欄位型別必填說明
itemsCommandServiceCredentialMetadata[]是未指定

範例

只讀 metadata

請求
{
  "pathParams": {
    "chatroom_id": "11111111-1111-4111-8111-111111111111",
    "command_id": "22222222-2222-4222-8222-222222222222",
    "credential_id": "33333333-3333-4333-8333-333333333333"
  }
}
回應200
{
  "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
    }
  ]
}
POST
路由範本/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_idpathuuid是目前有權管理的 Command UUID。
credential_idpathuuid是憑證 metadata UUID,不是 ctsp 明文 token。

回應結構

CommandServiceCredentialMetadata
欄位型別必填說明
chatroom_idstring (uuid)是未指定
command_idstring (uuid)是未指定
created_atstring (date-time)是未指定
expires_atstring (date-time)是未指定
idstring (uuid)是未指定
revoked_atstring (date-time) | null否未指定
service_account_idstring (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"
  }
}
回應200
{
  "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 下沒有這個憑證。
{
  "detail": "Service credential not found"
}
POST
路由範本/public/module/custom_tables/callback/command-output/{token_id}

將驗證過的 Run 輸出交給一個 CT Command

用途

使用封閉 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_idpathuuid是伺服器綁定的封閉 Run callback 識別碼,不是一般 API key。

請求結構

SandboxCommandOutputRequest
欄位型別必填說明
inputsobject是未指定

回應結構

CommandExecutionResponse
欄位型別必填說明
authorization_snapshotobject | null否Immutable command scope, tag, and referenced-table audit facts.
command_idstring | null否未指定
created_atstring (date-time) | null否未指定
duration_msinteger | null否未指定
errorstring | null否未指定
executed_atstring (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_idstring是未指定
idempotency_keystring | null否未指定
input_snapshotobject | null否未指定
invoker_client_idstring | null否未指定
invoker_idstring | null否未指定
outputsobject | null否Declared version-2 scalar or bounded relation outputs. Internal temporary relations and step rows are never exposed.
redactedboolean | 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_outputsstring[] | 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.
replayedboolean | 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_bodyobject | null否未指定
result_refsobject[] | null否未指定
staged_change_idstring | null否未指定
status"succeeded" | "staged" | "failed"是未指定
steps(CommandExecutionV1StepResponse | CommandExecutionV2StepResponse)[]否未指定
truncatedboolean | 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"
    }
  }
}
回應200
{
  "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。
{
  "detail": {
    "code": "output_schema_violation"
  }
}
409此 Run 已固定另一個有效輸入 digest。
{
  "detail": {
    "code": "output_command_conflict"
  }
}
409政策、schema 或凍結的 Command digest 已過期失效。
{
  "detail": {
    "code": "output_command_stale"
  }
}
404Callback capability 缺少/無效;後續即時 Run 狀態檢查可能以同一 code 回 409。
{
  "detail": {
    "code": "output_run_unavailable"
  }
}
POST
路由範本/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_idpathuuid是Scoped 版本 2 Command UUID;公司 Command 的 scope path 須包含 company_id。

請求結構

CtPrepareRequest
欄位型別必填說明
command_digeststring是未指定
contract_versioninteger是未指定
expected_versionsCtExpectedVersion[]否未指定
inputsRecord<string, JsonValue>是未指定
request_idempotency_keystring是未指定
selection_idstring (uuid) | null否未指定

回應結構

CtProposalResponse
欄位型別必填說明
approval_assignmentsApprovalAssignment[]是未指定
command_digeststring是未指定
command_idstring (uuid)是未指定
effect_previewEffectPreview[]是未指定
expected_versionsCtExpectedVersion[]是未指定
expires_atstring (date-time)是未指定
proposal_digeststring是未指定
proposal_idstring (uuid)是未指定
read_set_digeststring是未指定
semantic_effect_hashstring是未指定
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"
  }
}
回應201
{
  "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 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
403目前 Command 權限拒絕;凍結服務將 body 正規化為 Not found,不揭露私有中繼資料。
{
  "detail": "Not found"
}
404Command/proposal/operation/selection 不存在、不在此範圍,或此 principal 不可見;body 為 Not found。
{
  "detail": "Not found"
}
409提供的 Command digest 不再符合目前定義;依 reprepare 重新準備,不重播舊預覽。
{
  "detail": [
    {
      "type": "ct.definition_stale",
      "loc": [],
      "msg": "The frozen request is no longer valid.",
      "retry_advice": "reprepare",
      "operation_id": null
    }
  ]
}
POST
路由範本/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_idpathuuid是Scoped 版本 2 Command UUID;公司 Command 的 scope path 須包含 company_id。

請求結構

CtInvokeRequest
欄位型別必填說明
contract_versioninteger是未指定
expected_proposal_digeststring是未指定
proposal_idstring (uuid)是未指定
request_idempotency_keystring是未指定

回應結構

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_effectsExternalEffect[]是未指定
operation_idstring (uuid)是未指定
proposal_idstring (uuid)是未指定
receiptCtReceipt | null是未指定
replayedboolean是未指定

範例

套用已準備提案並保留收據

請求
{
  "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"
  }
}
回應200
{
  "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 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
403目前 Command 權限拒絕;凍結服務將 body 正規化為 Not found,不揭露私有中繼資料。
{
  "detail": "Not found"
}
404Command/proposal/operation/selection 不存在、不在此範圍,或此 principal 不可見;body 為 Not found。
{
  "detail": "Not found"
}
409提供的 Command digest 不再符合目前定義;依 reprepare 重新準備,不重播舊預覽。
{
  "detail": [
    {
      "type": "ct.definition_stale",
      "loc": [],
      "msg": "The frozen request is no longer valid.",
      "retry_advice": "reprepare",
      "operation_id": null
    }
  ]
}
503Commit 結果未知;保留 operation_id、查詢 operation 或使用相同 request key 重試,不直接換新 key。
{
  "detail": [
    {
      "type": "ct.operation_outcome_unknown",
      "loc": [],
      "msg": "Read the operation or retry the same request key to resolve the commit.",
      "retry_advice": "query_operation",
      "operation_id": "ffffffff-ffff-4fff-8fff-ffffffffffff"
    }
  ]
}
GET
路由範本/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_idpathuuid是伺服器回傳、位於此精確範圍的凍結提案 UUID。

回應結構

CtProposalResponse
欄位型別必填說明
approval_assignmentsApprovalAssignment[]是未指定
command_digeststring是未指定
command_idstring (uuid)是未指定
effect_previewEffectPreview[]是未指定
expected_versionsCtExpectedVersion[]是未指定
expires_atstring (date-time)是未指定
proposal_digeststring是未指定
proposal_idstring (uuid)是未指定
read_set_digeststring是未指定
semantic_effect_hashstring是未指定
status"prepared" | "pending_approval" | "approved" | "rejected" | "stale"是未指定

範例

檢視已準備提案

請求
{
  "pathParams": {
    "chatroom_id": "11111111-1111-4111-8111-111111111111",
    "proposal_id": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee"
  }
}
回應200
{
  "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 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
403目前 Command 權限拒絕;凍結服務將 body 正規化為 Not found,不揭露私有中繼資料。
{
  "detail": "Not found"
}
404Command/proposal/operation/selection 不存在、不在此範圍,或此 principal 不可見;body 為 Not found。
{
  "detail": "Not found"
}
GET
路由範本/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_idpathuuid是伺服器回傳的持久 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_effectsExternalEffect[]是未指定
operation_idstring (uuid)是未指定
proposal_idstring (uuid)是未指定
receiptCtReceipt | null是未指定
replayedboolean是未指定

範例

讀取原始已提交操作

請求
{
  "pathParams": {
    "chatroom_id": "11111111-1111-4111-8111-111111111111",
    "operation_id": "ffffffff-ffff-4fff-8fff-ffffffffffff"
  }
}
回應200
{
  "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 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
403目前 Command 權限拒絕;凍結服務將 body 正規化為 Not found,不揭露私有中繼資料。
{
  "detail": "Not found"
}
404Command/proposal/operation/selection 不存在、不在此範圍,或此 principal 不可見;body 為 Not found。
{
  "detail": "Not found"
}
POST
路由範本/private/module/custom_tables/{scopeWithId}/commands/{command_id}/selections

凍結 query selection

用途

擷取版本 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_idpathuuid是Scoped 版本 2 Command UUID;公司 Command 的 scope path 須包含 company_id。

請求結構

CtSelectionRequest
欄位型別必填說明
command_digeststring是未指定
inputsRecord<string, JsonValue>是未指定
maximum_rowsinteger是未指定
request_idempotency_keystring是未指定

回應結構

CtSelectionResponse
欄位型別必填說明
content_digeststring是未指定
created_atstring (date-time)是未指定
expires_atstring (date-time)是未指定
manifest_refstring (uuid)是未指定
next_cursorstring | null是未指定
query_digeststring是未指定
recordsCtExpectedVersion[]是未指定
row_countinteger是未指定
selection_idstring (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"
  }
}
回應201
{
  "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 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
403目前 Command 權限拒絕;凍結服務將 body 正規化為 Not found,不揭露私有中繼資料。
{
  "detail": "Not found"
}
404Command/proposal/operation/selection 不存在、不在此範圍,或此 principal 不可見;body 為 Not found。
{
  "detail": "Not found"
}
409提供的 Command digest 不再符合目前定義;依 reprepare 重新準備,不重播舊預覽。
{
  "detail": [
    {
      "type": "ct.definition_stale",
      "loc": [],
      "msg": "The frozen request is no longer valid.",
      "retry_advice": "reprepare",
      "operation_id": null
    }
  ]
}
GET
路由範本/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_idpathuuid是伺服器回傳、位於此精確範圍的凍結 selection UUID。
limitqueryint (1–100)否Selection 每頁筆數:1–100,預設 100。
cursorquerystring (1–512)否選用的不透明 selection cursor,1–512 字元;不可跨 selection 重用。

回應結構

CtSelectionResponse
欄位型別必填說明
content_digeststring是未指定
created_atstring (date-time)是未指定
expires_atstring (date-time)是未指定
manifest_refstring (uuid)是未指定
next_cursorstring | null是未指定
query_digeststring是未指定
recordsCtExpectedVersion[]是未指定
row_countinteger是未指定
selection_idstring (uuid)是未指定

範例

讀取 selection 第一頁

請求
{
  "pathParams": {
    "chatroom_id": "11111111-1111-4111-8111-111111111111",
    "selection_id": "12121212-1212-4212-8212-121212121212"
  },
  "query": {
    "limit": "100"
  }
}
回應200
{
  "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 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
403目前 Command 權限拒絕;凍結服務將 body 正規化為 Not found,不揭露私有中繼資料。
{
  "detail": "Not found"
}
404Command/proposal/operation/selection 不存在、不在此範圍,或此 principal 不可見;body 為 Not found。
{
  "detail": "Not found"
}
POST
路由範本/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_enabledboolean否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.
definitionCommandDefinition-Input是Typed inputs and ordered multi-table write steps.
descriptionstring否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.
namestring是Command display name; unique within its scope.
tag_idstring是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_enabledboolean否Whether the chatroom agent may see and invoke this command.
authority_owner_idstring | null否G07a server-derived owner of a definition-authority revision.
chatroom_idstring | null否Chatroom scope id
command_digeststring | null否未指定
company_idstring | null否Company scope id
created_atstring (date-time)是未指定
created_bystring是未指定
definitionCommandDefinition-Output | null否Full definition. Omitted (with definition_redacted=true) for an execute-only caller of a restricted Command, who sees only its interface.
definition_redactedboolean否G07a: true when the caller may invoke this restricted Command but not manage its tables; `interface` then replaces `definition`.
department_idstring | null否Department scope id
descriptionstring否未指定
execute_policy"open" | "restricted"否G07a invocation policy (see CommandCreateRequest).
idstring是Command UUID
interfaceCommandInterface | null否Inputs and output names only (execute-only view).
is_deletedboolean否Soft-delete marker
lifecycle_redactedboolean否True when callback and lifecycle-trigger configuration is omitted because the caller can execute but cannot manage every target table.
namestring是未指定
tag_idstring | null否未指定
updated_atstring (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"
            ]
          }
        }
      }
    }
  }
}
回應200
{
  "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 佔位符。

狀態發生條件錯誤本文
400definition 未通過建置驗證;errors 會列出所有問題,例如 callback URL 的主機解析到私有、loopback 或 link-local 位址。
{
  "detail": {
    "error": "invalid_command_definition",
    "errors": [
      "callback 'notify': webhook_url host resolves to a private/loopback/link-local address; blocked (set WEBHOOK_ALLOW_PRIVATE_IPS=true on self-hosted deployments)"
    ]
  }
}
400callback 指定的 `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 檢查。
{
  "detail": {
    "error": "invalid_command_definition",
    "errors": [
      "callback 'notify': api tool not found"
    ]
  }
}
400`definition.examples` 其中一筆沒有通過這個定義自己的 input 驗證。`index` 是位置、`example` 是名稱、`message` 是引擎原因的前 512 字元——與 `/execute` 執行的是同一套驗證。
{
  "detail": {
    "error": "command_example_invalid",
    "example": "一般出貨",
    "index": 0,
    "message": "input 'quantity': expected integer, got str"
  }
}
400version 2 definition 的 step kind 是 list/object。Create、update、validate 回靜態 type/loc/msg issue,不含 input 或 ctx;其他 request 驗證維持 422。
{
  "detail": [
    {
      "type": "command_step_kind_type",
      "loc": [
        "body",
        "definition"
      ],
      "msg": "Command version 2 step kind must be a scalar"
    }
  ]
}
403請求使用 UserAPIKey 而非 JWT 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
403Definition 帶有 `attachment_copies`(後端 5.22.0),而儲存的使用者並非同時符合下列條件的帳號:存活、不是服務帳號,且讀得到 grant 的來源欄位並寫得到其目的欄位。這項檢查在「管理每一張被引用資料表」的門檻之後執行,所以沒有管理每一張被引用資料表的使用者會先得到一般的 `404`(建立時是 `Referenced table not found in this scope`);validate 則把同一個 `403` 放進 `errors[]`。
{
  "detail": {
    "error": "attachment_copy_grant_forbidden",
    "path": "attachment_copies[0]",
    "message": "the grant's author must be able to read its source column and write its destination column"
  }
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
404步驟引用了此範圍內不存在的資料表,或呼叫者無權管理的資料表;兩種情況回傳相同內容。
{
  "detail": "Referenced table not found in this scope"
}
404只會在競態時出現:tag 通過了編譯時的檢查,但在 POST/PUT 加鎖重新檢查時已不存在於此範圍。未知的 tag 通常會改回 422 author 階段的 command_tables_outside_tag 信封。
{
  "detail": "Tag not found in this scope"
}
409此範圍內已有同名的活指令。自 2026-07-28 起,刪除的指令會立即釋放名字,所以只有活的持有者會撞名。
{
  "detail": {
    "error": "command_name_conflict",
    "name": "建立訂單並扣庫存"
  }
}
409兩筆編寫寫入爭用同一個指令範圍,InnoDB 中止了這一筆。交易已回滾,沒有任何指令被建立——直接重送請求即可。
{
  "detail": "Concurrent write conflict (lock); please retry the request."
}
409無法鎖定範圍根節點(聊天室、部門或公司那一列),因為它不存在或已被軟刪除,例如在請求途中被刪除。沒有寫入任何資料。建立、更新與刪除都會先取得這把鎖,才動到指令本身。
{
  "detail": {
    "error": "command_scope_unavailable"
  }
}
422編譯時的 tag 檢查失敗:tag_id 在此範圍內找不到對應的 tag,或編譯後程式(直接或遞移地)依賴的某張資料表沒有掛在這個 tag 上。這個 author 階段信封不帶 tag_id 或 table_ids;請修正 tag 的資料表集合或定義。
{
  "detail": {
    "error": "command_tables_outside_tag",
    "phase": "author",
    "message": "Command dependencies must belong to its tag",
    "retryable": false,
    "details": {}
  }
}
422只會在競態時出現:某張引用的資料表在編譯時檢查與加鎖重新檢查之間離開了 tag_id;table_ids 會列出不符合的資料表。編譯時的檢查本身回的是 author 階段信封,不帶 tag_id 或 table_ids。
{
  "detail": {
    "error": "command_tables_outside_tag",
    "tag_id": "33333333-3333-4333-8333-333333333333",
    "table_ids": [
      "55555555-5555-4555-8555-555555555555"
    ]
  }
}
422編譯後的定義完全沒有引用任何自訂資料表——只有 let、只有 callback,或根本沒有 step。在這道閘門之前,同樣的 body 會回 200 並存下一支讓所有權限檢查都視為無害的指令。
{
  "detail": {
    "error": "command_references_no_table",
    "message": "a command must reference at least one custom table in this scope"
  }
}
422effect_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);不會儲存任何東西。
{
  "detail": {
    "error": "effect_identity_invalid",
    "phase": "author",
    "path": "effect_identity.key[0]",
    "message": "effect_identity key terms cannot reference $input: stable external-id inputs require a service-principal invoker, which this build does not support",
    "retryable": false,
    "details": {}
  }
}
422Definition 宣告了保留給 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 不受影響。
{
  "detail": {
    "error": "command_output_reserved",
    "phase": "author",
    "message": "approval_provenance is reserved for kernel-owned provenance",
    "retryable": false,
    "details": {},
    "path": "outputs.approval_provenance"
  }
}
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 個節點)。
{
  "detail": {
    "error": "command_public_value_unsafe",
    "phase": "author",
    "message": "Public item 'outputs.lines' has an unsafe source: whole-row output",
    "retryable": false,
    "details": {},
    "path": "outputs.lines"
  }
}
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`。
{
  "detail": {
    "error": "ambiguous_reference",
    "phase": "author",
    "message": "Table reference is ambiguous; use @table:<id>",
    "retryable": false,
    "details": {}
  }
}
422Callback 或生命週期 trigger 的形狀規則沒有通過 request 驗證(URL 形式、header、body 大小、url 與 api_tool_id 擇一、名稱不可重複、指向已知的 callback 名稱)。Validate 也會回同樣的 422,而不是報告。
{
  "detail": [
    {
      "type": "value_error",
      "loc": [
        "body",
        "definition",
        "callbacks",
        0,
        "url"
      ],
      "msg": "Value error, callback url must be an absolute http(s) URL"
    }
  ]
}
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` 指出被違反的規則。
{
  "detail": {
    "error": "invalid_expression",
    "phase": "author",
    "message": "source.step must name a select step of this Command",
    "retryable": false,
    "details": {},
    "path": "attachment_copies[0]"
  }
}
422definition 設了 `authority: "definition"`,但這個 command 不是 `execute_policy: "restricted"`(PUT 時省略 policy 會保留目前的值)。
{
  "detail": {
    "error": "definition_requires_restricted",
    "message": "authority `definition` requires execute_policy `restricted` (grants decide who may invoke it)"
  }
}
422`table_access` 沒有恰好指名 definition 引用的資料表;`missing_table_ids` 與 `extra_table_ids` 列出差異。只有建立、更新與 `/validate` 會做這項檢查,IaC apply 不會。
{
  "detail": {
    "error": "definition_ceiling_invalid",
    "message": "table_access must name exactly the tables this command references",
    "missing_table_ids": [
      "55555555-5555-4555-8555-555555555555"
    ],
    "extra_table_ids": []
  }
}
GET
路由範本/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

參數

欄位位置型別必填說明
skipqueryint >= 0否略過前幾筆可見指令,預設為 0。
limitqueryint 1..200否最多回傳幾筆指令,可填 1 到 200,預設為 50。
namequerystring 1..64否指令的完整名稱,1 到 64 個字元。回傳名稱相符且你看得到的那支指令,沒有就回空的 items。想取得單一支指令的定義、又不想列出整個範圍時使用。

回應結構

CommandListResponse
欄位型別必填說明
itemsCommandResponse[]否未指定
totalinteger是未指定

範例

列出聊天室中第一頁指令

請求
{
  "pathParams": {
    "chatroom_id": "11111111-1111-4111-8111-111111111111"
  },
  "query": {
    "skip": "0",
    "limit": "20"
  }
}
回應200
{
  "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": "建立訂單並扣庫存"
  }
}
回應200
{
  "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 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
GET
路由範本/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

參數

欄位位置型別必填說明
skipqueryint >= 0否略過前幾筆可見指令,預設為 0。
limitqueryint 1..200否最多回傳幾筆指令,可填 1 到 200,預設為 50。
namequerystring 1..64否指令的完整名稱,1 到 64 個字元。回傳名稱相符且你看得到的那支指令,沒有就回空的 items。想取得單一支指令的定義、又不想列出整個範圍時使用。

回應結構

CommandSummaryListResponse
欄位型別必填說明
itemsCommandSummaryResponse[]否未指定
totalinteger是未指定

範例

列出前 50 支指令的摘要

請求
{
  "pathParams": {
    "chatroom_id": "11111111-1111-4111-8111-111111111111"
  },
  "query": {
    "skip": "0",
    "limit": "50"
  }
}
回應200
{
  "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 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
GET
路由範本/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_idpathstring 1..36是要讀取的指令 UUID。

回應結構

CommandResponse
欄位型別必填說明
agent_enabledboolean否Whether the chatroom agent may see and invoke this command.
authority_owner_idstring | null否G07a server-derived owner of a definition-authority revision.
chatroom_idstring | null否Chatroom scope id
command_digeststring | null否未指定
company_idstring | null否Company scope id
created_atstring (date-time)是未指定
created_bystring是未指定
definitionCommandDefinition-Output | null否Full definition. Omitted (with definition_redacted=true) for an execute-only caller of a restricted Command, who sees only its interface.
definition_redactedboolean否G07a: true when the caller may invoke this restricted Command but not manage its tables; `interface` then replaces `definition`.
department_idstring | null否Department scope id
descriptionstring否未指定
execute_policy"open" | "restricted"否G07a invocation policy (see CommandCreateRequest).
idstring是Command UUID
interfaceCommandInterface | null否Inputs and output names only (execute-only view).
is_deletedboolean否Soft-delete marker
lifecycle_redactedboolean否True when callback and lifecycle-trigger configuration is omitted because the caller can execute but cannot manage every target table.
namestring是未指定
tag_idstring | null否未指定
updated_atstring (date-time)是未指定
visibility"public" | "private"否未指定

範例

在呼叫前讀取指令定義

請求
{
  "pathParams": {
    "chatroom_id": "11111111-1111-4111-8111-111111111111",
    "command_id": "22222222-2222-4222-8222-222222222222"
  }
}
回應200
{
  "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 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
404指令不存在於此範圍、已被軟刪除、沒有引用任何資料表或引用了呼叫者無法讀取的資料表、它的 tag 已不再涵蓋每一張引用的資料表,或指令是 private 且呼叫者既不是建立者、也不管理全部被引用資料表。`restricted` 指令不看資料表可讀性與 `visibility`:只有既不管理全部被引用資料表、也沒有 execute grant 的呼叫者才會得到 404。
{
  "detail": "Command not found"
}
PUT
路由範本/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_idpathstring 1..36是要取代的指令 UUID。

請求結構

CommandUpdateRequest
欄位型別必填說明
agent_enabledboolean否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.
definitionCommandDefinition-Input是Typed inputs and ordered multi-table write steps.
descriptionstring否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.
namestring是Command display name; unique within its scope.
tag_idstring是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_enabledboolean否Whether the chatroom agent may see and invoke this command.
authority_owner_idstring | null否G07a server-derived owner of a definition-authority revision.
chatroom_idstring | null否Chatroom scope id
command_digeststring | null否未指定
company_idstring | null否Company scope id
created_atstring (date-time)是未指定
created_bystring是未指定
definitionCommandDefinition-Output | null否Full definition. Omitted (with definition_redacted=true) for an execute-only caller of a restricted Command, who sees only its interface.
definition_redactedboolean否G07a: true when the caller may invoke this restricted Command but not manage its tables; `interface` then replaces `definition`.
department_idstring | null否Department scope id
descriptionstring否未指定
execute_policy"open" | "restricted"否G07a invocation policy (see CommandCreateRequest).
idstring是Command UUID
interfaceCommandInterface | null否Inputs and output names only (execute-only view).
is_deletedboolean否Soft-delete marker
lifecycle_redactedboolean否True when callback and lifecycle-trigger configuration is omitted because the caller can execute but cannot manage every target table.
namestring是未指定
tag_idstring | null否未指定
updated_atstring (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"
            ]
          }
        }
      }
    }
  }
}
回應200
{
  "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 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
403Definition 帶有 `attachment_copies`(後端 5.22.0),而儲存的使用者並非同時符合下列條件的帳號:存活、不是服務帳號,且讀得到 grant 的來源欄位並寫得到其目的欄位。這項檢查在「管理每一張被引用資料表」的門檻之後執行,所以沒有管理每一張被引用資料表的使用者會先得到一般的 `404`(建立時是 `Referenced table not found in this scope`);validate 則把同一個 `403` 放進 `errors[]`。
{
  "detail": {
    "error": "attachment_copy_grant_forbidden",
    "path": "attachment_copies[0]",
    "message": "the grant's author must be able to read its source column and write its destination column"
  }
}
404指令不存在於此範圍,或呼叫者無法管理它目前引用的每一張資料表。
{
  "detail": "Command not found"
}
400version 2 definition 的 step kind 是 list/object。Create、update、validate 回靜態 type/loc/msg issue,不含 input 或 ctx;其他 request 驗證維持 422。
{
  "detail": [
    {
      "type": "command_step_kind_type",
      "loc": [
        "body",
        "definition"
      ],
      "msg": "Command version 2 step kind must be a scalar"
    }
  ]
}
409這支指令仍有執行被暫存待覆核;staged_change_ids 會列出這些暫存變更。
{
  "detail": {
    "error": "pending_staged_executions",
    "staged_change_ids": [
      "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"
    ]
  }
}
409此範圍內已有其他活指令使用這個新名稱。刪除的指令不再佔用名字。
{
  "detail": {
    "error": "command_name_conflict",
    "name": "建立訂單並扣庫存"
  }
}
409有資料表觸發器會呼叫這支指令,因此不能在 write 與 query 之間切換執行模式。
{
  "detail": {
    "error": "command_mode_trigger_conflict",
    "command_id": "22222222-2222-4222-8222-222222222222",
    "from_mode": "write",
    "to_mode": "query",
    "message": "this command is invoked by a table trigger; its execution mode cannot be changed while the trigger references it"
  }
}
409有資料表觸發器會呼叫這支指令,因此新定義不得新增對需覆核資料表的引用。
{
  "detail": {
    "error": "approval_trigger_conflict",
    "command_id": "22222222-2222-4222-8222-222222222222",
    "target_table_id": "44444444-4444-4444-8444-444444444444",
    "target_table_name": "訂單",
    "rule_id": "rule_3f9a1c2b7d4e",
    "message": "this command is invoked by a table trigger; it cannot add a reference to a table that requires approval"
  }
}
409該指令已記錄過業務事件,而這次 PUT 變更或移除了它的 effect identity(namespace、kind、literal term,或各 row term 所讀的資料表與 metadata)。回應是 author 階段的 program error `effect_identity_locked`。其他編輯仍然允許。
{
  "detail": {
    "error": "effect_identity_locked",
    "phase": "author",
    "step": null,
    "path": "effect_identity",
    "message": "This Command has recorded business events; its effect identity (namespace, kind, key terms and their source rows) cannot change or be removed",
    "retryable": false,
    "details": {}
  }
}
409兩筆編寫寫入爭用同一個指令範圍,InnoDB 中止了這一筆。交易已回滾,已儲存的定義維持不變——直接重送請求即可。
{
  "detail": "Concurrent write conflict (lock); please retry the request."
}
409無法鎖定範圍根節點(聊天室、部門或公司那一列),因為它不存在或已被軟刪除,例如在請求途中被刪除。沒有寫入任何資料。建立、更新與刪除都會先取得這把鎖,才動到指令本身。
{
  "detail": {
    "error": "command_scope_unavailable"
  }
}
422編譯時的 tag 檢查失敗:tag_id 在此範圍內找不到對應的 tag,或編譯後程式(直接或遞移地)依賴的某張資料表沒有掛在這個 tag 上。這個 author 階段信封不帶 tag_id 或 table_ids;請修正 tag 的資料表集合或定義。
{
  "detail": {
    "error": "command_tables_outside_tag",
    "phase": "author",
    "message": "Command dependencies must belong to its tag",
    "retryable": false,
    "details": {}
  }
}
422只會在競態時出現:新定義引用的某張資料表在編譯時檢查與加鎖重新檢查之間離開了 tag_id;table_ids 會列出不符合的資料表。
{
  "detail": {
    "error": "command_tables_outside_tag",
    "tag_id": "33333333-3333-4333-8333-333333333333",
    "table_ids": [
      "55555555-5555-4555-8555-555555555555"
    ]
  }
}
422新的定義編譯後資料表集合是空的。這也是既有零資料表指令無法原地修好的原因——除非新定義引用了資料表,否則 PUT 對新舊定義一視同仁地拒絕。
{
  "detail": {
    "error": "command_references_no_table",
    "message": "a command must reference at least one custom table in this scope"
  }
}
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` 指出被違反的規則。
{
  "detail": {
    "error": "invalid_expression",
    "phase": "author",
    "message": "source.step must name a select step of this Command",
    "retryable": false,
    "details": {},
    "path": "attachment_copies[0]"
  }
}
422Definition 宣告了保留給 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 不受影響。
{
  "detail": {
    "error": "command_output_reserved",
    "phase": "author",
    "message": "approval_provenance is reserved for kernel-owned provenance",
    "retryable": false,
    "details": {},
    "path": "outputs.approval_provenance"
  }
}
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 個節點)。
{
  "detail": {
    "error": "command_public_value_unsafe",
    "phase": "author",
    "message": "Public item 'outputs.lines' has an unsafe source: whole-row output",
    "retryable": false,
    "details": {},
    "path": "outputs.lines"
  }
}
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`。
{
  "detail": {
    "error": "ambiguous_reference",
    "phase": "author",
    "message": "Table reference is ambiguous; use @table:<id>",
    "retryable": false,
    "details": {}
  }
}
422Callback 或生命週期 trigger 的形狀規則沒有通過 request 驗證(URL 形式、header、body 大小、url 與 api_tool_id 擇一、名稱不可重複、指向已知的 callback 名稱)。Validate 也會回同樣的 422,而不是報告。
{
  "detail": [
    {
      "type": "value_error",
      "loc": [
        "body",
        "definition",
        "callbacks",
        0,
        "url"
      ],
      "msg": "Value error, callback url must be an absolute http(s) URL"
    }
  ]
}
422definition 設了 `authority: "definition"`,但這個 command 不是 `execute_policy: "restricted"`(PUT 時省略 policy 會保留目前的值)。
{
  "detail": {
    "error": "definition_requires_restricted",
    "message": "authority `definition` requires execute_policy `restricted` (grants decide who may invoke it)"
  }
}
422`table_access` 沒有恰好指名 definition 引用的資料表;`missing_table_ids` 與 `extra_table_ids` 列出差異。只有建立、更新與 `/validate` 會做這項檢查,IaC apply 不會。
{
  "detail": {
    "error": "definition_ceiling_invalid",
    "message": "table_access must name exactly the tables this command references",
    "missing_table_ids": [
      "55555555-5555-4555-8555-555555555555"
    ],
    "extra_table_ids": []
  }
}
DELETE
路由範本/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_idpathstring 1..36是要刪除的指令 UUID。

回應結構

CommandResponse
欄位型別必填說明
agent_enabledboolean否Whether the chatroom agent may see and invoke this command.
authority_owner_idstring | null否G07a server-derived owner of a definition-authority revision.
chatroom_idstring | null否Chatroom scope id
command_digeststring | null否未指定
company_idstring | null否Company scope id
created_atstring (date-time)是未指定
created_bystring是未指定
definitionCommandDefinition-Output | null否Full definition. Omitted (with definition_redacted=true) for an execute-only caller of a restricted Command, who sees only its interface.
definition_redactedboolean否G07a: true when the caller may invoke this restricted Command but not manage its tables; `interface` then replaces `definition`.
department_idstring | null否Department scope id
descriptionstring否未指定
execute_policy"open" | "restricted"否G07a invocation policy (see CommandCreateRequest).
idstring是Command UUID
interfaceCommandInterface | null否Inputs and output names only (execute-only view).
is_deletedboolean否Soft-delete marker
lifecycle_redactedboolean否True when callback and lifecycle-trigger configuration is omitted because the caller can execute but cannot manage every target table.
namestring是未指定
tag_idstring | null否未指定
updated_atstring (date-time)是未指定
visibility"public" | "private"否未指定

範例

刪除未被引用的指令

請求
{
  "pathParams": {
    "chatroom_id": "11111111-1111-4111-8111-111111111111",
    "command_id": "22222222-2222-4222-8222-222222222222"
  }
}
回應200
{
  "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 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
404指令不存在於此範圍,或呼叫者既無法管理它引用的每一張資料表、也不具備指令所屬範圍的權威(聊天室建立者/管理員、部門管理員或公司管理員)。這個範圍權威後備正是資料表被刪或被 purge 之後指令仍刪得掉的原因;兩項檢查都不過的人拿到的就是這顆不可枚舉的 404。
{
  "detail": "Command not found"
}
409這支指令仍有執行被暫存待覆核。
{
  "detail": {
    "error": "pending_staged_executions",
    "staged_change_ids": [
      "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"
    ]
  }
}
409仍有資料表觸發器帶著指向這支指令的 invoke_command 動作;references 會列出每張資料表與觸發器。
{
  "detail": {
    "error": "command_referenced_by_trigger",
    "command_id": "22222222-2222-4222-8222-222222222222",
    "references": [
      {
        "table_id": "44444444-4444-4444-8444-444444444444",
        "table_name": "訂單",
        "trigger_id": "trg_5b8e2d9c4a1f",
        "trigger_name": "訂單建立後記錄"
      }
    ]
  }
}
409兩筆編寫寫入爭用同一個指令範圍,InnoDB 中止了這一筆。交易已回滾,指令仍然存在——直接重送請求即可。
{
  "detail": "Concurrent write conflict (lock); please retry the request."
}
409無法鎖定範圍根節點(聊天室、部門或公司那一列),因為它不存在或已被軟刪除,例如在請求途中被刪除。沒有寫入任何資料。建立、更新與刪除都會先取得這把鎖,才動到指令本身。
{
  "detail": {
    "error": "command_scope_unavailable"
  }
}
GET
路由範本/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_idpathstring 1..36是要管理其 execute grant 的受限 command 的 id。

回應結構

CommandExecuteGrantList
欄位型別必填說明
itemsCommandExecuteGrantResponse[]否未指定
totalinteger是未指定

範例

列出一個受限 command 的 grant

請求
{
  "pathParams": {
    "chatroom_id": "11111111-1111-4111-8111-111111111111",
    "command_id": "22222222-2222-4222-8222-222222222222"
  }
}
回應200
{
  "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 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
404這個 scope 內沒有這個 command,或呼叫者沒有管理它引用的每一張資料表(統一的 command 404)。
{
  "detail": "Command not found"
}
POST
路由範本/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_idpathstring 1..36是要管理其 execute grant 的受限 command 的 id。

請求結構

CommandExecuteGrantCreate
欄位型別必填說明
principal_idstring是未指定
principal_type"user" | "department" | "chatroom" | "client"是user | department (live membership) | chatroom (acting room / live member) | client

回應結構

CommandExecuteGrantResponse
欄位型別必填說明
command_idstring是未指定
created_atstring (date-time) | null否未指定
created_bystring | null否未指定
idstring是未指定
principal_idstring是未指定
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"
  }
}
回應200
{
  "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 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
404這個 scope 內沒有這個 command,或呼叫者沒有管理它引用的每一張資料表(統一的 command 404)。
{
  "detail": "Command not found"
}
404這個主體不是該 command 所屬公司中存活的使用者、部門或聊天室,也不是房間屬於該公司的既有 client。回應是統一的,所以 grant 請求絕不能用來探測別家公司的 id。
{
  "detail": "Grant principal not found"
}
422Request 驗證失敗:`principal_type` 必須是 `user`、`department`、`chatroom` 或 `client`,`principal_id` 為 1–36 個字元。
{
  "detail": [
    {
      "type": "literal_error",
      "loc": [
        "body",
        "principal_type"
      ],
      "msg": "Input should be 'user', 'department', 'chatroom' or 'client'",
      "input": "team",
      "ctx": {
        "expected": "'user', 'department', 'chatroom' or 'client'"
      }
    }
  ]
}
DELETE
路由範本/private/module/custom_tables/{scopeWithId}/commands/{command_id}/grants/{grant_id}

撤銷 execute grant

用途

刪除一個 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_idpathstring 1..36是要管理其 execute grant 的受限 command 的 id。
grant_idpathstring 1..36是要撤銷的 execute grant 的 id。

回應結構

CommandExecuteGrantResponse
欄位型別必填說明
command_idstring是未指定
created_atstring (date-time) | null否未指定
created_bystring | null否未指定
idstring是未指定
principal_idstring是未指定
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"
  }
}
回應200
{
  "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 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
404這個 scope 內沒有這個 command,或呼叫者沒有管理它引用的每一張資料表(統一的 command 404)。
{
  "detail": "Command not found"
}
404這個 grant id 不屬於這個 command。
{
  "detail": "Grant not found"
}
POST
路由範本/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_idpathstring 1..36是要執行的指令 UUID。
dbqueryint否Redis 邏輯資料庫編號,因共用 client 相依而外洩到 schema 上。它是內部旋鈕,不屬於指令契約,請不要填。
dry_runqueryboolean否設為 true 可預覽版本 2 的 command:它執行與真正呼叫相同的驗證、授權與規則檢查、之後整個回滾,並以 status "preview" 回傳會產生的 outputs。不會持久化任何東西:沒有資料列、history、待審項目、trigger、callback,也沒有 idempotency 保留。

請求結構

CommandExecuteRequest
欄位型別必填說明
expected_contract_digeststring | 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_keystring | null否Actor-bucketed idempotency key; failed executions do not block retry.
inputsobject是Values for the command's declared inputs; server context is excluded.
reasonstring | 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_snapshotobject | null否Immutable command scope, tag, and referenced-table audit facts.
command_idstring | null否未指定
created_atstring (date-time) | null否未指定
duration_msinteger | null否未指定
errorstring | null否未指定
executed_atstring (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_idstring是未指定
idempotency_keystring | null否未指定
input_snapshotobject | null否未指定
invoker_client_idstring | null否未指定
invoker_idstring | null否未指定
outputsobject | null否Declared version-2 scalar or bounded relation outputs. Internal temporary relations and step rows are never exposed.
redactedboolean | 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_outputsstring[] | 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.
replayedboolean | 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_bodyobject | null否未指定
result_refsobject[] | null否未指定
staged_change_idstring | null否未指定
status"succeeded" | "staged" | "failed"是未指定
steps(CommandExecutionV1StepResponse | CommandExecutionV2StepResponse)[]否未指定
truncatedboolean | 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"
  }
}
回應200
{
  "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"
  }
}
回應200
{
  "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 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
400這次執行展開後會超過 1000 筆 row action 上限;actual 是觸發上限的實際數量。
{
  "detail": {
    "error": "cap_exceeded",
    "cap": 1000,
    "actual": 1240
  }
}
400對版本 1 的 command 使用 dry_run;預覽需要版本 2 的定義。
{
  "detail": "Preview requires a version-2 Command."
}
400decimal input 不是符合宣告精度與小數位數的純十進位字串:送了 JSON 數字、位數過多、allow_negative 為 false 時送了負數,或送了 -0。
{
  "detail": "input 'unit_price': invalid decimal value"
}
400identity 型別的 input 指向的使用者或社群媒體 client 不屬於此範圍。
{
  "detail": {
    "error": "identity_not_associated",
    "input": "assignee"
  }
}
400版本 1 的 update 或 delete 步驟把選填 input 綁在 match 條件裡,而呼叫者沒有送這個值。現在整次執行失敗,而不是把那個 key 丟掉:丟掉會放寬選列條件,打到建置者從未鎖定的資料列。
{
  "detail": "step 'consume': optional input cannot omit a match key"
}
400被省略的選填 input 出現在 $case 裡——某個分支的 then,或 else。所有分支都必須綁得出值,因為分支要更晚才會對著鎖定的前影像挑選,所以「這個分支綁不出來」只能是錯誤,不能靠猜。
{
  "detail": "step 'order': optional input cannot omit a $case result"
}
400步驟中的資料列寫入沒有通過 record core 自己的 input 驗證——未知或無法存取的 attachment id,或該 scope 沒有的 principal id。detail 就是 core 的原始訊息;v5.10.11 之前這裡會被收斂成「Command execution failed」。
{
  "detail": "Unknown or inaccessible attachment ids: ['aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa']"
}
413某個步驟的 insert/update 會讓序列化後的 `data` 超過 `CUSTOM_TABLE_MAX_RECORD_BYTES`(預設 1 MiB)。需審核的寫入會在送進暫存前就檢查,所以過大的受審寫入會當場被拒,而不是通過審核後才失敗。
{
  "detail": {
    "error": "record_too_large",
    "message": "Record data is 1310720 bytes; the limit is 1048576 bytes",
    "limit": 1048576,
    "size": 1310720
  }
}
403版本 2 程式從呼叫者無法讀取的資料表 select 或 join。過去 row ACL 會把它變成永不匹配的條件,該步驟回傳空 relation,於是「先查再寫」的去重步驟查不到東西,就寫進了一筆重複資料。
{
  "detail": "Read access not granted for this table."
}
403程式有 insert 步驟,而該資料表解析出的 can_insert 是 false。這道檢查與讀取閘門在 `_prepare_program_access` 的同一輪完成——在任何 action core 能 flush 之前——所以不會留下半筆資料。
{
  "detail": "Insert access not granted for this table."
}
403程式有 update 或 delete 步驟,而該資料表解析出的 can_edit 是 "none"。與 insert 同一輪;filtered 或 own 授權在這裡會通過,改在稍後 ACL 範圍內的 match 查詢之後逐列把關。
{
  "detail": "Edit access not granted for this table."
}
403程式要寫入的某張 channel 治理資料表,替你解析不出 scope。REST 不綁 acting room,floor 改為只依執行者逐表解析,因此訊息是「不帶房間」的版本——你名下沒有任何房間在那張表上宣告 scope_values,或候選房間不只一個而無法解析。`table_id` 指出被拒的資料表,`rule_id` 指出讓它進入治理的 channel 規則。
{
  "detail": {
    "error": "scp_scope_undeclared",
    "message": "no chatroom of yours declares a scope on this table",
    "table_id": "55555555-5555-4555-8555-555555555555",
    "rule_id": "rule_3f9a1c2b7d4e"
  }
}
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` 判別,不要看訊息字串。
{
  "detail": {
    "error": "scp_out_of_scope",
    "message": "this write targets records outside your channel's scope",
    "table_id": "55555555-5555-4555-8555-555555555555",
    "rule_id": "rule_3f9a1c2b7d4e"
  }
}
403B10 歸因。insert 或 update 的連結值指向一筆確實存在、也通過你的 row ACL 的目標,只有目標資料表的 channel scope 把它藏起來——所以這條通道直接說出 floor 的 code,而不是收斂成統一的 404。在這個分支存在之前,同樣的 v2 寫入會掉進一般性的 500 `command_execution_failed`。當 floor 的判定是那樣時,`scp_scope_undeclared` 與 `scp_rule_dangling` 也會從這裡冒出來;這個形狀沒有 `rule_id`。
{
  "detail": {
    "error": "scp_out_of_scope",
    "message": "link target(s) 88888888-8888-4888-8888-888888888888: the link target exists but is outside your channel scope",
    "table_id": "44444444-4444-4444-8444-444444444444"
  }
}
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。
{
  "detail": {
    "error": "column_not_readable",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "Select references a column hidden from this invoker",
    "retryable": false,
    "details": {}
  }
}
403即時重新載入 principal 失敗:呼叫者已不是這家公司中啟用且已驗證的使用者,或已不是路徑上聊天室的成員。這個 body 刻意保持一致,不透露原因。
{
  "detail": "Command principal is not authorized"
}
403Principal 透過的房間——chatroom 掛載點上是路徑的聊天室,agent 通道上是工作階段的房間——在取得權限鎖之後發現已被刪除、移到其他公司或部門,或成員資格已不存在;外部 principal 則是沒有存活的 acting room,或其 client 已換了房間。沒有寫入任何資料。
{
  "detail": "Command principal is invalid"
}
403業務事件已經記錄,但該 command 的某個 update 或 delete step 所指向的資料表上,這位呼叫者的編輯權限是 own 或 filtered(不是 all,且不是管理者),或其任一寫入 step 所指向的資料表落在呼叫者的 SCP 房間範圍內,所以無法重新證明 replay。不寫入任何東西,也不揭露先前的 execution id 或結果;重試會得到相同答案。
{
  "detail": {
    "error": "ct.effect_identity_replay_forbidden",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "This business event was already recorded, and a row-scoped right cannot re-prove access to its rows",
    "retryable": false,
    "details": {}
  }
}
403這個 command 是 `restricted`,而呼叫者既不管理全部被引用的資料表,也沒有有效的 execute grant。它是帶 `phase: "execute"` 的 program-error 信封;MCP 工具改回 `not_found`。
{
  "detail": {
    "error": "command_execute_denied",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "This Command is restricted and the caller holds no execute grant",
    "retryable": false,
    "details": {}
  }
}
403owner 已不再有效的 `definition` authority command 會 fail closed:owner 不再存活、屬於其他公司、是服務帳號,或不再是每一張 `table_access` 資料表的 manager。必須由管理每一張資料表的 manager 重新儲存這個 command。
{
  "detail": {
    "error": "definition_authority_invalid",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "The Command's definer authority is no longer valid; a manager of every table must reauthor it",
    "retryable": false,
    "details": {}
  }
}
403command 引用的某張資料表在 `table_access` ceiling 中沒有項目,所以每次執行都會 fail closed,直到 manager 補上項目並重新儲存。
{
  "detail": {
    "error": "definition_ceiling_missing",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "A referenced table has no definer ceiling entry",
    "retryable": false,
    "details": {}
  }
}
404指令存在於此範圍,但沒有引用任何資料表(沒有存活指令的 id 則是下方遮蔽過的 `command_failed` 404)或引用了呼叫者無法讀取的資料表、它的 tag 已不再涵蓋每一張引用的資料表,或指令是 private 且呼叫者既不是建立者、也不管理全部被引用資料表。呼叫者無權執行的 `restricted` 指令會回 403 `command_execute_denied`,不是 404。
{
  "detail": "Command not found"
}
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`)。
{
  "detail": {
    "error": "command_failed",
    "message": "The command could not be completed.",
    "redacted": true
  }
}
404連結值指向的目標不存在、已軟刪除,或被 row ACL 對你藏起來。三種情況共用同一個 body,所以這條路由不會開出 id 列舉的破口。注意:這個 404 與同一路由另一個代表「Command not found」的 404 撞在一起——請讀 detail 字串,不要只看狀態碼。
{
  "detail": "linked record not found"
}
409require_approval 規則觸發,整份計畫被暫存而非提交。寫入是被扣住,不是遺失。
{
  "detail": {
    "error": "approval_required",
    "process_id": "cccccccc-cccc-4ccc-8ccc-cccccccccccc",
    "rule_id": "rule_3f9a1c2b7d4e",
    "rule_label": "高金額訂單需主管覆核",
    "staged_change_id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"
  }
}
409已有一筆 staged change 佔住這次執行要寫入的紀錄(或同一個建立 key),所以在那筆 staged change 結案之前寫入會被擋下。這次執行沒有暫存任何東西。
{
  "detail": {
    "error": "approval_pending",
    "process_id": "cccccccc-cccc-4ccc-8ccc-cccccccccccc",
    "staged_change_id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"
  }
}
409符合的 require_approval rule 指向的 Review 範本不存在、已被軟刪除或屬於其他公司,因此無法啟動審批流程。沒有暫存任何東西;請修正 rule 的範本。
{
  "detail": {
    "error": "approval_template_unusable",
    "rule_id": "rule_3f9a1c2b7d4e",
    "rule_label": "高金額訂單需主管覆核",
    "template_id": "ffffffff-ffff-4fff-8fff-ffffffffffff"
  }
}
409選填的 expected_contract_digest 不符合 command 的即時定義。檢查發生在 command input 驗證、idempotency 保留、建立稽核、row lock 或異動之前,因此沒有資料被改動,也不可原樣重試這份已過期的釘選 request。
{
  "detail": {
    "error": "command_contract_mismatch"
  }
}
409同一位執行者以相同 idempotency key 送出不同的 inputs。
{
  "detail": {
    "error": "idempotency_key_reuse"
  }
}
409另一個持有相同 key 的請求仍在執行中,等它結束後再重試。
{
  "detail": {
    "error": "idempotency_in_progress"
  }
}
409這把 idempotency key 已被另一次執行用過,而那次凍結的權限情境與這次不同——origin、acting room、principal、權限集合、指令定義或相依狀態有所不同。新的情境請用新的 key。
{
  "detail": {
    "error": "idempotency_context_conflict"
  }
}
409這支指令的 mode 是 "query",必須改用 /query 呼叫。
{
  "detail": {
    "error": "query_command_requires_query_route"
  }
}
409兩次執行爭用同一批資料列,InnoDB 中止了這一次。交易已回滾,沒有任何部分寫入落地,retryable 為 true——請用「同一把」idempotency key 重試:失敗路徑會釋放該 key 的保留,因此重用是合法的。
{
  "detail": {
    "error": "command_lock_conflict",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "Concurrent write conflict (lock); please retry the request.",
    "retryable": true,
    "details": {}
  }
}
409版本 2 指令儲存的 dependency contract 已與目前的資料表不符(schema、rules、審批設定、triggers、tag 或範圍有變動)。請重新儲存指令以重建 contract;原封不動重試會以同樣方式失敗。
{
  "detail": {
    "error": "schema_dependency_changed",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "Command schema dependencies changed",
    "retryable": false,
    "details": {}
  }
}
409版本 2 通道:指令列在驗證與套用之間被刪除,或它的範圍、tag 或定義摘要改變了。沒有寫入任何資料;請重新讀取指令後再重試。
{
  "detail": {
    "error": "source_authority_changed",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "Command authority changed before execution",
    "retryable": false,
    "details": {}
  }
}
409業務事件已經記錄,而這次執行驗證後的 inputs 或作者程式不同(details.semantic_match 為 false;details.original_execution_id 指出先前那次執行)。終局:重試或改變內容都不會成功。發生在審批 release 時,封套的 phase 為 release(重複 stage 的 inputs 與程式相同時,details.semantic_match 為 true),並讓 staged 變更以 apply_failed 結束。
{
  "detail": {
    "error": "ct.effect_identity_conflict",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "This business event was already recorded with different details; nothing was written",
    "retryable": false,
    "details": {
      "original_execution_id": "66666666-6666-4666-8666-666666666666",
      "semantic_match": false
    }
  }
}
409排除送審者(`exclude_requester`)之後,某個必要的審批 gate 沒有任何可核准者。送出會在任何 review process 出現之前就被拒絕;`gate_order` 指出該 gate。對於不管理每一張被引用資料表的 `authority: definition` command 呼叫者,只會回傳安全的 key,所以沒有 `gate_order`。
{
  "detail": {
    "error": "no_eligible_approver",
    "message": "Excluding the requester leaves a required review gate with no eligible approver",
    "gate_order": 1
  }
}
409這筆寫入需要排除送審者,但呼叫的主體沒有已驗證的人類身分(例如 service account),無法歸屬。不會暫存任何東西。
{
  "detail": {
    "error": "requester_unattributable",
    "message": "This review requires the requester to be excluded, but the submitting principal has no verified human identity"
  }
}
409`date_interval_exclusion` 規則拒絕此次寫入:`date_interval_exclusion_invalid_period`(建立時省略 end key、結束日為空字串或不晚於起始日,或 scope link 沒有恰好一個目標)、`date_interval_exclusion_overlap`(另一筆 live 資料列涵蓋了這段期間的一部分),或 `date_interval_exclusion_retry`(寫入等待期間資料表的規則被改變;沒有寫入任何東西,請重送)。body 是固定訊息,絕不會指出資料列、日期或 scope 值。
{
  "detail": {
    "error": "date_interval_exclusion_overlap",
    "message": "Date period overlaps another period in this partition."
  }
}
422版本 2 的 assert 步驟沒有得到 true。信封會指出步驟名稱與條件路徑,message 是建置者自訂的文字,而且沒有寫入任何資料。
{
  "detail": {
    "error": "assertion_failed",
    "phase": "execute",
    "message": "庫存不足,無法建立訂單",
    "retryable": false,
    "details": {},
    "step": "stock_ok",
    "path": "steps.stock_ok.condition"
  }
}
422作者標為 `public: true` 的 `assert` step 失敗,且呼叫者取得的是 status-only 結果(definer Command、未管理每一張被引用資料表的呼叫者)。Body 只保留 `error`、作者寫的 `message` 與 `redacted: true`。其他任何 `assertion_failed` 到了這種呼叫者手上都是被遮蔽的 `command_failed`。
{
  "detail": {
    "error": "assertion_failed",
    "message": "庫存不足,無法建立訂單",
    "redacted": true
  }
}
422版本 1 的 rule 失敗;rule 是規則名稱,message 是建置者自訂的文字。版本 2 的 `rules` 項目失敗時則回信封——{"error":"command_rule_failed","phase":"execute","message":…}——不帶規則名稱。
{
  "detail": {
    "error": "command_rule_failed",
    "rule": "qty_positive",
    "message": "數量必須大於 0"
  }
}
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`)。發生在任何保留、稽核列或鎖之前。
{
  "detail": [
    {
      "type": "value_error",
      "loc": [
        "body",
        "inputs"
      ],
      "msg": "Value error, command inputs exceed 1048576 bytes"
    }
  ]
}
400版本 1 通道:某個 action 的紀錄與連結閉包超過 2000 筆紀錄。link 圖需要超過 256 張資料表時,會回同一個錯誤碼,帶 `limit: 256` 且沒有 `actual`。
{
  "detail": {
    "error": "command_dependency_graph_too_large",
    "limit": 2000,
    "actual": 2143
  }
}
409版本 1 通道:連結的資料表已消失,或在取得鎖的過程中 link 或紀錄的連結關係發生變動。並行變動的情況是暫時性的——請重試同一個呼叫。
{
  "detail": {
    "error": "command_link_graph_changed"
  }
}
422版本 1:具體計畫會寫入超過一張設有 require_approval rule 的資料表,而一次執行永遠不能橫跨兩個審批對象。tables 會列出受控管的資料表。請把指令拆開。
{
  "detail": {
    "error": "multiple_approval_gated_tables",
    "tables": [
      "44444444-4444-4444-8444-444444444444",
      "55555555-5555-4555-8555-555555555555"
    ]
  }
}
422自後端 5.22.0 起:不含 child_collection step 的 Command,其中一次寫入觸發了一條核准規則,而該規則的 field_map 所讀取的來源資料表不在 Command 的 tag 內。table_ids 列出不在 tag 內的來源資料表,且沒有寫入任何東西。以前這是通用的 500 command_execution_failed。
{
  "detail": {
    "error": "command_tables_outside_tag",
    "tag_id": "33333333-3333-4333-8333-333333333333",
    "table_ids": [
      "55555555-5555-4555-8555-555555555555"
    ]
  }
}
422精確十進位的值超出界限——運算結果超過它的精度,或 SQL 係數超出 65 位數的範圍(此時信封會指出該 select 步驟與 `<step>.select` 路徑)。沒有寫入任何資料。
{
  "detail": {
    "error": "numeric_overflow",
    "phase": "execute",
    "step": null,
    "message": "Decimal expression exceeded its bound",
    "retryable": false,
    "details": {},
    "path": "steps.line_total.value"
  }
}
422精確十進位運算式在執行時無效,例如除以零或得到不是有限十進位數的值。path 會指向失敗的運算式。
{
  "detail": {
    "error": "numeric_invalid",
    "phase": "execute",
    "step": null,
    "message": "Decimal expression is invalid",
    "retryable": false,
    "details": {},
    "path": "outputs.unit_price.value"
  }
}
422`$first` 的 effect-identity key term 沒選到列,或它的 select step 被 when 略過。在 claim 時點就提出:沒有寫入任何東西,也沒有取得 claim。`$single` term 則維持 400 cardinality_failed。
{
  "detail": {
    "error": "effect_identity_unbound",
    "phase": "execute",
    "step": "stock",
    "path": null,
    "message": "The effect identity key selected no row; nothing was written",
    "retryable": false,
    "details": {}
  }
}
423版本 1 通道:計畫會碰到的某張資料表正被 schema 遷移持有。它在任何寫入之前就拋出;遷移結束後即可重試。
{
  "detail": "Table is currently locked for operation: add_column"
}
429暫存這個計畫會超過待審批的額度:每位執行者最多 500 筆待處理的 staged change,每張資料表最多 50 筆待處理的暫存新增。沒有暫存任何東西;請等審核者處理掉一些。
{
  "detail": {
    "error": "staged_cap_exceeded",
    "scope": "actor",
    "limit": 500
  }
}
500版本 2:具體計畫會寫入超過一張受審批控管的資料表。這個拒絕以 stage 階段的 command_execution_failed 拋出,而路由會把每個 command_execution_failed 都回成 500。請把指令拆開;原封不動重試會以同樣方式失敗。
{
  "detail": {
    "error": "command_execution_failed",
    "phase": "stage",
    "step": null,
    "path": null,
    "message": "A command plan spans multiple approval audiences",
    "retryable": false,
    "details": {}
  }
}
503Idempotency 儲存無法連線,為避免重複寫入而直接失敗。
{
  "detail": "Command idempotency service is unavailable"
}
504執行超過 30 秒的期限。details 為空、retryable 為 false;交易已回滾。
{
  "detail": {
    "error": "command_timeout",
    "phase": "execute",
    "message": "Command execution exceeded its deadline",
    "retryable": false,
    "details": {},
    "step": "consume",
    "path": null
  }
}
504某個 select statement 用完了單一 statement 的預算;details.limit 是該預算(單位毫秒,最多 5000),retryable 為 true。
{
  "detail": {
    "error": "query_timeout",
    "phase": "execute",
    "message": "Select statement timed out",
    "retryable": true,
    "details": {
      "limit": 5000
    },
    "step": "stock",
    "path": null
  }
}
POST
路由範本/private/module/custom_tables/{scopeWithId}/commands/{command_id}/query

執行 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_idpathstring 1..36是要執行的 query 模式指令 UUID。

請求結構

CommandQueryRequest
欄位型別必填說明
inputsobject是Values for the command's declared inputs; server context is excluded.
pageCommandQueryPage | null否Optional pagination window; defaults to the first page of 100 rows.

回應結構

CommandQueryResponse
欄位型別必填說明
next_cursorstring | null否Opaque cursor for the next page, or null when exhausted.
outputsobject否Declared scalar outputs (relation outputs are paginated via rows).
rowsobject[]否Projected rows of the paginated relation output for this page.
total_availableinteger | 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
    }
  }
}
回應200
{
  "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 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
400cursor 已無法對應這支指令的 dependency contract、inputs 或 output 名稱,通常是換頁之間 schema 或 inputs 變了。
{
  "detail": {
    "error": "cursor_binding_mismatch"
  }
}
400page.output 指的不是這支指令的 relation output。
{
  "detail": "page.output does not name a relation output"
}
400指令宣告了不只一個 relation output,卻省略了 page.output,路由無法判斷要分頁哪一個 output。
{
  "detail": "page.output is required for a command with multiple relation outputs"
}
400decimal input 不是符合宣告精度與小數位數的純十進位字串:送了 JSON 數字、位數過多、allow_negative 為 false 時送了負數,或送了 -0。
{
  "detail": "input 'unit_price': invalid decimal value"
}
422要保留的 Command pass 變更已關閉期間,卻沒有人的理由或核准釋放參照。Typed calendar 拒絕會回滾交易;範例的 rule/table/image 欄位指出被拒絕的期間變更。
{
  "detail": {
    "error": "ct.scope_override_reason_required",
    "message": "changing a closed period needs a reason: a person executes the Command with a reason, or the change is released through an approval",
    "rule_id": "rule_3f9a1c2b7d4e",
    "table_id": "44444444-4444-4444-8444-444444444444",
    "change_type": "update",
    "image": "pre"
  }
}
400identity 型別的 input 指向的使用者或社群媒體 client 不屬於此範圍。
{
  "detail": {
    "error": "identity_not_associated",
    "input": "assignee"
  }
}
403程式的 select 來源或 join 目標解析出的 can_read 是 none。在這道閘門之前,同樣的呼叫會回 200 加一個空 relation,agent 則回報「查無符合資料」。
{
  "detail": "Read access not granted for this table."
}
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。
{
  "detail": {
    "error": "column_not_readable",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "Select references a column hidden from this invoker",
    "retryable": false,
    "details": {}
  }
}
403即時重新載入 principal 失敗:呼叫者已不是這家公司中啟用且已驗證的使用者,或已不是路徑上聊天室的成員。這個 body 刻意保持一致,不透露原因。
{
  "detail": "Command principal is not authorized"
}
403Principal 透過的房間——chatroom 掛載點上是路徑的聊天室,agent 通道上是工作階段的房間——在取得權限鎖之後發現已被刪除、移到其他公司或部門,或成員資格已不存在;外部 principal 則是沒有存活的 acting room,或其 client 已換了房間。沒有寫入任何資料。
{
  "detail": "Command principal is invalid"
}
403這個 command 是 `restricted`,而呼叫者既不管理全部被引用的資料表,也沒有有效的 execute grant。它是帶 `phase: "execute"` 的 program-error 信封;MCP 工具改回 `not_found`。
{
  "detail": {
    "error": "command_execute_denied",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "This Command is restricted and the caller holds no execute grant",
    "retryable": false,
    "details": {}
  }
}
404指令不存在於此範圍、已被軟刪除、沒有引用任何資料表或引用了呼叫者無法讀取的資料表、它的 tag 已不再涵蓋每一張引用的資料表,或指令是 private 且呼叫者既不是建立者、也不管理全部被引用資料表。呼叫者無權執行的 `restricted` 指令會回 403 `command_execute_denied`,不是 404。
{
  "detail": "Command not found"
}
409這是一支寫入指令,請改用 /execute 呼叫。
{
  "detail": {
    "error": "not_a_query_command"
  }
}
409版本 2 指令儲存的 dependency contract 已與目前的資料表不符(schema、rules、審批設定、triggers、tag 或範圍有變動)。請重新儲存指令以重建 contract;原封不動重試會以同樣方式失敗。
{
  "detail": {
    "error": "schema_dependency_changed",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "Command schema dependencies changed",
    "retryable": false,
    "details": {}
  }
}
422這一頁序列化後超過 64 KiB 的輸出上限;請調小 page.limit 或縮減 output 的 select 欄位。
{
  "detail": {
    "error": "output_limit_exceeded",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "Command output exceeds its byte limit",
    "retryable": false,
    "details": {
      "actual": 71234,
      "limit": 65536,
      "resource": "output_bytes"
    }
  }
}
422精確十進位的值超出界限——運算結果超過它的精度,或 SQL 係數超出 65 位數的範圍(此時信封會指出該 select 步驟與 `<step>.select` 路徑)。沒有寫入任何資料。
{
  "detail": {
    "error": "numeric_overflow",
    "phase": "execute",
    "step": null,
    "message": "Decimal expression exceeded its bound",
    "retryable": false,
    "details": {},
    "path": "steps.line_total.value"
  }
}
422精確十進位運算式在執行時無效,例如除以零或得到不是有限十進位數的值。path 會指向失敗的運算式。
{
  "detail": {
    "error": "numeric_invalid",
    "phase": "execute",
    "step": null,
    "message": "Decimal expression is invalid",
    "retryable": false,
    "details": {},
    "path": "outputs.unit_price.value"
  }
}
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`)。發生在任何保留、稽核列或鎖之前。
{
  "detail": [
    {
      "type": "value_error",
      "loc": [
        "body",
        "inputs"
      ],
      "msg": "Value error, command inputs exceed 1048576 bytes"
    }
  ]
}
504某個 select statement 用完了單一 statement 的預算;details.limit 是該預算(單位毫秒,最多 5000),retryable 為 true。
{
  "detail": {
    "error": "query_timeout",
    "phase": "execute",
    "message": "Select statement timed out",
    "retryable": true,
    "details": {
      "limit": 5000
    },
    "step": "stock",
    "path": null
  }
}
GET
路由範本/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_idpathstring 1..36是要查詢執行紀錄的指令 UUID。
skipqueryint >= 0否略過前幾筆可見執行紀錄,預設為 0。
limitqueryint 1..200否最多回傳幾筆執行紀錄,可填 1 到 200,預設為 50。

回應結構

CommandExecutionListResponse
欄位型別必填說明
itemsCommandExecutionResponse[]否未指定
totalinteger是未指定

範例

列出某支指令的近期執行紀錄

請求
{
  "pathParams": {
    "chatroom_id": "11111111-1111-4111-8111-111111111111",
    "command_id": "22222222-2222-4222-8222-222222222222"
  },
  "query": {
    "skip": "0",
    "limit": "20"
  }
}
回應200
{
  "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 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
404指令不存在於此範圍、已被軟刪除、沒有引用任何資料表或引用了呼叫者無法讀取的資料表、它的 tag 已不再涵蓋每一張引用的資料表,或指令是 private 且呼叫者既不是建立者、也不管理全部被引用資料表。`restricted` 指令不看資料表可讀性與 `visibility`:只有既不管理全部被引用資料表、也沒有 execute grant 的呼叫者才會得到 404。
{
  "detail": "Command not found"
}
GET
路由範本/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_idpathstring 1..36是執行紀錄所屬的指令 UUID。
execution_idpathstring 1..36是要讀取的執行紀錄 UUID。

回應結構

CommandExecutionResponse
欄位型別必填說明
authorization_snapshotobject | null否Immutable command scope, tag, and referenced-table audit facts.
command_idstring | null否未指定
created_atstring (date-time) | null否未指定
duration_msinteger | null否未指定
errorstring | null否未指定
executed_atstring (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_idstring是未指定
idempotency_keystring | null否未指定
input_snapshotobject | null否未指定
invoker_client_idstring | null否未指定
invoker_idstring | null否未指定
outputsobject | null否Declared version-2 scalar or bounded relation outputs. Internal temporary relations and step rows are never exposed.
redactedboolean | 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_outputsstring[] | 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.
replayedboolean | 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_bodyobject | null否未指定
result_refsobject[] | null否未指定
staged_change_idstring | null否未指定
status"succeeded" | "staged" | "failed"是未指定
steps(CommandExecutionV1StepResponse | CommandExecutionV2StepResponse)[]否未指定
truncatedboolean | 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"
  }
}
回應200
{
  "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 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
404指令不存在於此範圍、已被軟刪除、沒有引用任何資料表或引用了呼叫者無法讀取的資料表、它的 tag 已不再涵蓋每一張引用的資料表,或指令是 private 且呼叫者既不是建立者、也不管理全部被引用資料表。`restricted` 指令不看資料表可讀性與 `visibility`:只有既不管理全部被引用資料表、也沒有 execute grant 的呼叫者才會得到 404。
{
  "detail": "Command not found"
}
404該指令下沒有這個 id 的執行紀錄,或呼叫者看不到它。
{
  "detail": "Command execution not found"
}
POST
路由範本/private/module/custom_tables/agent/commands/{command_id}/execute

以 agent 身分執行指令

用途

在可信的服務通道上執行指令;身分來自伺服器注入的 header,而不是路徑上的範圍區段。

適用時機

當 agent runtime 的模型工具需要代表特定「使用者+聊天室」或社群媒體 client 執行複合寫入時使用。

實際掛載路徑
  • 完整路由/private/module/custom_tables/agent/commands/{command_id}/execute

範圍

範圍是否提供驗證與權限
完整路由可使用resolve_command_service_principal (X-API-Key service token)

參數

欄位位置型別必填說明
command_idpathstring 1..36是要執行的指令 UUID;其範圍由綁定的 principal 推導。
dbqueryint否Redis 邏輯資料庫編號,因共用 client 相依而外洩到 schema 上,請不要填。

請求結構

CommandExecuteRequest
欄位型別必填說明
expected_contract_digeststring | 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_keystring | null否Actor-bucketed idempotency key; failed executions do not block retry.
inputsobject是Values for the command's declared inputs; server context is excluded.
reasonstring | 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_snapshotobject | null否Immutable command scope, tag, and referenced-table audit facts.
command_idstring | null否未指定
created_atstring (date-time) | null否未指定
duration_msinteger | null否未指定
errorstring | null否未指定
executed_atstring (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_idstring是未指定
idempotency_keystring | null否未指定
input_snapshotobject | null否未指定
invoker_client_idstring | null否未指定
invoker_idstring | null否未指定
outputsobject | null否Declared version-2 scalar or bounded relation outputs. Internal temporary relations and step rows are never exposed.
redactedboolean | 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_outputsstring[] | 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.
replayedboolean | 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_bodyobject | null否未指定
result_refsobject[] | null否未指定
staged_change_idstring | null否未指定
status"succeeded" | "staged" | "failed"是未指定
steps(CommandExecutionV1StepResponse | CommandExecutionV2StepResponse)[]否未指定
truncatedboolean | 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"
  }
}
回應200
{
  "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 佔位符。

狀態發生條件錯誤本文
400header 既沒有完整的 user + chatroom 組合,也沒有單獨的社群媒體 client id,或兩種形式混用。
{
  "detail": "Provide exactly user+chatroom or social-media-client identity"
}
400具型別的 channel headers 組合無效。X-TeamSync-Agent-Group-Scope 只在 line_group 與 line_room 時必填,其他 channel 一律不得出現。
{
  "detail": {
    "error": "invalid_agent_group_scope"
  }
}
401X-API-Key 與伺服器設定的指令服務 token 不符。
{
  "detail": "Invalid command service credential"
}
403指定的身分不存在、已停用,或不屬於它宣稱的聊天室或租戶。
{
  "detail": "Command principal is not authorized"
}
403Principal 透過的房間——chatroom 掛載點上是路徑的聊天室,agent 通道上是工作階段的房間——在取得權限鎖之後發現已被刪除、移到其他公司或部門,或成員資格已不存在;外部 principal 則是沒有存活的 acting room,或其 client 已換了房間。沒有寫入任何資料。
{
  "detail": "Command principal is invalid"
}
403Acting room 在 command 觸及的某張部門資料表上沒有 internal 授權。聊天室的綁定發生在解析任何資料表權限之前——對象是每一張受 channel 管控的資料表,沒有受管控的資料表時則是 id 最小的那張——所以這個回應會早於逐表的新增、編輯與讀取 403。
{
  "detail": "This chatroom has no grant on this table."
}
403客戶 principal:受 channel 管控的資料表的 external 範圍不包含客戶自己的聊天室。
{
  "detail": "This chatroom has no external grant on this table."
}
403程式讀取了 acting room 未授予讀取權的資料表。這條通道的部門資料表權限釘在 acting room 上,執行者其他聊天室給的授權在這裡不算數。
{
  "detail": "Read access not granted for this table."
}
403insert 步驟打到 acting room 未授予 insert 權的資料表。與讀取 403 是同一道前置閘門,所以不會寫入任何資料。
{
  "detail": "Insert access not granted for this table."
}
403update 或 delete 步驟打到的資料表,在 acting room 下 `can_edit` 解析為 "none"。filtered 或 own 授權會通過這裡,改在稍後逐列把關。
{
  "detail": "Edit access not granted for this table."
}
403acting room 在程式要寫入的某張 channel 治理資料表上沒有宣告 scope_values。這條通道釘住房間,所以訊息會把房間點名——不綁房間的 REST 對應版本,同一個 code 會說 `no chatroom of yours declares a scope on this table`。
{
  "detail": {
    "error": "scp_scope_undeclared",
    "message": "chatroom 11111111-1111-4111-8111-111111111111 has no declared scope on this table",
    "table_id": "55555555-5555-4555-8555-555555555555",
    "rule_id": "rule_3f9a1c2b7d4e"
  }
}
403寫入 floor 拒絕了這筆資料列:acting room 宣告的 scope 涵蓋不到該步驟要寫的紀錄或它要設的連結。這是終局性的政策拒絕——從同一個房間重試不可能成功,agent protocol 也要求把訊息轉達給使用者後停手,不要繞路。
{
  "detail": {
    "error": "scp_out_of_scope",
    "message": "this write targets records outside your channel's scope",
    "table_id": "55555555-5555-4555-8555-555555555555",
    "rule_id": "rule_3f9a1c2b7d4e"
  }
}
403連結值指向的目標確實存在、也通過 ACL,只有目標資料表的 channel scope 對 acting room 把它藏起來。與 REST 通道相同的 B10 歸因;在它之前,這個情況會回 500 `command_execution_failed`。
{
  "detail": {
    "error": "scp_out_of_scope",
    "message": "link target(s) 88888888-8888-4888-8888-888888888888: the link target exists but is outside your channel scope",
    "table_id": "44444444-4444-4444-8444-444444444444"
  }
}
403業務事件已經記錄,但該 command 的某個 update 或 delete step 所指向的資料表上,這位呼叫者的編輯權限是 own 或 filtered(不是 all,且不是管理者),或其任一寫入 step 所指向的資料表落在呼叫者的 SCP 房間範圍內,所以無法重新證明 replay。不寫入任何東西,也不揭露先前的 execution id 或結果;重試會得到相同答案。
{
  "detail": {
    "error": "ct.effect_identity_replay_forbidden",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "This business event was already recorded, and a row-scoped right cannot re-prove access to its rows",
    "retryable": false,
    "details": {}
  }
}
403這個 command 是 `restricted`,而呼叫者既不管理全部被引用的資料表,也沒有有效的 execute grant。它是帶 `phase: "execute"` 的 program-error 信封;MCP 工具改回 `not_found`。
{
  "detail": {
    "error": "command_execute_denied",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "This Command is restricted and the caller holds no execute grant",
    "retryable": false,
    "details": {}
  }
}
403owner 已不再有效的 `definition` authority command 會 fail closed:owner 不再存活、屬於其他公司、是服務帳號,或不再是每一張 `table_access` 資料表的 manager。必須由管理每一張資料表的 manager 重新儲存這個 command。
{
  "detail": {
    "error": "definition_authority_invalid",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "The Command's definer authority is no longer valid; a manager of every table must reauthor it",
    "retryable": false,
    "details": {}
  }
}
403command 引用的某張資料表在 `table_access` ceiling 中沒有項目,所以每次執行都會 fail closed,直到 manager 補上項目並重新儲存。
{
  "detail": {
    "error": "definition_ceiling_missing",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "A referenced table has no definer ceiling entry",
    "retryable": false,
    "details": {}
  }
}
404從綁定 principal 的聊天室、部門或公司都看不到這個 id 的指令。
{
  "detail": "Command not found"
}
404指令存在、範圍也看得到,但 agent_enabled 是 false。回應內容與「id 不存在」完全相同,所以 agent 通道不會洩漏任何存在性訊號。
{
  "detail": "Command not found"
}
404連結目標不存在、已軟刪除,或被 row ACL 藏起來——三種情況共用同一個 body。與上面的 Command not found 404 不同,請讀 detail 字串。
{
  "detail": "linked record not found"
}
409同一位 principal 以相同 idempotency key 送出不同的 inputs。
{
  "detail": {
    "error": "idempotency_key_reuse"
  }
}
409require_approval 規則觸發,計畫已被暫存待覆核。
{
  "detail": {
    "error": "approval_required",
    "process_id": "cccccccc-cccc-4ccc-8ccc-cccccccccccc",
    "rule_id": "rule_3f9a1c2b7d4e",
    "rule_label": "高金額訂單需主管覆核",
    "staged_change_id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"
  }
}
409Command 的定義在本回合的 agent 工具建立之後發生變更。請求在保留、稽核、加鎖或異動前就被拒絕;請重新讀取 command。在確認通道上,於提案與確認回合之間做的編輯會改以 proposal_mismatch 回報給 agent,所以這個 409 只涵蓋確認回合檢查之後才發生的編輯。
{
  "detail": {
    "error": "command_contract_mismatch"
  }
}
409這把 idempotency key 已被另一次執行用過,而那次凍結的權限情境與這次不同——origin、acting room、principal、權限集合、指令定義或相依狀態有所不同。新的情境請用新的 key。
{
  "detail": {
    "error": "idempotency_context_conflict"
  }
}
409兩次執行爭用同一批資料列,InnoDB 中止了這一次。交易已回滾,沒有任何部分寫入落地,retryable 為 true——請用「同一把」idempotency key 重試:失敗路徑會釋放該 key 的保留,因此重用是合法的。
{
  "detail": {
    "error": "command_lock_conflict",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "Concurrent write conflict (lock); please retry the request.",
    "retryable": true,
    "details": {}
  }
}
409業務事件已經記錄,而這次執行驗證後的 inputs 或作者程式不同(details.semantic_match 為 false;details.original_execution_id 指出先前那次執行)。終局:重試或改變內容都不會成功。發生在審批 release 時,封套的 phase 為 release(重複 stage 的 inputs 與程式相同時,details.semantic_match 為 true),並讓 staged 變更以 apply_failed 結束。
{
  "detail": {
    "error": "ct.effect_identity_conflict",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "This business event was already recorded with different details; nothing was written",
    "retryable": false,
    "details": {
      "original_execution_id": "66666666-6666-4666-8666-666666666666",
      "semantic_match": false
    }
  }
}
409排除送審者(`exclude_requester`)之後,某個必要的審批 gate 沒有任何可核准者。送出會在任何 review process 出現之前就被拒絕;`gate_order` 指出該 gate。對於不管理每一張被引用資料表的 `authority: definition` command 呼叫者,只會回傳安全的 key,所以沒有 `gate_order`。
{
  "detail": {
    "error": "no_eligible_approver",
    "message": "Excluding the requester leaves a required review gate with no eligible approver",
    "gate_order": 1
  }
}
409這筆寫入需要排除送審者,但呼叫的主體沒有已驗證的人類身分(例如 service account),無法歸屬。不會暫存任何東西。
{
  "detail": {
    "error": "requester_unattributable",
    "message": "This review requires the requester to be excluded, but the submitting principal has no verified human identity"
  }
}
409`date_interval_exclusion` 規則拒絕此次寫入:`date_interval_exclusion_invalid_period`(建立時省略 end key、結束日為空字串或不晚於起始日,或 scope link 沒有恰好一個目標)、`date_interval_exclusion_overlap`(另一筆 live 資料列涵蓋了這段期間的一部分),或 `date_interval_exclusion_retry`(寫入等待期間資料表的規則被改變;沒有寫入任何東西,請重送)。body 是固定訊息,絕不會指出資料列、日期或 scope 值。
{
  "detail": {
    "error": "date_interval_exclusion_overlap",
    "message": "Date period overlaps another period in this partition."
  }
}
422版本 2 的 assert 步驟沒有得到 true。信封會指出步驟名稱與條件路徑,message 是建置者自訂的文字,而且沒有寫入任何資料。
{
  "detail": {
    "error": "assertion_failed",
    "phase": "execute",
    "message": "庫存不足,無法建立訂單",
    "retryable": false,
    "details": {},
    "step": "stock_ok",
    "path": "steps.stock_ok.condition"
  }
}
422`$first` 的 effect-identity key term 沒選到列,或它的 select step 被 when 略過。在 claim 時點就提出:沒有寫入任何東西,也沒有取得 claim。`$single` term 則維持 400 cardinality_failed。
{
  "detail": {
    "error": "effect_identity_unbound",
    "phase": "execute",
    "step": "stock",
    "path": null,
    "message": "The effect identity key selected no row; nothing was written",
    "retryable": false,
    "details": {}
  }
}
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`)。發生在任何保留、稽核列或鎖之前。
{
  "detail": [
    {
      "type": "value_error",
      "loc": [
        "body",
        "inputs"
      ],
      "msg": "Value error, command inputs exceed 1048576 bytes"
    }
  ]
}
422必要的具型別 header 缺漏或格式錯誤。X-TeamSync-Agent-Channel 必須是 ChatChannelType 值;X-TeamSync-Agent-Group-Scope 一旦出現,就必須恰好是 64 個小寫十六進位字元。這些 shape 錯誤會先由 request validation 拒絕,之後才輪到 400 的組合檢查。
{
  "detail": [
    {
      "type": "missing",
      "loc": [
        "header",
        "X-TeamSync-Agent-Channel"
      ],
      "msg": "Field required",
      "input": null
    }
  ]
}
503伺服器未設定 CUSTOM_TABLE_COMMAND_SERVICE_TOKEN。
{
  "detail": "Custom-table command service is not configured"
}
POST
路由範本/private/module/custom_tables/agent/commands/{command_id}/query

以 agent 身分執行 query 模式指令

用途

在可信的服務通道上執行唯讀指令,回傳一頁投影後的分頁資料。

適用時機

用於只讀取的 agent 工具——查詢、彙總、篩選清單——讓模型完全碰不到寫入通道。

實際掛載路徑
  • 完整路由/private/module/custom_tables/agent/commands/{command_id}/query

範圍

範圍是否提供驗證與權限
完整路由可使用resolve_command_service_principal (X-API-Key service token)

參數

欄位位置型別必填說明
command_idpathstring 1..36是要執行的 query 模式指令 UUID;其範圍由綁定的 principal 推導。

請求結構

CommandQueryRequest
欄位型別必填說明
inputsobject是Values for the command's declared inputs; server context is excluded.
pageCommandQueryPage | null否Optional pagination window; defaults to the first page of 100 rows.

回應結構

CommandQueryResponse
欄位型別必填說明
next_cursorstring | null否Opaque cursor for the next page, or null when exhausted.
outputsobject否Declared scalar outputs (relation outputs are paginated via rows).
rowsobject[]否Projected rows of the paginated relation output for this page.
total_availableinteger | 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"
    }
  }
}
回應200
{
  "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 佔位符。

狀態發生條件錯誤本文
400header 既沒有完整的 user + chatroom 組合,也沒有單獨的社群媒體 client id,或兩種形式混用。
{
  "detail": "Provide exactly user+chatroom or social-media-client identity"
}
401X-API-Key 與伺服器設定的指令服務 token 不符。
{
  "detail": "Invalid command service credential"
}
403指定的身分不存在、已停用,或不屬於它宣稱的聊天室或租戶。
{
  "detail": "Command principal is not authorized"
}
403Principal 透過的房間——chatroom 掛載點上是路徑的聊天室,agent 通道上是工作階段的房間——在取得權限鎖之後發現已被刪除、移到其他公司或部門,或成員資格已不存在;外部 principal 則是沒有存活的 acting room,或其 client 已換了房間。沒有寫入任何資料。
{
  "detail": "Command principal is invalid"
}
400cursor 已無法對應這支指令的 dependency contract、inputs 或 output 名稱,通常是換頁之間 schema 或 inputs 變了。
{
  "detail": {
    "error": "cursor_binding_mismatch"
  }
}
400page.output 指的不是這支指令的 relation output。
{
  "detail": "page.output does not name a relation output"
}
400指令宣告了不只一個 relation output,卻省略了 page.output,路由無法判斷要分頁哪一個 output。
{
  "detail": "page.output is required for a command with multiple relation outputs"
}
400decimal input 不是符合宣告精度與小數位數的純十進位字串:送了 JSON 數字、位數過多、allow_negative 為 false 時送了負數,或送了 -0。
{
  "detail": "input 'unit_price': invalid decimal value"
}
400identity 型別的 input 指向的使用者或社群媒體 client 不屬於此範圍。
{
  "detail": {
    "error": "identity_not_associated",
    "input": "assignee"
  }
}
403Acting room 在 command 觸及的某張部門資料表上沒有 internal 授權。聊天室的綁定發生在解析任何資料表權限之前——對象是每一張受 channel 管控的資料表,沒有受管控的資料表時則是 id 最小的那張——所以這個回應會早於逐表的新增、編輯與讀取 403。
{
  "detail": "This chatroom has no grant on this table."
}
403客戶 principal:受 channel 管控的資料表的 external 範圍不包含客戶自己的聊天室。
{
  "detail": "This chatroom has no external grant on this table."
}
403select 來源或 join 目標在 acting room 的授權下解析出 can_read 為 none,因此呼叫大聲失敗,而不是回一頁空資料讓模型讀成「什麼都沒有」。
{
  "detail": "Read access not granted for this table."
}
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。
{
  "detail": {
    "error": "column_not_readable",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "Select references a column hidden from this invoker",
    "retryable": false,
    "details": {}
  }
}
403這個 command 是 `restricted`,而呼叫者既不管理全部被引用的資料表,也沒有有效的 execute grant。它是帶 `phase: "execute"` 的 program-error 信封;MCP 工具改回 `not_found`。
{
  "detail": {
    "error": "command_execute_denied",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "This Command is restricted and the caller holds no execute grant",
    "retryable": false,
    "details": {}
  }
}
404從綁定 principal 的聊天室、部門或公司都看不到這個 id 的指令。
{
  "detail": "Command not found"
}
409這是一支寫入指令,必須改走 agent 的 execute 路由。
{
  "detail": {
    "error": "not_a_query_command"
  }
}
409版本 2 指令儲存的 dependency contract 已與目前的資料表不符(schema、rules、審批設定、triggers、tag 或範圍有變動)。請重新儲存指令以重建 contract;原封不動重試會以同樣方式失敗。
{
  "detail": {
    "error": "schema_dependency_changed",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "Command schema dependencies changed",
    "retryable": false,
    "details": {}
  }
}
422這一頁序列化後超過 64 KiB 的輸出上限;請調小 page.limit 或縮減 output 的 select 欄位。
{
  "detail": {
    "error": "output_limit_exceeded",
    "phase": "execute",
    "step": null,
    "path": null,
    "message": "Command output exceeds its byte limit",
    "retryable": false,
    "details": {
      "actual": 71234,
      "limit": 65536,
      "resource": "output_bytes"
    }
  }
}
422精確十進位的值超出界限——運算結果超過它的精度,或 SQL 係數超出 65 位數的範圍(此時信封會指出該 select 步驟與 `<step>.select` 路徑)。沒有寫入任何資料。
{
  "detail": {
    "error": "numeric_overflow",
    "phase": "execute",
    "step": null,
    "message": "Decimal expression exceeded its bound",
    "retryable": false,
    "details": {},
    "path": "steps.line_total.value"
  }
}
422精確十進位運算式在執行時無效,例如除以零或得到不是有限十進位數的值。path 會指向失敗的運算式。
{
  "detail": {
    "error": "numeric_invalid",
    "phase": "execute",
    "step": null,
    "message": "Decimal expression is invalid",
    "retryable": false,
    "details": {},
    "path": "outputs.unit_price.value"
  }
}
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`)。發生在任何保留、稽核列或鎖之前。
{
  "detail": [
    {
      "type": "value_error",
      "loc": [
        "body",
        "inputs"
      ],
      "msg": "Value error, command inputs exceed 1048576 bytes"
    }
  ]
}
504某個 select statement 用完了單一 statement 的預算;details.limit 是該預算(單位毫秒,最多 5000),retryable 為 true。
{
  "detail": {
    "error": "query_timeout",
    "phase": "execute",
    "message": "Select statement timed out",
    "retryable": true,
    "details": {
      "limit": 5000
    },
    "step": "stock",
    "path": null
  }
}
POST
路由範本/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_enabledboolean否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.
definitionCommandDefinition-Input是Typed inputs and ordered multi-table write steps.
descriptionstring否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.
namestring是Command display name; unique within its scope.
tag_idstring是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
欄位型別必填說明
advisoriesCommandValidationAdvisory[]否未指定
compiledCompiledCommandFacts | null否未指定
errorsCommandValidationError[]否未指定
validboolean是未指定

範例

驗證一個 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"
            ]
          }
        }
      }
    }
  }
}
回應200
{
  "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`。
{
  "detail": "Custom-table command routes require JWT authentication"
}
400version 2 definition 的 step kind 是 list/object。Create、update、validate 回靜態 type/loc/msg issue,不含 input 或 ctx;其他 request 驗證維持 422。
{
  "detail": [
    {
      "type": "command_step_kind_type",
      "loc": [
        "body",
        "definition"
      ],
      "msg": "Command version 2 step kind must be a scalar"
    }
  ]
}
403聊天室範圍:呼叫者不是該聊天室成員,且角色低於部門管理者。來自其他部門的非成員需要公司管理者角色,否則會得到 `Insufficient permissions, not a member, and not at the same department (Required company manager).`。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
404聊天室範圍:路徑上的聊天室不存在、已被軟刪除,或(自後端 5.21.0 起)屬於其他公司。
{
  "detail": "Chatroom Not found"
}
404部門範圍:路徑上的部門不存在或屬於其他公司。
{
  "detail": "Department not found"
}
404公司範圍:路徑上的公司不是呼叫者的公司。這裡回 404 而非 403,避免此 id 變成存在性探測工具。
{
  "detail": "Company not found"
}
422request body 不是合法的 `CommandCreateRequest`。schema 層級的失敗是 FastAPI validation error,不會進到報告的 `errors` 陣列。
{
  "detail": [
    {
      "type": "missing",
      "loc": [
        "body",
        "tag_id"
      ],
      "msg": "Field required",
      "input": {
        "name": "建立訂單並扣庫存",
        "description": "一次寫入訂單並扣減對應品項庫存;庫存不足時整筆回滾。",
        "agent_enabled": true,
        "visibility": "public",
        "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"
                ]
              }
            }
          }
        }
      }
    }
  ]
}
POST
路由範本/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_idstring是Per-device/tab thread key; the server derives the thread id from company/room/user/client.
messagestring | null否未指定
resumeComposerResumeDecision | null否Answer to a pending ``save_command`` approval.

回應結構

ComposerTurnAccepted
欄位型別必填說明
poll_after_msinteger否Suggested delay before the first poll.
poll_pathstring是GET …/turn path with `client_id` and this `turn_id` already filled in; add `&wait_seconds=N` (≤ 60) to long-poll.
statestring否未指定
thread_idstring是未指定
turn_idstring是未指定

範例

請 composer 做一個 private 指令(202 handle)

請求
{
  "pathParams": {
    "chatroom_id": "11111111-1111-4111-8111-111111111111"
  },
  "body": {
    "client_id": "tab-1",
    "message": "幫我做一個登記客訴的指令,只有我自己能用"
  }
}
回應202
{
  "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 有一項已用盡。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
403請求使用 UserAPIKey 而非 JWT 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
404路徑上的聊天室不存在或已被軟刪除。
{
  "detail": "Chatroom Not found"
}
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 保持開啟。
{
  "detail": {
    "error": "job_model_not_configured",
    "job": "custom_table_command_composer",
    "pin_path": "/root/model-catalog/job-models/custom_table_command_composer"
  }
}
409這個 thread 沒有任何待審的儲存,卻送了 `resume`。
{
  "detail": {
    "error": "no_pending_approval",
    "message": "This thread has no save waiting for approval."
  }
}
409同一個 `client_id` 還有回合在跑(per-thread lock)。請輪詢 `GET …/turn`,或用 `DELETE …/thread` 丟棄 thread 以釋放這個 id。
{
  "detail": {
    "error": "turn_in_progress",
    "message": "Another turn is already in progress on this thread."
  }
}
422釘選的模型被政策拒絕或無法建構(有 code 時會說明原因)。
{
  "detail": {
    "error": "composer_model_unavailable",
    "code": "external_policy_model_owner_unmatched"
  }
}
422body 驗證。`send exactly one of message or resume` 來自 model validator:兩個都送、或兩個都不送,都是同一個錯誤。
{
  "detail": [
    {
      "type": "value_error",
      "loc": [
        "body"
      ],
      "msg": "Value error, send exactly one of message or resume"
    }
  ]
}
503這個部署尚未設定 composer 的 checkpoint 儲存。`POST …/chat`、`GET …/history` 與 `DELETE …/thread` 回 `503 composer_unavailable`,WebSocket 則以 1013 關閉;只讀 Redis 的 `GET …/turn` 與只提供文件的 `GET …/ws/docs` 仍可使用。
{
  "detail": {
    "error": "composer_unavailable",
    "message": "Composer checkpoint storage is not configured on this deployment."
  }
}
GET
路由範本/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_idquerystring ^[A-Za-z0-9_-]{1,64}$是`POST …/chat` 用的 thread key。必填——它決定要回報哪一個 thread。
wait_secondsqueryint 0..60否回合仍在執行時,最多保留回應這麼多秒(0–60,預設 0)。`0` 表示立即回傳;更大的值會回 422。
turn_idquerystring ^[0-9a-f]{32}$否202 handle 裡的 `turn_id`。帶上它就只會回報那一次回合,絕不會拿別的回合紀錄頂替。

回應結構

ComposerTurnStatusResponse
欄位型別必填說明
elapsed_secondsnumber | null否Since the turn started (running) or its duration (done/error)
errorComposerTurnError | null否Set when `state == error`
finished_atstring (date-time) | null否未指定
lane"rest" | "ws" | null否Which lane started the turn
resultComposerChatResponse | null否Set when `state == done`
started_atstring (date-time) | null否未指定
state"running" | "done" | "error" | "idle"是未指定
thread_idstring是未指定
turn_idstring | null否未指定

範例

Long-poll 一個已結束的回合(等待儲存審核)

請求
{
  "pathParams": {
    "chatroom_id": "11111111-1111-4111-8111-111111111111"
  },
  "query": {
    "client_id": "tab-1",
    "turn_id": "0f4c1b7a9e2d4a6b8c0d2e4f6a8b0c1d",
    "wait_seconds": "30"
  }
}
回應200
{
  "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呼叫者未通過聊天室的已加入成員/管理員存取檢查。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
403請求使用 UserAPIKey 而非 JWT 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
404路徑上的聊天室不存在或已被軟刪除。
{
  "detail": "Chatroom Not found"
}
422`client_id` 缺少或不符合 `^[A-Za-z0-9_-]{1,64}$`、`turn_id` 不是 32 個小寫十六進位字元,或 `wait_seconds` 不是 0–60 的整數。
{
  "detail": [
    {
      "type": "missing",
      "loc": [
        "query",
        "client_id"
      ],
      "msg": "Field required"
    }
  ]
}
GET
路由範本/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_idquerystring ^[A-Za-z0-9_-]{1,64}$是Thread key。必填——每個 `client_id` 一份逐字紀錄。

回應結構

ComposerHistoryResponse
欄位型別必填說明
draftComposerDraft | null否The whole draft (= a CommandCreateRequest plus an optional edit target).
messagesComposerMessage[]否未指定
pending_approvalComposerApprovalRequest | null否What the server is asking the user to approve before it persists.
thread_idstring是未指定
validationCommandValidationReport | 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"
  }
}
回應200
{
  "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呼叫者未通過聊天室的已加入成員/管理員存取檢查。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
403請求使用 UserAPIKey 而非 JWT 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
404路徑上的聊天室不存在或已被軟刪除。
{
  "detail": "Chatroom Not found"
}
503這個部署尚未設定 composer 的 checkpoint 儲存。
{
  "detail": {
    "error": "composer_unavailable",
    "message": "Composer checkpoint storage is not configured on this deployment."
  }
}
DELETE
路由範本/private/module/custom_tables/{scopeWithId}/commands/composer/thread

丟棄一個 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_idquerystring ^[A-Za-z0-9_-]{1,64}$是要丟棄的 thread key。必填。

回應結構

SuccessResponse
欄位型別必填說明
messagestring否未指定

範例

重置一個回合崩潰的 thread

請求
{
  "pathParams": {
    "chatroom_id": "11111111-1111-4111-8111-111111111111"
  },
  "query": {
    "client_id": "tab-1"
  }
}
回應200
{
  "message": "Success"
}

錯誤

驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。

狀態發生條件錯誤本文
403呼叫者未通過聊天室的已加入成員/管理員存取檢查。
{
  "detail": "Insufficient permissions, not a member (Required department manager)."
}
403請求使用 UserAPIKey 而非 JWT 驗證。
{
  "detail": "Custom-table command routes require JWT authentication"
}
404路徑上的聊天室不存在或已被軟刪除。
{
  "detail": "Chatroom Not found"
}
503這個部署尚未設定 composer 的 checkpoint 儲存。
{
  "detail": {
    "error": "composer_unavailable",
    "message": "Composer checkpoint storage is not configured on this deployment."
  }
}
GET
路由範本/private/module/custom_tables/{scopeWithId}/commands/composer/ws/docs
用途

WebSocket 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_framesComposerWsFrameDoc[]是未指定
close_codesComposerWsCloseCodeDoc[]是未指定
expected_flowstring[]是未指定
notesstring[]否未指定
rest_equivalentstring是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_eventsComposerWsFrameDoc[]是未指定
signature_endpointstring是Endpoint that issues the short-lived WebSocket signature
signature_hard_cap_secondsinteger是Absolute lifetime of a signature from issue
signature_recheck_secondsinteger是How often the socket re-validates the signature and the caller's chatroom membership (idle sockets included)
signature_requestobject是Example request body for the signature endpoint
signature_ttl_secondsinteger是Sliding TTL of a signature
websocket_urlstring是WebSocket path. Append ?signature={signature} and optionally &client_id={client_id}

範例

取得 socket 契約(節錄)

請求
{
  "pathParams": {
    "chatroom_id": "11111111-1111-4111-8111-111111111111"
  }
}
回應200
{
  "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。
{
  "detail": "Custom-table command routes require JWT authentication"
}

凍結提案與操作收據 說明 prepare、獨立正式寫入、selection、Review 義務與同 key 恢復。

Last updated on