Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
CREATE TYPE "SastArtifactIngestionStatus" AS ENUM (
'RECEIVING',
'PENDING_VALIDATION',
'ACCEPTED',
'REJECTED',
'QUARANTINED'
);

-- The v1 constraint did not permit a scanner run to exist while its write-only
-- ingress was open. It is replaced online by the post-migration v2 constraint.
ALTER TABLE "ScannerRun"
DROP CONSTRAINT IF EXISTS "ScannerRun_runtime_metadata_check";

CREATE TABLE "SastArtifactIngestion" (
"id" TEXT NOT NULL,
"tenantId" TEXT NOT NULL,
"repositoryBindingId" TEXT NOT NULL,
"scanRequestId" TEXT NOT NULL,
"attemptId" TEXT NOT NULL,
"scannerRunId" TEXT NOT NULL,
"workloadIdentityRef" TEXT NOT NULL,
"idempotencyKey" TEXT NOT NULL,
"envelopeDigest" TEXT NOT NULL,
"declaredContentDigest" TEXT NOT NULL,
"observedContentDigest" TEXT,
"declaredByteSize" INTEGER NOT NULL,
"observedByteSize" INTEGER,
"objectKey" TEXT,
"identityValidated" BOOLEAN NOT NULL DEFAULT false,
"status" "SastArtifactIngestionStatus" NOT NULL DEFAULT 'RECEIVING',
"validationMetadata" JSONB,
"rejectionReason" TEXT,
"receivedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "SastArtifactIngestion_pkey" PRIMARY KEY ("id"),
CONSTRAINT "SastArtifactIngestion_identity_check"
CHECK (
"identityValidated" = true
AND char_length("workloadIdentityRef") BETWEEN 1 AND 512
),
CONSTRAINT "SastArtifactIngestion_digest_check"
CHECK (
"envelopeDigest" ~ '^sha256:[a-f0-9]{64}$'
AND "declaredContentDigest" ~ '^sha256:[a-f0-9]{64}$'
AND (
"observedContentDigest" IS NULL
OR "observedContentDigest" ~ '^sha256:[a-f0-9]{64}$'
)
),
CONSTRAINT "SastArtifactIngestion_size_check"
CHECK (
"declaredByteSize" BETWEEN 1 AND 268435456
AND (
"observedByteSize" IS NULL
OR "observedByteSize" BETWEEN 0 AND 268435457
)
),
CONSTRAINT "SastArtifactIngestion_key_check"
CHECK (
char_length("idempotencyKey") BETWEEN 1 AND 1024
AND (
"objectKey" IS NULL
OR char_length("objectKey") BETWEEN 1 AND 2048
)
),
CONSTRAINT "SastArtifactIngestion_lifecycle_check"
CHECK (
COALESCE(
(
"status" = 'RECEIVING'
AND "objectKey" IS NULL
AND "observedContentDigest" IS NULL
AND "observedByteSize" IS NULL
AND "rejectionReason" IS NULL
AND "receivedAt" IS NULL
)
OR (
"status" IN ('PENDING_VALIDATION', 'ACCEPTED')
AND "objectKey" IS NOT NULL
AND "observedContentDigest" IS NOT NULL
AND "observedByteSize" = "declaredByteSize"
AND "rejectionReason" IS NULL
AND "receivedAt" IS NOT NULL
)
OR (
"status" = 'REJECTED'
AND "objectKey" IS NULL
AND char_length("rejectionReason") BETWEEN 1 AND 255
AND "receivedAt" IS NOT NULL
)
OR (
"status" = 'QUARANTINED'
AND "objectKey" IS NOT NULL
AND "observedContentDigest" IS NOT NULL
AND "observedByteSize" IS NOT NULL
AND char_length("rejectionReason") BETWEEN 1 AND 255
AND "receivedAt" IS NOT NULL
),
false
)
)
);

CREATE UNIQUE INDEX "SastArtifactIngestion_scannerRunId_key"
ON "SastArtifactIngestion"("scannerRunId");
CREATE UNIQUE INDEX "SastArtifactIngestion_objectKey_key"
ON "SastArtifactIngestion"("objectKey");
CREATE UNIQUE INDEX "SastArtifactIngestion_idempotency_scope_key"
ON "SastArtifactIngestion"("tenantId", "scanRequestId", "idempotencyKey");
CREATE UNIQUE INDEX "SastArtifactIngestion_scanner_scope_key"
ON "SastArtifactIngestion"("scannerRunId", "attemptId", "tenantId", "repositoryBindingId", "scanRequestId");
CREATE INDEX "SastArtifactIngestion_tenantId_scanRequestId_status_idx"
ON "SastArtifactIngestion"("tenantId", "scanRequestId", "status");
CREATE INDEX "SastArtifactIngestion_attemptId_status_idx"
ON "SastArtifactIngestion"("attemptId", "status");
CREATE INDEX "SastArtifactIngestion_createdAt_idx"
ON "SastArtifactIngestion"("createdAt");

