Skip to content

feat(file): include public share status in File read output#5191

Merged
TheodoreSpeaks merged 5 commits into
stagingfrom
feat/file-read-share-status
Jun 24, 2026
Merged

feat(file): include public share status in File read output#5191
TheodoreSpeaks merged 5 commits into
stagingfrom
feat/file-read-share-status

Conversation

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator

Summary

  • The File block's Read operation now returns each workspace file's public share status alongside the file object.
  • Each returned file gains a share field — the public-safe share record (isActive, url, authType, hasPassword, allowedEmails, …) or null when the file isn't shared.
  • Shares are batch-fetched with getSharesForResources (one query for all files, no N+1).
  • Picker/upload input files have no canonical workspace id, so they carry share: null.

Follow-up to #5177 (Manage Sharing operation).

Type of Change

  • New feature

Testing

  • tsc --noEmit, bun run lint, bun run check:api-validation:strict, and the File block test suite all pass.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

The read operation now attaches each workspace file's public share status as a
"share" field (the share record, or null when not shared), batch-fetched via
getSharesForResources to avoid N+1. Picker/upload input files have no canonical
id and carry share: null.
@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 24, 2026 5:56am

Request Review

@cursor

cursor Bot commented Jun 24, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Read output now includes public share URLs and email allowlists for files the caller can already access; scope is limited to the existing authenticated file manage API.

Overview
The File tool read path now enriches each returned workspace file with a share object so workflows can see link visibility without a separate Manage Sharing step.

After loading files by ID, the route batch-loads public shares via getSharesForResources and maps each file to share: active shares expose visibility (from authType), url, and allowedEmails; missing or inactive shares use a private shape (visibility: 'private', url: null, empty allowlist). Picker/upload entries without a canonical workspace ID skip the lookup and get the same private default.

The File block files output description is updated to note that read results include share status.

Reviewed by Cursor Bugbot for commit cdddec3. Bugbot is set up for automated code reviews on this repo. Configure here.

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR enriches the File block's Read operation by attaching a share field to every returned workspace file, describing its public-link status. Shares are batch-fetched in a single query (getSharesForResources) to avoid N+1, and a documentation string in the block config is updated to reflect the new field.

  • Inactive or absent shares are mapped to { visibility: 'private', url: null, allowedEmails: [] }, so no URL or token is leaked for disabled links — a stronger guard than the manage_sharing path which returns an empty string URL.
  • Active shares surface { visibility: authType, url, allowedEmails } using the same vocabulary as the Manage Sharing operation.
  • Picker/upload files (no canonical workspace ID) unconditionally receive privateReadShare().

Confidence Score: 5/5

Safe to merge — the change is additive, the batch query is correct, and inactive/absent shares are properly masked before being returned.

The new read path handles every case cleanly: inactive shares return { visibility: 'private', url: null, allowedEmails: [] } rather than leaking the token or URL, picker/upload files unconditionally get the private sentinel, and the batch share fetch is guarded against an empty ID list. The change is additive (new field, no schema breakage) and the block description is updated to match.

No files require special attention.

Important Files Changed

Filename Overview
apps/sim/app/api/tools/file/manage/route.ts Adds share enrichment to the read case: batch-fetches shares, maps inactive/absent shares to a private sentinel (no URL/token exposed), and appends share to every returned file entry.
apps/sim/blocks/blocks/file.ts One-line documentation update to reflect the new share field on workspace file objects returned by the Read operation.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client as Workflow Executor
    participant Route as /api/tools/file/manage
    participant WF as WorkspaceFileManager
    participant SM as ShareManager

    Client->>Route: "POST {operation: "read", fileId/fileInput}"
    Route->>Route: "Resolve selectedFileIds & selectedInputFiles"
    Route->>WF: getWorkspaceFile(workspaceId, id) x N
    WF-->>Route: workspace file rows
    Route->>SM: getSharesForResources("file", selectedFileIds)
    Note over SM: Single batch query (no N+1)
    SM-->>Route: "Map<fileId, ShareRecord>"
    Route->>Route: workspaceFileToUserFile() for each file
    Route->>Route: toReadShare(file.id) — inactive/absent → privateReadShare()
    Route->>Route: concat inputFiles with privateReadShare()
    Route-->>Client: "{ files: [...{ ...file, share: { visibility, url, allowedEmails } }] }"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client as Workflow Executor
    participant Route as /api/tools/file/manage
    participant WF as WorkspaceFileManager
    participant SM as ShareManager

    Client->>Route: "POST {operation: "read", fileId/fileInput}"
    Route->>Route: "Resolve selectedFileIds & selectedInputFiles"
    Route->>WF: getWorkspaceFile(workspaceId, id) x N
    WF-->>Route: workspace file rows
    Route->>SM: getSharesForResources("file", selectedFileIds)
    Note over SM: Single batch query (no N+1)
    SM-->>Route: "Map<fileId, ShareRecord>"
    Route->>Route: workspaceFileToUserFile() for each file
    Route->>Route: toReadShare(file.id) — inactive/absent → privateReadShare()
    Route->>Route: concat inputFiles with privateReadShare()
    Route-->>Client: "{ files: [...{ ...file, share: { visibility, url, allowedEmails } }] }"
