Repository navigation
fix(rest): the import template answers to the import door's gates, not the export's (#20896) - #20977
Conversation
…t the export's (#20896) Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…ort door's gates (#20896) Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 27 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 7 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 15 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin ddd8f08773fcc55423c4556f0780614079dbf3ab && git checkout ddd8f08773fcc55423c4556f0780614079dbf3ab
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9b0de7de73699771b69649bf7b507fbd2a842260 8e2d1fda64a01d9023177e0df3dc00397f848c57 && git checkout -B drift-repro 9b0de7de73699771b69649bf7b507fbd2a842260 && git merge --no-ff 8e2d1fda64a01d9023177e0df3dc00397f848c57
node scripts/docs-audit/affected-docs.mjs --json 9b0de7de73699771b69649bf7b507fbd2a842260
|
…mplate-import-door
…envelope (#20896) check:route-envelope ratchets the flat sibling-code dialect down and refused the new 403 body; it is now built through sendError from @objectstack/types. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Read: card #20896 (body; analysis 5915133835; ruling A 5921162178, its Execution parameters binding; claim 5921450654; dev report 5922602955; seat ruling 5922621307); #18386 (body line 94 as corrected; verifications 5914688255 / 5917156039); ① Derived judgmentsAccept-set and surface changes the diff implies
Text, sentence by sentence (true at head unless marked)
② Semver level
③ Boundary flags
Check-runs on Implemented-by: VERDICT: PASS Adopted and posted by
Generated by Claude Code |
|
os-decision-facets 决策请求:确认更正 #18386 那条尚未发版的发布说明(PR #20977)· 2026-10-01T01:21Z
一句话问题: 按您同意的 #20896 裁定 A,导入模板改为看导入权限和新建权限。但 #18386 那条还没发版的发布说明,写的仍是「沿用导出的两道权限闸」。本 PR 把这句改成了与新行为一致的说法。仓库检查规定,改一条待发的发布说明要由人确认( 背景与前提(每条可复核):
选项 × 代价:
业务含义: A 等于客户看到的模板权限说明就是实际规则;B 等于同一个版本的发布说明自相矛盾。 时间要求: 只有在发版 PR #20639 合并之前确认并合入,这条说明才仍是"待发"。如果发版先发生,这句改动就变成勘误,本 PR 的 Clause-② 也要改为收紧。 四棱:
Prior rulings read: 推荐:A。 只看①选 A;②③④ 是否翻转:否。回退项:B。置信缺口:新行为由测试钉住,未在运行中的部署上实测。 裁后执行: 维护者答 A 后,席位把本 PR 转为 ready 并放入合并队列(Check Changeset 按设计保持红,本评论即其记录);合并后收尾 #20896,并在 objectui#9600 说明「下载模板需要新建权限,不需要导出权限」。答 B 则让 dev 撤回那一句再审。 你要做的: 回一个字母,A 或 B(可与 PR #20991 一起答)。 Generated by Claude Code |
The maintainer's answer: B, do not correct the 18386 note; the sentence change is reverted, and the gate change still lands
Generated by Claude Code |
…on main (#20896) The maintainer chose B on decision 5922804275: the pending note is not corrected. The template's own patch changeset stays. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Delta re-review on the head above. Read: card #20896 (body; analysis 5915133835; ruling A 5921162178, its Execution parameters binding; claim 5921450654; dev report 5922602955; seat ruling 5922621307; revert-round report 5923358262); on PR #20977 the prior PASS record and seat adoption 5922797800 (at The delta, measured. The head's parent is ① Derived judgmentsAccept-set and surface changes the diff implies — unchanged from the prior record, re-read on the identical code blob and judged RIGHT again:
Text, sentence by sentence, at this head
② Semver level
③ Boundary flags
Check-runs on Implemented-by: VERDICT: PASS Adopted and posted by
Generated by Claude Code |
Closes #20896
Clause-②: no (no schema or published-export change; the route and its closed parameter set are unchanged, as the ruling states)
Implements ruling A on the card (comment 5921162178, its execution parameters taken whole, per the claim 5921450654):
GET /api/v1/data/:object/export?template=trueis judged by the import door's gates, not the export door's. Branch base:f6ccca4a44.What changed
packages/rest/src/rest-server.tstemplatevalue readstruegoes through the newenforceImportTemplateGates. The value is read by the template reader itself (readTemplateMode, given thetemplatekey alone), so no second spelling of that reading exists. Every other request goes throughenforceApiAccess(..., 'export')andenforceExportPermissionexactly as before. The gates still run before the query-string gates, in the same position, sotemplate=true&limit=5from an importer is still refused400with the reason, not403.enforceImportTemplateGates:POST /data/:object/importmakes before it parses:enforceApiAccess(..., 'import')(404not exposed,405neither create nor update exposed).explain({ object, operation: 'create' }).allowed: false, or a throw, answers403 PERMISSION_DENIED. No security service, or one withoutexplain, allows, the stanceenforceExportPermissiontakes.templateparagraph ofDATA_EXPORT_PARAMSandanswerImportTemplatenow name the import door's gates.packages/rest/src/import-template-route.test.ts: the pins below. The three field-level-security fixtures now grant create throughexplaininstead ofcanExport, which no longer reaches the template.content/docs/permissions/permission-sets.mdx: theallowExportsection says the template is gated by create, neverallowExport..changeset/20896-template-import-door.md: patch, new. The still-pending.changeset/18386-export-import-template.mdis NOT edited: the maintainer answered B on the decision5922804275(「20991 20977 都不改」, 2026-10-01), and the one-sentence correction proposed at501dca7347was reverted at8e2d1fda64(the file is byte-identical to the merge base).Premise measured: there is no route-level caller create check to reuse
The dispatch asked to reuse the create check the import door enforces. Read at
f6ccca4a44:enforceApiAccess(..., 'import')(stage 1) andenforceApiAccess(..., 'import', { writeMode })(stage 2). Both are OBJECT-level (enable.apiMethods,404/405).runImportrecords the refusal as a failed row (PERMISSION_DENIED) inside a200report (the per-row catch inimport-runner.ts).So the template branch reuses stage 1 verbatim and asks the security service for the middleware's create verdict through
ISecurityService.explain. That member is not optional. The contract names it as the composition for an object-level verdict with no dedicated method, andmayReadRunStateinpackages/runtime/src/domains/automation.tsis the in-repo precedent. Nothing here is re-derived from permission sets. Stage 2 is not asked: it needs the write mode a request body names, and a template request names none.Fork clause: not triggered
The template's columns do not depend on a write mode.
templateColumns(schema, { explicitFields, permitted })takes none.permittedcomes fromISecurityService.getWritableFields(object, context), whose signature has no operation (plugin-security's implementation reads the field mask only). The import door'swriteModecheck is a gate (404/405) and hands nothing to the column computation. No mode was picked.Pins
In
import-template-route.test.ts:allowExport:200and the workbook;canExportnever asked.403 PERMISSION_DENIED, empty body,answerImportTemplatenever called,getWritableFieldsnever asked.allowExportand no create:403 PERMISSION_DENIEDontemplate=true, builder never called,canExportnever asked.403, never a grant.createwithoutlistserves the template; an object exposinggetandlistonly answers405 OBJECT_API_METHOD_NOT_ALLOWED, builder never called.allowExportit is403 EXPORT_NOT_PERMITTED, no row read, create verdict never asked. WithallowExportand no create it is200with the pre-change headers, text and sha256, create verdict never asked. The existing byte-identity block is unchanged and green.Each pin is ablated after this PR opens: a mutation proven on disk, the run red, the restore proven, the run green. The readings go in the
os-dev-reportcomment on the card, not here.Acceptance notes
explaindenies a caller whose permission sets resolve empty, where the middleware skips its CRUD gate. That is reachable only on a deployment with no baseline permission set, and it is the closed direction.mayReadRunStaterecords the same boundary.PERMISSION_DENIED, a standard code, so the error-code ledger does not change. It is the code the import door's row report carries for the same caller.Verification at the current head
8e2d1fda64501dca7347.check-empty-changeset --base origin/mainexit 0;check-changeset-no-majorandcheck-adr-0087-registrationexit 0.dispatch-gates: 93 accounted, 92 run, 1 NOT MEASURED (check:dual-build-cjs-loads; CI builds it). Dev report5923358262on The import template (GET /data/:object/export?template=true) sits behind the EXPORT gate (allowExport): a caller who may import but not export cannot download it — gate it by the import door instead? (from #18386 acceptance-6 verification) #20896.Verification at the prior head
501dca7347{ success: false, error: { code: PERMISSION_DENIED, message, details: { object } } }throughsendError(check:route-enveloperefused the flat form).dispatch-gates: 93 accounted, 92 run, 1 NOT MEASURED (check:dual-build-cjs-loads, which needs a full build; CI builds it).check-empty-changesetwas red by design at this head (the then-proposed correction of the pending feat(rest): 导出接口新增 ?template=true —— 输出只含「可填列」的 xlsx 导入模板 #18386 note, since reverted).Verification at opening
pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2 src/import-template-route.test.ts: 37 passed, 0 failed, at617255a015. That is the code commit; the head3c7d7dd4efadds only the changesets and the doc.testandtypecheck, the ablations anddispatch-gatesfinish after opening. Their exit codes are in the report on the card.Generated by Claude Code