ALTER TABLE "SastArtifactIngestion"
ADD CONSTRAINT "SastArtifactIngestion_tenantId_fkey"
FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id")
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "SastArtifactIngestion"
ADD CONSTRAINT "SastArtifactIngestion_repository_scope_fkey"
FOREIGN KEY ("repositoryBindingId", "tenantId")
REFERENCES "RepositoryBinding"("id", "tenantId")
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "SastArtifactIngestion"
ADD CONSTRAINT "SastArtifactIngestion_scan_scope_fkey"
FOREIGN KEY ("scanRequestId", "tenantId", "repositoryBindingId")
REFERENCES "ScanRequest"("id", "tenantId", "repositoryBindingId")
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "SastArtifactIngestion"
ADD CONSTRAINT "SastArtifactIngestion_attempt_scope_fkey"
FOREIGN KEY ("attemptId", "tenantId", "repositoryBindingId", "scanRequestId")
REFERENCES "SastScanAttempt"("id", "tenantId", "repositoryBindingId", "scanRequestId")
ON DELETE CASCADE ON UPDATE CASCADE;

-- ScannerRun scope uniqueness and the corresponding ingestion foreign key are
-- installed by the mandatory online schema step after this transaction.
132 changes: 91 additions & 41 deletions apps/api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ enum ScannerRunStatus {
SKIPPED
}

enum SastArtifactIngestionStatus {
RECEIVING
PENDING_VALIDATION
ACCEPTED
REJECTED
QUARANTINED
}

