feat: organizer file-drop import agent (volunteers/judges MVP)#518
feat: organizer file-drop import agent (volunteers/judges MVP)#518theianjones wants to merge 1 commit into
Conversation
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>
WalkthroughAdds a complete organizer file-drop import feature: a Drizzle schema for import runs, file parsing utilities (CSV/TSV/XLSX), server authorization/context helpers, a ChangesOrganizer File-Drop Import
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 winExport
OrganizerFileImportAgentfrom 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 winAdd required
@latannotations 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 winAdd required
@latcode 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 winAdd
@latreferences 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 winAdd required
@latcode 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (25)
apps/wodsmith-start/alchemy.run.tsapps/wodsmith-start/package.jsonapps/wodsmith-start/src/agents/organizer-file-import-agent.tsapps/wodsmith-start/src/components/organizer-import/import-review-drawer.tsxapps/wodsmith-start/src/components/organizer-import/import-shell.tsxapps/wodsmith-start/src/components/organizer-import/use-page-intent.tsapps/wodsmith-start/src/config/features.tsapps/wodsmith-start/src/db/schema.tsapps/wodsmith-start/src/db/schemas/agent-imports.tsapps/wodsmith-start/src/db/schemas/common.tsapps/wodsmith-start/src/lib/organizer-file-import/parse.tsapps/wodsmith-start/src/lib/organizer-file-import/schemas.tsapps/wodsmith-start/src/lib/organizer-file-import/validate.tsapps/wodsmith-start/src/routeTree.gen.tsapps/wodsmith-start/src/routes/api/agent-import/upload.tsapps/wodsmith-start/src/routes/compete/organizer/$competitionId.tsxapps/wodsmith-start/src/server-fns/organizer-file-import-fns.tsapps/wodsmith-start/src/server.tsapps/wodsmith-start/src/server/organizer-file-import/access.tsapps/wodsmith-start/src/server/organizer-file-import/context.tsapps/wodsmith-start/src/types/env.d.tsapps/wodsmith-start/test/lib/organizer-file-import/parse.test.tsapps/wodsmith-start/test/lib/organizer-file-import/validate.test.tslat.md/architecture.mdlat.md/organizer-dashboard.md
| /** | ||
| * 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, | ||
| }, | ||
| ) |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
🧩 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.
| @callable() | ||
| reset(): { ok: true } { | ||
| this.setState(initialAgentState) | ||
| return { ok: true } | ||
| } |
There was a problem hiding this comment.
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.
| function handleDragOver(e: React.DragEvent) { | ||
| if (!dropEnabled || !isFileDrag(e)) return | ||
| e.preventDefault() | ||
| } | ||
|
|
||
| async function handleDrop(e: React.DragEvent) { | ||
| if (!dropEnabled) return | ||
| e.preventDefault() |
There was a problem hiding this comment.
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" | |||
There was a problem hiding this comment.
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
| if (routeId.includes("/volunteers")) { | ||
| return { | ||
| routeKind: AGENT_IMPORT_ROUTE_KIND.VOLUNTEERS, | ||
| label: "Volunteers", | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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) |
There was a problem hiding this comment.
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.
| 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 | ||
| } |
There was a problem hiding this comment.
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
| 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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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>
| 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([ |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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 = |
There was a problem hiding this comment.
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<{ |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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>
| 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) { |
There was a problem hiding this comment.
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")) { |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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>
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_runstable (audit / retention / idempotency / Undo anchor) +AI_FILE_IMPORTfeature flagOrganizerFileImportAgentDurable Object — namespace + binding inalchemy.run.ts, WS routing + class re-export inserver.ts,Envaugmentation inenv.d.tsPrivate upload —
/api/agent-import/uploadstores 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), arefine(instruction)loop, and intent disambiguation. Duplicate detection runs deterministically server-side (classifyVolunteer) so existing-volunteer emails never reach the model.Confirm / Undo —
applyOrganizerImportFnis the only write path: re-validates each proposal, invites via the existinginviteUserToTeamhelper (sends invite emails on confirm), and records created entities for the receipt +undoImportFn.UI —
ImportShellmounted once in the organizer layout: full-page drop overlay + persistent click-to-upload dock (gated to volunteers/judges viausePageIntent).ImportReviewDrawerrenders 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 byAI_FILE_IMPORT.Tests & checks
parse+validateunit tests — 18 passingpnpm type-checkclean (0 errors), Biome cleanlat.mdupdated (architecture + organizer-dashboard);lat checkpassesBefore enabling
pnpm db:pushto apply the newagent_import_runstable to the dev branchAI_FILE_IMPORTfeature and grant it to the target team(s)pnpm alchemy:devto deploy the new DO binding locallyFollow-ups (intentionally out of scope)
🤖 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
agent_import_runstable and feature-flag gated access, enforced at page, upload route, socket, and server entrypoint.Migration
Written for commit 6dff925. Summary will update on new commits.
Summary by CodeRabbit
New Features
Chores