Assessor_2.0/server/postgres/migrations/003_ingestion_artifacts_job...

63 lines
3.9 KiB
SQL

CREATE TABLE assessor_import_artifacts (
id text PRIMARY KEY CHECK (btrim(id) <> ''),
project_id text NOT NULL REFERENCES assessor_projects(id) ON DELETE CASCADE,
assessment_id text REFERENCES assessor_assessments(id) ON DELETE CASCADE,
scan_run_id text REFERENCES assessor_scan_runs(id) ON DELETE CASCADE,
import_file_id text REFERENCES assessor_import_files(id) ON DELETE CASCADE,
parent_artifact_id text REFERENCES assessor_import_artifacts(id) ON DELETE CASCADE,
artifact_kind text NOT NULL CHECK (artifact_kind IN ('source-file', 'container-entry', 'baseline-package', 'derived-document')),
relative_path text NOT NULL CHECK (btrim(relative_path) <> '' AND relative_path !~ '(^[\\/]|(^|[\\/])\.\.([\\/]|$))'),
sha256 text NOT NULL CHECK (sha256 ~ '^[a-f0-9]{64}$'),
size_bytes bigint NOT NULL CHECK (size_bytes >= 0),
sensitivity text NOT NULL CHECK (sensitivity IN ('internal', 'confidential', 'secret')),
parse_status text NOT NULL CHECK (parse_status IN ('pending', 'parsed', 'partial', 'failed', 'unsupported')),
storage_kind text NOT NULL CHECK (storage_kind IN ('not-retained', 'backend-controlled')),
storage_key text,
created_at timestamptz NOT NULL,
data jsonb NOT NULL CHECK (jsonb_typeof(data) = 'object'),
CHECK ((storage_kind = 'backend-controlled' AND btrim(storage_key) <> '') OR (storage_kind = 'not-retained' AND storage_key IS NULL))
);
CREATE INDEX assessor_import_artifacts_project_idx ON assessor_import_artifacts(project_id, created_at, id);
CREATE INDEX assessor_import_artifacts_parent_idx ON assessor_import_artifacts(parent_artifact_id) WHERE parent_artifact_id IS NOT NULL;
CREATE INDEX assessor_import_artifacts_file_idx ON assessor_import_artifacts(import_file_id) WHERE import_file_id IS NOT NULL;
CREATE TABLE assessor_import_processing_runs (
id text PRIMARY KEY CHECK (btrim(id) <> ''),
project_id text NOT NULL REFERENCES assessor_projects(id) ON DELETE CASCADE,
artifact_id text NOT NULL REFERENCES assessor_import_artifacts(id) ON DELETE CASCADE,
supersedes_run_id text REFERENCES assessor_import_processing_runs(id) ON DELETE RESTRICT,
run_status text NOT NULL CHECK (run_status IN ('complete', 'partial', 'failed')),
started_at timestamptz NOT NULL,
completed_at timestamptz NOT NULL CHECK (completed_at >= started_at),
data jsonb NOT NULL CHECK (jsonb_typeof(data) = 'object'),
CHECK (supersedes_run_id IS NULL OR supersedes_run_id <> id)
);
CREATE INDEX assessor_processing_runs_artifact_idx ON assessor_import_processing_runs(artifact_id, started_at, id);
CREATE TABLE assessor_ingestion_jobs (
id text PRIMARY KEY CHECK (btrim(id) <> ''),
project_id text NOT NULL REFERENCES assessor_projects(id) ON DELETE CASCADE,
artifact_id text REFERENCES assessor_import_artifacts(id) ON DELETE CASCADE,
job_kind text NOT NULL CHECK (job_kind IN ('parse-artifact', 'extract-source', 'map-findings', 'reprocess-artifact')),
idempotency_key text NOT NULL CHECK (btrim(idempotency_key) <> ''),
job_status text NOT NULL CHECK (job_status IN ('queued', 'running', 'succeeded', 'dead-letter')),
attempts integer NOT NULL CHECK (attempts >= 0),
max_attempts integer NOT NULL CHECK (max_attempts >= 1 AND attempts <= max_attempts),
available_at timestamptz NOT NULL,
lease_owner text,
lease_expires_at timestamptz,
created_at timestamptz NOT NULL,
updated_at timestamptz NOT NULL,
data jsonb NOT NULL CHECK (jsonb_typeof(data) = 'object'),
UNIQUE (project_id, idempotency_key),
CHECK ((job_status = 'running' AND btrim(lease_owner) <> '' AND lease_expires_at IS NOT NULL)
OR (job_status <> 'running' AND lease_owner IS NULL AND lease_expires_at IS NULL))
);
CREATE INDEX assessor_ingestion_jobs_claim_idx
ON assessor_ingestion_jobs(job_status, available_at, lease_expires_at, created_at)
WHERE job_status IN ('queued', 'running');
CREATE INDEX assessor_ingestion_jobs_project_idx ON assessor_ingestion_jobs(project_id, created_at, id);