feat: Phase 1 完整整合 — E2B Sandbox + AuditLog + Reaper + Replaybook 對帳修復
This commit is contained in:
@@ -15,7 +15,8 @@
|
||||
"ioredis": "^5.11.1",
|
||||
"next": "16.2.7",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4"
|
||||
"react-dom": "19.2.4",
|
||||
"zod": "^3.23.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
|
||||
1
apps/web/prisma/generated/client/client.d.ts
vendored
Normal file
1
apps/web/prisma/generated/client/client.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./index"
|
||||
5
apps/web/prisma/generated/client/client.js
Normal file
5
apps/web/prisma/generated/client/client.js
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
module.exports = { ...require('.') }
|
||||
1
apps/web/prisma/generated/client/default.d.ts
vendored
Normal file
1
apps/web/prisma/generated/client/default.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./index"
|
||||
5
apps/web/prisma/generated/client/default.js
Normal file
5
apps/web/prisma/generated/client/default.js
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
module.exports = { ...require('#main-entry-point') }
|
||||
1
apps/web/prisma/generated/client/edge.d.ts
vendored
Normal file
1
apps/web/prisma/generated/client/edge.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./default"
|
||||
248
apps/web/prisma/generated/client/edge.js
Normal file
248
apps/web/prisma/generated/client/edge.js
Normal file
File diff suppressed because one or more lines are too long
274
apps/web/prisma/generated/client/index-browser.js
Normal file
274
apps/web/prisma/generated/client/index-browser.js
Normal file
@@ -0,0 +1,274 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
|
||||
const {
|
||||
Decimal,
|
||||
DbNull,
|
||||
JsonNull,
|
||||
AnyNull,
|
||||
NullTypes,
|
||||
makeStrictEnum,
|
||||
Public,
|
||||
getRuntime,
|
||||
skip
|
||||
} = require('./runtime/index-browser.js')
|
||||
|
||||
|
||||
const Prisma = {}
|
||||
|
||||
exports.Prisma = Prisma
|
||||
exports.$Enums = {}
|
||||
|
||||
/**
|
||||
* Prisma Client JS version: 7.8.0
|
||||
* Query Engine version: 3c6e192761c0362d496ed980de936e2f3cebcd3a
|
||||
*/
|
||||
Prisma.prismaVersion = {
|
||||
client: "7.8.0",
|
||||
engine: "3c6e192761c0362d496ed980de936e2f3cebcd3a"
|
||||
}
|
||||
|
||||
Prisma.PrismaClientKnownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)};
|
||||
Prisma.PrismaClientUnknownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientRustPanicError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientInitializationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientValidationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.Decimal = Decimal
|
||||
|
||||
/**
|
||||
* Re-export of sql-template-tag
|
||||
*/
|
||||
Prisma.sql = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.empty = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.join = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.raw = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.validator = Public.validator
|
||||
|
||||
/**
|
||||
* Extensions
|
||||
*/
|
||||
Prisma.getExtensionContext = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.defineExtension = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
|
||||
/**
|
||||
* Shorthand utilities for JSON filtering
|
||||
*/
|
||||
Prisma.DbNull = DbNull
|
||||
Prisma.JsonNull = JsonNull
|
||||
Prisma.AnyNull = AnyNull
|
||||
|
||||
Prisma.NullTypes = NullTypes
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Enums
|
||||
*/
|
||||
|
||||
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
|
||||
ReadUncommitted: 'ReadUncommitted',
|
||||
ReadCommitted: 'ReadCommitted',
|
||||
RepeatableRead: 'RepeatableRead',
|
||||
Serializable: 'Serializable'
|
||||
});
|
||||
|
||||
exports.Prisma.TaskScalarFieldEnum = {
|
||||
id: 'id',
|
||||
title: 'title',
|
||||
description: 'description',
|
||||
status: 'status',
|
||||
difficulty: 'difficulty',
|
||||
scope_clarity_score: 'scope_clarity_score',
|
||||
error_classification: 'error_classification',
|
||||
reward_amount: 'reward_amount',
|
||||
reward_currency: 'reward_currency',
|
||||
acceptance_criteria: 'acceptance_criteria',
|
||||
required_stack: 'required_stack',
|
||||
retry_count: 'retry_count',
|
||||
stripe_payment_intent_id: 'stripe_payment_intent_id',
|
||||
expires_at: 'expires_at',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at'
|
||||
};
|
||||
|
||||
exports.Prisma.ClaimScalarFieldEnum = {
|
||||
id: 'id',
|
||||
task_id: 'task_id',
|
||||
developer_wallet: 'developer_wallet',
|
||||
status: 'status',
|
||||
claim_token: 'claim_token',
|
||||
held_amount: 'held_amount',
|
||||
held_currency: 'held_currency',
|
||||
expires_at: 'expires_at',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at'
|
||||
};
|
||||
|
||||
exports.Prisma.SubmissionScalarFieldEnum = {
|
||||
id: 'id',
|
||||
task_id: 'task_id',
|
||||
claim_id: 'claim_id',
|
||||
status: 'status',
|
||||
deliverables: 'deliverables',
|
||||
estimated_judge_complete_at: 'estimated_judge_complete_at',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at'
|
||||
};
|
||||
|
||||
exports.Prisma.JudgeResultScalarFieldEnum = {
|
||||
id: 'id',
|
||||
submission_id: 'submission_id',
|
||||
overall_result: 'overall_result',
|
||||
tests: 'tests',
|
||||
artifacts: 'artifacts',
|
||||
error_classification: 'error_classification',
|
||||
error_signature: 'error_signature',
|
||||
retryable: 'retryable',
|
||||
resource_usage: 'resource_usage',
|
||||
judge_completed_at: 'judge_completed_at'
|
||||
};
|
||||
|
||||
exports.Prisma.AuditEventScalarFieldEnum = {
|
||||
id: 'id',
|
||||
actorType: 'actorType',
|
||||
actorId: 'actorId',
|
||||
action: 'action',
|
||||
entityType: 'entityType',
|
||||
entityId: 'entityId',
|
||||
beforeState: 'beforeState',
|
||||
afterState: 'afterState',
|
||||
reason: 'reason',
|
||||
metadata: 'metadata',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
|
||||
exports.Prisma.LedgerEntryScalarFieldEnum = {
|
||||
id: 'id',
|
||||
task_id: 'task_id',
|
||||
phase: 'phase',
|
||||
idempotency_key: 'idempotency_key',
|
||||
stripe_object_id: 'stripe_object_id',
|
||||
response_status: 'response_status',
|
||||
http_status: 'http_status',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at'
|
||||
};
|
||||
|
||||
exports.Prisma.SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
};
|
||||
|
||||
exports.Prisma.JsonNullValueInput = {
|
||||
JsonNull: Prisma.JsonNull
|
||||
};
|
||||
|
||||
exports.Prisma.NullableJsonNullValueInput = {
|
||||
DbNull: Prisma.DbNull,
|
||||
JsonNull: Prisma.JsonNull
|
||||
};
|
||||
|
||||
exports.Prisma.QueryMode = {
|
||||
default: 'default',
|
||||
insensitive: 'insensitive'
|
||||
};
|
||||
|
||||
exports.Prisma.JsonNullValueFilter = {
|
||||
DbNull: Prisma.DbNull,
|
||||
JsonNull: Prisma.JsonNull,
|
||||
AnyNull: Prisma.AnyNull
|
||||
};
|
||||
|
||||
exports.Prisma.NullsOrder = {
|
||||
first: 'first',
|
||||
last: 'last'
|
||||
};
|
||||
|
||||
|
||||
exports.Prisma.ModelName = {
|
||||
Task: 'Task',
|
||||
Claim: 'Claim',
|
||||
Submission: 'Submission',
|
||||
JudgeResult: 'JudgeResult',
|
||||
AuditEvent: 'AuditEvent',
|
||||
LedgerEntry: 'LedgerEntry'
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a stub Prisma Client that will error at runtime if called.
|
||||
*/
|
||||
class PrismaClient {
|
||||
constructor() {
|
||||
return new Proxy(this, {
|
||||
get(target, prop) {
|
||||
let message
|
||||
const runtime = getRuntime()
|
||||
if (runtime.isEdge) {
|
||||
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
|
||||
- Use Prisma Accelerate: https://pris.ly/d/accelerate
|
||||
- Use Driver Adapters: https://pris.ly/d/driver-adapters
|
||||
`;
|
||||
} else {
|
||||
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
|
||||
}
|
||||
|
||||
message += `
|
||||
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
|
||||
|
||||
throw new Error(message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.PrismaClient = PrismaClient
|
||||
|
||||
Object.assign(exports, Prisma)
|
||||
11506
apps/web/prisma/generated/client/index.d.ts
vendored
Normal file
11506
apps/web/prisma/generated/client/index.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
248
apps/web/prisma/generated/client/index.js
Normal file
248
apps/web/prisma/generated/client/index.js
Normal file
File diff suppressed because one or more lines are too long
144
apps/web/prisma/generated/client/package.json
Normal file
144
apps/web/prisma/generated/client/package.json
Normal file
@@ -0,0 +1,144 @@
|
||||
{
|
||||
"name": "prisma-client-e5a0d29448aef14ee47356f474ec26ee939c82359a834a5929b1331dfd872836",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"browser": "default.js",
|
||||
"exports": {
|
||||
"./client": {
|
||||
"require": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./edge.js",
|
||||
"workerd": "./edge.js",
|
||||
"worker": "./edge.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"import": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./edge.js",
|
||||
"workerd": "./edge.js",
|
||||
"worker": "./edge.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"require": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./edge.js",
|
||||
"workerd": "./edge.js",
|
||||
"worker": "./edge.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"import": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./edge.js",
|
||||
"workerd": "./edge.js",
|
||||
"worker": "./edge.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./extension": {
|
||||
"types": "./extension.d.ts",
|
||||
"require": "./extension.js",
|
||||
"import": "./extension.js",
|
||||
"default": "./extension.js"
|
||||
},
|
||||
"./index-browser": {
|
||||
"types": "./index.d.ts",
|
||||
"require": "./index-browser.js",
|
||||
"import": "./index-browser.js",
|
||||
"default": "./index-browser.js"
|
||||
},
|
||||
"./index": {
|
||||
"types": "./index.d.ts",
|
||||
"require": "./index.js",
|
||||
"import": "./index.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./edge": {
|
||||
"types": "./edge.d.ts",
|
||||
"require": "./edge.js",
|
||||
"import": "./edge.js",
|
||||
"default": "./edge.js"
|
||||
},
|
||||
"./runtime/client": {
|
||||
"types": "./runtime/client.d.ts",
|
||||
"node": {
|
||||
"require": "./runtime/client.js",
|
||||
"default": "./runtime/client.js"
|
||||
},
|
||||
"require": "./runtime/client.js",
|
||||
"import": "./runtime/client.mjs",
|
||||
"default": "./runtime/client.mjs"
|
||||
},
|
||||
"./runtime/wasm-compiler-edge": {
|
||||
"types": "./runtime/wasm-compiler-edge.d.ts",
|
||||
"require": "./runtime/wasm-compiler-edge.js",
|
||||
"import": "./runtime/wasm-compiler-edge.mjs",
|
||||
"default": "./runtime/wasm-compiler-edge.mjs"
|
||||
},
|
||||
"./runtime/index-browser": {
|
||||
"types": "./runtime/index-browser.d.ts",
|
||||
"require": "./runtime/index-browser.js",
|
||||
"import": "./runtime/index-browser.mjs",
|
||||
"default": "./runtime/index-browser.mjs"
|
||||
},
|
||||
"./generator-build": {
|
||||
"require": "./generator-build/index.js",
|
||||
"import": "./generator-build/index.js",
|
||||
"default": "./generator-build/index.js"
|
||||
},
|
||||
"./sql": {
|
||||
"require": {
|
||||
"types": "./sql.d.ts",
|
||||
"node": "./sql.js",
|
||||
"default": "./sql.js"
|
||||
},
|
||||
"import": {
|
||||
"types": "./sql.d.ts",
|
||||
"node": "./sql.mjs",
|
||||
"default": "./sql.mjs"
|
||||
},
|
||||
"default": "./sql.js"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"version": "7.8.0",
|
||||
"sideEffects": false,
|
||||
"dependencies": {
|
||||
"@prisma/client-runtime-utils": "7.8.0"
|
||||
},
|
||||
"imports": {
|
||||
"#wasm-compiler-loader": {
|
||||
"edge-light": "./wasm-edge-light-loader.mjs",
|
||||
"workerd": "./wasm-worker-loader.mjs",
|
||||
"worker": "./wasm-worker-loader.mjs",
|
||||
"default": "./wasm-worker-loader.mjs"
|
||||
},
|
||||
"#main-entry-point": {
|
||||
"require": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./edge.js",
|
||||
"workerd": "./edge.js",
|
||||
"worker": "./edge.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"import": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./edge.js",
|
||||
"workerd": "./edge.js",
|
||||
"worker": "./edge.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"default": "./index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
"use strict";var h=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var j=Object.prototype.hasOwnProperty;var D=(e,t)=>{for(var n in t)h(e,n,{get:t[n],enumerable:!0})},O=(e,t,n,_)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of M(t))!j.call(e,r)&&r!==n&&h(e,r,{get:()=>t[r],enumerable:!(_=T(t,r))||_.enumerable});return e};var B=e=>O(h({},"__esModule",{value:!0}),e);var xe={};D(xe,{QueryCompiler:()=>F,__wbg_Error_e83987f665cf5504:()=>q,__wbg_Number_bb48ca12f395cd08:()=>C,__wbg_String_8f0eb39a4a4c2f66:()=>k,__wbg___wbindgen_boolean_get_6d5a1ee65bab5f68:()=>W,__wbg___wbindgen_debug_string_df47ffb5e35e6763:()=>V,__wbg___wbindgen_in_bb933bd9e1b3bc0f:()=>z,__wbg___wbindgen_is_object_c818261d21f283a4:()=>L,__wbg___wbindgen_is_string_fbb76cb2940daafd:()=>P,__wbg___wbindgen_is_undefined_2d472862bd29a478:()=>Q,__wbg___wbindgen_jsval_loose_eq_b664b38a2f582147:()=>Y,__wbg___wbindgen_number_get_a20bf9b85341449d:()=>G,__wbg___wbindgen_string_get_e4f06c90489ad01b:()=>J,__wbg___wbindgen_throw_b855445ff6a94295:()=>X,__wbg_entries_e171b586f8f6bdbf:()=>H,__wbg_getTime_14776bfb48a1bff9:()=>K,__wbg_get_7bed016f185add81:()=>Z,__wbg_get_with_ref_key_1dc361bd10053bfe:()=>v,__wbg_instanceof_ArrayBuffer_70beb1189ca63b38:()=>ee,__wbg_instanceof_Uint8Array_20c8e73002f7af98:()=>te,__wbg_isSafeInteger_d216eda7911dde36:()=>ne,__wbg_length_69bca3cb64fc8748:()=>re,__wbg_length_cdd215e10d9dd507:()=>_e,__wbg_new_0_f9740686d739025c:()=>oe,__wbg_new_1acc0b6eea89d040:()=>ce,__wbg_new_5a79be3ab53b8aa5:()=>ie,__wbg_new_68651c719dcda04e:()=>se,__wbg_new_e17d9f43105b08be:()=>ue,__wbg_prototypesetcall_2a6620b6922694b2:()=>fe,__wbg_set_3f1d0b984ed272ed:()=>be,__wbg_set_907fb406c34a251d:()=>de,__wbg_set_c213c871859d6500:()=>ae,__wbg_set_message_82ae475bb413aa5c:()=>ge,__wbg_set_wasm:()=>N,__wbindgen_cast_2241b6af4c4b2941:()=>le,__wbindgen_cast_4625c577ab2ec9ee:()=>we,__wbindgen_cast_9ae0607507abb057:()=>pe,__wbindgen_cast_d6cd19b81560fd6e:()=>ye,__wbindgen_init_externref_table:()=>me});module.exports=B(xe);var A=()=>{};A.prototype=A;let o;function N(e){o=e}let p=null;function a(){return(p===null||p.byteLength===0)&&(p=new Uint8Array(o.memory.buffer)),p}let y=new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0});y.decode();const U=2146435072;let S=0;function R(e,t){return S+=t,S>=U&&(y=new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0}),y.decode(),S=t),y.decode(a().subarray(e,e+t))}function m(e,t){return e=e>>>0,R(e,t)}let f=0;const g=new TextEncoder;"encodeInto"in g||(g.encodeInto=function(e,t){const n=g.encode(e);return t.set(n),{read:e.length,written:n.length}});function l(e,t,n){if(n===void 0){const i=g.encode(e),d=t(i.length,1)>>>0;return a().subarray(d,d+i.length).set(i),f=i.length,d}let _=e.length,r=t(_,1)>>>0;const s=a();let c=0;for(;c<_;c++){const i=e.charCodeAt(c);if(i>127)break;s[r+c]=i}if(c!==_){c!==0&&(e=e.slice(c)),r=n(r,_,_=c+e.length*3,1)>>>0;const i=a().subarray(r+c,r+_),d=g.encodeInto(e,i);c+=d.written,r=n(r,_,c,1)>>>0}return f=c,r}let b=null;function u(){return(b===null||b.buffer.detached===!0||b.buffer.detached===void 0&&b.buffer!==o.memory.buffer)&&(b=new DataView(o.memory.buffer)),b}function x(e){return e==null}function I(e){const t=typeof e;if(t=="number"||t=="boolean"||e==null)return`${e}`;if(t=="string")return`"${e}"`;if(t=="symbol"){const r=e.description;return r==null?"Symbol":`Symbol(${r})`}if(t=="function"){const r=e.name;return typeof r=="string"&&r.length>0?`Function(${r})`:"Function"}if(Array.isArray(e)){const r=e.length;let s="[";r>0&&(s+=I(e[0]));for(let c=1;c<r;c++)s+=", "+I(e[c]);return s+="]",s}const n=/\[object ([^\]]+)\]/.exec(toString.call(e));let _;if(n&&n.length>1)_=n[1];else return toString.call(e);if(_=="Object")try{return"Object("+JSON.stringify(e)+")"}catch{return"Object"}return e instanceof Error?`${e.name}: ${e.message}
|
||||
${e.stack}`:_}function $(e,t){return e=e>>>0,a().subarray(e/1,e/1+t)}function w(e){const t=o.__wbindgen_externrefs.get(e);return o.__externref_table_dealloc(e),t}const E=typeof FinalizationRegistry>"u"?{register:()=>{},unregister:()=>{}}:new FinalizationRegistry(e=>o.__wbg_querycompiler_free(e>>>0,1));class F{__destroy_into_raw(){const t=this.__wbg_ptr;return this.__wbg_ptr=0,E.unregister(this),t}free(){const t=this.__destroy_into_raw();o.__wbg_querycompiler_free(t,0)}compileBatch(t){const n=l(t,o.__wbindgen_malloc,o.__wbindgen_realloc),_=f,r=o.querycompiler_compileBatch(this.__wbg_ptr,n,_);if(r[2])throw w(r[1]);return w(r[0])}constructor(t){const n=o.querycompiler_new(t);if(n[2])throw w(n[1]);return this.__wbg_ptr=n[0]>>>0,E.register(this,this.__wbg_ptr,this),this}compile(t){const n=l(t,o.__wbindgen_malloc,o.__wbindgen_realloc),_=f,r=o.querycompiler_compile(this.__wbg_ptr,n,_);if(r[2])throw w(r[1]);return w(r[0])}}Symbol.dispose&&(F.prototype[Symbol.dispose]=F.prototype.free);function q(e,t){return Error(m(e,t))}function C(e){return Number(e)}function k(e,t){const n=String(t),_=l(n,o.__wbindgen_malloc,o.__wbindgen_realloc),r=f;u().setInt32(e+4*1,r,!0),u().setInt32(e+4*0,_,!0)}function W(e){const t=e,n=typeof t=="boolean"?t:void 0;return x(n)?16777215:n?1:0}function V(e,t){const n=I(t),_=l(n,o.__wbindgen_malloc,o.__wbindgen_realloc),r=f;u().setInt32(e+4*1,r,!0),u().setInt32(e+4*0,_,!0)}function z(e,t){return e in t}function L(e){const t=e;return typeof t=="object"&&t!==null}function P(e){return typeof e=="string"}function Q(e){return e===void 0}function Y(e,t){return e==t}function G(e,t){const n=t,_=typeof n=="number"?n:void 0;u().setFloat64(e+8*1,x(_)?0:_,!0),u().setInt32(e+4*0,!x(_),!0)}function J(e,t){const n=t,_=typeof n=="string"?n:void 0;var r=x(_)?0:l(_,o.__wbindgen_malloc,o.__wbindgen_realloc),s=f;u().setInt32(e+4*1,s,!0),u().setInt32(e+4*0,r,!0)}function X(e,t){throw new Error(m(e,t))}function H(e){return Object.entries(e)}function K(e){return e.getTime()}function Z(e,t){return e[t>>>0]}function v(e,t){return e[t]}function ee(e){let t;try{t=e instanceof ArrayBuffer}catch{t=!1}return t}function te(e){let t;try{t=e instanceof Uint8Array}catch{t=!1}return t}function ne(e){return Number.isSafeInteger(e)}function re(e){return e.length}function _e(e){return e.length}function oe(){return new Date}function ce(){return new Object}function ie(e){return new Uint8Array(e)}function se(){return new Map}function ue(){return new Array}function fe(e,t,n){Uint8Array.prototype.set.call($(e,t),n)}function be(e,t,n){e[t]=n}function de(e,t,n){return e.set(t,n)}function ae(e,t,n){e[t>>>0]=n}function ge(e,t){global.PRISMA_WASM_PANIC_REGISTRY.set_message(m(e,t))}function le(e,t){return m(e,t)}function we(e){return BigInt.asUintN(64,e)}function pe(e){return e}function ye(e){return e}function me(){const e=o.__wbindgen_externrefs,t=e.grow(4);e.set(0,void 0),e.set(t+0,void 0),e.set(t+1,null),e.set(t+2,!0),e.set(t+3,!1)}0&&(module.exports={QueryCompiler,__wbg_Error_e83987f665cf5504,__wbg_Number_bb48ca12f395cd08,__wbg_String_8f0eb39a4a4c2f66,__wbg___wbindgen_boolean_get_6d5a1ee65bab5f68,__wbg___wbindgen_debug_string_df47ffb5e35e6763,__wbg___wbindgen_in_bb933bd9e1b3bc0f,__wbg___wbindgen_is_object_c818261d21f283a4,__wbg___wbindgen_is_string_fbb76cb2940daafd,__wbg___wbindgen_is_undefined_2d472862bd29a478,__wbg___wbindgen_jsval_loose_eq_b664b38a2f582147,__wbg___wbindgen_number_get_a20bf9b85341449d,__wbg___wbindgen_string_get_e4f06c90489ad01b,__wbg___wbindgen_throw_b855445ff6a94295,__wbg_entries_e171b586f8f6bdbf,__wbg_getTime_14776bfb48a1bff9,__wbg_get_7bed016f185add81,__wbg_get_with_ref_key_1dc361bd10053bfe,__wbg_instanceof_ArrayBuffer_70beb1189ca63b38,__wbg_instanceof_Uint8Array_20c8e73002f7af98,__wbg_isSafeInteger_d216eda7911dde36,__wbg_length_69bca3cb64fc8748,__wbg_length_cdd215e10d9dd507,__wbg_new_0_f9740686d739025c,__wbg_new_1acc0b6eea89d040,__wbg_new_5a79be3ab53b8aa5,__wbg_new_68651c719dcda04e,__wbg_new_e17d9f43105b08be,__wbg_prototypesetcall_2a6620b6922694b2,__wbg_set_3f1d0b984ed272ed,__wbg_set_907fb406c34a251d,__wbg_set_c213c871859d6500,__wbg_set_message_82ae475bb413aa5c,__wbg_set_wasm,__wbindgen_cast_2241b6af4c4b2941,__wbindgen_cast_4625c577ab2ec9ee,__wbindgen_cast_9ae0607507abb057,__wbindgen_cast_d6cd19b81560fd6e,__wbindgen_init_externref_table});
|
||||
BIN
apps/web/prisma/generated/client/query_compiler_fast_bg.wasm
Normal file
BIN
apps/web/prisma/generated/client/query_compiler_fast_bg.wasm
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
3386
apps/web/prisma/generated/client/runtime/client.d.ts
vendored
Normal file
3386
apps/web/prisma/generated/client/runtime/client.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
86
apps/web/prisma/generated/client/runtime/client.js
Normal file
86
apps/web/prisma/generated/client/runtime/client.js
Normal file
File diff suppressed because one or more lines are too long
90
apps/web/prisma/generated/client/runtime/index-browser.d.ts
vendored
Normal file
90
apps/web/prisma/generated/client/runtime/index-browser.d.ts
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
import { AnyNull } from '@prisma/client-runtime-utils';
|
||||
import { DbNull } from '@prisma/client-runtime-utils';
|
||||
import { Decimal } from '@prisma/client-runtime-utils';
|
||||
import { isAnyNull } from '@prisma/client-runtime-utils';
|
||||
import { isDbNull } from '@prisma/client-runtime-utils';
|
||||
import { isJsonNull } from '@prisma/client-runtime-utils';
|
||||
import { isObjectEnumValue } from '@prisma/client-runtime-utils';
|
||||
import { JsonNull } from '@prisma/client-runtime-utils';
|
||||
import { NullTypes } from '@prisma/client-runtime-utils';
|
||||
|
||||
export { AnyNull }
|
||||
|
||||
declare type Args<T, F extends Operation> = T extends {
|
||||
[K: symbol]: {
|
||||
types: {
|
||||
operations: {
|
||||
[K in F]: {
|
||||
args: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} ? T[symbol]['types']['operations'][F]['args'] : any;
|
||||
|
||||
export { DbNull }
|
||||
|
||||
export { Decimal }
|
||||
|
||||
declare type Exact<A, W> = (A extends unknown ? (W extends A ? {
|
||||
[K in keyof A]: Exact<A[K], W[K]>;
|
||||
} : W) : never) | (A extends Narrowable ? A : never);
|
||||
|
||||
export declare function getRuntime(): GetRuntimeOutput;
|
||||
|
||||
declare type GetRuntimeOutput = {
|
||||
id: RuntimeName;
|
||||
prettyName: string;
|
||||
isEdge: boolean;
|
||||
};
|
||||
|
||||
export { isAnyNull }
|
||||
|
||||
export { isDbNull }
|
||||
|
||||
export { isJsonNull }
|
||||
|
||||
export { isObjectEnumValue }
|
||||
|
||||
export { JsonNull }
|
||||
|
||||
/**
|
||||
* Generates more strict variant of an enum which, unlike regular enum,
|
||||
* throws on non-existing property access. This can be useful in following situations:
|
||||
* - we have an API, that accepts both `undefined` and `SomeEnumType` as an input
|
||||
* - enum values are generated dynamically from DMMF.
|
||||
*
|
||||
* In that case, if using normal enums and no compile-time typechecking, using non-existing property
|
||||
* will result in `undefined` value being used, which will be accepted. Using strict enum
|
||||
* in this case will help to have a runtime exception, telling you that you are probably doing something wrong.
|
||||
*
|
||||
* Note: if you need to check for existence of a value in the enum you can still use either
|
||||
* `in` operator or `hasOwnProperty` function.
|
||||
*
|
||||
* @param definition
|
||||
* @returns
|
||||
*/
|
||||
export declare function makeStrictEnum<T extends Record<PropertyKey, string | number>>(definition: T): T;
|
||||
|
||||
declare type Narrowable = string | number | bigint | boolean | [];
|
||||
|
||||
export { NullTypes }
|
||||
|
||||
declare type Operation = 'findFirst' | 'findFirstOrThrow' | 'findUnique' | 'findUniqueOrThrow' | 'findMany' | 'create' | 'createMany' | 'createManyAndReturn' | 'update' | 'updateMany' | 'updateManyAndReturn' | 'upsert' | 'delete' | 'deleteMany' | 'aggregate' | 'count' | 'groupBy' | '$queryRaw' | '$executeRaw' | '$queryRawUnsafe' | '$executeRawUnsafe' | 'findRaw' | 'aggregateRaw' | '$runCommandRaw';
|
||||
|
||||
declare namespace Public {
|
||||
export {
|
||||
validator
|
||||
}
|
||||
}
|
||||
export { Public }
|
||||
|
||||
declare type RuntimeName = 'workerd' | 'deno' | 'netlify' | 'node' | 'bun' | 'edge-light' | '';
|
||||
|
||||
declare function validator<V>(): <S>(select: Exact<S, V>) => S;
|
||||
|
||||
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation>(client: C, model: M, operation: O): <S>(select: Exact<S, Args<C[M], O>>) => S;
|
||||
|
||||
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation, P extends keyof Args<C[M], O>>(client: C, model: M, operation: O, prop: P): <S>(select: Exact<S, Args<C[M], O>[P]>) => S;
|
||||
|
||||
export { }
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
"use strict";var s=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var f=Object.prototype.hasOwnProperty;var a=(e,t)=>{for(var n in t)s(e,n,{get:t[n],enumerable:!0})},y=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of p(t))!f.call(e,i)&&i!==n&&s(e,i,{get:()=>t[i],enumerable:!(r=g(t,i))||r.enumerable});return e};var x=e=>y(s({},"__esModule",{value:!0}),e);var M={};a(M,{AnyNull:()=>o.AnyNull,DbNull:()=>o.DbNull,Decimal:()=>m.Decimal,JsonNull:()=>o.JsonNull,NullTypes:()=>o.NullTypes,Public:()=>l,getRuntime:()=>c,isAnyNull:()=>o.isAnyNull,isDbNull:()=>o.isDbNull,isJsonNull:()=>o.isJsonNull,isObjectEnumValue:()=>o.isObjectEnumValue,makeStrictEnum:()=>u});module.exports=x(M);var l={};a(l,{validator:()=>d});function d(...e){return t=>t}var b=new Set(["toJSON","$$typeof","asymmetricMatch",Symbol.iterator,Symbol.toStringTag,Symbol.isConcatSpreadable,Symbol.toPrimitive]);function u(e){return new Proxy(e,{get(t,n){if(n in t)return t[n];if(!b.has(n))throw new TypeError("Invalid enum value: ".concat(String(n)))}})}var N=()=>{var e,t;return((t=(e=globalThis.process)==null?void 0:e.release)==null?void 0:t.name)==="node"},E=()=>{var e,t;return!!globalThis.Bun||!!((t=(e=globalThis.process)==null?void 0:e.versions)!=null&&t.bun)},S=()=>!!globalThis.Deno,R=()=>typeof globalThis.Netlify=="object",h=()=>typeof globalThis.EdgeRuntime=="object",C=()=>{var e;return((e=globalThis.navigator)==null?void 0:e.userAgent)==="Cloudflare-Workers"};function k(){var n;return(n=[[R,"netlify"],[h,"edge-light"],[C,"workerd"],[S,"deno"],[E,"bun"],[N,"node"]].flatMap(r=>r[0]()?[r[1]]:[]).at(0))!=null?n:""}var O={node:"Node.js",workerd:"Cloudflare Workers",deno:"Deno and Deno Deploy",netlify:"Netlify Edge Functions","edge-light":"Edge Runtime (Vercel Edge Functions, Vercel Edge Middleware, Next.js (Pages Router) Edge API Routes, Next.js (App Router) Edge Route Handlers or Next.js Middleware)"};function c(){let e=k();return{id:e,prettyName:O[e]||e,isEdge:["workerd","deno","netlify","edge-light"].includes(e)}}var o=require("@prisma/client-runtime-utils"),m=require("@prisma/client-runtime-utils");0&&(module.exports={AnyNull,DbNull,Decimal,JsonNull,NullTypes,Public,getRuntime,isAnyNull,isDbNull,isJsonNull,isObjectEnumValue,makeStrictEnum});
|
||||
//# sourceMappingURL=index-browser.js.map
|
||||
File diff suppressed because one or more lines are too long
101
apps/web/prisma/generated/client/schema.prisma
Normal file
101
apps/web/prisma/generated/client/schema.prisma
Normal file
@@ -0,0 +1,101 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
output = "./generated/client"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
}
|
||||
|
||||
model Task {
|
||||
id String @id @default(uuid())
|
||||
title String
|
||||
description String
|
||||
status String // Enum: TaskStatus (OPEN, EXECUTING, VERIFYING, COMPLETED, FAILED, etc)
|
||||
difficulty String // Enum: TaskDifficulty (HELLO_WORLD, COMPONENT, VIEW, EPIC)
|
||||
scope_clarity_score Float
|
||||
error_classification String? // Enum: TaskErrorClassification
|
||||
reward_amount Int // Stored in cents
|
||||
reward_currency String // USD, TWD, USDC
|
||||
acceptance_criteria Json // Contains validation_mode, test_file_content, rules
|
||||
required_stack String[]
|
||||
retry_count Int @default(0)
|
||||
stripe_payment_intent_id String?
|
||||
expires_at DateTime?
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
|
||||
claims Claim[]
|
||||
submissions Submission[]
|
||||
}
|
||||
|
||||
model Claim {
|
||||
id String @id @default(uuid())
|
||||
task_id String
|
||||
task Task @relation(fields: [task_id], references: [id])
|
||||
developer_wallet String
|
||||
status String // EXECUTING, CANCELLED, VERIFYING, COMPLETED
|
||||
claim_token String @unique // Idempotency token for this claim
|
||||
held_amount Int
|
||||
held_currency String
|
||||
expires_at DateTime
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
|
||||
submissions Submission[]
|
||||
}
|
||||
|
||||
model Submission {
|
||||
id String @id @default(uuid())
|
||||
task_id String
|
||||
task Task @relation(fields: [task_id], references: [id])
|
||||
claim_id String
|
||||
claim Claim @relation(fields: [claim_id], references: [id])
|
||||
status String // VERIFYING, JUDGED
|
||||
deliverables Json // Files and notes submitted
|
||||
estimated_judge_complete_at DateTime?
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
|
||||
judge_results JudgeResult[]
|
||||
}
|
||||
|
||||
model JudgeResult {
|
||||
id String @id @default(uuid())
|
||||
submission_id String
|
||||
submission Submission @relation(fields: [submission_id], references: [id])
|
||||
overall_result String // PASS, FAIL, TIMEOUT
|
||||
tests Json // Array of test results
|
||||
artifacts Json? // screenshot_url, logs_url, diff_url
|
||||
error_classification String? // Enum: JudgeErrorClassification
|
||||
error_signature String?
|
||||
retryable Boolean @default(false)
|
||||
resource_usage Json // cpu_ms, mem_peak_mb, io_bytes
|
||||
judge_completed_at DateTime @default(now())
|
||||
}
|
||||
|
||||
model AuditEvent {
|
||||
id String @id @default(uuid())
|
||||
actorType String
|
||||
actorId String?
|
||||
action String
|
||||
entityType String
|
||||
entityId String
|
||||
beforeState Json?
|
||||
afterState Json?
|
||||
reason String?
|
||||
metadata Json?
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
model LedgerEntry {
|
||||
id String @id @default(cuid())
|
||||
task_id String
|
||||
phase String
|
||||
idempotency_key String @unique
|
||||
stripe_object_id String?
|
||||
response_status String
|
||||
http_status Int
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
export default import('./query_compiler_fast_bg.wasm?module')
|
||||
5
apps/web/prisma/generated/client/wasm-worker-loader.mjs
Normal file
5
apps/web/prisma/generated/client/wasm-worker-loader.mjs
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
export default import('./query_compiler_fast_bg.wasm')
|
||||
@@ -1,5 +1,6 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
output = "./generated/client"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
@@ -86,3 +87,15 @@ model AuditEvent {
|
||||
metadata Json?
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
model LedgerEntry {
|
||||
id String @id @default(cuid())
|
||||
task_id String
|
||||
phase String
|
||||
idempotency_key String @unique
|
||||
stripe_object_id String?
|
||||
response_status String
|
||||
http_status Int
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { PrismaClient } from "./prisma/generated/client";
|
||||
import { TaskStatus, TaskDifficulty } from "@agent-bounty/contracts";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
export const dynamic = 'force-dynamic';
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { logAuditEvent } from "@/lib/audit";
|
||||
import { TaskStatus } from "@agent-bounty/contracts";
|
||||
import { releasePayment } from "@/lib/payment";
|
||||
|
||||
// Optional: restrict to cron secret
|
||||
// export const maxDuration = 60; // Next.js edge/serverless config
|
||||
@@ -49,6 +51,8 @@ export async function GET(req: Request) {
|
||||
afterState: { status: TaskStatus.OPEN },
|
||||
reason: `Claim ${claim.id} expired at ${claim.expires_at.toISOString()}`,
|
||||
});
|
||||
|
||||
await releasePayment(tx, claim.task_id, `${claim.id}-release`);
|
||||
});
|
||||
rolledBackIds.push(claim.task_id);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export const dynamic = 'force-dynamic';
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import {
|
||||
ListOpenTasksRequestSchema,
|
||||
@@ -10,7 +11,9 @@ import { prisma } from "@/lib/prisma";
|
||||
import { runSubmissionInSandbox } from "@/lib/sandbox";
|
||||
import { logAuditEvent } from "@/lib/audit";
|
||||
import { redis } from "@/lib/redis";
|
||||
import { authHold, capturePayment } from "@/lib/payment";
|
||||
import crypto from "crypto";
|
||||
import { z } from "zod";
|
||||
|
||||
export async function POST(request: NextRequest, props: { params: Promise<{ tool: string }> }) {
|
||||
const params = await props.params;
|
||||
@@ -87,6 +90,15 @@ export async function POST(request: NextRequest, props: { params: Promise<{ tool
|
||||
}
|
||||
});
|
||||
|
||||
await authHold(
|
||||
tx,
|
||||
task.id,
|
||||
task.reward_amount,
|
||||
task.reward_currency,
|
||||
parsed.developer_wallet,
|
||||
`${newClaim.id}-auth-hold`
|
||||
);
|
||||
|
||||
await logAuditEvent(tx, {
|
||||
actorType: "AGENT",
|
||||
actorId: parsed.developer_wallet,
|
||||
@@ -166,51 +178,56 @@ export async function POST(request: NextRequest, props: { params: Promise<{ tool
|
||||
// Fire and forget
|
||||
runSubmissionInSandbox(
|
||||
submission.id,
|
||||
parsed.deliverables as Record<string, string>,
|
||||
parsed.deliverables as unknown as Record<string, string>,
|
||||
criteria.test_file_content
|
||||
).then(async (result) => {
|
||||
// Update submission
|
||||
await prisma.submission.update({
|
||||
where: { id: submission.id },
|
||||
data: { status: "JUDGED" }
|
||||
});
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// Update submission
|
||||
await tx.submission.update({
|
||||
where: { id: submission.id },
|
||||
data: { status: "JUDGED" }
|
||||
});
|
||||
|
||||
// Create JudgeResult
|
||||
await prisma.judgeResult.create({
|
||||
data: {
|
||||
submission_id: submission.id,
|
||||
overall_result: result.overall_result,
|
||||
tests: result.tests,
|
||||
artifacts: result.artifacts,
|
||||
error_classification: result.error_classification,
|
||||
resource_usage: result.resource_usage
|
||||
// Create JudgeResult
|
||||
await tx.judgeResult.create({
|
||||
data: {
|
||||
submission_id: submission.id,
|
||||
overall_result: result.overall_result,
|
||||
tests: result.tests,
|
||||
artifacts: result.artifacts,
|
||||
error_classification: result.error_classification,
|
||||
resource_usage: result.resource_usage
|
||||
}
|
||||
});
|
||||
|
||||
// Update Task & Claim Status
|
||||
const newTaskStatus = result.overall_result === JudgeOverallResult.PASS
|
||||
? TaskStatus.COMPLETED
|
||||
: TaskStatus.FAILED_RETRYABLE;
|
||||
|
||||
await tx.task.update({
|
||||
where: { id: submission.task_id },
|
||||
data: { status: newTaskStatus }
|
||||
});
|
||||
|
||||
await tx.claim.update({
|
||||
where: { id: submission.claim_id },
|
||||
data: { status: newTaskStatus }
|
||||
});
|
||||
|
||||
if (newTaskStatus === TaskStatus.COMPLETED) {
|
||||
await capturePayment(tx, submission.task_id, `${submission.claim_id}-capture`);
|
||||
}
|
||||
});
|
||||
|
||||
// Update Task & Claim Status
|
||||
const newTaskStatus = result.overall_result === JudgeOverallResult.PASS
|
||||
? TaskStatus.COMPLETED
|
||||
: TaskStatus.FAILED_RETRYABLE;
|
||||
|
||||
await prisma.task.update({
|
||||
where: { id: submission.task_id },
|
||||
data: { status: newTaskStatus }
|
||||
});
|
||||
|
||||
await prisma.claim.update({
|
||||
where: { id: submission.claim_id },
|
||||
data: { status: newTaskStatus }
|
||||
});
|
||||
|
||||
// @ts-ignore prisma transaction client vs prisma client
|
||||
await logAuditEvent(prisma, {
|
||||
actorType: "SYSTEM",
|
||||
action: "JUDGE_COMPLETE",
|
||||
entityType: "TASK",
|
||||
entityId: submission.task_id,
|
||||
beforeState: { status: TaskStatus.VERIFYING },
|
||||
afterState: { status: newTaskStatus },
|
||||
metadata: { overall_result: result.overall_result, error_classification: result.error_classification }
|
||||
await logAuditEvent(tx, {
|
||||
actorType: "SYSTEM",
|
||||
action: "JUDGE_COMPLETE",
|
||||
entityType: "TASK",
|
||||
entityId: submission.task_id,
|
||||
beforeState: { status: TaskStatus.VERIFYING },
|
||||
afterState: { status: newTaskStatus },
|
||||
metadata: { overall_result: result.overall_result, error_classification: result.error_classification }
|
||||
});
|
||||
});
|
||||
}).catch(console.error);
|
||||
}
|
||||
@@ -225,13 +242,35 @@ export async function POST(request: NextRequest, props: { params: Promise<{ tool
|
||||
}
|
||||
|
||||
case "check_payout_status": {
|
||||
// Mocked for now until Settlement phase is implemented
|
||||
const parsed = z.object({ task_id: z.string().uuid() }).parse(body);
|
||||
|
||||
const task = await prisma.task.findUnique({ where: { id: parsed.task_id } });
|
||||
if (!task) {
|
||||
throw new Error("Task not found");
|
||||
}
|
||||
|
||||
const ledger = await prisma.ledgerEntry.findFirst({
|
||||
where: { task_id: parsed.task_id },
|
||||
orderBy: { created_at: 'desc' }
|
||||
});
|
||||
|
||||
if (!ledger) {
|
||||
return NextResponse.json({
|
||||
task_id: task.id,
|
||||
phase: task.status === "COMPLETED" ? "PAYOUT_READY" : "NO_LEDGER",
|
||||
amount: task.reward_amount,
|
||||
currency: task.reward_currency,
|
||||
updated_at: task.updated_at.toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
task_id: body.task_id,
|
||||
phase: "PAYOUT_READY",
|
||||
amount: 100,
|
||||
currency: "USD",
|
||||
updated_at: new Date().toISOString(),
|
||||
task_id: task.id,
|
||||
phase: ledger.phase,
|
||||
amount: task.reward_amount,
|
||||
currency: task.reward_currency,
|
||||
updated_at: ledger.updated_at.toISOString(),
|
||||
ledger_entry: ledger
|
||||
});
|
||||
}
|
||||
|
||||
@@ -240,6 +279,16 @@ export async function POST(request: NextRequest, props: { params: Promise<{ tool
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`[API Gateway] Error handling ${tool}:`, error);
|
||||
return NextResponse.json({ error: error.message || String(error) }, { status: 400 });
|
||||
|
||||
if (error.name === "ZodError") {
|
||||
return NextResponse.json({ error_type: "InvalidParams", message: error.errors }, { status: 400 });
|
||||
}
|
||||
|
||||
const msg = error.message || String(error);
|
||||
if (msg.includes("not OPEN") || msg.includes("not EXECUTING") || msg.includes("Invalid claim token") || msg.includes("not found")) {
|
||||
return NextResponse.json({ error_type: "StateConflict", message: msg }, { status: 409 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ error_type: "InternalError", message: msg }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
127
apps/web/src/lib/payment.ts
Normal file
127
apps/web/src/lib/payment.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
// In Phase 2, we use a Mock implementation for Stripe to ensure our DB state machine
|
||||
// and idempotency rules are solid before integrating the real Stripe SDK.
|
||||
|
||||
export async function authHold(
|
||||
tx: Prisma.TransactionClient,
|
||||
taskId: string,
|
||||
amount: number,
|
||||
currency: string,
|
||||
wallet: string,
|
||||
idempotencyKey: string
|
||||
) {
|
||||
// Check idempotency
|
||||
const existing = await tx.ledgerEntry.findUnique({
|
||||
where: { idempotency_key: idempotencyKey },
|
||||
});
|
||||
if (existing) {
|
||||
if (existing.response_status === "SUCCESS") return existing;
|
||||
throw new Error(`Previous authHold failed for idempotencyKey: ${idempotencyKey}`);
|
||||
}
|
||||
|
||||
// --- MOCK STRIPE CALL ---
|
||||
// In real life: const intent = await stripe.paymentIntents.create({ amount, currency, payment_method_types: ['card'], capture_method: 'manual', metadata: { taskId, wallet } });
|
||||
const mockStripeObjectId = `pi_mock_hold_${crypto.randomUUID()}`;
|
||||
// ------------------------
|
||||
|
||||
return await tx.ledgerEntry.create({
|
||||
data: {
|
||||
task_id: taskId,
|
||||
phase: "AUTH_HOLD",
|
||||
idempotency_key: idempotencyKey,
|
||||
stripe_object_id: mockStripeObjectId,
|
||||
response_status: "SUCCESS",
|
||||
http_status: 200,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function capturePayment(
|
||||
tx: Prisma.TransactionClient,
|
||||
taskId: string,
|
||||
idempotencyKey: string
|
||||
) {
|
||||
const existing = await tx.ledgerEntry.findUnique({
|
||||
where: { idempotency_key: idempotencyKey },
|
||||
});
|
||||
if (existing) {
|
||||
if (existing.response_status === "SUCCESS") return existing;
|
||||
throw new Error(`Previous capturePayment failed for idempotencyKey: ${idempotencyKey}`);
|
||||
}
|
||||
|
||||
// We should find the AUTH_HOLD record to get the intent ID in real life
|
||||
const holdEntry = await tx.ledgerEntry.findFirst({
|
||||
where: { task_id: taskId, phase: "AUTH_HOLD", response_status: "SUCCESS" },
|
||||
orderBy: { created_at: "desc" }
|
||||
});
|
||||
|
||||
if (!holdEntry || !holdEntry.stripe_object_id) {
|
||||
throw new Error("Cannot capture without a successful AUTH_HOLD");
|
||||
}
|
||||
|
||||
// --- MOCK STRIPE CALL ---
|
||||
// In real life: const capture = await stripe.paymentIntents.capture(holdEntry.stripe_object_id);
|
||||
const mockStripeObjectId = `ch_mock_capture_${crypto.randomUUID()}`;
|
||||
// ------------------------
|
||||
|
||||
return await tx.ledgerEntry.create({
|
||||
data: {
|
||||
task_id: taskId,
|
||||
phase: "CAPTURE",
|
||||
idempotency_key: idempotencyKey,
|
||||
stripe_object_id: mockStripeObjectId,
|
||||
response_status: "SUCCESS",
|
||||
http_status: 200,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function releasePayment(
|
||||
tx: Prisma.TransactionClient,
|
||||
taskId: string,
|
||||
idempotencyKey: string
|
||||
) {
|
||||
const existing = await tx.ledgerEntry.findUnique({
|
||||
where: { idempotency_key: idempotencyKey },
|
||||
});
|
||||
if (existing) {
|
||||
if (existing.response_status === "SUCCESS") return existing;
|
||||
throw new Error(`Previous releasePayment failed for idempotencyKey: ${idempotencyKey}`);
|
||||
}
|
||||
|
||||
const holdEntry = await tx.ledgerEntry.findFirst({
|
||||
where: { task_id: taskId, phase: "AUTH_HOLD", response_status: "SUCCESS" },
|
||||
orderBy: { created_at: "desc" }
|
||||
});
|
||||
|
||||
if (!holdEntry || !holdEntry.stripe_object_id) {
|
||||
// If there was no hold to begin with, releasing is a no-op but we log it as SUCCESS
|
||||
return await tx.ledgerEntry.create({
|
||||
data: {
|
||||
task_id: taskId,
|
||||
phase: "RELEASE",
|
||||
idempotency_key: idempotencyKey,
|
||||
stripe_object_id: null,
|
||||
response_status: "SUCCESS",
|
||||
http_status: 200,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// --- MOCK STRIPE CALL ---
|
||||
// In real life: const cancel = await stripe.paymentIntents.cancel(holdEntry.stripe_object_id);
|
||||
const mockStripeObjectId = `re_mock_release_${crypto.randomUUID()}`;
|
||||
// ------------------------
|
||||
|
||||
return await tx.ledgerEntry.create({
|
||||
data: {
|
||||
task_id: taskId,
|
||||
phase: "RELEASE",
|
||||
idempotency_key: idempotencyKey,
|
||||
stripe_object_id: mockStripeObjectId,
|
||||
response_status: "SUCCESS",
|
||||
http_status: 200,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { PrismaClient } from "../../prisma/generated/client";
|
||||
|
||||
const globalForPrisma = global as unknown as { prisma: PrismaClient };
|
||||
|
||||
|
||||
@@ -26,12 +26,12 @@ export async function runSubmissionInSandbox(
|
||||
|
||||
// Run tests
|
||||
const result = await sandbox.commands.run("npx vitest run test.spec.tsx --reporter json", {
|
||||
timeout: 120000 // 2 minutes
|
||||
timeoutMs: 120000 // 2 minutes
|
||||
});
|
||||
|
||||
let overall = JudgeOverallResult.FAIL;
|
||||
let errorClass = result.exitCode === 0 ? null : JudgeErrorClassification.TEST_FAIL;
|
||||
let parsedTests = [];
|
||||
let overall: JudgeOverallResult = JudgeOverallResult.FAIL;
|
||||
let errorClass: JudgeErrorClassification | null = result.exitCode === 0 ? null : JudgeErrorClassification.TEST_FAIL;
|
||||
let parsedTests: any[] = [];
|
||||
|
||||
if (result.exitCode === 0) {
|
||||
overall = JudgeOverallResult.PASS;
|
||||
@@ -70,7 +70,7 @@ export async function runSubmissionInSandbox(
|
||||
};
|
||||
} finally {
|
||||
if (sandbox) {
|
||||
await sandbox.close();
|
||||
await sandbox.kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,26 +162,11 @@ export const ClaimTaskResponseSchema = z.object({
|
||||
// submit_solution Request/Response
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
const DeliverableFileSchema = z.object({
|
||||
path: z.string().min(1).max(260, "路徑長度超過限制"),
|
||||
content: z
|
||||
.string()
|
||||
.min(10, "檔案內容不得為空")
|
||||
.max(500_000, "單檔案內容超過 500KB 限制"),
|
||||
language: z.enum(["typescript", "javascript", "tsx", "jsx", "css", "html"]),
|
||||
});
|
||||
|
||||
export const SubmitSolutionRequestSchema = z.object({
|
||||
task_id: UUIDSchema,
|
||||
/** 接案時取得的冪等憑證,防止重複提交 */
|
||||
claim_token: z.string().uuid(),
|
||||
deliverables: z.object({
|
||||
files: z
|
||||
.array(DeliverableFileSchema)
|
||||
.min(1, "至少需要提交一個檔案")
|
||||
.max(20, "單次提交不得超過 20 個檔案"),
|
||||
notes: z.string().max(1000).optional(),
|
||||
}),
|
||||
deliverables: z.record(z.string(), z.string()),
|
||||
});
|
||||
|
||||
export const SubmitSolutionResponseSchema = z.object({
|
||||
|
||||
@@ -5,7 +5,7 @@ describe("Contracts Zod Validation", () => {
|
||||
it("should validate a correct ListOpenTasks payload", () => {
|
||||
const payload = {
|
||||
skills: ["React", "TypeScript"],
|
||||
max_difficulty: TaskDifficulty.INTERMEDIATE,
|
||||
difficulty: TaskDifficulty.COMPONENT,
|
||||
limit: 10,
|
||||
};
|
||||
const result = ListOpenTasksRequestSchema.safeParse(payload);
|
||||
|
||||
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@@ -35,6 +35,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: 19.2.4
|
||||
version: 19.2.4(react@19.2.4)
|
||||
zod:
|
||||
specifier: ^3.23.0
|
||||
version: 3.25.76
|
||||
devDependencies:
|
||||
'@tailwindcss/postcss':
|
||||
specifier: ^4
|
||||
@@ -3050,6 +3053,9 @@ packages:
|
||||
peerDependencies:
|
||||
zod: ^3.25.0 || ^4.0.0
|
||||
|
||||
zod@3.25.76:
|
||||
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
||||
|
||||
zod@4.4.3:
|
||||
resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
|
||||
|
||||
@@ -4593,8 +4599,8 @@ snapshots:
|
||||
'@babel/parser': 7.29.7
|
||||
eslint: 9.39.4(jiti@2.7.0)
|
||||
hermes-parser: 0.25.1
|
||||
zod: 4.4.3
|
||||
zod-validation-error: 4.0.2(zod@4.4.3)
|
||||
zod: 3.25.76
|
||||
zod-validation-error: 4.0.2(zod@3.25.76)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -6133,8 +6139,10 @@ snapshots:
|
||||
dependencies:
|
||||
zod: 4.4.3
|
||||
|
||||
zod-validation-error@4.0.2(zod@4.4.3):
|
||||
zod-validation-error@4.0.2(zod@3.25.76):
|
||||
dependencies:
|
||||
zod: 4.4.3
|
||||
zod: 3.25.76
|
||||
|
||||
zod@3.25.76: {}
|
||||
|
||||
zod@4.4.3: {}
|
||||
|
||||
128
test_idempotency.ts
Normal file
128
test_idempotency.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { PrismaClient } from "./apps/web/prisma/generated/client/index.js";
|
||||
import { TaskStatus, MCPToolName, SettlementPhase, TaskDifficulty } from "./packages/contracts/src/enums/index.js";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log("=== Idempotency and Payment Flow Test ===");
|
||||
|
||||
// 1. Create a mock task
|
||||
const taskId = uuidv4();
|
||||
await prisma.task.create({
|
||||
data: {
|
||||
id: taskId,
|
||||
title: "Test Task",
|
||||
difficulty: TaskDifficulty.COMPONENT,
|
||||
status: TaskStatus.OPEN,
|
||||
requirements: "Test requirements",
|
||||
base_reward_cents: 500,
|
||||
}
|
||||
});
|
||||
console.log(`[+] Created Task ${taskId}`);
|
||||
|
||||
// 2. Simulate Claim Task (Auth Hold)
|
||||
console.log(`\n[+] Simulating MCP: claim_task`);
|
||||
|
||||
// First attempt (Should succeed and create Auth Hold)
|
||||
const agentId = "agent-test-1";
|
||||
const idempotencyKeyClaim = `${taskId}_${MCPToolName.CLAIM_TASK}`;
|
||||
|
||||
const claim1 = await prisma.$transaction(async (tx) => {
|
||||
// authHold logic
|
||||
const existing = await tx.ledgerEntry.findUnique({ where: { idempotency_key: idempotencyKeyClaim }});
|
||||
if (existing) {
|
||||
console.log("Idempotent hit for claim_task!");
|
||||
return existing;
|
||||
}
|
||||
|
||||
await tx.task.update({
|
||||
where: { id: taskId },
|
||||
data: { status: TaskStatus.EXECUTING, agent_id: agentId }
|
||||
});
|
||||
|
||||
return await tx.ledgerEntry.create({
|
||||
data: {
|
||||
task_id: taskId,
|
||||
phase: SettlementPhase.AUTH_HOLD,
|
||||
amount_cents: 500,
|
||||
currency: "USD",
|
||||
stripe_object_id: "pi_mock_" + uuidv4(),
|
||||
idempotency_key: idempotencyKeyClaim
|
||||
}
|
||||
});
|
||||
});
|
||||
console.log(`[1] First claim_task attempt created ledger entry: ${claim1.id}`);
|
||||
|
||||
// Second attempt (Should hit idempotency key and NOT duplicate)
|
||||
const claim2 = await prisma.$transaction(async (tx) => {
|
||||
const existing = await tx.ledgerEntry.findUnique({ where: { idempotency_key: idempotencyKeyClaim }});
|
||||
if (existing) {
|
||||
console.log("Idempotent hit for claim_task!");
|
||||
return existing;
|
||||
}
|
||||
// Should not reach here
|
||||
throw new Error("Idempotency failed!");
|
||||
});
|
||||
console.log(`[2] Second claim_task attempt returned same entry: ${claim2.id}`);
|
||||
|
||||
// 3. Simulate Submit Solution (Capture)
|
||||
console.log(`\n[+] Simulating MCP: submit_solution`);
|
||||
const idempotencyKeySubmit = `${taskId}_${MCPToolName.SUBMIT_SOLUTION}`;
|
||||
|
||||
const submit1 = await prisma.$transaction(async (tx) => {
|
||||
const existing = await tx.ledgerEntry.findUnique({ where: { idempotency_key: idempotencyKeySubmit }});
|
||||
if (existing) {
|
||||
console.log("Idempotent hit for submit_solution!");
|
||||
return existing;
|
||||
}
|
||||
|
||||
await tx.task.update({
|
||||
where: { id: taskId },
|
||||
data: { status: TaskStatus.COMPLETED }
|
||||
});
|
||||
|
||||
return await tx.ledgerEntry.create({
|
||||
data: {
|
||||
task_id: taskId,
|
||||
phase: SettlementPhase.CAPTURE,
|
||||
amount_cents: 500,
|
||||
currency: "USD",
|
||||
stripe_object_id: claim1.stripe_object_id,
|
||||
idempotency_key: idempotencyKeySubmit
|
||||
}
|
||||
});
|
||||
});
|
||||
console.log(`[1] First submit_solution attempt created ledger entry: ${submit1.id}`);
|
||||
|
||||
const submit2 = await prisma.$transaction(async (tx) => {
|
||||
const existing = await tx.ledgerEntry.findUnique({ where: { idempotency_key: idempotencyKeySubmit }});
|
||||
if (existing) {
|
||||
console.log("Idempotent hit for submit_solution!");
|
||||
return existing;
|
||||
}
|
||||
throw new Error("Idempotency failed!");
|
||||
});
|
||||
console.log(`[2] Second submit_solution attempt returned same entry: ${submit2.id}`);
|
||||
|
||||
// Verify DB state
|
||||
const finalTask = await prisma.task.findUnique({ where: { id: taskId }, include: { ledger_entries: true } });
|
||||
console.log(`\n=== Final Verification ===`);
|
||||
console.log(`Task Status: ${finalTask?.status}`);
|
||||
console.log(`Ledger Entries: ${finalTask?.ledger_entries.length}`);
|
||||
finalTask?.ledger_entries.forEach(entry => {
|
||||
console.log(` - Phase: ${entry.phase}, Amount: ${entry.amount_cents}, Key: ${entry.idempotency_key}`);
|
||||
});
|
||||
|
||||
if (finalTask?.status === TaskStatus.COMPLETED && finalTask.ledger_entries.length === 2) {
|
||||
console.log("\n✅ End-to-End Simulation Passed! Idempotency and State transitions are correct.");
|
||||
} else {
|
||||
console.log("\n❌ Simulation Failed.");
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.catch(console.error)
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
Reference in New Issue
Block a user