Repository navigation
docs(adr): ADR-0138 amendment — D2 class 7, the guest upload door bound to a public form - #25619
objectstack-fleet[bot] wants to merge 3 commits into
Conversation
…nd to a public form Records letter B of ruling 6377731715 (#25496): a guest file is taken only for a file field the public form declares, limited in size and type by that field, stored unattached and private in the form's organization (D4), attached by the submission that follows or collected after a declared TTL, under the form doors' own anti-abuse posture. ADR-0056 gains the dated back-pointer line. No code: the door is built by #25572 after this lands. Claude-Session: https://claude.ai/code/session_01JVTd3PprStGa26aez5omLB Co-authored-by: Claude <noreply@anthropic.com>
…r-0138-guest-upload
…wording and the record line Claude-Session: https://claude.ai/code/session_01JVTd3PprStGa26aez5omLB Co-authored-by: Claude <noreply@anthropic.com>
维护者速读改了什么: 本 PR 只改文档。ADR-0138 的「访客可以进的门」(D2)加第 7 类:绑定在公开表单上的访客上传门,ADR-0056 加一行回指。不含代码,合并后系统行为不变。门本身是 #25572 第二阶段的活。 为什么改: hotsrm 的供应商注册公开表单要上传营业执照,而现在访客上传不了任何文件,表单一旦有必填附件就提交不了。裁决 B 的意思是「先写 ADR,再写代码」。 风险与代价(含回滚):
席位意见: 推荐 C,带前提。
你要做的(一个动作): 在本 PR 回复一个字母 C / B / A。选 C 请顺带告诉我 hotsrm 跑在自托管还是云托管。 席位核对
Generated by Claude Code |
|
Ruled: C · the maintainer, verbatim, in C = the seat record's option (6383110676), in its words: 访客表单不收附件。供应商先注册账号、登录后再填表上传:不开新的匿名写入门,复用已登录的上传路径;发布时响亮拒绝「公开表单声明了文件字段」. Consequence for this PR: it is closed unmerged in this act. ADR-0138's door list keeps no guest upload door, so nothing in it changes. What C sets in motion (each on its card):
Generated by Claude Code |
Part of #25572
维护者速读(草稿)
改了什么
在 ADR-0138 的「访客可以进的门」清单(D2)里加第 7 类门:绑定在公开表单上的访客上传门。本 PR 只改文档:ADR-0138 正文,以及 ADR-0056 状态行加一行回指。不含任何代码,上线后系统行为不变。门本身由 #25572 的第二阶段实现,等本文合并后再派。
为什么改
hotsrm 的供应商注册公开表单要求访客在第一次提交时上传营业执照扫描件。现在访客上传不了任何文件(所有上传接口对未登录访客一律拒绝),表单里一旦有必填的附件字段,访客就无法提交。按裁决 B,匿名写入文件是安全边界,所以先把规则写进 ADR,再写代码。
ADR 写下的规则,逐条对应裁决原文:只收表单里声明了的文件字段;大小与类型按该字段的声明限制;文件先以「未挂接」状态存进表单所属的组织;随后的提交引用它就挂到那条记录上,否则过一段短时间自动清理(时长由实现声明);与表单相同的反滥用姿态:按地址限速、不可列举、访客读不回任何人的文件。
风险与代价(含回滚)
按原文,两种空态下这个门照样开放。
席位意见
你要做的(一个动作)
在本 PR 上回复一个字母:A = 按原文批准(两种空态下门照开,与表单现状一致);B = 两种空态下门不开(部署没开限速、或字段没声明大小与类型时,不提供访客上传),由席位改一段文字后再请您批准。
Clause-②: no
Metadata-protocol: none
Summary
Records letter B of ruling record
6377731715on #25496 (decision batch #388 item 4, maintainer 「同意」), carried on #25445 by the director's6377853567and the maintainer account's6377887908: an ADR-0138 amendment that declares a guest upload door bound to a public form, as D2's class 7. ⛔ No implementation code: the ruling orders the ADR first and the door after it lands, so this PR is phase 1 of #25572 and the card stays open for phase 2 (E5).Files:
docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md(the amendment) anddocs/adr/0056-permission-model-landing-verification.md(one dated back-pointer line).What the amendment states
Every constraint of the two ruling records is one rule of the new
#### Class 7subsection under D2, each with the code it binds to, cited by symbol onorigin/main:packages/rest/src/form-routes.ts#resolveFormBySlug); the storage upload doors keep refusing a caller with no session.FILE_REFERENCE_TYPES).maxSizeandaccept(packages/spec/src/data/field.zod.ts), judged before a byte is stored, beside the deployment upload limit, and judged again by the attaching record write (assertFileConstraints).acl: 'private',owner_idunset (D1), organization from D4's clarification (1), never from the client.claimFile, ADR-0104 D3 wave 2).LifecycleService, ADR-0057 §3.3), bytes reclaimed by thesys_filereap guard. The TTL is a bound the implementation declares; no existing constant covers it (measured below).Kept in step: the status line, TL;DR, D2's table, its adoption sentence and enforcer line, the Enforcement map, Consequences, What the ruling did not settle (items 4 and 5), the Execution plan (new row E5, #25572, with its pins) and References.
Convention followed
ADR-0138's own amendment form, as its 2026-10-13 and 2026-10-14 amendments use it: a dated status-line continuation (
· **Amended** (DATE, ruling record, maintainer words) — what changed), and the change written in place where the record's structure holds it. Here that is D2, whose text says "naming a new door class is an amendment of this record". The record whose text the change alters gets a dated back-pointer continuation, as ADR-0138's criterion 4 and its D3 item 5 amendment did for ADR-0090 and ADR-0086: here ADR-0056's D2 line, which counts six classes.docs/adr/has no index file, andscripts/adr-anchors/holds one file per anchored code path, which lands with code, so this PR adds none.File surface: the claim names ADR-0138 plus any index or anchor file the ADR process requires. The ADR-0056 back-pointer line is beyond that literal list. It is added because of the precedent above, and it is declared here.
Measurements (
origin/maine649c2d91e; re-checked unchanged at10b8250ad5for every path cited)POST /storage/uploadis not a route. It was retired from the dispatcher (packages/runtime/src/route-ledger.ts, the storage block), so it answers 404 to every caller. The real upload doors are/storage/upload/presigned,/completeand/chunked(registerStorageRoutes), and each refuses a sessionless caller first with 401AUTH_REQUIRED(requireUploadSession). The premise holds: a guest cannot upload, so a required file field makes a public form unsubmittable.sys_filedeclaresretentionof7donly whenstatusispending, and attlondeleted_at(30d). Neither reaches a completed file that no record claims (stranded-orphan-inventory.ts: such rows are never a sweep candidate). So no platform constant covers the guest TTL. The ADR leaves the value to the implementation, as a declared bound.createInboundRateLimitMiddleware, armed byserver.security.rateLimit(opt-in,enableddefaults to false). No app underexamples/arms it, and hotcrm's docs (objectstack-ai/hotcrmat8a14b56,content/docs/reference/performance-and-limits.mdx) say it declares none. The ADR binds the door to that limiter and states the unarmed empty state.Open question for the maintainer (What the ruling did not settle, item 4)
The ruling names the form doors' posture and the field's limits, and both can be empty: an unarmed limiter, or a file field with neither
maxSizenoraccept. A (as written): the door opens in both, exactly as the form doors do today. B: the door stays shut until the limiter is armed and the field declares its limits, the shape ADR-0121 D6 gives an anonymous endpoint. B adds no key and no gate script. My recommendation is B: "rate limit per address" is a named constraint, and under A it holds on no measured deployment. The text carries A because A is the ruled words read literally ("与表单同样"). Choosing B changes item 4 and two empty-state sentences.Acceptance notes
Observations, not filed:
sys_filein system context and claims or copies the file. Whether a guest submission may name a file the door did not store is E5's to measure and, if it narrows the submit, to declare. Carrier: storage/public forms: an anonymous public-form submission cannot carry a file —/storage/uploadanswers 404 to the guest principal, so a required attachment field is impossible on a public form #25572 (E5).packages/core/src/security/assemble-execution-context.ts, the dogfood sweep classification and test headers, andADR_0138_D2_DOOR_CLASS_ROWSin the conformance test. Carrier: storage/public forms: an anonymous public-form submission cannot carry a file —/storage/uploadanswers 404 to the guest principal, so a required attachment field is impossible on a public form #25572 (E5).Verification
Docs-only diff, so there are no tests, no build of a touched package, no
pnpm lint(no package touched) and no ablation. The gates were derived bynode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon head4285f5e455: 20 commands, all run, each exit 0. Among them:check-adr-symbol-anchors(2408 anchors found, the 21 this diff adds each found as a declaration),check-adr-links(773 link destinations found),check:adr-anchors,check:doc-authoring,check:nul-bytes,check:pm-governed-mergesandcheck:pm-prior-rulings.--ranreconciliation: 20 derived, 20 run, 0 NOT-MEASURED, 0 UNRUN.check:doc-formula-expressionsfirst answered exit 3 (prerequisite not built). After building@objectstack/formulaand@objectstack/lintunder the verify lock, it answered 0. Tier H: this PR stays draft for the maintainer's approval.Generated by Claude Code