Skip to content

fix(approvals+storage): decision attachments — name, open, correct download filename (#3504)#3505

Open
baozhoutao wants to merge 2 commits into
mainfrom
claude/approval-attachment-authz
Open

fix(approvals+storage): decision attachments — name, open, correct download filename (#3504)#3505
baozhoutao wants to merge 2 commits into
mainfrom
claude/approval-attachment-authz

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #3504. Frontend companion: objectstack-ai/objectui#2820.

Problem

A decision attachment in the approval inbox timeline (审批动态) showed a nameless "附件" chip that did nothing when clicked — and even when the file did download, it was named after the opaque signed-URL token (eyJrIjoiYXR0YWNo…) as application/octet-stream.

Root cause 1 — approvals read path stringified descriptors

sys_approval_action.attachments is a Field.file, which stores rich descriptors { id, name, url, mimeType, size } (a fileId is resolved to a full descriptor on write) — not fileId strings. rowFromAction mapped the column with .map(String), collapsing each descriptor to the literal "[object Object]". Every listActions consumer received garbage.

Root cause 2 — storage download had no filename/type

Presigned downloads served application/octet-stream with no Content-Disposition, and the signed token carried no filename, so browsers saved files under the URL token.

Changes

approvals (@objectstack/spec, @objectstack/plugin-approvals)

  • New ApprovalActionAttachment; ApprovalActionRow.attachments is now ApprovalActionAttachment[]. rowFromAction passes descriptors through (tolerating a bare-string fileId). Decision input stays string[]. Consumers no longer need read access to the system sys_file object.

storage (@objectstack/spec, @objectstack/service-storage)

  • getSignedUrl / getPresignedDownload take optional PresignedDownloadOptions { filename, contentType, disposition }.
  • REST download routes (GET /storage/files/:id/url, /:id) pass the sys_file name + mime_type.
  • Local adapter carries them in the signed token; _local/raw emits Content-Type + RFC 5987 Content-Disposition (ASCII fallback + filename*=UTF-8''…). S3 adapter uses ResponseContentType / ResponseContentDisposition. Default inline preserves in-browser preview.

Testing

  • @objectstack/plugin-approvals: 165/165 (incl. new descriptor-passthrough cases).
  • @objectstack/service-storage: 104/104 (incl. new token-metadata + Content-Disposition helper cases).
  • End-to-end in app-showcase (approvals + storage): chip shows signed-contract.pdf, click opens the real PDF, and the download responds content-type: application/pdf + content-disposition: inline; filename="signed-contract.pdf".

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 26, 2026 8:16am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/plugin-approvals, packages/services, @objectstack/spec.

106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/plugin-approvals, packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via packages/services, @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/audit-service.mdx (via packages/services)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/services, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/settings-service.mdx (via packages/services)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-approvals, packages/services, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/services, @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/plugin-approvals, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/plugin-approvals, @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

baozhoutao and others added 2 commits July 26, 2026 03:54
… and correct download filename (#3504)

The approval inbox timeline showed a nameless "附件" chip that did nothing on
click, and even the downloaded file was named after the opaque URL token. Two
root causes, both in the framework:

approvals — `sys_approval_action.attachments` (a `Field.file`) stores rich
descriptors `{ id, name, url, mimeType, size }`, but `rowFromAction` mapped them
with `.map(String)`, collapsing each to "[object Object]". `ApprovalActionRow.
attachments` is now `ApprovalActionAttachment[]`; the descriptor carries name +
url, so consumers label/open attachments without reading the system `sys_file`.

storage — presigned downloads served `application/octet-stream` with no
`Content-Disposition`. `getSignedUrl`/`getPresignedDownload` now take
`PresignedDownloadOptions { filename, contentType, disposition }`; the REST
download routes pass the `sys_file` name+mime; the local adapter carries them in
the token and `_local/raw` emits an RFC 5987 Content-Disposition; S3 bakes the
same into the signed URL. Default `inline` preserves in-browser preview.

Verified end-to-end in app-showcase. Refs objectui #2820.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…aces

Additive only (0 breaking): ApprovalActionAttachment + PresignedDownloadOptions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(approvals+storage): 审批决策附件在时间线里没名字、点击无反应、下载名是乱码 token (objectui #2820)

1 participant