@@ -201,6 +201,76 @@ async function upsertQueue(
201201 } ) ;
202202}
203203
204+ // get_queue's `consumerTasks` (internal-packages/dashboard-agent/src/tool-api.ts,
205+ // consumerTasksForQueue) is read off the env's CURRENT worker's tasks - for a
206+ // DEVELOPMENT env that's the latest BackgroundWorker by createdAt (never a deployment;
207+ // see findCurrentWorkerFromEnvironment in workerDeployment.server.ts) - matching a
208+ // BackgroundWorkerTask whose queueConfig.name equals the queue name. Without one, the
209+ // queue looks unconsumed and the agent's honest "no deployed consumer" diagnosis
210+ // preempts whatever the scenario is actually testing.
211+ async function ensureConsumerTask (
212+ ctx : Ctx ,
213+ env : RuntimeEnvironment ,
214+ queue : { id : string ; name : string ; concurrencyLimit : number | null } ,
215+ taskSlug : string
216+ ) {
217+ let currentWorker = await ctx . prisma . backgroundWorker . findFirst ( {
218+ where : { runtimeEnvironmentId : env . id } ,
219+ orderBy : { createdAt : "desc" } ,
220+ } ) ;
221+ if ( ! currentWorker ) {
222+ // A fresh per-member dev env (no `trigger dev` session yet) has no worker at all -
223+ // mint a minimal one so the scenario is seedable without that manual step. Tagged
224+ // "uat-dev-worker-1" so `clean` can remove it; a real `trigger dev` session
225+ // afterward naturally supersedes it as the env's current worker.
226+ currentWorker = await ctx . prisma . backgroundWorker . upsert ( {
227+ where : {
228+ projectId_runtimeEnvironmentId_version : {
229+ projectId : ctx . projectId ,
230+ runtimeEnvironmentId : env . id ,
231+ version : "uat-dev-worker-1" ,
232+ } ,
233+ } ,
234+ create : {
235+ friendlyId : generateFriendlyId ( "worker" ) ,
236+ // engine defaults to V1 - determineEngineVersion() reads the LATEST worker's engine
237+ // to gate every queue/run route for the env, so an unset engine here would 400
238+ // every queue lookup in this dev env, not just this fixture's own queue.
239+ engine : "V2" ,
240+ contentHash : "uat-dev-worker-hash" ,
241+ sdkVersion : "0.0.0-uat" ,
242+ cliVersion : "0.0.0-uat" ,
243+ projectId : ctx . projectId ,
244+ runtimeEnvironmentId : env . id ,
245+ version : "uat-dev-worker-1" ,
246+ metadata : { } ,
247+ } ,
248+ update : { } ,
249+ } ) ;
250+ }
251+
252+ await ctx . prisma . backgroundWorkerTask . upsert ( {
253+ where : { workerId_slug : { workerId : currentWorker . id , slug : taskSlug } } ,
254+ create : {
255+ friendlyId : generateFriendlyId ( "task" ) ,
256+ projectId : ctx . projectId ,
257+ runtimeEnvironmentId : env . id ,
258+ workerId : currentWorker . id ,
259+ slug : taskSlug ,
260+ filePath : "src/trigger/uat-fixtures.ts" ,
261+ queueConfig : { name : queue . name , concurrencyLimit : queue . concurrencyLimit } ,
262+ queueId : queue . id ,
263+ triggerSource : "STANDARD" ,
264+ } ,
265+ update : {
266+ queueConfig : { name : queue . name , concurrencyLimit : queue . concurrencyLimit } ,
267+ queueId : queue . id ,
268+ } ,
269+ } ) ;
270+
271+ return currentWorker ;
272+ }
273+
204274type RunFields = {
205275 idempotencyKey : string ;
206276 env : RuntimeEnvironment ;
@@ -344,6 +414,9 @@ async function seedCkInvisible(ctx: Ctx) {
344414 const queue = await upsertQueue ( ctx , ctx . devEnv , queueName , 3 ) ;
345415 record ( "S3" , "queue" , queue . friendlyId , `${ queueName } (concurrencyKey, limit 3)` ) ;
346416
417+ const consumerWorker = await ensureConsumerTask ( ctx , ctx . devEnv , queue , "uat-ck-consumer-task" ) ;
418+ record ( "S3" , "consumer task" , "uat-ck-consumer-task" , `on worker ${ consumerWorker . version } ` ) ;
419+
347420 const run = await upsertRun ( ctx , {
348421 idempotencyKey : "uat-ck-admitted" ,
349422 env : ctx . devEnv ,
@@ -623,27 +696,44 @@ async function seedRecurred(ctx: Ctx) {
623696 } ,
624697 update : { status : "RESOLVED" , resolvedAt, resolvedInVersion : "uat" , resolvedBy : user ?. id } ,
625698 } ) ;
699+ // get_error/list_errors ask for the friendly `error_<fingerprint>` id (ErrorId.toFriendlyId,
700+ // apps/webapp/app/presenters/v3/ApiErrorGroupPresenter.server.ts) - name it explicitly so
701+ // the tester doesn't have to fish it out of a list_errors call first.
702+ const askableId = `error_${ errorFingerprint } ` ;
626703 record ( "S10" , "ErrorGroupState" , errorGroup . id , `resolvedAt=${ resolvedAt . toISOString ( ) } ` ) ;
704+ record ( "S10" , "ask-able id" , askableId , `taskIdentifier=${ taskIdentifier } ` ) ;
627705
628706 const version = Date . now ( ) ;
707+ // ClickHouse SQL string literals backslash-unescape before the JSON parser ever sees the
708+ // value, so JSON.stringify's `\n` (2 chars) becomes a raw newline byte inside the JSON
709+ // text - invalid JSON. Escape backslashes first so ClickHouse's unescape leaves `\n`
710+ // intact for the JSON parser; escape quotes after (order matters, or '' would double-escape).
629711 const errorJson = JSON . stringify ( {
630712 data : {
631713 type : "Error" ,
632714 message : "uat recurred fixture error" ,
633715 stack : "Error: uat recurred fixture error\n at uatFixture (uat.ts:1:1)" ,
634716 } ,
635- } ) . replace ( / ' / g, "''" ) ;
717+ } )
718+ . replace ( / \\ / g, "\\\\" )
719+ . replace ( / ' / g, "''" ) ;
636720
637- console . log ( "\nS10: Postgres side done. ClickHouse errors_v1 is a materialized view over" ) ;
638- console . log ( "task_runs_v2 - run this manually to make the error 'recur' after resolvedAt:\n" ) ;
721+ console . log ( "\nS10: Postgres side done. ClickHouse errors_v1 AND error_occurrences_v1 are both" ) ;
722+ console . log (
723+ "materialized views over task_runs_v2 (matched on error_fingerprint != '' + a failure"
724+ ) ;
725+ console . log ( "status) - run this manually to make the error 'recur' after resolvedAt. Omitting" ) ;
726+ console . log ( "error_fingerprint here silently excludes the row from BOTH views, so get_error and" ) ;
727+ console . log ( `list_errors both miss it. Ask about: ${ askableId } \n` ) ;
639728 console . log (
640729 `clickhouse-client --query "INSERT INTO trigger_dev.task_runs_v2 ` +
641730 `(environment_id, organization_id, project_id, run_id, friendly_id, environment_type, ` +
642- `engine, status, task_identifier, queue, task_version, error, created_at, updated_at, _version) ` +
731+ `engine, status, task_identifier, error_fingerprint, queue, task_version, error, ` +
732+ `created_at, updated_at, _version) ` +
643733 `VALUES ('${ ctx . devEnv . id } ', '${ ctx . orgId } ', '${ ctx . projectId } ', 'uat-recurred-run', ` +
644734 `'run_uatrecurred', 'DEVELOPMENT', 'V2', 'COMPLETED_WITH_ERRORS', '${ taskIdentifier } ', ` +
645- `'uat-recurred-task', 'uat', '${ errorJson } ', ' ${ formatChDateTime ( lastSeen ) } ', ` +
646- `'${ formatChDateTime ( lastSeen ) } ', ${ version } )"\n`
735+ `'${ errorFingerprint } ', ' uat-recurred-task', 'uat', '${ errorJson } ', ` +
736+ `'${ formatChDateTime ( lastSeen ) } ', ' ${ formatChDateTime ( lastSeen ) } ', ${ version } )"\n`
647737 ) ;
648738}
649739
@@ -724,23 +814,33 @@ async function clean(ctx: ProjectCtx) {
724814 } ,
725815 } ) ;
726816
727- // BackgroundWorker -> WorkerDeployment is onDelete: Cascade.
728- await ctx . prisma . backgroundWorker . deleteMany ( {
817+ await ctx . prisma . errorGroupState . deleteMany ( {
818+ where : {
819+ environmentId : { in : boundedIn ( envs . map ( ( e ) => e . id ) ) } ,
820+ taskIdentifier : "uat-recurred-task" ,
821+ } ,
822+ } ) ;
823+
824+ // The consumer-task fixture row first: ensureConsumerTask may have attached it to the
825+ // env's REAL current worker (not a fixture), so this must run before any worker delete.
826+ await ctx . prisma . backgroundWorkerTask . deleteMany ( {
729827 where : {
730828 runtimeEnvironmentId : { in : boundedIn ( envs . map ( ( e ) => e . id ) ) } ,
731- version : "uat-dirty-1 " ,
829+ slug : "uat-ck-consumer-task " ,
732830 } ,
733831 } ) ;
734832
735- await ctx . prisma . errorGroupState . deleteMany ( {
833+ // Fixture workers only ("uat-dirty-1" for S6, "uat-dev-worker-1" when ensureConsumerTask
834+ // had to mint one). BackgroundWorker -> WorkerDeployment/BackgroundWorkerTask is Cascade.
835+ await ctx . prisma . backgroundWorker . deleteMany ( {
736836 where : {
737- environmentId : { in : boundedIn ( envs . map ( ( e ) => e . id ) ) } ,
738- taskIdentifier : "uat-recurred-task" ,
837+ runtimeEnvironmentId : { in : boundedIn ( envs . map ( ( e ) => e . id ) ) } ,
838+ version : { in : [ "uat-dirty-1" , "uat-dev-worker-1" ] } ,
739839 } ,
740840 } ) ;
741841
742842 console . log (
743- `Cleaned ${ runIds . length } runs, uat-* queues, dirty-deploy worker, error group ` +
843+ `Cleaned ${ runIds . length } runs, uat-* queues, dirty-deploy worker, consumer task, error group ` +
744844 `(swept ${ envs . length } envs in the project).`
745845 ) ;
746846}
0 commit comments