Loading

Reviews (5): Last reviewed commit: "chore(file): drop verbose comment in rea..." | Re-trigger Greptile

Comment thread apps/sim/app/api/tools/file/manage/route.ts Outdated
@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR enriches the File block's Read operation so each returned workspace file carries a share field containing its public share record (isActive, url, authType, hasPassword, allowedEmails) or null if the file has no share entry. Shares are fetched in a single batched query via getSharesForResources, eliminating any N+1 risk; picker/upload input files (no canonical workspace ID) always receive share: null.

  • route.ts: The read case now calls getSharesForResources and spreads the result onto each mapped file object; the existing manage_sharing and all other operations are unchanged.
  • file.ts / get.ts: Output descriptions updated to document the new share field, scoped to the read operation.

Confidence Score: 4/5

The change is narrow and well-contained; the batched share fetch is correct and avoids N+1. One behavioral difference from the rest of the file: inactive share tokens and URLs are returned in the read response even though manage_sharing deliberately strips the URL when a share is inactive.

The logic is correct and the manage_sharing branch is untouched. The only notable gap is that manage_sharing applies { ...share, url: '' } for inactive shares but the new read path passes the raw ShareRecord through, exposing the token to read-only workspace members. This is a design inconsistency rather than an exploitable bug, so it does not block the merge.

apps/sim/app/api/tools/file/manage/route.ts — specifically the inactive-share URL treatment in the read case around line 429.

Important Files Changed

Filename Overview
apps/sim/app/api/tools/file/manage/route.ts Adds batch share-status enrichment to the read case; works correctly but passes inactive share tokens/URLs to the client unlike the manage_sharing case which strips the URL when inactive.
apps/sim/blocks/blocks/file.ts Description-only update to the files output; correctly qualifies the new share field as specific to the read operation.
apps/sim/tools/file/get.ts Description-only update to the fileReadTool outputs; accurately reflects the new share field in the returned file objects.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant FileManageAPI as /api/tools/file/manage
    participant WorkspaceFileManager
    participant ShareManager as share-manager (getSharesForResources)

    Client->>FileManageAPI: "POST { operation: "read", fileId: [...] }"
    FileManageAPI->>FileManageAPI: assertActiveWorkspaceAccess
    FileManageAPI->>WorkspaceFileManager: getWorkspaceFile(workspaceId, id) x N
    WorkspaceFileManager-->>FileManageAPI: workspaceFile[]
    FileManageAPI->>ShareManager: getSharesForResources("file", selectedFileIds)
    ShareManager-->>FileManageAPI: "Map<fileId, ShareRecord>"
    FileManageAPI->>FileManageAPI: map files to userFiles with share field
    FileManageAPI->>FileManageAPI: concat picker/upload files with share: null
    FileManageAPI-->>Client: "{ files: [{ ...userFile, share: ShareRecord | null }] }"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client
    participant FileManageAPI as /api/tools/file/manage
    participant WorkspaceFileManager
    participant ShareManager as share-manager (getSharesForResources)

    Client->>FileManageAPI: "POST { operation: "read", fileId: [...] }"
    FileManageAPI->>FileManageAPI: assertActiveWorkspaceAccess
    FileManageAPI->>WorkspaceFileManager: getWorkspaceFile(workspaceId, id) x N
    WorkspaceFileManager-->>FileManageAPI: workspaceFile[]
    FileManageAPI->>ShareManager: getSharesForResources("file", selectedFileIds)
    ShareManager-->>FileManageAPI: "Map<fileId, ShareRecord>"
    FileManageAPI->>FileManageAPI: map files to userFiles with share field
    FileManageAPI->>FileManageAPI: concat picker/upload files with share: null
    FileManageAPI-->>Client: "{ files: [{ ...userFile, share: ShareRecord | null }] }"
Loading

Reviews (2): Last reviewed commit: "feat(file): include public share status ..." | Re-trigger Greptile

Comment thread apps/sim/app/api/tools/file/manage/route.ts Outdated
Comment thread apps/sim/app/api/tools/file/manage/route.ts Outdated
…internals

Read's per-file "share" is now { visibility, url, allowedEmails } using the
same visibility vocabulary as Manage Sharing: 'private' when not shared (url
null, no config) instead of null, otherwise public/password/email/sso with the
link. Drops row internals (id, token, resourceType, resourceId, isActive,
hasPassword).
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2d3707c. Configure here.

Comment thread apps/sim/app/api/tools/file/manage/route.ts Outdated
Input (picker/upload) files only have a synthetic id (storage key/URL), so
looking them up in the shares map could collide with a canonical file id and
attach the wrong share. Give them an explicit private share instead.
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

@TheodoreSpeaks TheodoreSpeaks merged commit 8c5da02 into staging Jun 24, 2026
16 checks passed
@TheodoreSpeaks TheodoreSpeaks deleted the feat/file-read-share-status branch June 24, 2026 06:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant