Skip to content

feat(api): scope endpoints and live-link groundwork for Files, Links, Storage - #1069

Merged
Zach Dunn (zachdunn) merged 14 commits into
mainfrom
claude/change-feeds-web-ui-37caca
Oct 4, 2026
Merged

Zach Dunn (zachdunn) merged 14 commits into
mainfrom
claude/change-feeds-web-ui-37caca

Conversation

@zachdunn

@zachdunn Zach Dunn (zachdunn) commented Oct 4, 2026 •

Copy link
Copy Markdown
Member

API groundwork for the workspace redesign: a Files tab organized by pull request, repo and page; a Links tab for live links and galleries; and a Storage tab for the bucket. In the UI, change feeds become live links. This PR is slice 1 of 5 and ships no UI.

What changes

  • One query behind live links and the signed-in views: new apps/api/src/pr-scope.ts. It matches gh.repo and an optional gh.number, excludes promoted copies, and does not require path. It takes an optional file-type filter and pages with a keyset cursor. Live links (findLatestRepoScreenshots) now run through it.
  • New endpoints under /v1/workspaces/:ws, session or bearer auth, files:read:
    • GET /pulls: PRs with media, newest first. Defaults to the last 90 days; all=1 lifts that. Each row has a title, state and up to 4 thumbnails.
    • GET /repos: repos with media and their open-PR counts.
    • GET /scope/:owner/:repo/files?number=&type=: one PR's or repo's files. The response also carries:
      • privateCount: null when the scope is too large to count.
      • liveLink: the existing live link for this scope, if any.
      • pull: branch, title and state.
  • Public live links:
    • GET /public/feeds/:id pages with ?cursor=, so long feeds no longer stop at 50 items.
    • New GET /public/feeds/:id/items/:item returns one item with its previous and next neighbours.
    • The public cursor carries {updatedAt, itemId}, never an object key.
  • Who created a link: a new feeds.source column records comment (GitHub App comment sync) or user (API). The API ignores any source a client sends.
  • Feed cap: only repo-wide feeds (number = 0) count toward the cap of 50. PR and issue feeds are uncapped, so PR comments no longer fall back to /f/ links once a workspace has 50 PRs.
  • PR title and state on the rollup: github_pr_activity gains title and state. The pull_request webhook writes them, and /pulls refreshes them when it reads the rows.
  • Repo names stored lowercase: gh.repo is lowercased on write.
  • One-time cleanup: POST /admin/file-metadata/backfill-gh-repo-case (supports ?dryRun=1) lowercases older rows. It is run by hand; the steps are in docs/ops.md.
  • Shared helpers: @uploads/comment-render/scope holds fileTypeClassFromKey, isInFeedScope and feedItemIdFor. The CLI gets a generated copy.

Privacy

  • A stored PR title is returned only for repos linked to the caller's workspace (github_repo_links). Lookups use resolveTitles with the member audience from fix(api): keep private PR titles out of other workspaces and public pages #1065.
  • When a different workspace uploads media for the same PR, that PR's rollup row drops the title and state the first workspace stored.
  • The title backfill and the webhook write only touch rows that belong to the caller's workspace.
  • Public endpoints never return source, linked-repo information, titles looked up for signed-in members, or object keys of private files.

Migrations (apply to production on merge)

  • 20261004120000_feeds_source.sql: adds feeds.source.
  • 20261004120100_pr_activity_title_state.sql: adds title and state, plus a cursor index.
  • 20261004120200_file_metadata_gh_repo_idx.sql: adds a partial index on gh.repo rows.

All three only add columns or indexes.

After deploy

Run the gh.repo case backfill once, dry run first. The steps are in docs/ops.md.

Testing

  • pnpm test, pnpm typecheck and pnpm check pass.
  • New tests run on the real-SQLite harness: scope query and cursor, feed source and cap, rollup title and state (including the second-workspace case), the /pulls, /repos and /scope routes, public pagination and the item endpoint, and the backfill.
  • EXPLAIN QUERY PLAN tests check that the scope query uses the new index.

Summary by CodeRabbit

  • New Features
    • Added workspace views for pull requests, repositories, and repository or pull-request files, with filtering, pagination, thumbnails, and live-link details.
    • Added public feed pagination and item pages with neighboring items and position details.
    • Pull-request listings now include titles and states, with filters for repository, state, and activity date.
    • Feeds now track their source; user-created repository feeds retain a limit, while pull-request and issue feeds are not capped.
  • Bug Fixes
    • Repository names are matched consistently regardless of letter case.

…rom repos

countPrivateScopeItems probed every key past the first page (up to 2,000), at two or more R2 operations each, which could pass the 1,000-subrequest ceiling and fail /scope with a 503 on busy repos. It now returns null when more than 300 keys need a probe, and probes none of them.

listWorkspaceRepos now drops gh.status=promoted shadows like scopeFrom, so a repo holding only shadows does not list and a shadow never sets lastUpdatedAt.