enum NormalizedFindingStatus {
OPEN
ACCEPTED
Expand Down Expand Up @@ -295,20 +303,21 @@ model Tenant {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

integrations ScmIntegration[]
repositoryBindings RepositoryBinding[]
scanRequests ScanRequest[]
scannerRuns ScannerRun[]
normalizedFindings NormalizedFinding[]
evidencePacks EvidencePack[]
policyDecisions PolicyDecision[]
aiAdvisoryMetadata AiAdvisoryMetadata[]
waivers Waiver[]
suppressions Suppression[]
auditEvents AuditEvent[]
sastCredentialLeases SastRepositoryCredentialLease[]
sastScanAttempts SastScanAttempt[]
users User[]
integrations ScmIntegration[]
repositoryBindings RepositoryBinding[]
scanRequests ScanRequest[]
scannerRuns ScannerRun[]
normalizedFindings NormalizedFinding[]
evidencePacks EvidencePack[]
policyDecisions PolicyDecision[]
aiAdvisoryMetadata AiAdvisoryMetadata[]
waivers Waiver[]
suppressions Suppression[]
auditEvents AuditEvent[]
sastCredentialLeases SastRepositoryCredentialLease[]
sastScanAttempts SastScanAttempt[]
sastArtifactIngestions SastArtifactIngestion[]
users User[]
}

model ScmIntegration {
Expand Down Expand Up @@ -344,11 +353,12 @@ model RepositoryBinding {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
integration ScmIntegration @relation(fields: [scmIntegrationId], references: [id], onDelete: Cascade)
scanRequests ScanRequest[]
sastCredentialLeases SastRepositoryCredentialLease[]
sastScanAttempts SastScanAttempt[]
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
integration ScmIntegration @relation(fields: [scmIntegrationId], references: [id], onDelete: Cascade)
scanRequests ScanRequest[]
sastCredentialLeases SastRepositoryCredentialLease[]
sastScanAttempts SastScanAttempt[]
sastArtifactIngestions SastArtifactIngestion[]

@@unique([id, tenantId])
@@unique([tenantId, scmIntegrationId, providerRepoId])
Expand All @@ -375,18 +385,19 @@ model ScanRequest {
completedAt DateTime?
updatedAt DateTime @updatedAt

tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
repositoryBinding RepositoryBinding @relation(fields: [repositoryBindingId], references: [id], onDelete: Cascade)
scannerRuns ScannerRun[]
findings NormalizedFinding[]
evidencePacks EvidencePack[]
policyDecisions PolicyDecision[]
aiAdvisoryMetadata AiAdvisoryMetadata[]
suppressions Suppression[]
auditEvents AuditEvent[]
sastQueueReservation SastQueueReservation?
sastCredentialLeases SastRepositoryCredentialLease[]
sastScanAttempts SastScanAttempt[]
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
repositoryBinding RepositoryBinding @relation(fields: [repositoryBindingId], references: [id], onDelete: Cascade)
scannerRuns ScannerRun[]
findings NormalizedFinding[]
evidencePacks EvidencePack[]
policyDecisions PolicyDecision[]
aiAdvisoryMetadata AiAdvisoryMetadata[]
suppressions Suppression[]
auditEvents AuditEvent[]
sastQueueReservation SastQueueReservation?
sastCredentialLeases SastRepositoryCredentialLease[]
sastScanAttempts SastScanAttempt[]
sastArtifactIngestions SastArtifactIngestion[]

@@unique([id, tenantId, repositoryBindingId])
@@index([tenantId])
Expand Down Expand Up @@ -459,12 +470,13 @@ model SastScanAttempt {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
repositoryBinding RepositoryBinding @relation(fields: [repositoryBindingId, tenantId], references: [id, tenantId], onDelete: Cascade, map: "SastScanAttempt_repository_scope_fkey")
scanRequest ScanRequest @relation(fields: [scanRequestId, tenantId, repositoryBindingId], references: [id, tenantId, repositoryBindingId], onDelete: Cascade, map: "SastScanAttempt_scan_scope_fkey")
scannerRuns ScannerRun[]
auditEvents AuditEvent[] @relation("SastScanAttemptAuditEvents")
finalAuditEvent AuditEvent? @relation("SastScanAttemptFinalAuditEvent", fields: [finalAuditEventId, id, tenantId], references: [id, attemptId, tenantId], onDelete: NoAction, onUpdate: NoAction, map: "SastScanAttempt_finalAuditEventId_fkey")
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
repositoryBinding RepositoryBinding @relation(fields: [repositoryBindingId, tenantId], references: [id, tenantId], onDelete: Cascade, map: "SastScanAttempt_repository_scope_fkey")
scanRequest ScanRequest @relation(fields: [scanRequestId, tenantId, repositoryBindingId], references: [id, tenantId, repositoryBindingId], onDelete: Cascade, map: "SastScanAttempt_scan_scope_fkey")
scannerRuns ScannerRun[]
artifactIngestions SastArtifactIngestion[]
auditEvents AuditEvent[] @relation("SastScanAttemptAuditEvents")
finalAuditEvent AuditEvent? @relation("SastScanAttemptFinalAuditEvent", fields: [finalAuditEventId, id, tenantId], references: [id, attemptId, tenantId], onDelete: NoAction, onUpdate: NoAction, map: "SastScanAttempt_finalAuditEventId_fkey")

@@unique([scanRequestId, attemptNumber])
@@unique([id, tenantId], map: "SastScanAttempt_id_tenantId_key")
Expand Down Expand Up @@ -592,19 +604,57 @@ model ScannerRun {
completedAt DateTime?
updatedAt DateTime @updatedAt

tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
scanRequest ScanRequest @relation(fields: [scanRequestId], references: [id], onDelete: Cascade)
attempt SastScanAttempt? @relation(fields: [attemptId, tenantId, repositoryBindingId, scanRequestId], references: [id, tenantId, repositoryBindingId, scanRequestId], onDelete: Cascade, map: "ScannerRun_attempt_scope_fkey")
findings NormalizedFinding[]
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
scanRequest ScanRequest @relation(fields: [scanRequestId], references: [id], onDelete: Cascade)
attempt SastScanAttempt? @relation(fields: [attemptId, tenantId, repositoryBindingId, scanRequestId], references: [id, tenantId, repositoryBindingId, scanRequestId], onDelete: Cascade, map: "ScannerRun_attempt_scope_fkey")
findings NormalizedFinding[]
artifactIngestion SastArtifactIngestion?

@@unique([attemptId, scanner])
@@unique([id, attemptId, tenantId, repositoryBindingId, scanRequestId], map: "ScannerRun_ingress_scope_key")
@@index([attemptId])
@@index([tenantId])
@@index([scanRequestId])
@@index([scanner])
@@index([status])
}

model SastArtifactIngestion {
id String @id
tenantId String
repositoryBindingId String
scanRequestId String
attemptId String
scannerRunId String @unique
workloadIdentityRef String
idempotencyKey String
envelopeDigest String
declaredContentDigest String
observedContentDigest String?
declaredByteSize Int
observedByteSize Int?
objectKey String? @unique
identityValidated Boolean @default(false)
status SastArtifactIngestionStatus @default(RECEIVING)
validationMetadata Json?
rejectionReason String?
receivedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
repositoryBinding RepositoryBinding @relation(fields: [repositoryBindingId, tenantId], references: [id, tenantId], onDelete: Cascade, map: "SastArtifactIngestion_repository_scope_fkey")
scanRequest ScanRequest @relation(fields: [scanRequestId, tenantId, repositoryBindingId], references: [id, tenantId, repositoryBindingId], onDelete: Cascade, map: "SastArtifactIngestion_scan_scope_fkey")
attempt SastScanAttempt @relation(fields: [attemptId, tenantId, repositoryBindingId, scanRequestId], references: [id, tenantId, repositoryBindingId, scanRequestId], onDelete: Cascade, map: "SastArtifactIngestion_attempt_scope_fkey")
scannerRun ScannerRun @relation(fields: [scannerRunId, attemptId, tenantId, repositoryBindingId, scanRequestId], references: [id, attemptId, tenantId, repositoryBindingId, scanRequestId], onDelete: Cascade, map: "SastArtifactIngestion_scanner_run_scope_fkey")

@@unique([tenantId, scanRequestId, idempotencyKey], map: "SastArtifactIngestion_idempotency_scope_key")
@@unique([scannerRunId, attemptId, tenantId, repositoryBindingId, scanRequestId], map: "SastArtifactIngestion_scanner_scope_key")
@@index([tenantId, scanRequestId, status])
@@index([attemptId, status])
@@index([createdAt])
}

model NormalizedFinding {
id String @id @default(uuid())
tenantId String
Expand Down
Loading
Loading