diff --git a/internal/infra/errs/errs.go b/internal/infra/errs/errs.go index 20dbe55..b4dc80e 100644 --- a/internal/infra/errs/errs.go +++ b/internal/infra/errs/errs.go @@ -66,6 +66,13 @@ const ( // Storage 域 CodeStoragePutFailed Code = "storage.put_failed" CodeStorageGetFailed Code = "storage.get_failed" + + // Jobs 域 + CodeJobNotFound Code = "job.not_found" + CodeJobInvalidType Code = "job.invalid_type" + CodeJobPayloadInvalid Code = "job.payload_invalid" + CodeJobPayloadTooLarge Code = "job.payload_too_large" + CodeWebhookNotConfigured Code = "webhook.not_configured" ) // AppError is the application's structured error type. It carries a stable @@ -171,6 +178,12 @@ func HTTPStatus(code Code) int { return http.StatusBadGateway case CodeLLMContextTooLong: return http.StatusBadRequest + case CodeJobNotFound: + return http.StatusNotFound + case CodeJobInvalidType, CodeJobPayloadInvalid, CodeJobPayloadTooLarge: + return http.StatusBadRequest + case CodeWebhookNotConfigured: + return http.StatusServiceUnavailable default: return http.StatusInternalServerError } diff --git a/internal/infra/errs/errs_test.go b/internal/infra/errs/errs_test.go index 6468698..22bd98d 100644 --- a/internal/infra/errs/errs_test.go +++ b/internal/infra/errs/errs_test.go @@ -70,6 +70,12 @@ func TestHTTPStatus(t *testing.T) { // Storage codes CodeStoragePutFailed: http.StatusBadGateway, CodeStorageGetFailed: http.StatusBadGateway, + // Jobs codes + CodeJobNotFound: http.StatusNotFound, + CodeJobInvalidType: http.StatusBadRequest, + CodeJobPayloadInvalid: http.StatusBadRequest, + CodeJobPayloadTooLarge: http.StatusBadRequest, + CodeWebhookNotConfigured: http.StatusServiceUnavailable, } for code, want := range cases { require.Equal(t, want, HTTPStatus(code), "code=%s", code) diff --git a/migrations/0005_jobs.down.sql b/migrations/0005_jobs.down.sql new file mode 100644 index 0000000..31d6438 --- /dev/null +++ b/migrations/0005_jobs.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE workspace_worktrees DROP COLUMN IF EXISTS prune_warning_at; +DROP TABLE IF EXISTS jobs; diff --git a/migrations/0005_jobs.up.sql b/migrations/0005_jobs.up.sql new file mode 100644 index 0000000..e5f9f7b --- /dev/null +++ b/migrations/0005_jobs.up.sql @@ -0,0 +1,24 @@ +-- ============ jobs(DB-backed 事件型任务) ============ +CREATE TABLE jobs ( + id UUID PRIMARY KEY, + type TEXT NOT NULL, + payload JSONB NOT NULL DEFAULT '{}'::jsonb, + status TEXT NOT NULL DEFAULT 'pending', + scheduled_at TIMESTAMPTZ NOT NULL DEFAULT now(), + attempts INT NOT NULL DEFAULT 0, + max_attempts INT NOT NULL DEFAULT 5, + leased_at TIMESTAMPTZ, + leased_by TEXT, + last_error TEXT, + completed_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), + CONSTRAINT jobs_status_check CHECK (status IN ('pending','running','completed','dead')) +); +CREATE INDEX idx_jobs_pickup ON jobs(scheduled_at, status) WHERE status = 'pending'; +CREATE INDEX idx_jobs_lease ON jobs(leased_at) WHERE status = 'running'; +CREATE INDEX idx_jobs_purge ON jobs(completed_at) WHERE status IN ('completed', 'dead'); + +-- ============ ALTER workspace_worktrees: add prune_warning_at ============ +ALTER TABLE workspace_worktrees + ADD COLUMN prune_warning_at TIMESTAMPTZ;