Skip to content

feat: organizer file-drop import agent (volunteers/judges MVP)#518

Open
theianjones wants to merge 1 commit into
mainfrom
feat/organizer-file-drop-import
Open

feat: organizer file-drop import agent (volunteers/judges MVP)#518
theianjones wants to merge 1 commit into
mainfrom
feat/organizer-file-drop-import

Conversation

@theianjones

@theianjones theianjones commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Drag a roster onto any organizer Volunteers / Judges page. An agent reads the file, matches it against existing volunteers, drafts proposed invitations, and writes nothing until the organizer confirms once. PII stays server-side; source files are stored privately.

Clones the AI Judge Scheduler's blessed pattern: model proposes → server validates → organizer confirms.

What's included

Data model & infra

  • agent_import_runs table (audit / retention / idempotency / Undo anchor) + AI_FILE_IMPORT feature flag
  • OrganizerFileImportAgent Durable Object — namespace + binding in alchemy.run.ts, WS routing + class re-export in server.ts, Env augmentation in env.d.ts

Private upload/api/agent-import/upload stores the file under an unguessable R2 key (agent-imports/{competitionId}/{importRunId}/{file}), computes a sha-256 checksum, and returns no public URL.

Agent — proposal-only tools (get_page_context, get_import_table, propose_volunteer, revoke_proposal, ask_clarification, mark_complete), a refine(instruction) loop, and intent disambiguation. Duplicate detection runs deterministically server-side (classifyVolunteer) so existing-volunteer emails never reach the model.

Confirm / UndoapplyOrganizerImportFn is the only write path: re-validates each proposal, invites via the existing inviteUserToTeam helper (sends invite emails on confirm), and records created entities for the receipt + undoImportFn.

UIImportShell mounted once in the organizer layout: full-page drop overlay + persistent click-to-upload dock (gated to volunteers/judges via usePageIntent). ImportReviewDrawer renders the proposals as the preview with per-row exclude, refine-by-prompt, a single confirm, and a receipt with Undo.

The 4-layer entitlement contract

Re-checked at the page loader, the write server fn, the agent WS route, and the agent @callable() entrypoint (requireFileImportAgentAccess), all gated by AI_FILE_IMPORT.

Tests & checks

  • parse + validate unit tests — 18 passing
  • pnpm type-check clean (0 errors), Biome clean
  • lat.md updated (architecture + organizer-dashboard); lat check passes

Before enabling

  • pnpm db:push to apply the new agent_import_runs table to the dev branch
  • Seed the AI_FILE_IMPORT feature and grant it to the target team(s)
  • pnpm alchemy:dev to deploy the new DO binding locally

Follow-ups (intentionally out of scope)

  • Events / event-detail import + inline-diff surface (schemas are forward-compatible)
  • XLSX parsing verified under Workers + a binary fixture test
  • Full-screen review variant; hard-private bucket / signed reads for source files

🤖 Generated with Claude Code


Summary by cubic

Drag-and-drop roster import on organizer Volunteers and Judges pages. Files are parsed on the server, matched to existing volunteers, previewed as proposed invites, and only applied after a single confirm; source files are stored privately.

  • New Features

    • Private upload at /api/agent-import/upload; files go to a private R2 key with checksum and no public URL.
    • Session-scoped import worker streams proposals; each drop is isolated and reconnects cleanly.
    • Server-side parsing for CSV/TSV/XLSX and deterministic dedupe; existing-volunteer emails never leave the server.
    • Review drawer with per-row exclude, optional instruction to refine, single confirm to apply, and Undo.
    • A single apply path re-validates, sends team invites, and records created entities for audit and Undo.
    • New agent_import_runs table and feature-flag gated access, enforced at page, upload route, socket, and server entrypoint.
    • Import surface is mounted once in the organizer layout with an overlay and dock; only shown on Volunteers/Judges routes.
  • Migration

    • Apply database changes to create the new import table.
    • Grant the file import feature to the target team(s).
    • Start the app with the new Durable Object binding deployed in your dev environment.

Written for commit 6dff925. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added AI-assisted file-drop import for organizer volunteer/judge management. Users can drag-and-drop or select CSV, TSV, or XLSX files on organizer pages; the system parses tables and uses AI to draft import proposals for review, refinement, and confirmation with undo support.
  • Chores

    • Added runtime dependencies for CSV/spreadsheet parsing.

Drag a roster onto an organizer page; an agent reads it, matches against
existing volunteers, drafts proposals, and writes nothing until the organizer
confirms once. Clones the AI Judge Scheduler "model proposes -> server
validates -> organizer confirms" architecture.

Backend
- agent_import_runs table + id generator + AI_FILE_IMPORT feature flag
- OrganizerFileImportAgent Durable Object (proposal-only tools, refine loop,
  intent disambiguation); namespace + binding in alchemy.run.ts; WS routing +
  re-export in server.ts; Env augmentation in env.d.ts
- Private upload route /api/agent-import/upload (unguessable R2 key, no public
  URL, sha-256 checksum) - PII stays server-side
- Shared schemas/parse/validate under src/lib/organizer-file-import (pure,
  unit-tested); server-only access (4-layer entitlement gating) + context
  loaders under src/server/organizer-file-import
- Server fns: createImportRunFn, loadFileImportContextFn (paywall probe),
  applyOrganizerImportFn (only write path, reuses inviteUserToTeam, records
  applied entities), undoImportFn

UI
- ImportShell mounted in the organizer layout: full-page drop overlay +
  persistent click-to-upload dock, gated to volunteers/judges by usePageIntent
- ImportReviewDrawer: streamed proposals as the preview, per-row exclude,
  refine-by-prompt, single confirm, receipt + undo

Tests: parse + validate unit tests (18 passing). Type-check + biome clean.
lat.md updated (architecture + organizer-dashboard); lat check passes.

Events/event-detail import, inline-diff surface, and XLSX fixture tests are
scoped as documented follow-ups. Requires `pnpm db:push` (new table) and
seeding the AI_FILE_IMPORT feature before enabling for a team.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds a complete organizer file-drop import feature: a Drizzle schema for import runs, file parsing utilities (CSV/TSV/XLSX), server authorization/context helpers, a POST /api/agent-import/upload route, a Cloudflare Durable Object agent (OrganizerFileImportAgent) that uses AI tool-calling to draft volunteer proposals, server functions for applying/undoing imports, and a React ImportShell/ImportReviewDrawer UI wired into the organizer competition layout.

Changes

Organizer File-Drop Import

