Skip to Content
概念內容、Digest 與下載

內容、Digest 與下載

前端碰位元組的地方有五個:context 上傳、執行 input、產出物下載、永久公開連結,以及很小的公開目錄。見 架構。

1. 建置內容:有圍籬的上傳(不是只宣告 digest)

Company manager 對 公司擁有(owner_kind=company)的環境走這條路。策展環境上傳會 404。

  1. POST /environments/{id}/context-uploads,帶 declared_bytes(1..1 GiB)與 archive_format(zip/tar/tar.gz)。
  2. PUT /environments/{id}/context-uploads/{session_id},標頭 X-Sandbox-Upload-Capability,Content-Type: application/octet-stream。Body ≤ declared_bytes。
  3. POST .../complete → owned_object_id + digest。
  4. POST /environments/{id}/versions 帶那個 owned_object_id(可選、必須相符的 source_digest)。

這不是聊天室/DCC 的 blob_id,也不是 R2 presign。Capability TTL 是 15 分鐘(DEFAULT_CAPABILITY_TTL_SECONDS)。PUT 還沒開始就過期就重新 init。

建立版本時單獨送 source_digest 只有在該 digest 已對應一個完成的 context 物件時才接受(重試/測試)。產品 UI 不該跳過上傳。

上限:每個封存 1 GiB、20 個 live staging slot、每公司 20 GiB 宣告量、每分鐘 5 次 init。未完成的上傳會佔住一個 slot,直到你呼叫 abort,或該 session 的 24 小時 TTL(UPLOAD_SESSION_TTL_SECONDS)過期;已完成的 session 不計入。slot 用滿時的 429 連 build 的 scan report 寫入也會一起卡住。錯誤:413 context_too_large、429 staging_slot_exhausted/upload_rate_limited、409 capability_fenced、503 capability_unavailable。

可輪詢的 session 狀態:uploading → completed,或 aborted/abort_pending。

2. 執行 input:canonical JSON

提交執行時,input 是任意 JSON,但會當 canonical JSON 驗證(parse_canonical_json):

限制值
UTF-8 大小≤ 1 MiB(MAX_INPUT_JSON_UTF8_BYTES)
巢狀深度≤ 64(MAX_JSON_DEPTH)
節點數≤ 100,000(MAX_JSON_NODES)
其他不可有 NUL、不可有重複鍵

超過回 422。提交端點還有讀 body 之前的大小閘:過大的 Content-Length 直接 413 request_too_large(不會把 1 GiB JSON 讀進記憶體)。

3. 下載產出物:capability token,不是 URL

  1. GET /chatrooms/{id}/runs/{run_id}/content → 布林旗標 has_input/has_output/has_log/has_artifact_bundle(+ input_digest)。只有旗標。 state 可能是 "missing"。
  2. GET /chatrooms/{id}/runs/{run_id}/artifacts → metadata(relative_path、byte_size、digest、deletion_state、declared_content_type……)。
  3. POST /chatrooms/{id}/runs/{run_id}/artifacts/{artifact_id}/download → 短效 capability_token + expires_at + content_type/content_disposition/x_content_type_options: nosniff。
capability_token = token_urlsafe(32) # 不透明;不是 URL;不是任何租戶路由的 Bearer expires_at = now + 5 minutes # _DOWNLOAD_CAPABILITY_TTL content_type = application/octet-stream content_disposition = attachment; filename="<sanitized>" x_content_type_options = nosniff

/private/module/sandbox 底下沒有串流產出物位元組的 GET,也沒有文件化的兌換標頭。不要發明兌換 URL。不要把這個 token 跟 X-Sandbox-Upload-Capability(上傳)或公開確認目錄搞混。

產品 UI 今天能做的:

  • 用 GET .../artifacts 顯示中繼資料。
  • 打 POST .../download 證明目前 principal 可以碰這個 artifact_id。
  • 把回傳的 token 當授權證明,給未來的位元組傳遞面用。expires_at 過了再發一次。
  • has_output/has_log 只是旗標。REST 不回那些本體。Agent 工具 sandbox_job_status 可能回有界、不可信預覽(output ≤64 KiB,log 頭尾 ≤16 KiB)。見 Agent toolkit。

規則:

  • 授權永遠用 artifact_id(+公司/聊天室擁有權),不用 key。
  • deletion_state != active → 下載 404。
  • 下載 capability 是 5 分鐘;上傳 capability 是 15 分鐘。不要混用。

4. 永久公開連結(不必登入)

一次 run 的 log 或 output 物件(不含個別產出物)可以拿到一個永久、不必登入的下載 URL——給 TeamSync 之外的人看結果用:

POST /chatrooms/{id}/runs/{run_id}/{kind}/public-link # kind: log | output # → { run_id, object_kind, url, token, revoked, created_at } DELETE /chatrooms/{id}/runs/{run_id}/{kind}/public-link # 撤銷 # → 同樣的形狀,revoked: true
  • 設計上就是永久——沒有 TTL。 連結會一直活著,除非你撤銷它,或底層物件離開 deletion_state=active(保留期滿、scrub、offboarding)——兩種情況都會讓公開 URL 從此永遠 404。
  • 鑄造是冪等的:連結還活著時再鑄一次,回的是同一個 token/URL。先撤銷再鑄一次,才會拿到新 token——舊的永遠死了。
  • 鑄造/撤銷需要跟本頁其他動作一樣的 run 內容可見性(房間成員資格/管理者階梯)。連結一旦鑄出,本身就沒有任何驗證——誰拿到 token 誰就能下載那個物件。
  • 公開 URL 是 GET /public/sandbox/artifacts/{token}——沒有 /private 前綴、不必登入、不在 /private/module/sandbox 底下。回應是完整 buffer 後的 attachment(Content-Disposition: attachment、X-Content-Type-Options: nosniff、Cache-Control: private, max-age=3600);不是轉址,也不是 presigned provider URL。物件大於 100 MiB 時同樣回 404(MAX_PUBLIC_STREAM_BYTES),即使連結還活著、物件仍是 active;provider 讀取失敗也一樣回 404。
  • kind 只有 log 或 output——不含 artifact。個別產出物檔案沒有永久公開連結,它們仍然只能走上面 5 分鐘的下載 capability。
  • 鑄造/撤銷之前,該 run 必須已經有對應的 log/output 物件:GET .../content 的 has_log/has_output 為 false 時,POST(與 DELETE).../{kind}/public-link 回 404。先輪詢到 run 進入 terminal 且旗標為 true 再鑄連結。
  • 受限的 Sandbox API key 不能鑄造或撤銷公開連結:請求在 scope 過濾層就被擋下,回 405(Access to POST … is not allowed for your role,detail 帶的是實際請求路徑)。

因為這個連結是永久、猜不到但也不是什麼機密,把「鑄一個公開連結」當成任何「除非你記得撤銷否則不可逆」的分享動作來看——把「分享」按鈕直接接上這個功能之前,先想清楚產出物是不是敏感內容。

Digest 與內容定址

擁有物件的儲存 key 由後端產生且不會全域重用,形如 sandbox/{company_id}/{object_kind}/{token};object_kind ∈ context/input/output/log/artifact_bundle/report。前端拿不到這些 key。授權用資源 id(owned_object_id、artifact_id、session id)。

Last updated on