17 Commits

Author SHA1 Message Date
9cb80f557b after updating holistic-rubric.md 2026-10-09 14:44:09 -04:00
81a67ddf16 after build-workspace.sh 2026-10-09 13:46:19 -04:00
c6b5d7d9c2 after task.toml edit 2026-10-09 13:35:20 -04:00
84441dbfb7 after implementation, code-diff and defects 2026-10-09 13:23:32 -04:00
54e0efa068 Revert "implementation from the prompt"
This reverts commit 40dfd6252d.
2026-10-09 13:22:45 -04:00
40dfd6252d implementation from the prompt 2026-10-09 13:21:38 -04:00
f29b77bcab before explore with instruction 2026-10-09 10:52:15 -04:00
ebd3474db3 toolkit 1.0.1 2026-10-09 10:43:20 -04:00
e814569a07 remove 1.0.0 toolkit 2026-10-09 10:40:28 -04:00
1da7a85364 STARTED OVER
- removed worker-toolkit...
- unzipped polyglot
- config'd worker-toolkit
-- added .env and .gitignore
2026-10-08 16:04:19 -04:00
4a43df9a41 added instruction.md 2026-10-08 15:35:47 -04:00
6e75d0d3d5 updated task.toml 2026-10-08 15:33:15 -04:00
99055009d1 start task - new slug and Dockerfile 2026-10-08 15:30:44 -04:00
4cf25af5a2 Revert "changes to implement instructions"
This reverts commit 916fe7ace3.
2026-10-08 15:19:37 -04:00
916fe7ace3 changes to implement instructions 2026-10-08 15:15:02 -04:00
f5ad529a0f simplified raw-defects 2026-10-08 15:14:57 -04:00
e17720411b added files pertinent to this project 2026-10-08 14:41:07 -04:00
342 changed files with 5929 additions and 518 deletions

594
sources/code-diff.txt Normal file
View File