docs/ops.md: the backfill dry-run comment now matches the route's output.
@changeset-bot

changeset-bot Bot commented Oct 4, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 7df2056

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@zachdunn

Copy link
Copy Markdown
Member Author

CodeRabbit (@coderabbitai) review

@coderabbitai

coderabbitai Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f1a10465-75b5-4146-a2a3-bc818ee07d1d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4cdd2f7e-a227-45bd-9552-030a0a6f44be
📥 Commits

Reviewing files that changed from the base of the PR and between 35409dc and 7df2056.

⛔ Files ignored due to path filters (1)
  • packages/uploads/src/comment-render-scope.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (38)
  • apps/api/migrations/20261004120000_feeds_source.sql
  • apps/api/migrations/20261004120100_pr_activity_title_state.sql
  • apps/api/migrations/20261004120200_file_metadata_gh_repo_idx.sql
  • apps/api/package.json
  • apps/api/src/feed-service.ts
  • apps/api/src/feeds.ts
  • apps/api/src/file-metadata.ts
  • apps/api/src/file-type-sql.ts
  • apps/api/src/gh-repo-case-backfill.ts
  • apps/api/src/github-comment.test.ts
  • apps/api/src/github-comment.ts
  • apps/api/src/github-pr-activity.ts
  • apps/api/src/github-repo-links.ts
  • apps/api/src/github-webhook-queue.test.ts
  • apps/api/src/github-webhook.ts
  • apps/api/src/index.ts
  • apps/api/src/poster.ts
  • apps/api/src/pr-scope.ts
  • apps/api/src/routes/admin.ts
  • apps/api/src/routes/feeds.ts
  • apps/api/src/routes/public-feeds.ts
  • apps/api/src/routes/workspace-github.ts
  • apps/api/src/routes/workspace-scope.ts
  • apps/api/src/scope-service.ts
  • apps/api/src/scope-wire.ts
  • apps/api/test/comment-render-scope.test.ts
  • apps/api/test/feeds-sqlite.test.ts
  • apps/api/test/file-type-sql.test.ts
  • apps/api/test/gh-repo-case-backfill-sqlite.test.ts
  • apps/api/test/github-pr-activity-sqlite.test.ts
  • apps/api/test/pr-scope-sqlite.test.ts
  • apps/api/test/routes-feeds.test.ts
  • apps/api/test/routes-workspace-scope.test.ts
  • apps/web/public/.well-known/openapi.json
  • docs/ops.md
  • packages/comment-render/package.json
  • packages/comment-render/src/scope.ts
  • packages/uploads/scripts/inline-shared.mjs

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

This change adds workspace-scoped pull, repository, and file endpoints; PR activity rollups; paginated public feeds with source attribution; and lowercase gh.repo metadata handling with an admin backfill.

Changes

Workspace Scope and PR Activity

Layer / File(s) Summary
Scope queries and shared contracts
apps/api/src/pr-scope.ts, apps/api/src/file-metadata.ts, apps/api/src/file-type-sql.ts, apps/api/src/scope-wire.ts, packages/comment-render/src/scope.ts, apps/api/test/pr-scope-sqlite.test.ts, related migrations, exports, and tests
Adds shared scope filters, pagination cursors, file-type handling, and DTOs. Stores and searches gh.repo values in lowercase.
PR activity rollups
apps/api/src/github-pr-activity.ts, apps/api/src/github-webhook.ts, apps/api/src/github-repo-links.ts, apps/api/test/github-pr-activity-sqlite.test.ts, apps/api/src/routes/workspace-github.ts, related migration and tests
Adds paginated workspace PR activity, open-pull counts, and webhook title/state updates. Clears title and state when activity ownership changes.
Workspace scope endpoints
apps/api/src/routes/workspace-scope.ts, apps/api/src/scope-service.ts, apps/api/src/index.ts, apps/api/test/routes-workspace-scope.test.ts, apps/api/src/poster.ts
Adds authenticated pull, repository, and scope-file routes. Responses include cursors, thumbnails, live-link details, and private-file counts.

Public Feeds

Layer / File(s) Summary
Feed source and public pagination
apps/api/src/feeds.ts, apps/api/src/feed-service.ts, apps/api/src/routes/public-feeds.ts, apps/api/src/github-comment.ts, apps/api/test/routes-feeds.test.ts, apps/web/public/.well-known/openapi.json, related migration, package exports, and tests
Adds feed source values and source-aware repo-feed caps. Public feed responses support cursors, and the item route returns an item with neighboring IDs and position. Comment sync sets the source to comment and uses the shared item ID helper.

Repository Metadata Backfill

Layer / File(s) Summary
Batched case backfill
apps/api/src/gh-repo-case-backfill.ts, apps/api/src/routes/admin.ts, apps/api/test/gh-repo-case-backfill-sqlite.test.ts, docs/ops.md
Adds a dry-run option and bounded batches for lowercasing existing gh.repo values. Adds an admin endpoint and operator instructions.