Layer / File(s) Summary
DB schema, Zod schemas, and shared contracts
src/config/features.ts, src/db/schemas/agent-imports.ts, src/db/schemas/common.ts, src/db/schema.ts, src/lib/organizer-file-import/schemas.ts, src/types/env.d.ts
Defines agentImportRunsTable with lifecycle statuses (createdappliedrejected) and route kinds, the AppliedEntity undo interface, createAgentImportRunId, the AI_FILE_IMPORT feature flag, ORGANIZER_FILE_IMPORT_AGENT env binding, and all shared Zod schemas for agent state, volunteer/event proposals, tool inputs, and client→agent messages.
File parsing and volunteer classification
src/lib/organizer-file-import/parse.ts, src/lib/organizer-file-import/validate.ts, package.json, test/lib/organizer-file-import/parse.test.ts, test/lib/organizer-file-import/validate.test.ts
Adds papaparse/xlsx dependencies and implements ParsedTable parsing for CSV/TSV/XLSX/plain-text with MIME/extension routing and row bounding. Adds deterministic classifyVolunteer, isBlockedVolunteer, and isApplicableVolunteer helpers. Includes Vitest suites for both.
Server authorization scopes and context helpers
src/server/organizer-file-import/access.ts, src/server/organizer-file-import/context.ts
Adds FileImportScope/FileImportRunScope interfaces, scope resolvers (loadFileImportScope, loadFileImportScopeByRun), three authorization entry points (requireFileImportTeamAccess, requireFileImportRequestAccess, requireFileImportAgentAccess), userHasManageCompetitionsPermission, plus loadPageContext, loadExistingVolunteers, and readImportFile (R2 download + parse).
Upload API route and DO routing infrastructure
src/routes/api/agent-import/upload.ts, src/server.ts, alchemy.run.ts, src/routeTree.gen.ts
Adds POST /api/agent-import/upload (auth, 15MB limit, SHA-256 checksum, R2 storage, DB update to uploaded). Registers the organizer-file-import-agent Durable Object namespace in alchemy.run.ts. Adds an agent routing branch in server.ts that validates instance name format and session userId before forwarding to the DO stub. Registers the new route in the generated route tree.
Server functions: create run, apply, and undo
src/server-fns/organizer-file-import-fns.ts
Implements createImportRunFn (insert CREATED run), loadFileImportContextFn (paywall probe), applyOrganizerImportFn (idempotent volunteer invite creation via inviteUserToTeam, appliedEntities recording, APPLIED status), and undoImportFn (delete tinv_ invitations, REJECTED status).
OrganizerFileImportAgent Durable Object
src/agents/organizer-file-import-agent.ts
Implements the Durable Object agent class with start, refine, stop, reset, and markApplied public methods; #loadContext/#generate/#handleRunError private helpers; and buildTools defining get_page_context, get_import_table, propose_volunteer, revoke_proposal, ask_clarification, and mark_complete AI tools with bounded thinking log and state broadcast.
UI: ImportShell, ImportReviewDrawer, and organizer layout wiring
src/components/organizer-import/*, src/routes/compete/organizer/$competitionId.tsx, lat.md/architecture.md, lat.md/organizer-dashboard.md
Adds usePageIntent (route→import intent mapping), ImportShell (drag-and-drop/file-pick entry, createImportRunFn + upload), ImportReviewDrawer (agent state binding, ProposalCard, ImportReceipt, confirm/refine/undo). Wraps the organizer competition layout Outlet in ImportShell. Updates architecture and dashboard docs.

Sequence Diagram

sequenceDiagram
    actor Organizer
    participant ImportShell
    participant ServerFns as Server Functions
    participant UploadRoute as POST /api/agent-import/upload
    participant R2
    participant DB as agentImportRunsTable
    participant ServerRouter as server.ts agent router
    participant AgentDO as OrganizerFileImportAgent DO
    participant AIGateway as AI Gateway

    Organizer->>ImportShell: drop/pick file
    ImportShell->>ServerFns: createImportRunFn(competitionId, routeKind)
    ServerFns->>DB: INSERT status=created → importRunId
    ImportShell->>UploadRoute: FormData(file, importRunId)
    UploadRoute->>R2: put(agent-imports/…)
    UploadRoute->>DB: UPDATE status=uploaded, r2Key, checksum
    ImportShell->>ServerRouter: WebSocket /agents/organizer-file-import-agent/{id}__{userId}
    ServerRouter->>AgentDO: forward request
    AgentDO->>AgentDO: requireFileImportAgentAccess
    AgentDO->>R2: readImportFile → ParsedTable
    AgentDO->>DB: loadExistingVolunteers
    AgentDO->>AIGateway: generateText(tools, kickoff prompt)
    AIGateway-->>AgentDO: propose_volunteer tool calls
    AgentDO-->>ImportShell: setState broadcast (volunteerProposals)
    Organizer->>ImportShell: confirm proposals
    ImportShell->>ServerFns: applyOrganizerImportFn(proposals)
    ServerFns->>DB: inviteUserToTeam × N, UPDATE status=applied
    ServerFns-->>ImportShell: ApplyImportResult (added/skipped/failed)
    Organizer->>ImportShell: undo
    ImportShell->>ServerFns: undoImportFn(importRunId)
    ServerFns->>DB: delete tinv_ invitations, UPDATE status=rejected
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • wodsmith/thewodapp#355: Both PRs extend lat.md/architecture.md and lat.md/organizer-dashboard.md with new organizer-facing AI agent documentation patterns.

Poem

🐇 A file drops down with a gentle thud,
The rabbit parses each row through the mud.
CSV, TSV, XLSX too—
"Propose a volunteer!" the AI knew.
With undo receipts and a warm toast glow,
The import drawer puts on quite a show! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. 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 accurately and concisely describes the main feature being introduced: a file-drop import agent for organizer roster management focused on volunteers and judges.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/organizer-file-drop-import

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

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
apps/wodsmith-start/src/server.ts (2)

100-105: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Export OrganizerFileImportAgent from the worker entrypoint.

The new DO namespace is configured with className: "OrganizerFileImportAgent", but this entrypoint does not export that class. That will break DO instantiation at runtime.

Suggested fix
 // Workers runtime requires Durable Object classes to be exported from the entry point
 export { JudgeSchedulerAgent } from "./agents/judge-scheduler-agent"
+export { OrganizerFileImportAgent } from "./agents/organizer-file-import-agent"
 export { ManualRegistrationWorkflow } from "./workflows/manual-registration-workflow"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/wodsmith-start/src/server.ts` around lines 100 - 105, The
OrganizerFileImportAgent class is configured as a Durable Object namespace but
is not exported from the server entrypoint, which will cause runtime
instantiation failures. Add an export statement for OrganizerFileImportAgent
following the same pattern used for other exported agent classes like
JudgeSchedulerAgent, importing it from its respective agents directory module.
Ensure the export is placed with the other agent/workflow exports in the file to
maintain consistency.

109-170: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add required @lat annotations for this new routing logic.

The added server routing code lacks the required code-reference tags.

As per coding guidelines, **/*.{js,ts,rs,go,c,h,py} must tie source code to concepts using // @lat: [[section-id]].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/wodsmith-start/src/server.ts` around lines 109 - 170, The new server
routing logic in the createServerEntry fetch handler is missing required `@lat`
code-reference annotations as per coding guidelines. Add // `@lat`: [[section-id]]
annotations to the routing logic blocks that handle the agent routing for
"judge-scheduler-agent" and "organizer-file-import-agent" namespaces, as well as
to the main fetch handler that processes the agent routing. Ensure each major
section of the routing logic is properly annotated with the appropriate section
identifiers to maintain code-reference tracking.

Source: Coding guidelines