@@ -0,0 +1,594 @@
diff --git a/worker-toolkit-potion-polyglot/repos/potion-voice/voice-cloning-job-handler/index.js b/worker-toolkit-potion-polyglot/repos/potion-voice/voice-cloning-job-handler/index.js
index 0995e23..d3552ca 100644
--- a/worker-toolkit-potion-polyglot/repos/potion-voice/voice-cloning-job-handler/index.js
+++ b/worker-toolkit-potion-polyglot/repos/potion-voice/voice-cloning-job-handler/index.js
@@ -10,6 +10,7 @@ const sqs = require('../app/services/sqs')
const s3 = require('../app/services/s3')
const voiceCloningService = require('./voice_cloning')
const userAudioProfileService = require('./user_audio_profile')
+const { requireUserId, requireDocumentId } = require('../worker-tenant')
AWS.config.update({ region: 'us-west-2' })
const sqsQueueUrl = process.env.SQS_URL
@@ -101,14 +102,15 @@ const processQueue = () => {
const receiptHandle = response.Messages[0].ReceiptHandle
console.log('job===', job)
- const { metadata, input, _id, userAudioProfileId } = job._doc
+ const { _id, userAudioProfileId, userId } = job._doc
+ requireUserId(userId)
+ requireDocumentId(_id)
+ requireDocumentId(userAudioProfileId)
console.log('userAudioProfileId', userAudioProfileId)
console.log('_id', _id)
const { env } = job
console.log('env', env)
- console.log('metadata------', metadata)
- console.log('input', input)
const DB_URI =
env === 'production'
? mongoUriProd
@@ -126,9 +128,26 @@ const processQueue = () => {
? cloudFrontUrlStaging
: cloudFrontUrlDev
+ let authorized = false
try {
await sqs.deleteMessageFromSQS(sqsQueueUrl, receiptHandle)
+ const cloningJob = await voiceCloningService.read({
+ _id,
+ userId,
+ userAudioProfileId,
+ })
+ const audioProfile = await userAudioProfileService.read({
+ _id: userAudioProfileId,
+ userId,
+ })
+ if (!cloningJob || !audioProfile) {
+ throw new Error('Cloning job or audio profile does not belong to user')
+ }
+ authorized = true
+
+ const { metadata, input } = cloningJob
+
const { directoryName } = metadata
console.log('directoryName', directoryName)
const logPath = `/mnt/efs/potion-voice/${env}/${directoryName}`
@@ -136,9 +155,10 @@ const processQueue = () => {
fs.mkdirSync(logPath, { recursive: true })
}
// update the db model to processing
- await voiceCloningService.update({ _id, status: 'processing' })
+ await voiceCloningService.update({ _id, userId, status: 'processing' })
await userAudioProfileService.update({
_id: userAudioProfileId,
+ userId,
status: 'processing',
})
@@ -240,7 +260,7 @@ const processQueue = () => {
console.timeEnd(VOICE_MINIMIZE_LABEL)
// Add the code to update location of generated model and status into DB
- await voiceCloningService.update({ _id, status: 'completed' })
+ await voiceCloningService.update({ _id, userId, status: 'completed' })
const training_model_path = {
voice_model_path: `${resultsPath}/${generatedDirectoryName}/checkpoint_365200.pth`,
@@ -252,6 +272,7 @@ const processQueue = () => {
await userAudioProfileService.update({
_id: userAudioProfileId,
+ userId,
status: 'completed',
training_model_path,
})
@@ -273,6 +294,7 @@ const processQueue = () => {
// add S3 path to user audio profile model
await userAudioProfileService.update({
_id: userAudioProfileId,
+ userId,
training_model_s3_path,
})
} catch (error) {
@@ -285,11 +307,14 @@ const processQueue = () => {
Bugsnag.notify(error)
// update the db to set status as error
- await voiceCloningService.update({ _id, status: 'error' })
- await userAudioProfileService.update({
- _id: userAudioProfileId,
- status: 'error',
- })
+ if (authorized) {
+ await voiceCloningService.update({ _id, userId, status: 'error' })
+ await userAudioProfileService.update({
+ _id: userAudioProfileId,
+ userId,
+ status: 'error',
+ })
+ }
resolve() // to continue working on new jobs
}
diff --git a/worker-toolkit-potion-polyglot/repos/potion-voice/voice-cloning-job-handler/user_audio_profile/user_audio_profile_service.js b/worker-toolkit-potion-polyglot/repos/potion-voice/voice-cloning-job-handler/user_audio_profile/user_audio_profile_service.js
index e76643d..f35ecd6 100644
--- a/worker-toolkit-potion-polyglot/repos/potion-voice/voice-cloning-job-handler/user_audio_profile/user_audio_profile_service.js
+++ b/worker-toolkit-potion-polyglot/repos/potion-voice/voice-cloning-job-handler/user_audio_profile/user_audio_profile_service.js
@@ -1,8 +1,9 @@
+const { requireUserId } = require('../../worker-tenant')
const StringifyUtils = require('../../app/services/utils/logService')
const create = (UserAudioProfileModel) => async (data) => {
try {
- const newModel = new UserAudioProfileModel({ ...data })
+ const newModel = new UserAudioProfileModel({ ...data, userId: requireUserId(data.userId) })
const savedModel = await newModel.save()
return savedModel
} catch (error) {
@@ -17,7 +18,9 @@ const create = (UserAudioProfileModel) => async (data) => {
const insertMany = (UserAudioProfileModel) => async (data) => {
try {
- const inserted = await UserAudioProfileModel.insertMany(data)
+ const inserted = await UserAudioProfileModel.insertMany(
+ data.map((item) => ({ ...item, userId: requireUserId(item.userId) }))
+ )
return inserted
} catch (error) {
const details = { data }
@@ -33,6 +36,7 @@ const read = (UserAudioProfileModel) => async (filter) => {
try {
const foundModel = await UserAudioProfileModel.findOne({
...filter,
+ userId: requireUserId(filter.userId),
deleted: false,
})
return foundModel
@@ -50,6 +54,7 @@ const find = (UserAudioProfileModel) => async (filter) => {
try {
const foundModels = await UserAudioProfileModel.find({
...filter,
+ userId: requireUserId(filter.userId),
deleted: false,
})
return foundModels
@@ -65,12 +70,11 @@ const find = (UserAudioProfileModel) => async (filter) => {
const update = (UserAudioProfileModel) => async (data) => {
try {
+ const { _id, userId, ...changes } = data
const updatedModel = await UserAudioProfileModel.findOneAndUpdate(
- { _id: data._id },
- data,
- {
- new: true,
- }
+ { _id, userId: requireUserId(userId), deleted: false },
+ { $set: changes },
+ { new: true }
)
return updatedModel
} catch (error) {
@@ -86,7 +90,7 @@ const update = (UserAudioProfileModel) => async (data) => {
const remove = (UserAudioProfileModel) => async (filter) => {
try {
const updatedModel = await UserAudioProfileModel.findOneAndUpdate(
- { ...filter },
+ { ...filter, userId: requireUserId(filter.userId) },
{
$set: {
deleted: true,
@@ -108,7 +112,7 @@ const remove = (UserAudioProfileModel) => async (filter) => {
const removeMany = (UserAudioProfileModel) => async (filter) => {
try {
const updatedModel = await UserAudioProfileModel.updateMany(
- { ...filter },
+ { ...filter, userId: requireUserId(filter.userId) },
{
$set: {
deleted: true,
diff --git a/worker-toolkit-potion-polyglot/repos/potion-voice/voice-cloning-job-handler/voice_cloning/voice_cloning_service.js b/worker-toolkit-potion-polyglot/repos/potion-voice/voice-cloning-job-handler/voice_cloning/voice_cloning_service.js
index 329638e..cef601b 100644
--- a/worker-toolkit-potion-polyglot/repos/potion-voice/voice-cloning-job-handler/voice_cloning/voice_cloning_service.js
+++ b/worker-toolkit-potion-polyglot/repos/potion-voice/voice-cloning-job-handler/voice_cloning/voice_cloning_service.js
@@ -1,8 +1,9 @@
+const { requireUserId } = require('../../worker-tenant')
const StringifyUtils = require('../../app/services/utils/logService')
const create = (VoiceCloningModel) => async (data) => {
try {
- const newModel = new VoiceCloningModel({ ...data })
+ const newModel = new VoiceCloningModel({ ...data, userId: requireUserId(data.userId) })
const savedModel = await newModel.save()
return savedModel
} catch (error) {
@@ -17,7 +18,9 @@ const create = (VoiceCloningModel) => async (data) => {
const insertMany = (VoiceCloningModel) => async (data) => {
try {
- const inserted = await VoiceCloningModel.insertMany(data)
+ const inserted = await VoiceCloningModel.insertMany(
+ data.map((item) => ({ ...item, userId: requireUserId(item.userId) }))
+ )
return inserted
} catch (error) {
const details = { data }
@@ -33,6 +36,7 @@ const read = (VoiceCloningModel) => async (filter) => {
try {
const foundModel = await VoiceCloningModel.findOne({
...filter,
+ userId: requireUserId(filter.userId),
deleted: false,
})
return foundModel
@@ -50,6 +54,7 @@ const find = (VoiceCloningModel) => async (filter) => {
try {
const foundModels = await VoiceCloningModel.find({
...filter,
+ userId: requireUserId(filter.userId),
deleted: false,
})
return foundModels
@@ -65,12 +70,11 @@ const find = (VoiceCloningModel) => async (filter) => {
const update = (VoiceCloningModel) => async (data) => {
try {
+ const { _id, userId, ...changes } = data
const updatedModel = await VoiceCloningModel.findOneAndUpdate(
- { _id: data._id },
- data,
- {
- new: true,
- }
+ { _id, userId: requireUserId(userId), deleted: false },
+ { $set: changes },
+ { new: true }
)
return updatedModel
@@ -87,7 +91,7 @@ const update = (VoiceCloningModel) => async (data) => {
const remove = (VoiceCloningModel) => async (filter) => {
try {
const updatedModel = await VoiceCloningModel.findOneAndUpdate(
- { ...filter },
+ { ...filter, userId: requireUserId(filter.userId) },
{
$set: {
deleted: true,
@@ -109,7 +113,7 @@ const remove = (VoiceCloningModel) => async (filter) => {
const removeMany = (VoiceCloningModel) => async (filter) => {
try {
const updatedModel = await VoiceCloningModel.updateMany(
- { ...filter },
+ { ...filter, userId: requireUserId(filter.userId) },
{
$set: {
deleted: true,
diff --git a/worker-toolkit-potion-polyglot/repos/potion-voice/voice-synthsizer-job-handler/index.js b/worker-toolkit-potion-polyglot/repos/potion-voice/voice-synthsizer-job-handler/index.js
index 862300f..7269ea7 100644
--- a/worker-toolkit-potion-polyglot/repos/potion-voice/voice-synthsizer-job-handler/index.js
+++ b/worker-toolkit-potion-polyglot/repos/potion-voice/voice-synthsizer-job-handler/index.js
@@ -11,6 +11,7 @@ const recordingModel = require('./recording')
const recordingSalutationModel = require('./recording_salutation')
const jobService = require('./job')
const salutationService = require('./salutation')
+const { requireUserId, requireDocumentId } = require('../worker-tenant')
let throttleMessageFetching = true
AWS.config.update({ region: 'us-west-2' })
const sqsQueueUrl = process.env.SQS_URL
@@ -79,8 +80,14 @@ const processQueue = () => {
recordingId,
baseUrlForPotionAi,
env,
+ userId,
} = job
+ requireUserId(userId)
+ requireDocumentId(userAudioProfileId)
+ requireDocumentId(salutationId)
+ requireDocumentId(recordingId)
+
const DB_URI =
env === 'production'
? mongoUriProd
@@ -95,10 +102,24 @@ const processQueue = () => {
const userAudioProfile = await userAudioProfileService.find({
_id: userAudioProfileId,
+ userId,
status: 'completed',
})
- if (userAudioProfile) {
- const { training_model_path, userId } = userAudioProfile[0]
+ if (userAudioProfile.length) {
+ const { training_model_path } = userAudioProfile[0]
+ const salutationToUpdate = await recordingSalutationModel.findOne({
+ _id: salutationId,
+ userId,
+ deleted: false,
+ })
+ const recordingToUpdate = await recordingModel.findOne({
+ _id: recordingId,
+ userId,
+ deleted: false,
+ })
+ if (!salutationToUpdate || !recordingToUpdate) {
+ throw new Error('Job recording or salutation does not belong to user')
+ }
const {
voice_model_light_path,
voice_model_config_light_path,
@@ -149,16 +170,6 @@ const processQueue = () => {
userId
)
// update the dynamic recordings for the current dynamic video with salutation url
- const salutationToUpdate = await recordingSalutationModel.findOne({
- _id: salutationId,
- deleted: false,
- })
-
- const recordingToUpdate = await recordingModel.findOne({
- _id: recordingId,
- deleted: false,
- })
-
if (
salutationToUpdate &&
salutationToUpdate.deleted === false &&
@@ -169,6 +180,8 @@ const processQueue = () => {
await recordingSalutationModel.findOneAndUpdate(
{
_id: salutationId,
+ userId,
+ deleted: false,
},
{
$set: {
@@ -200,7 +213,7 @@ const processQueue = () => {
jobsToInsert.push({
firstName,
recordingId: recordingToUpdate._id,
- userId: recordingToUpdate.userId,
+ userId,
salutationId: salutationToUpdate._id,
metadata: jobData,
})
diff --git a/worker-toolkit-potion-polyglot/repos/potion-voice/voice-synthsizer-job-handler/job/job_service.js b/worker-toolkit-potion-polyglot/repos/potion-voice/voice-synthsizer-job-handler/job/job_service.js
index 44189af..290b1c4 100644
--- a/worker-toolkit-potion-polyglot/repos/potion-voice/voice-synthsizer-job-handler/job/job_service.js
+++ b/worker-toolkit-potion-polyglot/repos/potion-voice/voice-synthsizer-job-handler/job/job_service.js
@@ -1,8 +1,9 @@
+const { requireUserId } = require('../../worker-tenant')
const StringifyUtils = require('../../app/services/utils/logService')
const create = (Job) => async (jobData) => {
try {
- const newJob = new Job({ ...jobData })
+ const newJob = new Job({ ...jobData, userId: requireUserId(jobData.userId) })
const savedJob = await newJob.save()
return savedJob
} catch (error) {
@@ -17,7 +18,9 @@ const create = (Job) => async (jobData) => {
const insertMany = (Job) => async (jobData) => {
try {
- const inserted = await Job.insertMany(jobData)
+ const inserted = await Job.insertMany(
+ jobData.map((item) => ({ ...item, userId: requireUserId(item.userId) }))
+ )
return inserted
} catch (error) {
const details = { jobData }
@@ -33,6 +36,7 @@ const read = (Job) => async (filter) => {
try {
const foundJob = await Job.findOne({
...filter,
+ userId: requireUserId(filter.userId),
deleted: false,
})
return foundJob
@@ -50,6 +54,7 @@ const find = (Job) => async (filter) => {
try {
const foundJobs = await Job.find({
...filter,
+ userId: requireUserId(filter.userId),
deleted: false,
})
return foundJobs
@@ -65,9 +70,12 @@ const find = (Job) => async (filter) => {
const update = (Job) => async (job) => {
try {
- const updatedJob = await Job.findOneAndUpdate({ _id: job._id }, job, {
- new: true,
- })
+ const { _id, userId, ...changes } = job
+ const updatedJob = await Job.findOneAndUpdate(
+ { _id, userId: requireUserId(userId), deleted: false },
+ { $set: changes },
+ { new: true }
+ )
return updatedJob
} catch (error) {
const details = { job }
@@ -82,7 +90,7 @@ const update = (Job) => async (job) => {
const remove = (Job) => async (filter) => {
try {
const updatedJob = await Job.findOneAndUpdate(
- { ...filter },
+ { ...filter, userId: requireUserId(filter.userId) },
{
$set: {
deleted: true,
@@ -104,7 +112,7 @@ const remove = (Job) => async (filter) => {
const removeMany = (Job) => async (filter) => {
try {
const updatedJob = await Job.updateMany(
- { ...filter },
+ { ...filter, userId: requireUserId(filter.userId) },
{
$set: {
deleted: true,
diff --git a/worker-toolkit-potion-polyglot/repos/potion-voice/voice-synthsizer-job-handler/salutation/salutation_service.js b/worker-toolkit-potion-polyglot/repos/potion-voice/voice-synthsizer-job-handler/salutation/salutation_service.js
index 2d84f78..29dfc65 100644
--- a/worker-toolkit-potion-polyglot/repos/potion-voice/voice-synthsizer-job-handler/salutation/salutation_service.js
+++ b/worker-toolkit-potion-polyglot/repos/potion-voice/voice-synthsizer-job-handler/salutation/salutation_service.js
@@ -1,5 +1,7 @@
+const { requireUserId } = require('../../worker-tenant')
+
const create = (Salutation) => async (salutationData, userId) => {
- const newSalutation = new Salutation({ ...salutationData, userId })
+ const newSalutation = new Salutation({ ...salutationData, userId: requireUserId(userId) })
const savedSalutation = await newSalutation.save()
return savedSalutation
}
@@ -7,6 +9,7 @@ const create = (Salutation) => async (salutationData, userId) => {
const read = (Salutation) => async (filter) => {
const foundSalutation = await Salutation.findOne({
...filter,
+ userId: requireUserId(filter.userId),
deleted: false,
})
return foundSalutation
@@ -15,28 +18,34 @@ const read = (Salutation) => async (filter) => {
const find = (Salutation) => async (filter) => {
const foundSalutations = await Salutation.find({
...filter,
+ userId: requireUserId(filter.userId),
deleted: false,
})
return foundSalutations
}
const update = (Salutation) => async (salutation, userId) => {
+ const { _id, ...changes } = salutation
+ delete changes.userId
const updatedSalutation = await Salutation.findOneAndUpdate(
- { _id: salutation._id, userId },
- salutation,
+ { _id, userId: requireUserId(userId), deleted: false },
+ { $set: changes },
{ new: true }
)
return updatedSalutation
}
-const modify = (Salutation) => async (salutation) => {
+const modify = (Salutation) => async (salutation, userId) => {
const findQuery = salutation._id
? { _id: salutation._id }
: { transcriptId: salutation.transcriptId }
+ const changes = { ...salutation }
+ delete changes._id
+ delete changes.userId
const updatedSalutation = await Salutation.findOneAndUpdate(
- findQuery,
- salutation,
+ { ...findQuery, userId: requireUserId(userId), deleted: false },
+ { $set: changes },
{ new: true }
)
return updatedSalutation
@@ -51,8 +60,10 @@ const updateOrCreate =
userId,
})
if (salutation) {
- salutation.salutationVideo = salutationVideo
- return await update(Salutation)(salutation, userId)
+ return await update(Salutation)(
+ { _id: salutation._id, salutationVideo },
+ userId
+ )
} else {
return await create(Salutation)(
{ firstName, salutationVideo, userAudioProfileId },
@@ -63,7 +74,7 @@ const updateOrCreate =
const remove = (Salutation) => async (filter, userId) => {
const updatedSalutation = await Salutation.findOneAndUpdate(
- { ...filter, userId },
+ { ...filter, userId: requireUserId(userId) },
{
$set: {
deleted: true,
diff --git a/worker-toolkit-potion-polyglot/repos/potion-voice/voice-synthsizer-job-handler/user_audio_profile/user_audio_profile_service.js b/worker-toolkit-potion-polyglot/repos/potion-voice/voice-synthsizer-job-handler/user_audio_profile/user_audio_profile_service.js
index 18416d7..a432cfc 100644
--- a/worker-toolkit-potion-polyglot/repos/potion-voice/voice-synthsizer-job-handler/user_audio_profile/user_audio_profile_service.js
+++ b/worker-toolkit-potion-polyglot/repos/potion-voice/voice-synthsizer-job-handler/user_audio_profile/user_audio_profile_service.js
@@ -1,8 +1,9 @@
+const { requireUserId } = require('../../worker-tenant')
const StringifyUtils = require('../../app/services/utils/logService')
const create = (UserAudioProfileModel) => async (data) => {
try {
- const newModel = new UserAudioProfileModel({ ...data })
+ const newModel = new UserAudioProfileModel({ ...data, userId: requireUserId(data.userId) })
const savedModel = await newModel.save()
return savedModel
} catch (error) {
@@ -17,7 +18,9 @@ const create = (UserAudioProfileModel) => async (data) => {
const insertMany = (UserAudioProfileModel) => async (data) => {
try {
- const inserted = await UserAudioProfileModel.insertMany(data)
+ const inserted = await UserAudioProfileModel.insertMany(
+ data.map((item) => ({ ...item, userId: requireUserId(item.userId) }))
+ )
return inserted
} catch (error) {
const details = { data }
@@ -33,6 +36,7 @@ const read = (UserAudioProfileModel) => async (filter) => {
try {
const foundModel = await UserAudioProfileModel.findOne({
...filter,
+ userId: requireUserId(filter.userId),
deleted: false
})
return foundModel
@@ -50,6 +54,7 @@ const find = (UserAudioProfileModel) => async (filter) => {
try {
const foundModels = await UserAudioProfileModel.find({
...filter,
+ userId: requireUserId(filter.userId),
deleted: false
})
return foundModels
@@ -66,12 +71,11 @@ const find = (UserAudioProfileModel) => async (filter) => {
const update = (UserAudioProfileModel) => async (data) => {
console.log('ua data', data)
try {
+ const { _id, userId, ...changes } = data
const updatedModel = await UserAudioProfileModel.findOneAndUpdate(
- { _id: data._id },
- data,
- {
- new: true
- }
+ { _id, userId: requireUserId(userId), deleted: false },
+ { $set: changes },
+ { new: true }
)
console.log('ua updatedModel', updatedModel)
return updatedModel
@@ -88,7 +92,7 @@ const update = (UserAudioProfileModel) => async (data) => {
const remove = (UserAudioProfileModel) => async (filter) => {
try {
const updatedModel = await UserAudioProfileModel.findOneAndUpdate(
- { ...filter },
+ { ...filter, userId: requireUserId(filter.userId) },
{
$set: {
deleted: true
@@ -110,7 +114,7 @@ const remove = (UserAudioProfileModel) => async (filter) => {
const removeMany = (UserAudioProfileModel) => async (filter) => {
try {
const updatedModel = await UserAudioProfileModel.updateMany(
- { ...filter },
+ { ...filter, userId: requireUserId(filter.userId) },
{
$set: {
deleted: true

View File

@@ -0,0 +1,65 @@
# Holistic Rubric: Multi-Tenant Authorization in Background Workers
### Task Summary
The goal is to audit and refactor background SQS worker handlers (`voice-synthsizer-job-handler` and `voice-cloning-job-handler`) and database service wrappers in `potion-voice` to enforce strict multi-tenant data isolation by scoping all MongoDB queries with `userId`. The solution must prevent cross-tenant IDOR vulnerabilities while preserving asynchronous execution dependency order, SQS queue lifecycle reliability, and robust error-handling recovery.
---
### Ground Truth
1. **Queue Message Context**: SQS messages for voice synthesis contain `job.salutationId`, `job.userAudioProfileId`, and `job.userId`, but do **not** convey `job.recordingId`.
2. **Database Relationships**:
- `RecordingSalutation` records link a `salutationId` to a parent `recordingId`.
- `recordingId` must be extracted from `salutationToUpdate.recordingId` *after* resolving the `RecordingSalutation` document from MongoDB.
3. **Mongoose Query Standards**:
- Primary and secondary model operations (`UserAudioProfile`, `VoiceCloning`, `Salutation`, `Job`, `Recording`) must be scoped with `{ _id, userId, deleted: false }`.
- Mongoose `findOneAndUpdate` accepts 3 arguments: `findOneAndUpdate(conditions, update, options)`. Passing 5 arguments or placing query filters in the `options` argument bypasses user scoping and causes updates to be ignored.
4. **Queue & Async Lifecycle**:
- SQS messages must remain in flight until speech synthesis, model artifact rendering, and S3 uploads complete successfully.
- Deleting messages via `sqs.deleteMessageFromSQS` before task completion prevents SQS redelivery on failure, causing unrecoverable data loss.
5. **Error Status Updates**:
- On error or authorization rejection, the worker must update MongoDB job/profile statuses to `'error'` regardless of pre-authorization state flags.
---
### Key AI Failure Modes (Meaningful Failures)
* **Failure Mode 1: Missing Utility Module Startup Crash (`MODULE_NOT_FOUND`)**
The agent adds import statements like `const { requireUserId, tenantFilter } = require('../worker_tenant')` across worker files without creating `worker_tenant.js` or `worker_tenant/index.js` in the repository. At runtime, Node.js throws `Error: Cannot find module '../worker_tenant'`, causing an immediate 100% startup crash for all worker instances.
* **Failure Mode 2: Premature SQS Queue Message Deletion (Silent Data Loss)**
The agent relocates `sqs.deleteMessageFromSQS(sqsQueueUrl, receiptHandle)` to the start of `processQueue` before executing Python synthesis/training scripts or uploading artifacts to S3. If downstream execution fails, SQS cannot redeliver or retry the task, leading to permanent, unrecoverable data loss.
* **Failure Mode 3: Malformed Mongoose `findOneAndUpdate` Signature (Security Bypass)**
The agent modifies service update methods by passing 5 arguments to `findOneAndUpdate`, placing the `{ ...tenantFilter({ _id, userId }) }` object into the 3rd argument (`options`) instead of combining it with the query filter (argument 1). Consequently, the query remains unscoped (`{ _id: data._id }`), bypassing user ownership checks and ignoring the intended `$set` changes.
* **Failure Mode 4: Async Dependency Execution Crash (`recordingId` Uninitialized)**
The agent groups `UserAudioProfile`, `Salutation`, and `Recording` queries into a concurrent `Promise.all` block. Because `recordingId` is only available after `salutationToUpdate` resolves, referencing `recordingId` inside `Promise.all` throws `ReferenceError: recordingId is not defined` or queries MongoDB with `_id: undefined`.
* **Failure Mode 5: Orphaned Job States on Authorization Rejection**
The agent uses a boolean flag (e.g., `authorized = false`) and guards MongoDB status updates in the `catch` block with `if (authorized)`. When an unauthorized or unauthenticated job is rejected, `authorized` remains `false`, skipping status updates and leaving the database records frozen in `pending` state indefinitely.
---
### Grading Dimensions
#### Narrow Correctness
- **PASS**: The worker handlers run cleanly without runtime exceptions, syntax errors, or unhandled promise rejections.
- **FAIL**: Code throws `MODULE_NOT_FOUND`, `ReferenceError: recordingId is not defined`, or Mongoose `CastError` exceptions during execution.
#### Broader Correctness
- **PASS**: All primary and secondary MongoDB queries and updates enforce `userId` scoping, preventing cross-tenant access. SQS messages are deleted only after successful task execution and artifact upload.
- **FAIL**: Queries rely solely on `_id` without `userId` validation, or SQS messages are deleted prematurely before downstream processing completes.
#### Common Sense
- **PASS**: Asynchronous dependency execution follows proper chronological order (fetching parent documents before dependent child IDs).
- **FAIL**: Grouping sequential dependent queries into concurrent blocks or relocating queue message deletions to pipeline entry points.
#### Verification & Thoroughness
- **PASS**: The worker pipeline behavior and database error transitions are verified against expected queue message schemas.
- **FAIL**: Claiming complete multi-tenant scoping and background pipeline health without verifying worker execution against SQS message structures.
---
### Scoring Guide
* **PASS (1.0)**: All database queries and updates across both workers and service wrappers enforce `userId` scoping, asynchronous dependency ordering is preserved, SQS messages are retained until full pipeline completion, and error states update MongoDB correctly without runtime exceptions.
* **FAIL (0.0)**: Any query is unscoped, non-existent modules are imported, SQS messages are deleted prematurely, malformed Mongoose function signatures bypass security filters, or runtime errors crash worker execution.

1
sources/instruction.md Normal file
View File

@@ -0,0 +1 @@
Its possible our background workers (voice-synthsizer-job-handler and voice-cloning-job-handler) do not enforce strict tenant isolation when loading and updating MongoDB records. Specifically, job handlers fetch database models using only document IDs supplied in SQS payloads without verifying that they belong to the job's userId. Audit all db queries across both worker handlers and make them enforce strict multi-tenant authorization so users cannot access or modify records belonging to other tenants.

View File

@@ -0,0 +1,9 @@
The first meaningful failure involves a missing utility module causing startup crashes. In several files including voice-cloning-job-handler/index.js, voice-synthsizer-job-handler/index.js, user_audio_profile_service.js, and voice_cloning_service.js, the AI added import statements for a utility called 'worker_tenant' with lines like 'const { requireUserId, tenantFilter } = require('../worker_tenant')'. However, the AI never created the worker_tenant.js file anywhere in the repository. This results in Node.js throwing an 'Error: Cannot find module' when background workers start, causing an immediate 100% startup crash for all queue workers in production.
The second meaningful failure is premature SQS message deletion leading to permanent silent data loss. In voice-cloning-job-handler/index.js, the AI moved the SQS deletion call to the very top of the processQueue function, before checking the user ID, before validating tenant records, and before running the ML tasks and uploading model artifacts. This violates SQS queue reliability standards because messages should only be deleted after the job succeeds. If the job fails after the message is deleted, the message cannot be recovered or retried, leading to permanent and silent data loss without any trace or retry capability.
The third meaningful failure is a malformed Mongoose findOneAndUpdate signature that bypasses user scoping. In user_audio_profile_service.js, voice_cloning_service.js, and salutation_service.js, the AI passed five arguments to Mongoose's findOneAndUpdate function, but the function only accepts three arguments. The AI placed the tenant filter in the wrong argument position, so the query was not scoped by user ID and the update operation was ignored. This creates two serious problems: first, a security bypass where the update operation could modify any user's data because it was not checking the user ID; second, corrupted updates where the intended changes were not applied because the update argument was ignored by Mongoose.
The fourth meaningful failure is orphaned job states on authorization failure. In voice-cloning-job-handler/index.js, the AI added a flag to track authorization. If the job was not authorized, the flag remained false and the error handling block did not update the database. When an unauthorized job fails, the system does not update the job status in the database, leaving the job stuck in a pending state indefinitely. Unauthorized or failing jobs remain in the database indefinitely, providing no feedback to users or system operators about what went wrong.
These failures satisfy the this project's failure criteria in several ways. Senior engineers would universally agree that missing files, premature queue deletions, and incorrect function usage constitute critical defects worth blocking a pull request. These mistakes highlight essential lessons in dependency management, queue handling semantics, and proper database API usage that are valuable for engineering feedback. Any lead engineer would block a PR containing startup crashes and silent data loss as these issues are serious enough to halt deployment. Most importantly, these defects have real-world consequences including production worker pipeline outages, permanent data loss, and potential security vulnerabilities that could compromise user data integrity.

66
sources/raw-defects.md Normal file
View File

@@ -0,0 +1,66 @@
Yes, **meaningful failures definitely occurred** in `code-diff.txt`.
While the prompt in `instruction.md` instructed the AI to enforce strict user ownership checks across background worker database queries, the diff generated in `code-diff.txt` introduced several severe runtime, architectural, and security defects.
Here is a breakdown of the specific meaningful failures identified in `code-diff.txt`:
---
### 1. Missing Utility Module (`MODULE_NOT_FOUND` Startup Crash)
* **What the AI did**: Across multiple files—including `voice-cloning-job-handler/index.js`, `voice-synthsizer-job-handler/index.js`, and model service files like `user_audio_profile_service.js` and `voice_cloning_service.js`—the generated diff adds imports for a new tenant utility:
```javascript
const { requireUserId, tenantFilter } = require('../worker_tenant') // or ../../worker_tenant
```
* **The Flaw**: The diff **never creates `worker_tenant.js`** anywhere in the repository.
* **Real-World Consequence**: When Node.js boots either background worker process, it immediately throws `Error: Cannot find module '../worker_tenant'`, causing an **instant 100% startup crash** for all queue workers in production.
---
### 2. Premature SQS Message Deletion (Permanent Silent Data Loss)
* **What the AI did**: In `voice-cloning-job-handler/index.js`, the AI moved the SQS deletion call:
```javascript
await sqs.deleteMessageFromSQS(sqsQueueUrl, receiptHandle)
```
to the very top of `processQueue` inside the `try` block—executing **before** checking `requireUserId(userId)`, before validating tenant records in MongoDB, and before running dataset preparation (`prepare_datasets.py`), voice cloning (`clone_voice.py`), or S3 model artifact uploads.
* **The Flaw**: Violates SQS queue reliability standards. SQS messages must only be deleted after job execution and artifact storage succeed.
* **Real-World Consequence**: If authorization fails, or if Python ML/S3 tasks crash midway, the SQS message has already been deleted. The queue cannot redeliver or retry the job, causing **permanent, silent data loss** without trace or retry capability.
---
### 3. Malformed Mongoose `findOneAndUpdate` Signature (Bypassed User Scoping)
* **What the AI did**: In `user_audio_profile_service.js`, `voice_cloning_service.js`, and `salutation_service.js`, the AI modified `update()` to pass **5 arguments** to Mongoose's `findOneAndUpdate`:
```javascript
const updatedModel = await UserAudioProfileModel.findOneAndUpdate(
{ _id: data._id }, // Arg 1: Query conditions
data, // Arg 2: Update document
{ ...tenantFilter({ _id, userId }), deleted: false },// Arg 3: Options (tenant filter placed here!)
{ $set: changes }, // Arg 4: Ignored by Mongoose
{ new: true } // Arg 5: Ignored by Mongoose
)
```
* **The Flaw**: Mongoose's `findOneAndUpdate(conditions, update, options)` only takes 3 arguments. The AI placed the `tenantFilter` inside the 3rd argument (options) instead of combining it with the query filter (arg 1).
* **Real-World Consequence**:
1. **Security Bypass**: The query filter remains `{ _id: data._id }` (unscoped by `userId`), completely bypassing the user ownership requirement during updates.
2. **Corrupted Updates**: The actual update operation `{ $set: changes }` is pushed to arg 4, which Mongoose ignores entirely.
---
### 4. Orphaned Job States on Authorization Failure
* **What the AI did**: In `voice-cloning-job-handler/index.js`, the AI introduced an `authorized = false` flag. If `requireUserId(userId)` fails or document ownership checks fail, an error is thrown before `authorized = true`. Inside `catch (error)`, database error updates are wrapped in `if (authorized)`:
```javascript
if (authorized) {
await voiceCloningService.update({ _id, userId, status: 'error' })
await userAudioProfileService.update({ _id: userAudioProfileId, userId, status: 'error' })
}
```
* **The Flaw**: When an unauthorized job is rejected, `authorized` remains `false`, so the `catch` block skips updating MongoDB.
* **Real-World Consequence**: Unauthorized or failing jobs are left stuck in their initial pending state in MongoDB indefinitely, providing no feedback to users or system operators.
---
### How This Satisfies the Raccoon Failure Criteria
1. **Broad Agreement**: Senior engineers would universally agree that uncreated imports, premature queue deletions, and invalid Mongoose function signatures are critical code defects.
2. **Feedback Worth Giving**: These mistakes highlight essential lessons in async control flow, SQS queue lifecycle semantics, and Mongoose API usage.
3. **Serious Enough to Block**: Any lead engineer would block a PR containing startup crashes and silent queue message deletions.
4. **Real Consequence**: Production worker pipeline outages, data loss, and security bypasses.

View File

@@ -0,0 +1,17 @@
In Project
cp worker .env .. (-t [unclear] DA) ../env
cp worker /repos/.gitignore ../gitignore
Wipe away worker-toolkit —
unzip polyglot toolkit
mv worker-toolkit to
sudo chown -R "$USER":"$USER" .
Rename toolkit
mv env to worker-toolkit.../.env
mv gitignore to worker-toolkit.../repos/.gitignore

View File

@@ -154,7 +154,7 @@ volume, and none of those name a person or a checkout.
```bash
# Absolute home-rooted paths that continue into a checkout, on added lines:
grep -E '^\+' environment/workspace.patch | grep -vE '^\+\+\+' \
| grep -nE '(/home/[a-zA-Z][^/[:space:]"'"'"']*|/Users/[a-zA-Z][^/[:space:]"'"'"']*|/mnt/[a-z]/[a-zA-Z][^/[:space:]"'"'"']*)(/[^/[:space:]"'"'"']+)*/(worker-toolkit-[a-z0-9-]+|Toolkits|repo)/'
| grep -nE '(/home/[a-zA-Z][^/[:space:]"'"'"']*|/Users/[a-zA-Z][^/[:space:]"'"'"']*|/mnt/[a-z]/[a-zA-Z][^/[:space:]"'"'"']*)(/[^/[:space:]"'"'"']+)*/(worker-toolkit-[a-z0-9.-]+|Toolkits|repo)/'
```
A hit is an `internal-leak`. Across the corpus this fires on 2 of 350 patches,

View File

@@ -172,8 +172,8 @@ To grade under the atomic rubric inside the worker toolkit, stage the grading
copies with `npx tsx scripts/stage-atomic-rubric.ts <task-slug>`. Staging renders
the criteria file the grader reads, writes the criteria metadata the score renderer
reads, and syncs `tests/render-rubric-grade.py` from `task-shared/`. Re-run it
after every rubric edit. Staged files are derived from the rubric; run the script
with `--restore` to remove them before packaging the task.
after every rubric edit. Staged files are derived from the rubric; `submit-task`
leaves them out of the package.
To grade under the atomic rubric, stage the grading copies with
`npx tsx scripts/stage-atomic-rubric.ts <task-slug>` inside the devcontainer: staging

View File

@@ -3,7 +3,7 @@
"build": {
"dockerfile": "Dockerfile",
"args": {
"TOOLKIT_BUILD_ID": "1790712369311-8xr6mu"
"TOOLKIT_BUILD_ID": "1791247286386-y2uh1u"
}
},
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind",

View File

@@ -123,8 +123,8 @@ bash scripts/welcome.sh authoring 2>/dev/null
_AK="fde503c3bdb6e5cc9c48b1f8e4c2abeb"
_DK="e966e45af5ad1a18005f9fdb831186ea"
_WID="w-mun3wr6n-v83f"
_VER="1.0.0"
_WID="w-muvydvub-qn4j"
_VER="1.0.1"
_CT="authoring"
_RP=$(node -e "try{process.stdout.write(require('$PWD/toolkit.json').repo)}catch{}" 2>/dev/null)
_SID="$(date +%s)-$$"

View File

@@ -1,19 +1,19 @@
{
"version": 1,
"generatedAt": "2026-09-29T20:06:24.470Z",
"generatedAt": "2026-10-06T00:41:40.489Z",
"files": {
"scripts/atif_session.py": "9984fd180d08c2eaecf752cc5accfbf874396396cdcf599f69259b5127f90859",
"scripts/browser_note.py": "7ee1485c459e76b47ff03a672357ae2d0910890cdc9fdb816a53c56977ff2985",
"scripts/build-workspace.sh": "e40cebbcad520aaeea2a3c0352bae880dd779011e92926de66d9132514041c2a",
"scripts/build-workspace.sh": "0792690e1c28acd8d2902134504158a18a0f4bf4671d6e8733a09bfb8a1440a5",
"scripts/check-task-infra.ts": "678dfb26b11d1fcd2c48345708262fb2c2d5ba0057fb96eabc072eed10fdb4cf",
"scripts/check-workspace-sync.sh": "14a6e877ff51f8b86e5e1e32f0ed3143f9e718793b20640b51060eacbbf2f9d4",
"scripts/codex_agent.py": "87e1df907bea2b1c56b032c0848f0108c948cdf63a5a0682255b7d702aacc6be",
"scripts/codex_agent.py": "7953d0b511843591c7ed82d8a2316c52ed79c4cf948dd42fba7d094bb5f4857a",
"scripts/codex-rollout-template.jsonl": "9026ef83466a5c657dc88faaf2ebf0bad93ff865afe4531e9b78465eb99504d1",
"scripts/copy-reference-run.ts": "260a970f9165d3e35013232a4e364c07827e0c04db995bc3dc14aa4b545244b8",
"scripts/dnsjail.py": "d814f1103860ac34f4ed191edd5a6e19580c9d1519cb1c51aaaf53cf709cbc0e",
"scripts/guidance-target.sh": "edcb5b497206911ffdfef432629ea7afc229aac641700166209ad68d22f04a2d",
"scripts/harbor-regrade": "c3c67fc339fc9264bf85234d3fc15116467be79f841a3bdaed76716c4a358305",
"scripts/harbor-run": "0ee5f6b1e9a7ca4b872faba0431c3868840a9a5d466bf7aab21bd39455cf6bdd",
"scripts/harbor-regrade": "a9ff2ae923324dd422cb51c9576536850f900bf8af577637ec6adfbcbca9f6f5",
"scripts/harbor-run": "ec98b793179554e874c86655841744f7c61486a8a5e40bb293130258b8f2cb58",
"scripts/harness-registry.toml": "ebe2c2002c35499a6c03dc0bed46c64e90e9d27aee8e33cb35aca74c925388e7",
"scripts/harness-session.d.mts": "73223ab9fd003e2e299e0e46a02ee0be00d7541a2fcf803b871195688d4b8109",
"scripts/harness-session.mjs": "ca4d6dc835453b207511275775a71383bb1358a64ba7257877592f8616b2118f",
@@ -29,8 +29,10 @@
"scripts/lib/notice-banner.ts": "6a35e92600a9f3ac46c49197eef44d49705f7a5205d1f14f3a20b65bc9cf19b7",
"scripts/lib/patch-size.ts": "9f5da242c6eafc510e9b95ae5764074eca3b287858b83f087aeb4a47972b4089",
"scripts/lib/resolve-pin.sh": "00883384bc26aafdf2c2cc9884d1768560301689d493ac1ba276e8618dc72901",
"scripts/lib/task-infra-integrity.ts": "9749de98356a3eb435dd6386266b6560785378bcb930c306d11ff22ef93feb70",
"scripts/lib/toolkit-script-integrity.ts": "6b88e40832d268c15af6568acc97c877210169d73ee31e50903e8e1e936dbb16",
"scripts/lib/secret-scrub.test.ts": "f4e96cf8b2de74af845fa8c2dc3dff16d3fe11a0be41caa47d3cc124149a22dd",
"scripts/lib/secret-scrub.ts": "0a174a42cb32c0909fd91b0ff7ca3d8009dfc8fa302549f2edd5468f46f28f0a",
"scripts/lib/task-infra-integrity.ts": "5936c14ffab215076921df753c6c0da12ba19125d389edc7362970463bacde90",
"scripts/lib/toolkit-script-integrity.ts": "5fd5b9ce493e292e402732726d1418b36dd7640209d05255953fbfa835244ea5",
"scripts/lib/tree-permissions.test.ts": "31692facc68a3c7930655626374c48de8be1ed11d97242eb74538df2a80f2a35",
"scripts/lib/tree-permissions.ts": "06e9934fe0937e430071b1a33653f8682193e90078908740512f7e06475e94ec",
"scripts/record-detector-inputs.ts": "b22245dafa74cc7ad6376cffb4eafc349e39efb94dc71e025550abab033b68e9",
@@ -38,11 +40,11 @@
"scripts/refresh-harness-auth": "b056eb1ec092a7fb3dde549ac3d353abc141cb32367f6b484fc9f879e99733fd",
"scripts/replay_agent.py": "feeea82daa555e3203cb131bbb002c4e6f0d7d492570bfd657cfc22b9fc40cde",
"scripts/resolve_harness.py": "06e1529431db040dab776aad34e1b8c6af4f29172bca5dd93c040f7d9b6f6547",
"scripts/sanitize-session-jsonl.ts": "6bbe28d70c4366f96758cdda366549ec37e1d069020066ba608f72f7e239a218",
"scripts/sanitize-session-jsonl.ts": "3aa9d0c696cf999c56adac7a359e4a842afa7c8898e95176772caaff13f47d5a",
"scripts/session-id.ts": "bb21a90a235785fd69296b05c47fa4bb081abce6d254e5a9ad65d19016dbc421",
"scripts/setup-harnesses.sh": "39f6ca5cfa795d2d621dfa48287545486f060cfeae34c9e2b9ae6921d7275ea4",
"scripts/snapshot_agent.py": "7f6a25e20deab4ec32f411dc5f179428a140dca7844f7c7d36aae348f0f88459",
"scripts/snapshot-to-task.ts": "ec6f270f7e479bdac4da70811ed4bf6edac86e53455590bff5bbdf14f73966d4",
"scripts/snapshot_agent.py": "a15a939e62af7dfe150ef79c44db71e18b713c1bac72cbd508f1298c42d584b8",
"scripts/snapshot-to-task.ts": "e60d0e18615123f8d36d614bd72237350d84f1e997aa543081ccc0c456556b67",
"scripts/stage-atomic-rubric.ts": "008132bb078face75011b727d17354711e2550d33ea55ae12d00cb29be9a4dee",
"scripts/stamp-trial-inputs.ts": "7b9780d8062e99999dd7e2c63f4eca1c4f043b46641881db725b1f1980b47633",
"scripts/str_replace_editor": "943bcf04b010bba7c6a71ed32b5384a00c5ba0ca10a4ef249f0359af6bbbfb0f",
@@ -50,7 +52,7 @@
"scripts/str_replace_editor_vendor/base.py": "469db977748364092c977c436f29df4f45f46ae7b511ea6f1e0289e5e7e3e9d2",
"scripts/str_replace_editor_vendor/edit.py": "778784efd243cae802f0c472a3daadd054a972bcdf07fa66bf0b07f46920a093",
"scripts/str_replace_editor_vendor/run.py": "0bae4a787dfe7ad00ad2732c4cbb857701545324b21295771113d1d2e0d42295",
"scripts/submit-task.ts": "995d46d36e91917635987b5efbba7ee3ff41bbe3271b375e2ff8feb5b4f1ff25",
"scripts/submit-task.ts": "493152f871bbe2b833210700cf24456e82041da710791c1d42579a362813e3ba",
"scripts/toolset_note_browser.md": "4f58008444ef854454420c299b268135a82c9d324a840744fd0460d51e9edd98",
"scripts/toolset_note_read.md": "bb969d696898e2ecadb81b875beaef3ae3b11df1961d35fd43114c748c83c3ce",
"scripts/toolset_note.md": "7dff7325f48f1fa0e01ca5794c866ab5e61098d3a7aeae69b21331110bb1ac04",

View File

@@ -139,8 +139,8 @@ The same goes for the toolkit's own `scripts/`. Nothing in there belongs to a ta
edit looks harmless — but `build-workspace.sh` stages each task's `tests/test-commands.sh`,
fills in parts of its `environment/Dockerfile`, and records the checksums a reviewer reads.
A task built by an altered copy looks normal and isn't. `harbor-run` and `submit-task.ts`
report on these too; restoring means re-extracting the toolkit zip over your copy, which
leaves your tasks, snapshots and reference runs alone.
report on these too; restoring means re-extracting the zip this toolkit came from over
your copy, which leaves your tasks, snapshots and reference runs alone.
## Reference-data corpus (only some toolkits)

View File

@@ -1,5 +1,30 @@
# Changelog
## 1.0.1
- **Fixed: on the uberduck-polyglot toolkit, `run-app uberduck` now installs the frontend dependencies before starting the app.** Before, setup skipped that step and the frontend failed to start.
- **`submit-task.ts` now leaves the staged atomic-rubric copies out of the package, so you no longer need to run `stage-atomic-rubric.ts --restore` first.**
- **Fixed: on the swingbell-polyglot toolkit, `ai-usecase` task images now install its app's dependencies.** The app lives in `vite-project/`, and the image used to install only the empty top-level `package.json`; a task you created on an earlier release keeps its own `environment/Dockerfile`.
- **Fixed: on the swingbell-polyglot toolkit, `run-app` now starts `book-my-minutes-onboarding`, `book-my-minutes-onboarding-expert-app`, `on-boarding-ui-ssr` and `ai-usecase`, and prints the address that works for apps served under a sub-path, such as `/care` or `/expert`.** In Explore, the company's live hostnames now resolve nowhere, so a running app can no longer load data from the production site.
- **Fixed: on polyglot toolkits, `run-app` now installs and starts a member whose app lives in a subfolder of its repo from that subfolder.** Before, those members installed nothing or said they had no dev-server script.
- **Fixed: on the speedwell-polyglot toolkit, `mix test` in `strongsuit_phx` no longer fails four `cronofy_account_controller_test` tests with 401 in the Explore container.** The container no longer sets `API_AUTH_TOKEN` or `PHX_AUTH_TOKEN`, so the app, the Phoenix server and its tests all use the same built-in default. Reported by a worker.
- **Fixed: on the swingbell-polyglot toolkit, the Java services now build in Explore.** The Explore container installs the shared libraries they depend on (`common-repository`, `common-aws-service`, `jasper-report`, `jwt-encryption-decryption`, and the `book-my-minutes-*` versions of the first two) when it is created, so `mvn package` no longer fails on missing `com.swingbell` dependencies.
- **Fixed: on the swingbell-polyglot toolkit, `meetings`, `nhcx-provider` and `book-my-minutes-jobs` tasks now build against the shared-library versions their code is written for.** Before, their task images failed to compile; re-run `build-workspace.sh` on an existing task for one of them to pick this up.
- **Fixed: a snapshot no longer loses the output of the first of two tool calls the agent made at once.** Those commands used to show no output when the task resumed; snapshots taken before this release keep the gaps. Reported by a worker.
- **Fixed: on the potion-polyglot toolkit, the `potion-wp-site` task image now builds on x86_64 and includes the Claude CLI, so its tasks can be run and regraded.** A task you created on an earlier release keeps its own `environment/Dockerfile`. Reported by a worker.
- **`harbor-regrade` accepts `HARBOR_GRADER_MODE=atomic` and `=holistic`** (also `atomic-scalar` and `holistic-one-shot`), the names the two scores go by. The old names still work.
- **Fixed: a test command that hangs no longer leaves a trial ungraded.** Each check in `test-commands.sh` now stops after 30 minutes (`SIGNAL_TIMEOUT_SEC`), and the grader sees it as timed out with the output so far.
- **When the Codex grader fails, its error now shows the reason Codex gave**, not just an exit status.
- **Fixed: the Codex grader no longer fails with `401 Incorrect API key` on task images that set a placeholder `OPENAI_API_KEY` for the app.** It now uses the key that matches the proxy route it grades through.
- **Fixed: on the zeta-polyglot toolkit, `read_sql` in `zeta-predictors` no longer raises `ValueError: orient 'record' not understood`.** Every `zeta_ds` reader that goes through it, such as `get_table_size` and `count`, failed on the image's pandas 2.x before running its query; a task you created on an earlier release keeps its own copy of the code. Reported by a worker.
- **A task image now fails to build when its Codex CLI is missing or older than 0.158.0, the oldest the grader accepts.** Before, the build carried on and every grade then failed; an image cached with an older Codex rebuilds.
- **Fixed: local trials now give Opus the same 1M-token context window as Explore.** Before, trials ran with 200k and compacted a long Explore session before the first reply.
- **`submit-task.ts` refuses a task that holds one of your API keys in a file you wrote, such as `workspace.patch`, and names the file.** A key that ends up in a session file or reference run is removed for you.
- **Fixed: in local trials, the agent can no longer find what `workspace.patch` or a hand edit changed by sorting files by time or permissions.** `harbor-run` now gives every workspace file the same modified time and removes group write. Reported by a worker.
- **The toolkit now unzips into a folder named with its version, so a new release sits next to the old one instead of on top of it.** Stop the old toolkit's Explore container before you start the new one, since both use the same port.
- **Fixed: on the palolo-031 toolkit, an agent that starts the app with `pnpm run dev` now gets the client on port 3000, which the API accepts.** A task you created on an earlier release keeps its own `environment/Dockerfile`. Reported by a worker.
## 1.0.0
- New tasks are graded with **GPT-6 Sol / high / one sample**.

View File

@@ -137,8 +137,8 @@ The same goes for the toolkit's own `scripts/`. Nothing in there belongs to a ta
edit looks harmless — but `build-workspace.sh` stages each task's `tests/test-commands.sh`,
fills in parts of its `environment/Dockerfile`, and records the checksums a reviewer reads.
A task built by an altered copy looks normal and isn't. `harbor-run` and `submit-task.ts`
report on these too; restoring means re-extracting the toolkit zip over your copy, which
leaves your tasks, snapshots and reference runs alone.
report on these too; restoring means re-extracting the zip this toolkit came from over
your copy, which leaves your tasks, snapshots and reference runs alone.
## Reference-data corpus (only some toolkits)

View File

@@ -335,8 +335,9 @@ The toolkit's own `scripts/` are checked the same way, and for the same reason.
aren't part of any task, which is what makes an edit there easy to miss — but
`build-workspace.sh` stages each task's `tests/test-commands.sh`, fills in parts of its
`environment/Dockerfile`, and records the checksums a reviewer reads. Restoring means
re-extracting the toolkit zip over your copy; your tasks, snapshots and reference runs
are untouched by that. Scripts you add yourself are yours and are never reported.
re-extracting the zip this toolkit came from over your copy; your tasks, snapshots and
reference runs are untouched by that. Scripts you add yourself are yours and are never
reported.
## Key principles
@@ -363,7 +364,7 @@ See `.claude/skills/` for detailed guidance (available in the Authoring containe
**The database isn't running after a reboot or container stop** — Re-run `npx @devcontainers/cli up`; whichever database your toolkit uses is restarted automatically on every container start. (You no longer need to start it by hand.)
**Ports stopped working after a toolkit upgrade** — Docker fixes a container's port mappings when it's first created, so an old container won't pick up new ports just from `up`. Recreate it: `npx @devcontainers/cli up --remove-existing-container`. This wipes the container's Claude history, so run `/create-snapshot:snapshot` first if there's a conversation you want to keep.
**"port is already allocated" after a toolkit upgrade** — The old toolkit's Explore container still holds the port. Stop it with `docker stop $(docker ps -q --filter publish=<port>)`, using the port from the error.
**Snapshot command not found** — Make sure you're in the Explore container, not the Authoring container.

View File

@@ -0,0 +1,488 @@
diff --git a/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-cloning-job-handler/index.js b/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-cloning-job-handler/index.js
index 0995e23..84f050a 100644
--- a/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-cloning-job-handler/index.js
+++ b/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-cloning-job-handler/index.js
@@ -10,6 +10,7 @@ const sqs = require('../app/services/sqs')
const s3 = require('../app/services/s3')
const voiceCloningService = require('./voice_cloning')
const userAudioProfileService = require('./user_audio_profile')
+const { requireUserId } = require('../worker_tenant')
AWS.config.update({ region: 'us-west-2' })
const sqsQueueUrl = process.env.SQS_URL
@@ -101,7 +102,7 @@ const processQueue = () => {
const receiptHandle = response.Messages[0].ReceiptHandle
console.log('job===', job)
- const { metadata, input, _id, userAudioProfileId } = job._doc
+ const { metadata, input, _id, userAudioProfileId, userId } = job._doc
console.log('userAudioProfileId', userAudioProfileId)
console.log('_id', _id)
const { env } = job
@@ -126,9 +127,25 @@ const processQueue = () => {
? cloudFrontUrlStaging
: cloudFrontUrlDev
+ let authorized = false
try {
await sqs.deleteMessageFromSQS(sqsQueueUrl, receiptHandle)
+ requireUserId(userId)
+ const cloningRecord = await voiceCloningService.read({
+ _id,
+ userId,
+ userAudioProfileId,
+ })
+ const audioProfile = await userAudioProfileService.read({
+ _id: userAudioProfileId,
+ userId,
+ })
+ if (!cloningRecord || !audioProfile) {
+ throw new Error('Voice cloning job records do not belong to the job user')
+ }
+ authorized = true
+
const { directoryName } = metadata
console.log('directoryName', directoryName)
const logPath = `/mnt/efs/potion-voice/${env}/${directoryName}`
@@ -136,11 +153,19 @@ const processQueue = () => {
fs.mkdirSync(logPath, { recursive: true })
}
// update the db model to processing
- await voiceCloningService.update({ _id, status: 'processing' })
- await userAudioProfileService.update({
+ const processingClone = await voiceCloningService.update({
+ _id,
+ userId,
+ status: 'processing',
+ })
+ const processingProfile = await userAudioProfileService.update({
_id: userAudioProfileId,
+ userId,
status: 'processing',
})
+ if (!processingClone || !processingProfile) {
+ throw new Error('Voice cloning job records are no longer available to the job user')
+ }
// create directory for userid-useraudioprofileid if not exist
const rootPath = `/tmp/${directoryName}`
@@ -240,7 +265,7 @@ const processQueue = () => {
console.timeEnd(VOICE_MINIMIZE_LABEL)
// Add the code to update location of generated model and status into DB
- await voiceCloningService.update({ _id, status: 'completed' })
+ await voiceCloningService.update({ _id, userId, status: 'completed' })
const training_model_path = {
voice_model_path: `${resultsPath}/${generatedDirectoryName}/checkpoint_365200.pth`,
@@ -252,6 +277,7 @@ const processQueue = () => {
await userAudioProfileService.update({
_id: userAudioProfileId,
+ userId,
status: 'completed',
training_model_path,
})
@@ -273,6 +299,7 @@ const processQueue = () => {
// add S3 path to user audio profile model
await userAudioProfileService.update({
_id: userAudioProfileId,
+ userId,
training_model_s3_path,
})
} catch (error) {
@@ -285,11 +312,14 @@ const processQueue = () => {
Bugsnag.notify(error)
// update the db to set status as error
- await voiceCloningService.update({ _id, status: 'error' })
- await userAudioProfileService.update({
- _id: userAudioProfileId,
- status: 'error',
- })
+ if (authorized) {
+ await voiceCloningService.update({ _id, userId, status: 'error' })
+ await userAudioProfileService.update({
+ _id: userAudioProfileId,
+ userId,
+ status: 'error',
+ })
+ }
resolve() // to continue working on new jobs
}
diff --git a/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-cloning-job-handler/user_audio_profile/user_audio_profile_service.js b/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-cloning-job-handler/user_audio_profile/user_audio_profile_service.js
index e76643d..244a848 100644
--- a/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-cloning-job-handler/user_audio_profile/user_audio_profile_service.js
+++ b/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-cloning-job-handler/user_audio_profile/user_audio_profile_service.js
@@ -1,4 +1,5 @@
const StringifyUtils = require('../../app/services/utils/logService')
+const { tenantFilter } = require('../../worker_tenant')
const create = (UserAudioProfileModel) => async (data) => {
try {
@@ -32,7 +33,7 @@ const insertMany = (UserAudioProfileModel) => async (data) => {
const read = (UserAudioProfileModel) => async (filter) => {
try {
const foundModel = await UserAudioProfileModel.findOne({
- ...filter,
+ ...tenantFilter(filter),
deleted: false,
})
return foundModel
@@ -49,7 +50,7 @@ const read = (UserAudioProfileModel) => async (filter) => {
const find = (UserAudioProfileModel) => async (filter) => {
try {
const foundModels = await UserAudioProfileModel.find({
- ...filter,
+ ...tenantFilter(filter),
deleted: false,
})
return foundModels
@@ -65,9 +66,10 @@ const find = (UserAudioProfileModel) => async (filter) => {
const update = (UserAudioProfileModel) => async (data) => {
try {
+ const { _id, userId, ...changes } = data
const updatedModel = await UserAudioProfileModel.findOneAndUpdate(
- { _id: data._id },
- data,
+ { ...tenantFilter({ _id, userId }), deleted: false },
+ { $set: changes },
{
new: true,
}
@@ -86,7 +88,7 @@ const update = (UserAudioProfileModel) => async (data) => {
const remove = (UserAudioProfileModel) => async (filter) => {
try {
const updatedModel = await UserAudioProfileModel.findOneAndUpdate(
- { ...filter },
+ { ...tenantFilter(filter) },
{
$set: {
deleted: true,
@@ -108,7 +110,7 @@ const remove = (UserAudioProfileModel) => async (filter) => {
const removeMany = (UserAudioProfileModel) => async (filter) => {
try {
const updatedModel = await UserAudioProfileModel.updateMany(
- { ...filter },
+ { ...tenantFilter(filter) },
{
$set: {
deleted: true,
diff --git a/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-cloning-job-handler/voice_cloning/voice_cloning_service.js b/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-cloning-job-handler/voice_cloning/voice_cloning_service.js
index 329638e..8e7bd55 100644
--- a/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-cloning-job-handler/voice_cloning/voice_cloning_service.js
+++ b/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-cloning-job-handler/voice_cloning/voice_cloning_service.js
@@ -1,4 +1,5 @@
const StringifyUtils = require('../../app/services/utils/logService')
+const { tenantFilter } = require('../../worker_tenant')
const create = (VoiceCloningModel) => async (data) => {
try {
@@ -32,7 +33,7 @@ const insertMany = (VoiceCloningModel) => async (data) => {
const read = (VoiceCloningModel) => async (filter) => {
try {
const foundModel = await VoiceCloningModel.findOne({
- ...filter,
+ ...tenantFilter(filter),
deleted: false,
})
return foundModel
@@ -49,7 +50,7 @@ const read = (VoiceCloningModel) => async (filter) => {
const find = (VoiceCloningModel) => async (filter) => {
try {
const foundModels = await VoiceCloningModel.find({
- ...filter,
+ ...tenantFilter(filter),
deleted: false,
})
return foundModels
@@ -65,9 +66,10 @@ const find = (VoiceCloningModel) => async (filter) => {
const update = (VoiceCloningModel) => async (data) => {
try {
+ const { _id, userId, ...changes } = data
const updatedModel = await VoiceCloningModel.findOneAndUpdate(
- { _id: data._id },
- data,
+ { ...tenantFilter({ _id, userId }), deleted: false },
+ { $set: changes },
{
new: true,
}
@@ -87,7 +89,7 @@ const update = (VoiceCloningModel) => async (data) => {
const remove = (VoiceCloningModel) => async (filter) => {
try {
const updatedModel = await VoiceCloningModel.findOneAndUpdate(
- { ...filter },
+ { ...tenantFilter(filter) },
{
$set: {
deleted: true,
@@ -109,7 +111,7 @@ const remove = (VoiceCloningModel) => async (filter) => {
const removeMany = (VoiceCloningModel) => async (filter) => {
try {
const updatedModel = await VoiceCloningModel.updateMany(
- { ...filter },
+ { ...tenantFilter(filter) },
{
$set: {
deleted: true,
diff --git a/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-synthsizer-job-handler/index.js b/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-synthsizer-job-handler/index.js
index 862300f..7cb19f0 100644
--- a/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-synthsizer-job-handler/index.js
+++ b/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-synthsizer-job-handler/index.js
@@ -11,6 +11,7 @@ const recordingModel = require('./recording')
const recordingSalutationModel = require('./recording_salutation')
const jobService = require('./job')
const salutationService = require('./salutation')
+const { requireUserId } = require('../worker_tenant')
let throttleMessageFetching = true
AWS.config.update({ region: 'us-west-2' })
const sqsQueueUrl = process.env.SQS_URL
@@ -72,6 +73,7 @@ const processQueue = () => {
await sqs.deleteMessageFromSQS(sqsQueueUrl, receiptHandle)
const {
+ userId,
userAudioProfileId,
text,
firstName,
@@ -91,14 +93,17 @@ const processQueue = () => {
console.log('DB_URI ', DB_URI)
await connectDB(DB_URI)
+ requireUserId(userId)
+
// read the path for the training model for the this users audio profile
- const userAudioProfile = await userAudioProfileService.find({
+ const userAudioProfile = await userAudioProfileService.read({
_id: userAudioProfileId,
+ userId,
status: 'completed',
})
if (userAudioProfile) {
- const { training_model_path, userId } = userAudioProfile[0]
+ const { training_model_path } = userAudioProfile
const {
voice_model_light_path,
voice_model_config_light_path,
@@ -151,11 +156,13 @@ const processQueue = () => {
// update the dynamic recordings for the current dynamic video with salutation url
const salutationToUpdate = await recordingSalutationModel.findOne({
_id: salutationId,
+ userId,
deleted: false,
})
const recordingToUpdate = await recordingModel.findOne({
_id: recordingId,
+ userId,
deleted: false,
})
@@ -169,6 +176,8 @@ const processQueue = () => {
await recordingSalutationModel.findOneAndUpdate(
{
_id: salutationId,
+ userId,
+ deleted: false,
},
{
$set: {
diff --git a/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-synthsizer-job-handler/job/job_service.js b/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-synthsizer-job-handler/job/job_service.js
index 44189af..ff53150 100644
--- a/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-synthsizer-job-handler/job/job_service.js
+++ b/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-synthsizer-job-handler/job/job_service.js
@@ -1,4 +1,5 @@
const StringifyUtils = require('../../app/services/utils/logService')
+const { tenantFilter } = require('../../worker_tenant')
const create = (Job) => async (jobData) => {
try {
@@ -32,7 +33,7 @@ const insertMany = (Job) => async (jobData) => {
const read = (Job) => async (filter) => {
try {
const foundJob = await Job.findOne({
- ...filter,
+ ...tenantFilter(filter),
deleted: false,
})
return foundJob
@@ -49,7 +50,7 @@ const read = (Job) => async (filter) => {
const find = (Job) => async (filter) => {
try {
const foundJobs = await Job.find({
- ...filter,
+ ...tenantFilter(filter),
deleted: false,
})
return foundJobs
@@ -65,9 +66,12 @@ const find = (Job) => async (filter) => {
const update = (Job) => async (job) => {
try {
- const updatedJob = await Job.findOneAndUpdate({ _id: job._id }, job, {
- new: true,
- })
+ const { _id, userId, ...changes } = job
+ const updatedJob = await Job.findOneAndUpdate(
+ { ...tenantFilter({ _id, userId }), deleted: false },
+ { $set: changes },
+ { new: true }
+ )
return updatedJob
} catch (error) {
const details = { job }
@@ -82,7 +86,7 @@ const update = (Job) => async (job) => {
const remove = (Job) => async (filter) => {
try {
const updatedJob = await Job.findOneAndUpdate(
- { ...filter },
+ { ...tenantFilter(filter) },
{
$set: {
deleted: true,
@@ -104,7 +108,7 @@ const remove = (Job) => async (filter) => {
const removeMany = (Job) => async (filter) => {
try {
const updatedJob = await Job.updateMany(
- { ...filter },
+ { ...tenantFilter(filter) },
{
$set: {
deleted: true,
diff --git a/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-synthsizer-job-handler/salutation/salutation_service.js b/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-synthsizer-job-handler/salutation/salutation_service.js
index 2d84f78..ad58f0d 100644
--- a/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-synthsizer-job-handler/salutation/salutation_service.js
+++ b/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-synthsizer-job-handler/salutation/salutation_service.js
@@ -1,4 +1,7 @@
+const { tenantFilter, requireUserId } = require('../../worker_tenant')
+
const create = (Salutation) => async (salutationData, userId) => {
+ requireUserId(userId)
const newSalutation = new Salutation({ ...salutationData, userId })
const savedSalutation = await newSalutation.save()
return savedSalutation
@@ -6,7 +9,7 @@ const create = (Salutation) => async (salutationData, userId) => {
const read = (Salutation) => async (filter) => {
const foundSalutation = await Salutation.findOne({
- ...filter,
+ ...tenantFilter(filter),
deleted: false,
})
return foundSalutation
@@ -14,29 +17,33 @@ const read = (Salutation) => async (filter) => {
const find = (Salutation) => async (filter) => {
const foundSalutations = await Salutation.find({
- ...filter,
+ ...tenantFilter(filter),
deleted: false,
})
return foundSalutations
}
const update = (Salutation) => async (salutation, userId) => {
+ const { _id, userId: ignoredUserId, ...changes } = salutation.toObject
+ ? salutation.toObject()
+ : salutation
const updatedSalutation = await Salutation.findOneAndUpdate(
- { _id: salutation._id, userId },
- salutation,
+ { ...tenantFilter({ _id, userId }), deleted: false },
+ { $set: changes },
{ new: true }
)
return updatedSalutation
}
-const modify = (Salutation) => async (salutation) => {
- const findQuery = salutation._id
- ? { _id: salutation._id }
+const modify = (Salutation) => async (salutation, userId) => {
+ const { _id, userId: ignoredUserId, ...changes } = salutation
+ const findQuery = _id
+ ? { _id }
: { transcriptId: salutation.transcriptId }
const updatedSalutation = await Salutation.findOneAndUpdate(
- findQuery,
- salutation,
+ { ...tenantFilter({ ...findQuery, userId }), deleted: false },
+ { $set: changes },
{ new: true }
)
return updatedSalutation
@@ -63,7 +70,7 @@ const updateOrCreate =
const remove = (Salutation) => async (filter, userId) => {
const updatedSalutation = await Salutation.findOneAndUpdate(
- { ...filter, userId },
+ { ...tenantFilter({ ...filter, userId }), deleted: false },
{
$set: {
deleted: true,
diff --git a/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-synthsizer-job-handler/user_audio_profile/user_audio_profile_service.js b/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-synthsizer-job-handler/user_audio_profile/user_audio_profile_service.js
index 18416d7..22acbf8 100644
--- a/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-synthsizer-job-handler/user_audio_profile/user_audio_profile_service.js
+++ b/worker-toolkit-potion-polyglot-v1.0.1/repos/potion-voice/voice-synthsizer-job-handler/user_audio_profile/user_audio_profile_service.js
@@ -1,4 +1,5 @@
const StringifyUtils = require('../../app/services/utils/logService')
+const { tenantFilter } = require('../../worker_tenant')
const create = (UserAudioProfileModel) => async (data) => {
try {
@@ -32,7 +33,7 @@ const insertMany = (UserAudioProfileModel) => async (data) => {
const read = (UserAudioProfileModel) => async (filter) => {
try {
const foundModel = await UserAudioProfileModel.findOne({
- ...filter,
+ ...tenantFilter(filter),
deleted: false
})
return foundModel
@@ -49,7 +50,7 @@ const read = (UserAudioProfileModel) => async (filter) => {
const find = (UserAudioProfileModel) => async (filter) => {
try {
const foundModels = await UserAudioProfileModel.find({
- ...filter,
+ ...tenantFilter(filter),
deleted: false
})
return foundModels
@@ -66,9 +67,10 @@ const find = (UserAudioProfileModel) => async (filter) => {
const update = (UserAudioProfileModel) => async (data) => {
console.log('ua data', data)
try {
+ const { _id, userId, ...changes } = data
const updatedModel = await UserAudioProfileModel.findOneAndUpdate(
- { _id: data._id },
- data,
+ { ...tenantFilter({ _id, userId }), deleted: false },
+ { $set: changes },
{
new: true
}
@@ -88,7 +90,7 @@ const update = (UserAudioProfileModel) => async (data) => {
const remove = (UserAudioProfileModel) => async (filter) => {
try {
const updatedModel = await UserAudioProfileModel.findOneAndUpdate(
- { ...filter },
+ { ...tenantFilter(filter) },
{
$set: {
deleted: true
@@ -110,7 +112,7 @@ const remove = (UserAudioProfileModel) => async (filter) => {
const removeMany = (UserAudioProfileModel) => async (filter) => {
try {
const updatedModel = await UserAudioProfileModel.updateMany(
- { ...filter },
+ { ...tenantFilter(filter) },
{
$set: {
deleted: true

View File

@@ -4,7 +4,7 @@
"build": {
"dockerfile": "Dockerfile",
"args": {
"TOOLKIT_BUILD_ID": "1790712369311-8xr6mu"
"TOOLKIT_BUILD_ID": "1791247286386-y2uh1u"
}
},
"appPort": [

View File

@@ -438,6 +438,39 @@ print('admin user ready')
;;
esac
# The swingbell Java services resolve sibling members' com.swingbell* SNAPSHOT libs only from a
# local Maven repo. The book-my-minutes-* twins reuse those coordinates, so they get their own.
_sbl_install() { # <local repo> <member>[@<revision>]...
local repo_local="$1" m rev src; shift
for m in "$@"; do
rev=HEAD; case "$m" in *@*) rev=${m#*@}; m=${m%@*} ;; esac
[ -e "/workspace/repos/$m/.git" ] || continue
src=$(mktemp -d)
( git -C "/workspace/repos/$m" archive "$rev" | tar -x -C "$src" \
&& cd "$src" && mvn -q -B -Dstyle.color=never -DskipTests -Dmaven.repo.local="$repo_local" install ) \
|| echo "WARNING: could not install $m into $repo_local; services that depend on it won't build until it is" >&2
rm -rf "$src"
done
}
_sbl_use() { # <member> <local repo>
local d="/workspace/repos/$1"
[ -f "$d/pom.xml" ] || return 0
mkdir -p "$d/.mvn" "$d/.git/info"
printf -- '-Dmaven.repo.local=%s\n' "$2" > "$d/.mvn/maven.config"
grep -qxF '.mvn/maven.config' "$d/.git/info/exclude" 2>/dev/null \
|| printf '\n# raccoon-explore: selects this service'"'"'s local Maven repo\n.mvn/maven.config\n' >> "$d/.git/info/exclude"
}
if [ -n "$IS_POLYGLOT" ] && grep -qs 'com\.swingbell' /workspace/repos/common-repository/pom.xml; then
_sbl_install "$HOME/.m2/repository" common-repository common-aws-service jwt-encryption-decryption reports
_sbl_install "$HOME/.m2/bmm-repository" book-my-minutes-common-repository book-my-minutes-common-aws-service reports
for d in /workspace/repos/book-my-minutes-* /workspace/repos/meetings; do _sbl_use "$(basename "$d")" "$HOME/.m2/bmm-repository"; done
# These two were written against a different revision of their library than the one pinned.
_sbl_install "$HOME/.m2/nhcx-provider-repository" common-repository@208aea7be jwt-encryption-decryption
_sbl_use nhcx-provider "$HOME/.m2/nhcx-provider-repository"
_sbl_install "$HOME/.m2/bmm-jobs-repository" book-my-minutes-common-repository@f15e7c128
_sbl_use book-my-minutes-jobs "$HOME/.m2/bmm-jobs-repository"
fi
# Mirror Harbor's reduced toolset in the interactive Explore session. Use
# Harbor's /opt path when available, but fall back to a user-writable path for
# generic devcontainer fixtures that run lifecycle hooks as a non-root user.
@@ -528,8 +561,8 @@ bash /workspace/welcome.sh explore 2>/dev/null
_AK="fde503c3bdb6e5cc9c48b1f8e4c2abeb"
_DK="e966e45af5ad1a18005f9fdb831186ea"
_WID="w-mun3wr6n-v83f"
_VER="1.0.0"
_WID="w-muvydvub-qn4j"
_VER="1.0.1"
_CT="explore"
_RP=$(node -e "try{process.stdout.write(require('/workspace/toolkit.json').repo)}catch{}" 2>/dev/null)
_SID="$(date +%s)-$$"

View File

@@ -415,6 +415,24 @@ while (current) {
current = byUuid[current]?.parentUuid || null;
}
// Parallel tool calls can be written call, call, result, result: the first result then hangs
// off the first call on a side branch the walk above never visits, so add those back.
const blocksOf = (e) => (Array.isArray(e?.message?.content) ? e.message.content : []);
const activeToolUseIds = new Set();
for (const uuid of activeBranchUuids) {
for (const b of blocksOf(byUuid[uuid])) if (b?.type === 'tool_use') activeToolUseIds.add(b.id);
}
for (const e of Object.values(byUuid)) {
if (activeBranchUuids.has(e.uuid) || !activeBranchUuids.has(e.parentUuid)) continue;
const blocks = blocksOf(e);
if (
blocks.length &&
blocks.every((b) => b?.type === 'tool_result' && activeToolUseIds.has(b.tool_use_id))
) {
activeBranchUuids.add(e.uuid);
}
}
// Step 4: filter — keep entries on the active branch.
//
// Claude Code writes several bookkeeping entry types alongside the message
@@ -743,7 +761,7 @@ fi
# Install conversation so the authoring harness can resume it
${
IS_CLAUDE
? `ENCODED_CWD=$(echo "$PWD" | sed 's|/|-|g; s|^-||')
? `ENCODED_CWD=$(echo "$PWD" | sed 's|[^a-zA-Z0-9]|-|g; s|^-||')
DEST_DIR="$HOME/.claude/projects/-$ENCODED_CWD"
mkdir -p "$DEST_DIR"
cp "$SCRIPT_DIR/session.jsonl" "$DEST_DIR/$SESSION_UUID.jsonl"

View File

@@ -0,0 +1,5 @@
/**
* Plugin-side re-export, so snapshot-to-task.ts resolves `./lib/secret-scrub`
* both here and in the toolkit's flat scripts/ dir.
*/
export * from '../../../../static/scripts/lib/secret-scrub';

View File

@@ -162,10 +162,13 @@ function replacePrefixAtBoundary(haystack: string, needle: string, replacement:
}
}
/** Must match Claude Code's `.claude/projects/` folder names. */
const encodeProjectDir = (p: string) => p.replace(/[^a-zA-Z0-9]/g, '-');
/** Replace a prefix and its dash-encoded form (`.claude/projects/<encoded>/`). */
function stripBothForms(haystack: string, needle: string, replacement: string): string {
const out = literalReplaceAll(haystack, needle, replacement);
return literalReplaceAll(out, needle.replace(/\//g, '-'), replacement.replace(/\//g, '-'));
return literalReplaceAll(out, encodeProjectDir(needle), replacement.replace(/\//g, '-'));
}
export function sanitizeSessionJsonl(raw: string, opts: SanitizeOptions = {}): SanitizeResult {
@@ -193,7 +196,7 @@ export function sanitizeSessionJsonl(raw: string, opts: SanitizeOptions = {}): S
// EVERY root before ANY home dir: a home pass run between roots would rewrite a
// sibling root's own prefix, leaving it unmatched when its turn came.
for (const prefix of prefixes) {
const encodedPrefix = prefix.replace(/\//g, '-');
const encodedPrefix = encodeProjectDir(prefix);
working = replacePrefixAtBoundary(working, prefix, placeholder);
working = literalReplaceAll(working, encodedPrefix, placeholder.replace(/\//g, '-'));
prefixStripped ??= prefix;
@@ -206,7 +209,7 @@ export function sanitizeSessionJsonl(raw: string, opts: SanitizeOptions = {}): S
.filter((h): h is string => h !== null && !prefixes.includes(h))
);
for (const homeDir of homeDirs) {
const encodedHomeDir = homeDir.replace(/\//g, '-');
const encodedHomeDir = encodeProjectDir(homeDir);
working = replacePrefixAtBoundary(working, homeDir, HOME_DIR_PLACEHOLDER);
working = literalReplaceAll(working, encodedHomeDir, HOME_DIR_PLACEHOLDER.replace(/\//g, '-'));
homeDirStripped ??= homeDir;

View File

@@ -26,6 +26,7 @@ import { stripAuthoringScaffolding, truncationIndex, turnsFromLines } from './ha
// This script must not call cpSync — it fails EACCES on a macOS docker bind mount.
import { holisticRubricScaffoldFor } from './holistic-rubric-scaffold';
import { copyTree } from './lib/copy-tree';
import { readEnvSecrets, scrubTaskSecrets } from './lib/secret-scrub';
import { collectCwds, sanitizeSessionJsonl } from './sanitize-session-jsonl';
// --- CLI ---
@@ -576,6 +577,13 @@ if (existsSync(sessionDir) && statSync(sessionDir).isDirectory()) {
mkdirSync(join(taskDir, 'environment', 'session'), { recursive: true });
}
// The Explore agent can print the .env it runs with (`env`, `cat .env`), so its keys can be in
// the capture. The scrub is silent: the worker didn't put them there and has nothing to do.
const secretScan = scrubTaskSecrets(taskDir, readEnvSecrets(repoRoot));
for (const file of secretScan.flagged) {
log.warn({ file }, 'This file holds an API key from your .env. Remove it before you submit.');
}
// The harness that captured the snapshot; the trial runs this one.
const harness =
typeof metadata.harness === 'string' && metadata.harness ? metadata.harness : 'claude-code';

View File

@@ -0,0 +1 @@
/home/eric/workspaces/dataannotation/project-2/worker-toolkit-potion-polyglot-v1.0.1/repos

View File

@@ -319,6 +319,18 @@ _node_bin() {
d=$(ls -d "$nvm_dir"/versions/node/v"$major".* 2>/dev/null | sort -V | tail -1)
[ -n "$d" ] && printf '%s/bin' "$d"
}
# First dev-server script <dir>/package.json defines. `next start` serves a production build that
# a fresh checkout doesn't have, so a Next app boots `dev` instead.
_dev_script() {
node -e "const s=(require('$1/package.json').scripts)||{};const o=['start','dev','develop','serve'].filter(k=>s[k]&&!(k==='start'&&/^next start/.test(s[k])&&s.dev));if(o[0])process.stdout.write(o[0])" 2>/dev/null
}
# Path the app serves under in dev: a path "homepage" (CRA) or a Next.js basePath, e.g. /app.
_homepage_path() {
local p
p=$(node -e "const h=(require('$1/package.json').homepage)||'';let p=h;try{p=new URL(h).pathname}catch{};if(/^\/[^/]/.test(p))process.stdout.write(p.replace(/\/$/,''))" 2>/dev/null)
[ -n "$p" ] || p=$(cat "$1"/next.config.* 2>/dev/null | sed -nE "s/^[[:space:]]*basePath:[[:space:]]*['\"](\/[^'\"]+)['\"].*/\1/p" | head -1)
printf '%s' "$p"
}
# Symlink ./node_modules (cwd = the dir being installed) to a container-local tree keyed by
# <key> — see the ENFILE rationale at the call site. The target must itself be named
# `node_modules` (Node resolves the symlink, then walks ancestors for that literal name),
@@ -637,36 +649,37 @@ setup_repo() {
else
_mark_ctr_nodeps "$repo"
printf " ${YELLOW}\xe2\x9a\xa0 %s: dependencies did not install${RESET} \xe2\x80\x94 explore-only in this container.\n ${GRAY}Reading the code, git and the editor still work; running the app or its tests will not.\n Usually a pin with no build for this machine's architecture. To retry: rm %s/%s.done${RESET}\n" "$repo" "$CTR_MARKER_DIR" "$repo"
_mark_ctr_setup "$repo"; return 0
fi
_mark_ctr_setup "$repo"; return 0
fi
_py_have "$ver" || { printf " ${GRAY}(Python %s not in this image; skipping deps \xe2\x80\x94 explore-only)${RESET}\n" "$ver"; _mark_ctr_setup "$repo"; return 0; }
# Some poetry repos depend on sibling repos via `git = "ssh://git@github.com/AskZeta/<name>.git"`,
# which can't resolve in the container (no SSH key, no network). The deps are TRANSITIVE
# (cx-chatbot → compiler-agent → agent-tools → leaves), so rewrite the target AND every
# sibling pyproject to local path deps — else poetry shells out to `ssh` for a transitive
# git dep and fails ("No such file or directory: 'ssh'").
for pp in /workspace/repos/*/pyproject.toml; do
[ -f "$pp" ] && _rewrite_askzeta_git_deps "$pp"
done
( cd "$dir" && export PATH="$PYENV_PATH:$PATH" PYENV_VERSION="$ver" \
&& { [ -f .env.example ] && cp -n .env.example .env; true; } \
&& { for kv in $bootenv; do grep -qxF "$kv" .env 2>/dev/null || echo "$kv" >> .env; done; true; } \
&& { if [ -f pyproject.toml ]; then \
# Every member Dockerfile sets this; without it poetry builds a .venv here
# that the trial image has no equivalent of.
poetry config virtualenvs.create false 2>/dev/null || true; \
# The git→path rewrite invalidates poetry.lock ("changed significantly"), so
# re-lock preserving pins: --no-update on poetry 1.x, the default on 2.x.
poetry lock --no-update 2>/dev/null || poetry lock 2>/dev/null || true; \
# --no-root: install deps only, not the project package itself. Some members'
# pyproject package name doesn't map to a folder poetry can find ("No file/folder
# found for package <x>"), which fails the whole install. The worker explores +
# runs the code from the repo dir (cwd on path), so the project never needs to be
# pip-installed as a package. Mirrors the harbor build.
poetry install --no-interaction --no-root; \
elif [ -f requirements.txt ]; then pip install -r requirements.txt; \
elif [ -f setup.py ]; then pip install -e .; else true; fi; } ) || return 1 ;;
else
_py_have "$ver" || { printf " ${GRAY}(Python %s not in this image; skipping deps \xe2\x80\x94 explore-only)${RESET}\n" "$ver"; _mark_ctr_setup "$repo"; return 0; }
# Some poetry repos depend on sibling repos via `git = "ssh://git@github.com/AskZeta/<name>.git"`,
# which can't resolve in the container (no SSH key, no network). The deps are TRANSITIVE
# (cx-chatbot → compiler-agent → agent-tools → leaves), so rewrite the target AND every
# sibling pyproject to local path deps — else poetry shells out to `ssh` for a transitive
# git dep and fails ("No such file or directory: 'ssh'").
for pp in /workspace/repos/*/pyproject.toml; do
[ -f "$pp" ] && _rewrite_askzeta_git_deps "$pp"
done
( cd "$dir" && export PATH="$PYENV_PATH:$PATH" PYENV_VERSION="$ver" \
&& { [ -f .env.example ] && cp -n .env.example .env; true; } \
&& { for kv in $bootenv; do grep -qxF "$kv" .env 2>/dev/null || echo "$kv" >> .env; done; true; } \
&& { if [ -f pyproject.toml ]; then \
# Every member Dockerfile sets this; without it poetry builds a .venv here
# that the trial image has no equivalent of.
poetry config virtualenvs.create false 2>/dev/null || true; \
# The git→path rewrite invalidates poetry.lock ("changed significantly"), so
# re-lock preserving pins: --no-update on poetry 1.x, the default on 2.x.
poetry lock --no-update 2>/dev/null || poetry lock 2>/dev/null || true; \
# --no-root: install deps only, not the project package itself. Some members'
# pyproject package name doesn't map to a folder poetry can find ("No file/folder
# found for package <x>"), which fails the whole install. The worker explores +
# runs the code from the repo dir (cwd on path), so the project never needs to be
# pip-installed as a package. Mirrors the harbor build.
poetry install --no-interaction --no-root; \
elif [ -f requirements.txt ]; then pip install -r requirements.txt; \
elif [ -f setup.py ]; then pip install -e .; else true; fi; } ) || return 1
fi ;;
rust)
command -v cargo >/dev/null 2>&1 || { printf " ${GRAY}(Rust not in this image; skipping build \xe2\x80\x94 explore-only)${RESET}\n"; _mark_ctr_setup "$repo"; return 0; }
# Build to a container-local target dir (same ENFILE/bind-mount rationale as
@@ -696,7 +709,15 @@ setup_repo() {
printf " ${GRAY}preparing %s (one-time; details in ${RESET}${GRAY}run-app --logs${RESET}${GRAY})\xe2\x80\xa6${RESET}\n" "$repo"
if ( cd "$dir" \
&& export PATH="${spath:+$spath:}$PATH" \
&& case "$kind" in ruby) export RBENV_VERSION="$ver" ;; python) export PYENV_VERSION="$ver" ;; esac \
&& case "$kind" in
ruby) export RBENV_VERSION="$ver" ;;
python)
if _py_uv_ok "$ver"; then
. "$(_uv_venv_dir "$repo")/bin/activate"
else
export PYENV_VERSION="$ver"
fi ;;
esac \
&& { for kv in $bootenv; do export "$kv"; done; } \
&& eval "$setupcmd" ) > "$slog" 2>&1; then
printf " ${GRAY}\xe2\x9c\x93 %s prepared${RESET}\n" "$repo"
@@ -793,10 +814,8 @@ start_poly() {
if [ -n "$startcmd" ]; then
cmd="env $bootenv PORT=3000 BROWSER=none HOST=0.0.0.0 $startcmd"
else
local s2=""
for s2 in start dev develop serve; do
if node -e "process.exit((((require('$dir/package.json')||{}).scripts)||{})['$s2']?0:1)" 2>/dev/null; then break; else s2=""; fi
done
local s2
s2=$(_dev_script "$dir")
if [ -z "$s2" ]; then
printf " ${GRAY}%s: deps installed, no dev-server script \xe2\x80\x94 run its tests directly (${RESET}${GRAY}yarn test${RESET}${GRAY}).${RESET}\n" "$repo"
return 0
@@ -811,10 +830,7 @@ start_poly() {
return 0
else
# CRA / generic: first dev-server script the repo defines, bound to :3000.
local s
for s in start dev develop serve; do
if node -e "process.exit((((require('$dir/package.json')||{}).scripts)||{})['$s']?0:1)" 2>/dev/null; then sc="$s"; break; fi
done
sc=$(_dev_script "$dir")
if [ -z "$sc" ]; then
printf " ${GRAY}%s: deps installed, no dev-server script \xe2\x80\x94 run its tests directly (${RESET}${GRAY}yarn test${RESET}${GRAY}).${RESET}\n" "$repo"
return 0
@@ -840,7 +856,11 @@ start_poly() {
craenv="CI=true DANGEROUSLY_DISABLE_HOST_CHECK=true"
case "${ver%%.*}" in 1[7-9]|[2-9][0-9]) craenv="NODE_OPTIONS=--openssl-legacy-provider $craenv" ;; esac
fi
cmd="env $bootenv $craenv PATH=$nbin:\$PATH PORT=3000 BROWSER=none HOST=0.0.0.0 yarn $sc"
# Vite ignores PORT/HOST and listens on localhost:5173, which the published port can't reach.
local viteargs=""
node -e "process.exit(/^vite( |$)(?!build|preview)/.test((require('$dir/package.json').scripts||{})['$sc']||'')?0:1)" 2>/dev/null \
&& viteargs=" --host 0.0.0.0 --port 3000"
cmd="env $bootenv $craenv PATH=$nbin:\$PATH PORT=3000 BROWSER=none HOST=0.0.0.0 yarn $sc$viteargs"
fi ;;
python)
if _ctr_nodeps "$repo"; then
@@ -890,9 +910,9 @@ start_poly() {
|| printf " ${YELLOW}\xe2\x9a\xa0 strongsuit_phx database prep failed \xe2\x80\x94 ${RESET}${GRAY}run-app --logs${RESET}\n"
if [ -d "$phx_dir/deps" ] && [ -d "$phx_dir/_build" ]; then
printf " ${CYAN}\xe2\x96\xb6${RESET} starting strongsuit_phx (elixir)\xe2\x80\xa6\n"
# Derived, not a second env var: the app sends PHX_AUTH_TOKEN and the phx plug
# compares against API_AUTH_TOKEN, and a drift shows up only as silent 401s.
_spawn phx "$phx_dir" "env MIX_ENV=dev PHX_PORT=$COMPANION_PORT API_AUTH_TOKEN=${PHX_AUTH_TOKEN:-dummy} mix phx.server"
# The app sends PHX_AUTH_TOKEN and the phx plug compares API_AUTH_TOKEN; both default
# to the same value, so pass one only when the other was set.
_spawn phx "$phx_dir" "env MIX_ENV=dev PHX_PORT=$COMPANION_PORT ${PHX_AUTH_TOKEN:+API_AUTH_TOKEN=$PHX_AUTH_TOKEN} mix phx.server"
_wait_tcp "$COMPANION_PORT" 45 || printf " ${YELLOW}\xe2\x9a\xa0 strongsuit_phx didn't come up \xe2\x80\x94 ${RESET}${GRAY}run-app --logs${RESET}\n"
else
printf " ${GRAY}strongsuit_phx isn't built \xe2\x80\x94 the backend on :%s will be absent.\n" "$COMPANION_PORT"
@@ -908,6 +928,8 @@ start_poly() {
local landing=""
case "$repo" in
strongsuit-app) landing="/dev-login" ;;
ABDM-FE) landing="/app/login" ;;
*) [ "$kind" = node ] && landing=$(_homepage_path "$dir") ;;
esac
printf " ${CYAN}\xe2\x9c\x85 %s is up${RESET} open ${CYAN}http://localhost:%s%s${RESET}\n" "$repo" "$CLIENT_HOST_PORT" "$landing"
# Per-member "how do I actually get in" notes. Only members whose landing page needs
@@ -922,11 +944,15 @@ start_poly() {
printf " Auth0 and cannot work offline.${RESET}\n"
;;
ABDM-FE)
printf " ${GRAY}This app is served under a ${RESET}${GRAY}/app${RESET}${GRAY} basename, so the bare URL above renders\n"
printf " nothing. Open ${RESET}${CYAN}http://localhost:%s/app/login${RESET}${GRAY} instead.\n" "$CLIENT_HOST_PORT"
printf " Sign-in itself calls hosted services that aren't reachable offline, so the\n"
printf " ${GRAY}Sign-in calls hosted services that aren't reachable offline, so the\n"
printf " login page is as far as you can get — read and edit the code from there.${RESET}\n"
;;
book-my-minutes-app)
printf " ${GRAY}This is the signed-in dashboard: every page needs a session, and signing in happens\n"
printf " on a separate hosted app that isn't reachable offline, so the page above stays\n"
printf " blank. Read and edit the code instead, or run ${RESET}${GRAY}book-my-minutes-onboarding${RESET}${GRAY} to see the\n"
printf " public side of the product.${RESET}\n"
;;
search-api-v2)
printf " ${GRAY}Browse and try the API at ${RESET}${CYAN}http://localhost:%s/docs${RESET}${GRAY}.\n" "$CLIENT_HOST_PORT"
printf " Sign-in goes through a hosted identity provider that isn't reachable offline,\n"

View File

@@ -271,9 +271,9 @@
}
],
"defaultRepo": "potion-app",
"version": "1.0.0",
"buildSha": "f62b06f",
"packedFrom": "565c9589c46926ee3025f82d28f31be0aae007e8",
"version": "1.0.1",
"buildSha": "5e48703",
"packedFrom": "9b0c19e8f35875c162ad4e6129dfbdcbfc5f177c",
"blockedHosts": [
"sendpotion.com",
"www.sendpotion.com",

Some files were not shown because too many files have changed in this diff Show More