Skip to content

fix(files): stop promising immutable caching for documents compiled against other files - #8129

Merged
waleedlatif1 merged 5 commits into
stagingfrom
fix/serve-immutable-cache-referenced-docs
Sep 22, 2026
Merged

waleedlatif1 merged 5 commits into
stagingfrom
fix/serve-immutable-cache-referenced-docs

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • A versioned serve URL (?v=<updatedAt>) was always answered with a one-year immutable Cache-Control. That is right for a stored source — a content write rotates the storage key, so a given key's bytes never change — but wrong for a response resolved against OTHER files: a document compiled against the files it references, or a sim page inlining its images, is re-resolved on every request
  • Those bytes change when a referenced file changes, while this file's key and updatedAt stay put — so the URL is unchanged and the browser keeps serving a stale render until the document itself is edited
  • Cacheability is now a required property every resolver branch declares, so a branch added later cannot inherit the cacheable default by saying nothing
  • Those responses can't be given a cache lifetime instead, so to keep revalidation cheap authorized serves now carry an ETag and answer 304 to a matching If-None-Match — the focus refetch costs a conditional request instead of the whole file. Matching is weak, per RFC 9110
  • Corrects three comments claiming generated docs are edited in place under the same storage key. That stopped being true in feat(platform): settings permissions, admin, billing attribution/concurrency lims #5545 (2026-07-13), which made every content write allocate a new key — the caching rule was reasoned from the stale claim

Notes

  • The digest is deliberately not in createFileResponse: it costs a pass over the buffer (up to the 100MB transfer ceiling) and an immutable response is never revalidated, so it would pay that pass and never collect. Public assets and the assistant-image path are unchanged
  • Serving unchanged bytes still does the same server-side work (source read, referenced-file metadata, artifact lookup); the 304 saves the transfer. Deriving the validator before fetching the artifact would save that lookup too, and is left as a follow-up

Type of Change

  • Bug fix

Testing

Route tests cover both cache lifetimes; unit tests cover the validator, the 304, weak matching, list and wildcard matching, and validator uniqueness. Each new test was verified to fail against the previous behavior, and the required-cacheability gate was verified to fail the build when a branch omits it. apps/sim type-check, lint, 47 audits, docs-manifest check, and 340 tests across the files, workspace-files hook, and copilot doc suites 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)

…gainst other files

A versioned serve URL (`?v=<updatedAt>`) was always answered with a one-year
`immutable` Cache-Control. That holds for a stored source — a content write
rotates the storage key, so a given key's bytes never change — but not for a
response the route resolves against OTHER files: a document compiled against the
files it references, or a sim page inlining its images, recompiles on every
request. Those bytes change when a referenced file changes, while this file's key
and `updatedAt` stay put, so the whole URL is unchanged and the browser served a
stale render from cache until the document itself was edited.

The resolver now reports when it read referenced content, and the route withholds
the immutable lifetime for exactly those responses, keeping it for stored sources
and self-contained artifacts.

Also corrects three comments that claimed generated docs are edited in place under
the same storage key. That stopped being true in #5545 (2026-07-13), which made
every content write allocate a new key; the caching rule above was reasoned from
the stale claim.
@vercel

vercel Bot commented Sep 22, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated
docs Skipped Skipped Sep 22, 2026 6:39am UTC

Request Review

@greptile-apps

greptile-apps Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge; both previous findings are resolved and no new actionable defects remain.

Summary

This PR corrects file-serving cache semantics for generated content whose bytes depend on other workspace files.

  • Requires resolver and compiler branches to declare whether output depends on referenced files.
  • Keeps versioned stored-byte responses immutable while forcing reference-dependent output to revalidate.
  • Adds byte-derived ETags and conditional 304 responses for revalidated authorized serves.
  • Avoids hashing immutable responses, addressing the previous performance finding.
  • Updates tests and comments to reflect storage-key rotation and the new cacheability contract.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Resolve requested file] --> B{Depends on referenced files?}
    B -- Yes --> C[Use private no-cache must-revalidate]
    C --> D[Hash resolved bytes and attach ETag]
    D --> E{If-None-Match matches?}
    E -- Yes --> F[Return 304]
    E -- No --> G[Return 200 with body]
    B -- No --> H{Versioned URL?}
    H -- Yes --> I[Use one-year immutable caching]
    I --> J[Return 200 without computing ETag]
    H -- No --> C
Loading

Reviews (3) · Last reviewed commit: "improvement(files): compute a validator ..."

Comment thread apps/sim/lib/copilot/tools/server/files/doc-compile.ts
…erty

An optional boolean let a branch added to the resolver inherit the cacheable
default by saying nothing — the exact failure this change exists to prevent.
Cacheability is now a required field every branch must declare, so forgetting it
fails the build rather than silently promising a year of immutability.
…e whole body

A response the browser is told to revalidate carried no validator, so every check
re-sent the entire file. That is the cost a document compiled against other files
now pays on each window focus: it cannot be given a cache lifetime, because its
bytes really may have changed, so the only way to make the check cheap is to let
the client prove what it already holds.

Authorized serves now carry an ETag — the digest of the bytes about to be sent,
which is exact by construction however those bytes were produced — and answer 304
to a matching If-None-Match. Matching is weak, per RFC 9110, so a cache that
stored a weak validator still revalidates.

Kept out of createFileResponse deliberately: digesting costs a pass over the
buffer, up to the 100MB transfer ceiling, and a response served as immutable is
never revalidated, so it would pay that pass and never collect. Public assets and
the assistant-image path are unchanged.
…e path

The isolated-VM fallback returns before the static reference scan, so a document
it compiled never reported one — yet that path reads workspace files live through
its broker, which is what `onWorkspaceFileAccess` records. A versioned request for
such a document therefore still took a one-year immutable lifetime, and changing a
referenced file left the browser serving a stale render.

Fixed at the root rather than in that one branch: the flag is now required on
CompiledDocResult, so every compile path must declare it and a new one cannot
inherit a cacheable-forever answer by staying silent. Each site reports the union
of what the source references statically and what the compile actually touched —
neither alone is sufficient, since a failed read records no access and the broker
reaches files the static scan cannot see.

Making it required immediately surfaced a second case: the process-local compile
cache is shared with compiles that carried a workspace, so a cached entry can hold
contributor identities even when the reading call passes none. That branch now
reads the cached identities instead of assuming independence.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

Comment thread apps/sim/app/api/files/serve/[...path]/route.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 8 files

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/lib/copilot/tools/server/files/doc-compile.ts
Comment thread apps/sim/app/api/files/utils.ts
…revalidated

Both reviewers caught the same contradiction: the digest was documented as worth
paying only where a 304 can be collected, then applied to every authorized serve
including immutable ones, which are never revalidated. One place now decides, so
an immutable response takes the plain path and spends no pass over its buffer.

Also drops a redundant translation. The resolver was converting the compiler's
boolean into a string union and the cache rule was converting it straight back;
the producer's own required boolean now travels end to end, which is one fact in
one shape and keeps the same build-time guarantee that a new branch must declare it.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 8 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

@waleedlatif1
waleedlatif1 merged commit 32fcc63 into staging Sep 22, 2026
34 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/serve-immutable-cache-referenced-docs branch September 22, 2026 06:44

This branch was previously deployed

1 inactive deployment
Preview c6660759 Deployed Sep 22, 2026 by vercel[bot]
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