apps/wodsmith-start/src/types/env.d.ts (1)

13-28: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add required @lat code refs for this changed TypeScript segment.

This new binding block is missing the required code-to-concept reference annotation.

As per coding guidelines, **/*.{js,ts,rs,go,c,h,py} must tie source code to concepts using // @lat: [[section-id]].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/wodsmith-start/src/types/env.d.ts` around lines 13 - 28, The TypeScript
interface declaration for CloudflareEnv and the Cloudflare namespace Env in the
env.d.ts file is missing the required code-to-concept reference annotation. Add
a `// `@lat`: [[section-id]]` comment before or at the beginning of the global
declare block or the Cloudflare namespace interface to tie this code segment to
the appropriate concept section as per coding guidelines. The annotation should
reference the relevant section-id that corresponds to this binding configuration
block containing JUDGE_SCHEDULER_AGENT, ORGANIZER_FILE_IMPORT_AGENT, AI,
CF_ACCOUNT_ID, and CF_AIG_GATEWAY.

Source: Coding guidelines

apps/wodsmith-start/src/lib/organizer-file-import/parse.ts (1)

1-139: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add @lat references in this new parser module.

The added source code does not include the required concept-link annotations.

As per coding guidelines, **/*.{js,ts,rs,go,c,h,py} must tie source code to concepts using // @lat: [[section-id]].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/wodsmith-start/src/lib/organizer-file-import/parse.ts` around lines 1 -
139, The parse.ts module is missing required concept-link annotations that tie
source code to documented concepts. Add `// `@lat`: [[section-id]]` comments
throughout the file to annotate the major functions and logical sections
including parseDelimited, parseCsv, parseTsv, parseXlsx, parseImportFile, and
boundTableForModel. Place these annotations before the functions or sections
they describe, following the coding guidelines that require all TypeScript files
to include these concept references for documentation and traceability purposes.

Source: Coding guidelines

apps/wodsmith-start/src/agents/organizer-file-import-agent.ts (1)

1-560: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add required @lat code refs in this new agent module.

The added TypeScript source does not include the mandatory concept-link annotations.

As per coding guidelines, **/*.{js,ts,rs,go,c,h,py} must tie source code to concepts using // @lat: [[section-id]].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/wodsmith-start/src/agents/organizer-file-import-agent.ts` around lines 1
- 560, Add mandatory `// `@lat`: [[section-id]]` concept-link annotations
throughout the new agent module to tie source code to concepts as required by
coding guidelines. Place annotations before the class definition
OrganizerFileImportAgent, before each major callable method (start, refine,
stop, reset, markApplied), and before the significant helper functions
(buildTools, buildKickoffPrompt, buildRefinePrompt, getUserIdFromAgentName,
dedupeStrings) to establish proper code-to-concept traceability as per the
coding standards for TypeScript source files.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/wodsmith-start/alchemy.run.ts`:
- Around line 530-545: Add the required `// `@lat`: [[section-id]]` concept-link
annotation to the `organizerFileImportAgent` Durable Object namespace
definition. Place the annotation as a comment line immediately before the const
declaration to tie this infrastructure source segment to its corresponding
concept section. Apply the same annotation pattern to the additional related
code at lines 678-679 as mentioned in the review comment.

In `@apps/wodsmith-start/package.json`:
- Line 130: Update the xlsx dependency version from 0.18.5 to 0.20.2 or higher
in the package.json file. Locate the xlsx package dependency entry and change
the version specifier from "^0.18.5" to "^0.20.2" or a later version to address
the HIGH severity vulnerabilities. After updating, run npm install or your
package manager's equivalent to apply the changes.

In `@apps/wodsmith-start/src/agents/organizer-file-import-agent.ts`:
- Around line 218-222: The reset() method clears state without canceling any
in-flight generation operations, allowing stale updates to repopulate proposals
after reset. Modify the reset() method to abort or cancel any active generation
(likely stored in the agent state or a class property) before calling
this.setState(initialAgentState). Ensure the abort/cancel operation is called
first, then proceed with clearing the state and returning the success response.

In `@apps/wodsmith-start/src/components/organizer-import/import-shell.tsx`:
- Around line 51-58: The handleDragOver and handleDrop functions return early
when dropEnabled is false without calling e.preventDefault(), allowing the
browser's default file-drop behavior to navigate away from the app. Fix this by
calling e.preventDefault() at the start of both functions before any early
return statements, ensuring the native browser file-drop behavior is always
suppressed regardless of the dropEnabled state.

In `@apps/wodsmith-start/src/components/organizer-import/use-page-intent.ts`:
- Line 1: The file use-page-intent.ts is missing the required LAT code reference
comment at the beginning of the file. Add a `// `@lat`: [[section-id]]` comment at
the very start of the file (before the existing import statements) to tie this
source code to the appropriate concept reference according to the coding
guidelines. Replace section-id with the actual concept reference identifier that
this file belongs to.
- Around line 30-35: The condition checking routeId.includes("/volunteers") is
too broad and will match unintended routes like "/volunteers/shifts" and
"/volunteers/signup-questions". Replace the includes method call with an exact
equality check to match only the specific "/volunteers" roster route. Change the
condition to check if routeId exactly equals "/volunteers" rather than just
containing that substring, so that the AGENT_IMPORT_ROUTE_KIND.VOLUNTEERS intent
is returned only for the intended route.

In `@apps/wodsmith-start/src/routes/api/agent-import/upload.ts`:
- Line 78: The call to loadFileImportScopeByRun(importRunId) is outside the
try/catch block, so when it throws an error for an invalid importRunId, the
exception is unhandled and returns a 500 status instead of the appropriate 400
or 404 error. Move the loadFileImportScopeByRun call inside the existing
try/catch block in the same function so the error can be properly caught and
handled, allowing the API to return the correct HTTP status code for invalid or
missing import runs.

In `@apps/wodsmith-start/src/server/organizer-file-import/context.ts`:
- Around line 129-150: The readImportFile function currently fetches the import
run by ID alone without verifying it belongs to the authorized user or
competition context, creating a security vulnerability where a foreign run ID
could expose another user's file. Modify the WHERE clause in the
db.query.agentImportRunsTable.findFirst call to add additional conditions that
bind the query to trusted scope fields such as competition ID and/or creator ID
from the authorized context. This ensures the function fails closed when the
import run does not match the authorized scope, preventing unauthorized access
to import file payloads.
- Around line 29-170: The functions in this server module are missing the
required `@lat` source-to-concept reference annotations as mandated by the coding
guidelines for TypeScript files. Add `@lat` annotations in comment format (//
`@lat`: [[section-id]]) to the functions loadPageContext, loadExistingVolunteers,
readImportFile, parseVolunteerMetadata, and formatName to tie the source code to
relevant concept sections. Ensure each annotation clearly identifies the
corresponding architectural or domain concept that the function implements.

---

Outside diff comments:
In `@apps/wodsmith-start/src/agents/organizer-file-import-agent.ts`:
- Around line 1-560: Add mandatory `// `@lat`: [[section-id]]` concept-link
annotations throughout the new agent module to tie source code to concepts as
required by coding guidelines. Place annotations before the class definition
OrganizerFileImportAgent, before each major callable method (start, refine,
stop, reset, markApplied), and before the significant helper functions
(buildTools, buildKickoffPrompt, buildRefinePrompt, getUserIdFromAgentName,
dedupeStrings) to establish proper code-to-concept traceability as per the
coding standards for TypeScript source files.

In `@apps/wodsmith-start/src/lib/organizer-file-import/parse.ts`:
- Around line 1-139: The parse.ts module is missing required concept-link
annotations that tie source code to documented concepts. Add `// `@lat`:
[[section-id]]` comments throughout the file to annotate the major functions and
logical sections including parseDelimited, parseCsv, parseTsv, parseXlsx,
parseImportFile, and boundTableForModel. Place these annotations before the
functions or sections they describe, following the coding guidelines that
require all TypeScript files to include these concept references for
documentation and traceability purposes.

In `@apps/wodsmith-start/src/server.ts`:
- Around line 100-105: The OrganizerFileImportAgent class is configured as a
Durable Object namespace but is not exported from the server entrypoint, which
will cause runtime instantiation failures. Add an export statement for
OrganizerFileImportAgent following the same pattern used for other exported
agent classes like JudgeSchedulerAgent, importing it from its respective agents
directory module. Ensure the export is placed with the other agent/workflow
exports in the file to maintain consistency.
- Around line 109-170: The new server routing logic in the createServerEntry
fetch handler is missing required `@lat` code-reference annotations as per coding
guidelines. Add // `@lat`: [[section-id]] annotations to the routing logic blocks
that handle the agent routing for "judge-scheduler-agent" and
"organizer-file-import-agent" namespaces, as well as to the main fetch handler
that processes the agent routing. Ensure each major section of the routing logic
is properly annotated with the appropriate section identifiers to maintain
code-reference tracking.

In `@apps/wodsmith-start/src/types/env.d.ts`:
- Around line 13-28: The TypeScript interface declaration for CloudflareEnv and
the Cloudflare namespace Env in the env.d.ts file is missing the required
code-to-concept reference annotation. Add a `// `@lat`: [[section-id]]` comment
before or at the beginning of the global declare block or the Cloudflare
namespace interface to tie this code segment to the appropriate concept section
as per coding guidelines. The annotation should reference the relevant
section-id that corresponds to this binding configuration block containing
JUDGE_SCHEDULER_AGENT, ORGANIZER_FILE_IMPORT_AGENT, AI, CF_ACCOUNT_ID, and
CF_AIG_GATEWAY.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7a9422ab-5d40-49ff-905f-824c356783fb

📥 Commits

Reviewing files that changed from the base of the PR and between cd37de1 and 6dff925.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (25)
  • apps/wodsmith-start/alchemy.run.ts
  • apps/wodsmith-start/package.json
  • apps/wodsmith-start/src/agents/organizer-file-import-agent.ts
  • apps/wodsmith-start/src/components/organizer-import/import-review-drawer.tsx
  • apps/wodsmith-start/src/components/organizer-import/import-shell.tsx
  • apps/wodsmith-start/src/components/organizer-import/use-page-intent.ts
  • apps/wodsmith-start/src/config/features.ts
  • apps/wodsmith-start/src/db/schema.ts
  • apps/wodsmith-start/src/db/schemas/agent-imports.ts
  • apps/wodsmith-start/src/db/schemas/common.ts
  • apps/wodsmith-start/src/lib/organizer-file-import/parse.ts
  • apps/wodsmith-start/src/lib/organizer-file-import/schemas.ts
  • apps/wodsmith-start/src/lib/organizer-file-import/validate.ts
  • apps/wodsmith-start/src/routeTree.gen.ts
  • apps/wodsmith-start/src/routes/api/agent-import/upload.ts
  • apps/wodsmith-start/src/routes/compete/organizer/$competitionId.tsx
  • apps/wodsmith-start/src/server-fns/organizer-file-import-fns.ts
  • apps/wodsmith-start/src/server.ts
  • apps/wodsmith-start/src/server/organizer-file-import/access.ts
  • apps/wodsmith-start/src/server/organizer-file-import/context.ts
  • apps/wodsmith-start/src/types/env.d.ts
  • apps/wodsmith-start/test/lib/organizer-file-import/parse.test.ts
  • apps/wodsmith-start/test/lib/organizer-file-import/validate.test.ts
  • lat.md/architecture.md
  • lat.md/organizer-dashboard.md

Comment on lines +530 to +545
/**
* Durable Object namespace for the organizer file-drop import agent.
*
* Each dropped file is an isolated session keyed by `${importRunId}__${userId}`
* (a fresh ULID per drop) so concurrent imports never collide and a
* reconnecting organizer reattaches to the same in-flight proposal stream.
*
* @see src/agents/organizer-file-import-agent.ts
*/
const organizerFileImportAgent = DurableObjectNamespace(
"organizer-file-import-agent",
{
className: "OrganizerFileImportAgent",
sqlite: true,
},
)

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add @lat code refs for the new DO namespace and binding wiring.

