#公開寫入
#用途
在嚴格控制的條件下,允許未經認證的訪客建立或更新資料(例如提交評論)。公開端點可以呼叫 writes: true 的 method(Decision 4),但 §7.2 的每一條規則都是強制的(Decisions 21–26、38、39、41、47、49、53)。
#自然語言 source 範例
api/catalog/v1/submit-review/submit-review.md(路由 POST /catalog/v1/{slug}/reviews)
# Submit review
POST, path /catalog/v1/{slug}/reviews, public, no auth, tenant is determined by slug.
Visitors may leave reviews, so this writes data and requires captcha.
At most 5 per minute per IP and at most 100 per minute per tenant.
body: item_id (uuid), rating (1 to 5), comment (1 to 500 characters).
Create the review with review.submit; on success return 201 with id. If the item does not exist return 404 NOT_FOUND.
#編譯後的 lock md 範例
api/catalog/v1/submit-review/submit-review.lock.md(僅列 section;frontmatter 依 §2A.1,kind: api、scope: feature;這個 lock 上的安全項目:public、auth-removed、public-writes(高風險),以及新回應欄位的 output-new-field(中風險))。新 INSERT 的 write-sql 項目(中風險)屬於 review.submit method 的 lock,而不是這個 api lock。每個 PR 最多 5 個安全項目的上限是同一個 PR 內所有 lock 合計計算(Decision 34),所以這個 api lock 加上 review.submit 合共 5 項。review model 中可公開寫入/untrusted 的欄位是另一個安全項目,必須在更早的 PR 中合併並部署。
## Route
- method: POST
- path: /catalog/v1/{slug}/reviews
## Access
- public: true
- tenant: { from: path, param: slug }
- public-writes: true
- role: public_writer
- writable-columns: { reviews: [item_id, rating, comment] }
- challenge: { type: captcha, when: always }
- external-calls: forbidden
- daily-write-quota: platform-default
- audit-log: required
## Pipelines
### p1 -auth
- removed: true
### p2 rate-limit
- per-minute: 5
- key: ip
### p3 rate-limit
- per-minute: 100
- key: tenant
### p4 audit-log
- event: public-write
## Limits
- provider: main
- timeout: 10s
- body: 16KB
## Request
| field | type | rules | required |
|---|---|---|---|
| path.slug | string | | yes |
| body.item_id | uuid | | yes |
| body.rating | integer | min: 1, max: 5 | yes |
| body.comment | string | min: 1, max: 500, untrusted | yes |
## Response
| status | schema | headers |
|---|---|---|
| 201 | { id } | Location |
## Transform
- in: { item_id: req.body.item_id, rating: req.body.rating, comment: req.body.comment }
- out: { id: r.id }
## Steps
### s1 method
- op: method
- target: review.submit
- args: transform.in
- out: r
- source-lines: 6-6
### s2 respond
- op: respond
- status: 201
- value: transform.out
- location: /catalog/v1/{req.path.slug}/reviews/{r.id}
- source-lines: 6-6
## Errors
| status | code | from |
|---|---|---|
| 404 | NOT_FOUND | NOT_FOUND |
| 422 | VALIDATION_FAILED | VALIDATION_FAILED |
| 429 | RATE_LIMITED | RATE_LIMITED |
review.submit 會透過 item.get 檢查貨品(跨物件的讀寫一律經該物件自己的 method),並執行一條參數化的 INSERT INTO reviews (item_id, rating, comment) VALUES ($1, $2, $3);comment 在 review model 中標記為 untrusted: true, max: 500(§11.7)。
#安全考量
- 兩個開關:租戶必須啟用
public_access及public_write_access。public_write_access只能由租戶管理員在平台 UI 中啟用,沒有 API、CLI 或 source 途徑(Decisions 26、41)。未啟用public_access的租戶,以及已啟用public_access但未啟用public_write_access的租戶,都會收到完全相同的 404 內容,並補足到同一個固定最低耗時(300ms),使呼叫方無法分辨是哪個開關未開(§7.2)。 - 按端點啟用:在
## Access中設定public-writes: true(高風險)(§7.2)。 - 角色與授權:使用獨立的
public_writer角色;授權由 lock 產生,只限於實際用到的資料表、欄位和操作(§7.2、§10.7)。 - 操作:只允許
INSERT和UPDATE,而且只限writable-columns。UPDATE必須有以主鍵或 RLS 限定的WHERE(§7.2)。 - 黑名單:以下欄位永遠不可公開寫入:
role、roles、permissions、is_admin、tenant_id、owner、owner_id、created_by、approved、verified、password(只有下面的註冊規則允許)、deleted_at及所有系統欄位;物件可以再加入其他欄位(§7.2)。 - 密碼寫入(Decision 47):公開路徑只有在三項條件同時成立時才可寫入密碼欄位:method 具備
capabilities: [set-password](高風險)、寫入值是同一 method 中hash-password的輸出,以及 api lock 設有password-write: allow並在writable-columns列出密碼欄位。v1.7 的公開註冊本身絕不寫入密碼;密碼在驗證步驟設定(Decision 67,見 user-register.md)。 - 電郵驗證寫入(Decision 53):
email_verified_at(屬「verified」類黑名單欄位)只可由具備capabilities: [confirm-verification]的 method 寫入,並須在單一 UPDATE 中進行:WHERE 以hash-token輸出比對唯一的 token hash 欄位、檢查有效期和未驗證狀態,並清除 hash;api 需要verification-write: allow(高風險)。 - 注入租戶:
tenant_id由 slug 得出;body 含有tenant_id、id或created_by時以 422 拒絕,在## Request中宣告它們亦屬錯誤(Decision 23、§10.7)。 - 配額:預設每個租戶每日 1,000 次公開寫入、每個 IP 每日 50 次;租戶管理員可以調低,調高至最多 10,000 次須向平台申請。配額用完時只會封鎖公開寫入(429 加
Retry-After)(Decision 39、Decision 21)。 - 限流:IP 及租戶兩種限流都是必須的;
cost: high的步驟只有端點上限的一半(§7.2)。 - 防機械人(Decision 49):
challenge: { type: none | captcha | pow, when: always | after-failures, after-failures: <n> }。含 INSERT 的公開端點必須使用type: captcha或pow配when: always;登入類端點必須使用{ type: captcha, when: after-failures, after-failures: 3 };其他不含 INSERT 的公開寫入可用type: none。不含 INSERT 的密碼寫入(例如 verify-email)不需要 captcha,但仍需要 IP 加租戶限流(§7.2)。 - 審計:公開寫入端點不可移除
audit-log(§10.3)。 - 不可信內容:公開寫入的欄位在 model 中標記為
untrusted: true;所有輸出(包括已認證的讀取)都按 Content-Type 進行輸出編碼;每個欄位都要有長度上限,陣列要有數量上限(Decision 24)。 - 對外呼叫:公開路徑上禁止對外呼叫,除非設定
external-calls: allow(高風險)(§7.2)。 - 固定最低回應耗時:一般公開端點為 300ms,上述 404 回應同樣適用,使回應時間不會透露租戶或開關狀態(§0.1、§7.2、Decision 36)。
- 部署報告會列出所有公開寫入端點及可寫入欄位,並附上 warning(§7.2、§10.9)。
- 剩餘風險(繞過 captcha、垃圾內容)將由日後的內容審核 hook 處理(§14)。
#規格缺口
daily-write-quota: platform-default是唯一展示過的配額語法;租戶調低後的配額會否(以及如何)出現在 lock 中,未有規定(配額在平台 UI 中設定)。untrusted在這裡以## Request規則出現,在 model 中則是untrusted: true;model lock 的對應語法沒有展示。- 物件如何宣告額外的黑名單欄位,未有規定。
challenge: { type: pow }的參數(難度)未有規定。- model 層的
public_writer授權是所有公開端點的聯集,而每個 api 只取得自己的欄位(§12.8 註解);平台如何為同一個 role 實現按端點的授權沒有描述。