@@ -21,6 +21,11 @@ function emailDomain(email: string): string {
2121}
2222
2323type DatasetPopulateStatus = "building" | "live" | "failed" ;
24+ type DatasetPopulateBeginOutcome =
25+ | "started"
26+ | "not_found"
27+ | "forbidden"
28+ | "already_building" ;
2429type PopulateWorkflowRun = Awaited < ReturnType < typeof populateWorkflow . createRun > > ;
2530
2631function statusErrorMessage ( err : unknown ) : string {
@@ -40,6 +45,18 @@ async function setDatasetPopulateStatus(
4045 } ) ;
4146}
4247
48+ async function beginDatasetPopulate (
49+ datasetId : string ,
50+ ownerId : string ,
51+ ) : Promise < DatasetPopulateBeginOutcome > {
52+ const claim = await convex . mutation ( internal . datasets . beginPopulateInternal , {
53+ id : datasetId ,
54+ ownerId,
55+ } ) ;
56+
57+ return claim . outcome ;
58+ }
59+
4360async function sendDatasetReadyNotification ( {
4461 logger,
4562 clerk,
@@ -272,28 +289,25 @@ await fastify.register(async (instance) => {
272289 return reply . code ( 401 ) . send ( { error : "Authentication required" } ) ;
273290 }
274291
275- // Ownership check uses the INTERNAL (admin-callable, no-authz) getter.
276- // We can't use `api.datasets.get` here because that runs through
277- // `loadReadableDataset`, which requires either a Clerk-identified
278- // caller OR visibility="public". The backend's ConvexHttpClient is
279- // admin-authed but does NOT impersonate a user, so private datasets
280- // (the typical case) get rejected as `anonymous_private`.
281- //
282- // The /populate route enforces ownership against `req.auth.userId`
283- // (from the verified Clerk JWT) immediately below — that's the
284- // authoritative check, not Convex's user-identity authz.
285- const dataset = await convex . query ( internal . datasets . getInternal , {
286- id : parsed . data . datasetId ,
287- } ) ;
288- if ( ! dataset ) {
292+ const run = await populateWorkflow . createRun ( ) ;
293+ const populateOutcome = await beginDatasetPopulate (
294+ parsed . data . datasetId ,
295+ auth . userId ,
296+ ) ;
297+
298+ if ( populateOutcome === "not_found" ) {
289299 return reply . code ( 404 ) . send ( { error : "Dataset not found" } ) ;
290300 }
291- if ( dataset . ownerId !== auth . userId ) {
301+ if ( populateOutcome === "forbidden" ) {
292302 return reply . code ( 403 ) . send ( { error : "Not authorized to populate this dataset" } ) ;
293303 }
304+ if ( populateOutcome === "already_building" ) {
305+ return reply . code ( 409 ) . send ( { error : "Dataset is already being populated" } ) ;
306+ }
307+ if ( populateOutcome !== "started" ) {
308+ throw new Error ( `Unexpected populate claim outcome: ${ populateOutcome } ` ) ;
309+ }
294310
295- const run = await populateWorkflow . createRun ( ) ;
296- await setDatasetPopulateStatus ( parsed . data . datasetId , "building" ) ;
297311 void runPopulateWorkflowInBackground ( {
298312 input : parsed . data ,
299313 run,
0 commit comments