These new infrastructure source segments are missing required concept-link annotations.

As per coding guidelines, **/*.{js,ts,rs,go,c,h,py} must tie source code to concepts using // @lat: [[section-id]].

Also applies to: 678-679

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/wodsmith-start/alchemy.run.ts` around lines 530 - 545, Add the required
`// `@lat`: [[section-id]]` concept-link annotation to the
`organizerFileImportAgent` Durable Object namespace definition. Place the
annotation as a comment line immediately before the const declaration to tie
this infrastructure source segment to its corresponding concept section. Apply
the same annotation pattern to the additional related code at lines 678-679 as
mentioned in the review comment.

Source: Coding guidelines

"ua-parser-js": "^2.0.3",
"ulid": "^3.0.2",
"workers-ai-provider": "^3.1.14",
"xlsx": "^0.18.5",

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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for security advisories on xlsx package
gh api graphql -f query='
{
  securityVulnerabilities(first: 10, ecosystem: NPM, package: "xlsx") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Repository: wodsmith/thewodapp

Length of output: 1088


Upgrade xlsx to version 0.20.2 or later.

Version 0.18.5 is vulnerable to two HIGH severity CVEs:

  • Prototype Pollution (published 2023-04-24, affects < 0.19.3)
  • Regular Expression Denial of Service / ReDoS (published 2024-04-05, affects < 0.20.2)

These vulnerabilities pose a security risk when parsing untrusted user-uploaded Excel files. Upgrade to version 0.20.2 or higher to patch both issues.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/wodsmith-start/package.json` at line 130, Update the xlsx dependency
version from 0.18.5 to 0.20.2 or higher in the package.json file. Locate the
xlsx package dependency entry and change the version specifier from "^0.18.5" to
"^0.20.2" or a later version to address the HIGH severity vulnerabilities. After
updating, run npm install or your package manager's equivalent to apply the
changes.

Comment on lines +218 to +222
@callable()
reset(): { ok: true } {
this.setState(initialAgentState)
return { ok: true }
}

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make reset() cancel in-flight generation before clearing state.

reset() currently leaves active generation running, so stale tool updates can repopulate proposals after reset. Abort first, then clear state.

Suggested fix
 `@callable`()
 reset(): { ok: true } {
+  this.#abortController?.abort()
+  this.#abortController = null
   this.setState(initialAgentState)
   return { ok: true }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/wodsmith-start/src/agents/organizer-file-import-agent.ts` around lines
218 - 222, The reset() method clears state without canceling any in-flight
generation operations, allowing stale updates to repopulate proposals after
reset. Modify the reset() method to abort or cancel any active generation
(likely stored in the agent state or a class property) before calling
this.setState(initialAgentState). Ensure the abort/cancel operation is called
first, then proceed with clearing the state and returning the success response.

Comment on lines +51 to +58
function handleDragOver(e: React.DragEvent) {
if (!dropEnabled || !isFileDrag(e)) return
e.preventDefault()
}

async function handleDrop(e: React.DragEvent) {
if (!dropEnabled) return
e.preventDefault()

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Always suppress native browser file-drop behavior on the shell container.

Line 52/57 exits early when dropEnabled is false, so file drops can fall through to the browser default (open/navigate), which can yank users out of the app.

Suggested fix
 function handleDragOver(e: React.DragEvent) {
-	if (!dropEnabled || !isFileDrag(e)) return
+	if (!isFileDrag(e)) return
 	e.preventDefault()
+	if (!dropEnabled) return
 }
 
 async function handleDrop(e: React.DragEvent) {
-	if (!dropEnabled) return
+	if (isFileDrag(e)) e.preventDefault()
+	if (!dropEnabled) return
 	e.preventDefault()
 	dragDepth.current = 0
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/wodsmith-start/src/components/organizer-import/import-shell.tsx` around
lines 51 - 58, The handleDragOver and handleDrop functions return early when
dropEnabled is false without calling e.preventDefault(), allowing the browser's
default file-drop behavior to navigate away from the app. Fix this by calling
e.preventDefault() at the start of both functions before any early return
statements, ensuring the native browser file-drop behavior is always suppressed
regardless of the dropEnabled state.

@@ -0,0 +1,37 @@
import { useMatches } from "@tanstack/react-router"

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a LAT code reference in this TypeScript source file.

Line 1 starts a new .ts file without a // @lat: concept reference.

As per coding guidelines, **/*.{js,ts,rs,go,c,h,py} must tie source code to concepts using code refs (// @lat: [[section-id]]).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/wodsmith-start/src/components/organizer-import/use-page-intent.ts` at
line 1, The file use-page-intent.ts is missing the required LAT code reference
comment at the beginning of the file. Add a `// `@lat`: [[section-id]]` comment at
the very start of the file (before the existing import statements) to tie this
source code to the appropriate concept reference according to the coding
guidelines. Replace section-id with the actual concept reference identifier that
this file belongs to.

Source: Coding guidelines

Comment on lines +30 to +35
if (routeId.includes("/volunteers")) {
return {
routeKind: AGENT_IMPORT_ROUTE_KIND.VOLUNTEERS,
label: "Volunteers",
}
}

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restrict volunteers intent matching to the roster route only.

Line 30 uses routeId.includes("/volunteers"), which also matches routes like /volunteers/shifts and /volunteers/signup-questions. That enables import UI outside the intended scope.

Suggested fix
-	if (routeId.includes("/volunteers")) {
+	if (/\/volunteers(?:\/)?$/.test(routeId)) {
 		return {
 			routeKind: AGENT_IMPORT_ROUTE_KIND.VOLUNTEERS,
 			label: "Volunteers",
 		}
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (routeId.includes("/volunteers")) {
return {
routeKind: AGENT_IMPORT_ROUTE_KIND.VOLUNTEERS,
label: "Volunteers",
}
}
if (/\/volunteers(?:\/)?$/.test(routeId)) {
return {
routeKind: AGENT_IMPORT_ROUTE_KIND.VOLUNTEERS,
label: "Volunteers",
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/wodsmith-start/src/components/organizer-import/use-page-intent.ts`
around lines 30 - 35, The condition checking routeId.includes("/volunteers") is
too broad and will match unintended routes like "/volunteers/shifts" and
"/volunteers/signup-questions". Replace the includes method call with an exact
equality check to match only the specific "/volunteers" roster route. Change the
condition to check if routeId exactly equals "/volunteers" rather than just
containing that substring, so that the AGENT_IMPORT_ROUTE_KIND.VOLUNTEERS intent
is returned only for the intended route.


// Authorize against the run's competition (defense in depth) and
// confirm the uploader owns the run.
const scope = await loadFileImportScopeByRun(importRunId)

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Unhandled error for invalid importRunId returns 500 instead of 400/404.

loadFileImportScopeByRun throws "Import run not found" when the run doesn't exist, but this call is outside the try/catch block. An attacker probing with invalid IDs will see a 500 error, and the exception may clutter error logs.

Proposed fix
+			let scope: Awaited<ReturnType<typeof loadFileImportScopeByRun>>
+			try {
+				scope = await loadFileImportScopeByRun(importRunId)
+			} catch (err) {
+				logWarning({
+					message: "[AgentImport] Import run not found",
+					attributes: { importRunId },
+				})
+				return json({ error: "Import run not found" }, { status: 404 })
+			}
-			const scope = await loadFileImportScopeByRun(importRunId)
 			try {
 				await requireFileImportTeamAccess({
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/wodsmith-start/src/routes/api/agent-import/upload.ts` at line 78, The
call to loadFileImportScopeByRun(importRunId) is outside the try/catch block, so
when it throws an error for an invalid importRunId, the exception is unhandled
and returns a 500 status instead of the appropriate 400 or 404 error. Move the
loadFileImportScopeByRun call inside the existing try/catch block in the same
function so the error can be properly caught and handled, allowing the API to
return the correct HTTP status code for invalid or missing import runs.

Comment on lines +29 to +170
export async function loadPageContext(input: {
competitionId: string
routeKind: string
eventId: string | null
}): Promise<FileImportPageContext> {
const db = getDb()
const [competition] = await db
.select({ name: competitionsTable.name })
.from(competitionsTable)
.where(eq(competitionsTable.id, input.competitionId))
return {
competitionId: input.competitionId,
competitionName: competition?.name ?? "this competition",
routeKind: input.routeKind,
eventId: input.eventId,
}
}

/**
* Existing volunteers (volunteer system-role memberships + pending sign-ups)
* for a competition's volunteer team, flattened for dedup matching. Mirrors the
* membership join in judge-scheduler/context.ts#loadJudgeRoster but returns the
* whole roster (not only judges) with the email/name needed to match imports.
*/
export async function loadExistingVolunteers(
competitionTeamId: string | null,
): Promise<ExistingVolunteer[]> {
if (!competitionTeamId) return []
const db = getDb()

// Accepted volunteers are memberships; pending ones are invitations. Dedup
// must consider both — inviteVolunteerFn checks both before inviting.
const [memberships, invitations] = await Promise.all([
db
.select({
id: teamMembershipTable.id,
userId: teamMembershipTable.userId,
metadata: teamMembershipTable.metadata,
firstName: userTable.firstName,
lastName: userTable.lastName,
email: userTable.email,
})
.from(teamMembershipTable)
.leftJoin(userTable, eq(teamMembershipTable.userId, userTable.id))
.where(
and(
eq(teamMembershipTable.teamId, competitionTeamId),
eq(teamMembershipTable.roleId, SYSTEM_ROLES_ENUM.VOLUNTEER),
eq(teamMembershipTable.isSystemRole, true),
),
),
db
.select({
id: teamInvitationTable.id,
email: teamInvitationTable.email,
metadata: teamInvitationTable.metadata,
acceptedAt: teamInvitationTable.acceptedAt,
})
.from(teamInvitationTable)
.where(
and(
eq(teamInvitationTable.teamId, competitionTeamId),
eq(teamInvitationTable.roleId, SYSTEM_ROLES_ENUM.VOLUNTEER),
eq(teamInvitationTable.isSystemRole, true),
),
),
])

// Members first so an accepted-member match wins over a stale invitation.
const members: ExistingVolunteer[] = memberships.map((m) => {
const meta = parseVolunteerMetadata(m.metadata)
const email = m.email ?? meta?.signupEmail ?? null
const name = formatName(m.firstName, m.lastName) ?? meta?.signupName ?? null
return {
membershipId: m.id,
email,
name,
isInvite: false,
}
})

const invited: ExistingVolunteer[] = invitations
.filter((inv) => inv.acceptedAt === null)
.map((inv) => {
const meta = parseVolunteerMetadata(inv.metadata)
return {
membershipId: inv.id,
email: inv.email ?? null,
name: meta?.signupName ?? null,
isInvite: true,
}
})

return [...members, ...invited]
}

/**
* Read the uploaded file for a run from private R2 storage and parse it to a
* bounded table — server-side only, so PII never reaches the model unparsed.
*/
export async function readImportFile(importRunId: string): Promise<{
table: ParsedTable
truncated: boolean
}> {
const db = getDb()
const run = await db.query.agentImportRunsTable.findFirst({
where: eq(agentImportRunsTable.id, importRunId),
})
if (!run?.r2Key) {
throw new Error("Import file has not been uploaded yet")
}
const object = await env.R2_BUCKET.get(run.r2Key)
if (!object) {
throw new Error("Import file not found in storage")
}
const bytes = await object.arrayBuffer()
const parsed = await parseImportFile({
bytes,
mimeType: run.mimeType,
filename: run.originalFilename,
})
return boundTableForModel(parsed)
}

function parseVolunteerMetadata(
raw: string | null,
): VolunteerMembershipMetadata | null {
if (!raw) return null
try {
return JSON.parse(raw) as VolunteerMembershipMetadata
} catch {
return null
}
}

function formatName(
firstName: string | null | undefined,
lastName: string | null | undefined,
): string | null {
const full = [firstName, lastName].filter(Boolean).join(" ").trim()
return full || null
}

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add required @lat annotations in this server module.

The new code paths are missing the mandated source-to-concept references.

As per coding guidelines, **/*.{js,ts,rs,go,c,h,py} must tie source code to concepts using // @lat: [[section-id]].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/wodsmith-start/src/server/organizer-file-import/context.ts` around lines
29 - 170, The functions in this server module are missing the required `@lat`
source-to-concept reference annotations as mandated by the coding guidelines for
TypeScript files. Add `@lat` annotations in comment format (// `@lat`:
[[section-id]]) to the functions loadPageContext, loadExistingVolunteers,
readImportFile, parseVolunteerMetadata, and formatName to tie the source code to
relevant concept sections. Ensure each annotation clearly identifies the
corresponding architectural or domain concept that the function implements.

Source: Coding guidelines

Comment on lines +129 to +150
export async function readImportFile(importRunId: string): Promise<{
table: ParsedTable
truncated: boolean
}> {
const db = getDb()
const run = await db.query.agentImportRunsTable.findFirst({
where: eq(agentImportRunsTable.id, importRunId),
})
if (!run?.r2Key) {
throw new Error("Import file has not been uploaded yet")
}
const object = await env.R2_BUCKET.get(run.r2Key)
if (!object) {
throw new Error("Import file not found in storage")
}
const bytes = await object.arrayBuffer()
const parsed = await parseImportFile({
bytes,
mimeType: run.mimeType,
filename: run.originalFilename,
})
return boundTableForModel(parsed)

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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Scope readImportFile to the authorized context, not just importRunId.

This function fetches the import run by ID only and does not verify it belongs to the authorized competition/user context. Since callers authorize route access separately, a known foreign run ID could read another run’s file payload. Bind the query to trusted scope fields (e.g., competition and/or creator) and fail closed on mismatch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/wodsmith-start/src/server/organizer-file-import/context.ts` around lines
129 - 150, The readImportFile function currently fetches the import run by ID
alone without verifying it belongs to the authorized user or competition
context, creating a security vulnerability where a foreign run ID could expose
another user's file. Modify the WHERE clause in the
db.query.agentImportRunsTable.findFirst call to add additional conditions that
bind the query to trusted scope fields such as competition ID and/or creator ID
from the authorized context. This ensures the function fails closed when the
import run does not match the authorized scope, preventing unauthorized access
to import file payloads.

@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.

18 issues found across 26 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/wodsmith-start/src/components/organizer-import/import-shell.tsx">

<violation number="1" location="apps/wodsmith-start/src/components/organizer-import/import-shell.tsx:51">
P2: When `dropEnabled` is false, `handleDragOver` exits early without calling `e.preventDefault()`. This means a file dropped onto the shell container will trigger the browser's default behavior (opening the file and navigating away from the app). Even when the import feature is disabled, `preventDefault()` should be called on `dragover` and `drop` events to suppress the native file-open behavior.</violation>

<violation number="2" location="apps/wodsmith-start/src/components/organizer-import/import-shell.tsx:67">
P2: `processFile` allows concurrent starts, so repeated user actions can create duplicate import runs and uploads. Add an early return when an upload is already in progress.</violation>
</file>

<file name="apps/wodsmith-start/src/lib/organizer-file-import/parse.ts">

<violation number="1" location="apps/wodsmith-start/src/lib/organizer-file-import/parse.ts:75">
P2: XLSX header extraction loses columns when the sheet has headers but zero data rows. Read headers from the worksheet directly instead of first data row keys.</violation>
</file>

<file name="apps/wodsmith-start/src/server/organizer-file-import/context.ts">

<violation number="1" location="apps/wodsmith-start/src/server/organizer-file-import/context.ts:65">
P3: Unused `userId` column is fetched from `team_memberships` but never used. Remove it to keep query payload minimal and reduce maintenance noise.</violation>

<violation number="2" location="apps/wodsmith-start/src/server/organizer-file-import/context.ts:129">
P1: `readImportFile` fetches the import run by ID only, without verifying it belongs to the caller's authorized competition/user context. Since authorization is done at the route level but the agent name contains the `importRunId`, a user who knows a foreign run ID could potentially read another team's file payload. Bind the query to trusted scope fields (e.g., competition ID and/or creator user ID) and reject mismatches.</violation>
</file>

<file name="apps/wodsmith-start/src/routes/api/agent-import/upload.ts">

<violation number="1" location="apps/wodsmith-start/src/routes/api/agent-import/upload.ts:54">
P2: Missing runtime type validation for multipart fields allows malformed requests to crash the route. Validate `file` is `File` and `importRunId` is non-empty string before using them.</violation>

<violation number="2" location="apps/wodsmith-start/src/routes/api/agent-import/upload.ts:69">
P1: `application/octet-stream` currently bypasses extension checks, so unsupported binary files can be uploaded. Require a whitelisted extension when MIME type is octet-stream.</violation>

<violation number="3" location="apps/wodsmith-start/src/routes/api/agent-import/upload.ts:78">
P2: Invalid `importRunId` is not handled and can throw unhandled errors. Convert lookup failures to a controlled 4xx response.</violation>
</file>

<file name="apps/wodsmith-start/src/lib/organizer-file-import/schemas.ts">

<violation number="1" location="apps/wodsmith-start/src/lib/organizer-file-import/schemas.ts:35">
P1: `update` is allowed for volunteer proposals, but confirm path handles it like create and can send unintended invites. Restrict volunteer actions to implemented write semantics.</violation>

<violation number="2" location="apps/wodsmith-start/src/lib/organizer-file-import/schemas.ts:242">
P2: Event update input schema does not enforce `action: "update"` or a required target workout id. This weakens tool-contract validation and allows invalid update payloads.</violation>
</file>

<file name="apps/wodsmith-start/src/agents/organizer-file-import-agent.ts">

<violation number="1" location="apps/wodsmith-start/src/agents/organizer-file-import-agent.ts:185">
P2: Refine runs keep previous clarification state, causing stale clarification UI after the draft is updated. Clear clarification when starting a new refine cycle.</violation>

<violation number="2" location="apps/wodsmith-start/src/agents/organizer-file-import-agent.ts:219">
P1: `reset()` clears state without aborting the in-flight `#abortController`. If a generation is running, stale tool callbacks will continue to fire and repopulate `volunteerProposals` after the state has been wiped. Abort first, then clear.</violation>

<violation number="3" location="apps/wodsmith-start/src/agents/organizer-file-import-agent.ts:467">
P1: Duplicate-email proposals are not prevented server-side in the agent tool. Multiple proposals for the same email can be applied and resend invitation emails.</violation>
</file>

<file name="apps/wodsmith-start/src/server/organizer-file-import/access.ts">

<violation number="1" location="apps/wodsmith-start/src/server/organizer-file-import/access.ts:63">
P2: Missing routeKind/eventId invariant check allows `event_detail` imports without a target event. This can authorize/persist ambiguous scopes and apply logic against the wrong context.</violation>
</file>

<file name="apps/wodsmith-start/src/server.ts">

<violation number="1" location="apps/wodsmith-start/src/server.ts:153">
P2: Organizer import agent name check is over-permissive and does not enforce `importRunId` format. This weakens the intended guard against arbitrary DO identity materialization.</violation>

<violation number="2" location="apps/wodsmith-start/src/server.ts:162">
P1: New organizer-file-import DO route is wired, but its DO class is not exported from the entrypoint. This can break runtime instantiation for the new Durable Object binding.</violation>
</file>

<file name="apps/wodsmith-start/package.json">

<violation number="1" location="apps/wodsmith-start/package.json:130">
P1: The `xlsx` package at version 0.18.5 is vulnerable to CVE-2023-30533 (Prototype Pollution, affects <0.19.3) and CVE-2024-22363 (ReDoS, affects <0.20.2). Since this feature parses untrusted user-uploaded Excel files, these vulnerabilities pose a direct security risk. Note that `xlsx` on npm is no longer maintained — the patched versions are only available via the SheetJS CDN. Consider using `xlsx` from `https://cdn.sheetjs.com` at version ≥0.20.2, or an alternative library.</violation>
</file>

<file name="apps/wodsmith-start/src/components/organizer-import/use-page-intent.ts">

<violation number="1" location="apps/wodsmith-start/src/components/organizer-import/use-page-intent.ts:30">
P2: `routeId.includes("/volunteers")` is overly broad — it matches any route containing that substring (e.g. `/volunteers/shifts`, `/volunteers/signup-questions`), which would incorrectly enable the import UI on those pages. Use a stricter match (e.g. check that the route ends with `/volunteers` or equals the exact roster route ID).</violation>
</file>

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

Re-trigger cubic

{ status: 400 },
)
}
if (!ALLOWED_TYPES.has(file.type) && !hasAllowedExtension(file.name)) {

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.

P1: application/octet-stream currently bypasses extension checks, so unsupported binary files can be uploaded. Require a whitelisted extension when MIME type is octet-stream.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/wodsmith-start/src/routes/api/agent-import/upload.ts, line 69:

<comment>`application/octet-stream` currently bypasses extension checks, so unsupported binary files can be uploaded. Require a whitelisted extension when MIME type is octet-stream.</comment>

<file context>
@@ -0,0 +1,145 @@
+						{ status: 400 },
+					)
+				}
+				if (!ALLOWED_TYPES.has(file.type) && !hasAllowedExtension(file.name)) {
+					return json(
+						{ error: `Unsupported file type: ${file.type || file.name}` },
</file context>
Suggested change
if (!ALLOWED_TYPES.has(file.type) && !hasAllowedExtension(file.name)) {
if (
(!ALLOWED_TYPES.has(file.type) && !hasAllowedExtension(file.name)) ||
(file.type === "application/octet-stream" &&
!hasAllowedExtension(file.name))
) {

VOLUNTEER_AVAILABILITY.ALL_DAY,
])

export const proposalActionSchema = z.enum([

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.

P1: update is allowed for volunteer proposals, but confirm path handles it like create and can send unintended invites. Restrict volunteer actions to implemented write semantics.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/wodsmith-start/src/lib/organizer-file-import/schemas.ts, line 35:

<comment>`update` is allowed for volunteer proposals, but confirm path handles it like create and can send unintended invites. Restrict volunteer actions to implemented write semantics.</comment>

<file context>
@@ -0,0 +1,259 @@
+	VOLUNTEER_AVAILABILITY.ALL_DAY,
+])
+
+export const proposalActionSchema = z.enum([
+	"create",
+	"update",
</file context>

status: "pending",
}

const next = agent.state.volunteerProposals.filter(

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.

P1: Duplicate-email proposals are not prevented server-side in the agent tool. Multiple proposals for the same email can be applied and resend invitation emails.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/wodsmith-start/src/agents/organizer-file-import-agent.ts, line 467:

<comment>Duplicate-email proposals are not prevented server-side in the agent tool. Multiple proposals for the same email can be applied and resend invitation emails.</comment>

<file context>
@@ -0,0 +1,559 @@
+					status: "pending",
+				}
+
+				const next = agent.state.volunteerProposals.filter(
+					(p) => p.proposalId !== merged.proposalId,
+				)
</file context>

if (!session?.userId || session.userId !== userId) {
return new Response("Unauthorized", { status: 401 })
}
const ns =

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.

P1: New organizer-file-import DO route is wired, but its DO class is not exported from the entrypoint. This can break runtime instantiation for the new Durable Object binding.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/wodsmith-start/src/server.ts, line 162:

<comment>New organizer-file-import DO route is wired, but its DO class is not exported from the entrypoint. This can break runtime instantiation for the new Durable Object binding.</comment>

<file context>
@@ -143,6 +144,26 @@ const startEntry = createServerEntry({
+        if (!session?.userId || session.userId !== userId) {
+          return new Response("Unauthorized", { status: 401 })
+        }
+        const ns =
+          env.ORGANIZER_FILE_IMPORT_AGENT as unknown as DurableObjectNamespace<OrganizerFileImportAgent>
+        const stub = await getAgentByName(ns, name)
</file context>

* Read the uploaded file for a run from private R2 storage and parse it to a
* bounded table — server-side only, so PII never reaches the model unparsed.
*/
export async function readImportFile(importRunId: string): Promise<{

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.

P1: readImportFile fetches the import run by ID only, without verifying it belongs to the caller's authorized competition/user context. Since authorization is done at the route level but the agent name contains the importRunId, a user who knows a foreign run ID could potentially read another team's file payload. Bind the query to trusted scope fields (e.g., competition ID and/or creator user ID) and reject mismatches.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/wodsmith-start/src/server/organizer-file-import/context.ts, line 129:

<comment>`readImportFile` fetches the import run by ID only, without verifying it belongs to the caller's authorized competition/user context. Since authorization is done at the route level but the agent name contains the `importRunId`, a user who knows a foreign run ID could potentially read another team's file payload. Bind the query to trusted scope fields (e.g., competition ID and/or creator user ID) and reject mismatches.</comment>

<file context>
@@ -0,0 +1,170 @@
+ * Read the uploaded file for a run from private R2 storage and parse it to a
+ * bounded table — server-side only, so PII never reaches the model unparsed.
+ */
+export async function readImportFile(importRunId: string): Promise<{
+	table: ParsedTable
+	truncated: boolean
</file context>

throw new Error("Competition not found")
}

const eventId = input.eventId ?? null

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.

P2: Missing routeKind/eventId invariant check allows event_detail imports without a target event. This can authorize/persist ambiguous scopes and apply logic against the wrong context.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/wodsmith-start/src/server/organizer-file-import/access.ts, line 63:

<comment>Missing routeKind/eventId invariant check allows `event_detail` imports without a target event. This can authorize/persist ambiguous scopes and apply logic against the wrong context.</comment>

<file context>
@@ -0,0 +1,243 @@
+		throw new Error("Competition not found")
+	}
+
+	const eventId = input.eventId ?? null
+	if (eventId) {
+		const [row] = await db
</file context>

// base32 is accepted case-insensitively by this regex, and the only
// double underscore is the `__` separator. Reject anything else so a
// caller can't materialize arbitrary DO identities via this route.
const match = /^([a-z0-9_-]{1,128})__([a-z0-9_-]{1,128})$/i.exec(name)

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.

P2: Organizer import agent name check is over-permissive and does not enforce importRunId format. This weakens the intended guard against arbitrary DO identity materialization.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/wodsmith-start/src/server.ts, line 153:

<comment>Organizer import agent name check is over-permissive and does not enforce `importRunId` format. This weakens the intended guard against arbitrary DO identity materialization.</comment>

<file context>
@@ -143,6 +144,26 @@ const startEntry = createServerEntry({
+        // base32 is accepted case-insensitively by this regex, and the only
+        // double underscore is the `__` separator. Reject anything else so a
+        // caller can't materialize arbitrary DO identities via this route.
+        const match = /^([a-z0-9_-]{1,128})__([a-z0-9_-]{1,128})$/i.exec(name)
+        if (!match) {
+          return new Response("Invalid agent name", { status: 400 })
</file context>
Suggested change
const match = /^([a-z0-9_-]{1,128})__([a-z0-9_-]{1,128})$/i.exec(name)
const match = /^(aimp_[0-9A-HJKMNP-TV-Z]{26})__([a-z0-9_-]{1,128})$/i.exec(name)

}
}

function handleDragOver(e: React.DragEvent) {

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.

P2: When dropEnabled is false, handleDragOver exits early without calling e.preventDefault(). This means a file dropped onto the shell container will trigger the browser's default behavior (opening the file and navigating away from the app). Even when the import feature is disabled, preventDefault() should be called on dragover and drop events to suppress the native file-open behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/wodsmith-start/src/components/organizer-import/import-shell.tsx, line 51:

<comment>When `dropEnabled` is false, `handleDragOver` exits early without calling `e.preventDefault()`. This means a file dropped onto the shell container will trigger the browser's default behavior (opening the file and navigating away from the app). Even when the import feature is disabled, `preventDefault()` should be called on `dragover` and `drop` events to suppress the native file-open behavior.</comment>

<file context>
@@ -0,0 +1,155 @@
+		}
+	}
+
+	function handleDragOver(e: React.DragEvent) {
+		if (!dropEnabled || !isFileDrag(e)) return
+		e.preventDefault()
</file context>

if (routeId.includes("/volunteers/judges")) {
return { routeKind: AGENT_IMPORT_ROUTE_KIND.JUDGES, label: "Judges" }
}
if (routeId.includes("/volunteers")) {

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.

P2: routeId.includes("/volunteers") is overly broad — it matches any route containing that substring (e.g. /volunteers/shifts, /volunteers/signup-questions), which would incorrectly enable the import UI on those pages. Use a stricter match (e.g. check that the route ends with /volunteers or equals the exact roster route ID).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/wodsmith-start/src/components/organizer-import/use-page-intent.ts, line 30:

<comment>`routeId.includes("/volunteers")` is overly broad — it matches any route containing that substring (e.g. `/volunteers/shifts`, `/volunteers/signup-questions`), which would incorrectly enable the import UI on those pages. Use a stricter match (e.g. check that the route ends with `/volunteers` or equals the exact roster route ID).</comment>

<file context>
@@ -0,0 +1,37 @@
+	if (routeId.includes("/volunteers/judges")) {
+		return { routeKind: AGENT_IMPORT_ROUTE_KIND.JUDGES, label: "Judges" }
+	}
+	if (routeId.includes("/volunteers")) {
+		return {
+			routeKind: AGENT_IMPORT_ROUTE_KIND.VOLUNTEERS,
</file context>

db
.select({
id: teamMembershipTable.id,
userId: teamMembershipTable.userId,

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.

P3: Unused userId column is fetched from team_memberships but never used. Remove it to keep query payload minimal and reduce maintenance noise.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/wodsmith-start/src/server/organizer-file-import/context.ts, line 65:

<comment>Unused `userId` column is fetched from `team_memberships` but never used. Remove it to keep query payload minimal and reduce maintenance noise.</comment>

<file context>
@@ -0,0 +1,170 @@
+		db
+			.select({
+				id: teamMembershipTable.id,
+				userId: teamMembershipTable.userId,
+				metadata: teamMembershipTable.metadata,
+				firstName: userTable.firstName,
</file context>

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