Priority: ⬇️ Low

Estimated code review effort: 4 (Complex) | ~60 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant workspaceScope
  participant scopeFilesHandler
  participant prScopeQuery
  participant Storage
  Client->>workspaceScope: Request scoped files
  workspaceScope->>scopeFilesHandler: Apply route authentication
  scopeFilesHandler->>prScopeQuery: Query files and cursor
  prScopeQuery-->>scopeFilesHandler: Return scope page
  scopeFilesHandler->>Storage: Hydrate file items
  Storage-->>scopeFilesHandler: Return visibility and URLs
  scopeFilesHandler-->>Client: Return files and pagination data
Loading

Merge Risk: ⚪ Minimal · up to 7df20

This API-only change adds workspace scope views, paginated public feeds, PR rollups, and a repository-case backfill. No confirmed defect blocks merging. Operators still need to run the documented backfill after deployment so older mixed-case repository files appear in the new views.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 7df20

Workspace authorization and public-content withholding remain in place. However, the new anonymous item endpoint performs scope-sized database reads and sequential hashing without application-level request limiting, creating a bounded resource-abuse risk. Production traffic controls and deployment sequencing remain unverified.

Retained concerns

  • Medium · security · inferred: The new anonymous item lookup performs a scope scan and sequential item-ID hashing before rejecting an absent item. Someone knowing a live feed ID can repeatedly trigger work proportional to as many as 2,000 matching keys without application-level rate limiting on this route. This expands the public resource-abuse surface of the shared API; production edge mitigation and operational impact are not established.
Security review details

Security Blast Radius

  • inferred — A known live feed ID permits anonymous work within that feed's workspace and scope, not arbitrary tenant selection. Repeated item lookups can nevertheless consume shared API and database resources. The backfill has all-workspace mutation scope, but requires existing administrative authority and only canonicalizes repository metadata values.

Security Findings and Attack Paths

  • inferred — The retained architecture concern is resource amplification: a valid-format absent item ID causes every scanned key to be hashed before a not-found response. The 2,000-key cap bounds each request, but no application limiter is registered on the public router. Exploitation impact depends on feed size, request volume and production edge controls; no outage or cross-tenant data disclosure was demonstrated.

Trust Boundaries and Controls

  • observed — All new workspace scope routes apply dual authentication and files:read before handlers. Session callers are checked for membership in the requested workspace; bearer validation uses that workspace to validate the token. Scope SQL also constrains rows by workspace.
  • observed — The admin router accepts the configured static secret or an active operator token, requiring operator:write for POST. Operator scopes are explicitly gated on platform-admin status at issuance, unlike workspace-governance scopes. Existing admin endpoints already perform global operations, so the new backfill does not establish a tenant-token privilege escalation.
  • observed — Workspace thumbnails bypass storage HEAD checks and project public URLs directly, but only after workspace authorization and files:read. The existing authenticated file-search route already returns the same URL projection without visibility annotations; this is counterevidence to treating thumbnail projection alone as a newly introduced privacy bypass.

Resilience and Maintainability Implications

  • inferred — Backfill interruption or concurrent repetition can leave counts temporarily stale but does not require rollback of completed rows: each update converges to the same lowercase value and preserves workspace/object ownership. Safe completion during mixed-version deployment is still unproven because older writers may reintroduce mixed-case values.

Hardening Proposals

  • proposed — Apply an explicit abuse budget to public item and cursor lookups, or replace whole-scope hashing with an indexed item-identity lookup. Preserve feed-scoped resolution and private-content withholding.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.90% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 102 functions across 31 files. (7 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the API scope endpoints and live-link groundwork, which are central changes in the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 54.90% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 102 functions across 31 files. (7 skipped: 7 unsupported.)

✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit pages feeds at dawn,
Then hops through scopes from dusk till morn.
New cursors mark the files in flight,
PR states settle into sight.
“gh.repo” turns lowercase, neat,
And carrots crown the shipped-code feat.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
uploads-api 7df2056 Commit Preview URL

Branch Preview URL
Oct 04 2026, 09:47 PM

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
uploads-auth 7df2056 Oct 04 2026, 09:47 PM

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
uploads-web 7df2056 Commit Preview URL

Branch Preview URL
Oct 04 2026, 09:47 PM

@zachdunn
Zach Dunn (zachdunn) added this pull request to stack #1071 October 4, 2026 22:29
@zachdunn Zach Dunn (zachdunn) added the coderabbit:review Trigger CodeRabbit review for the PR. label Oct 4, 2026
@zachdunn
Zach Dunn (zachdunn) merged commit de65be0 into main Oct 4, 2026
6 checks passed
@zachdunn
Zach Dunn (zachdunn) deleted the claude/change-feeds-web-ui-37caca branch October 4, 2026 23:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

coderabbit:review Trigger CodeRabbit review for the PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant