postgres

package
v0.1.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 15, 2026 License: Apache-2.0 Imports: 73 Imported by: 0

Documentation

Overview

Package postgres provides PostgreSQL-backed repositories (pgx/v5) and applies migrations via goose. Used when SYNAPSE_DB_DSN is set; otherwise the server falls back to in-memory persistence for dev.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AcquireSingletonLock

func AcquireSingletonLock(ctx context.Context, pool *pgxpool.Pool, role string) (*pgxpool.Conn, bool, error)

AcquireSingletonLock takes a session-level advisory lock (keyed by role) on a DEDICATED connection the caller holds for the whole process lifetime – releasing it drops the lock. A second instance OF THE SAME ROLE gets ok=false so it can fail fast (the repos still ignore tenant_id, so two same-role writers would race). Returns the held connection (retain it; Release at shutdown), whether the lock was obtained, and any error.

func CheckRLSRuntimeRole added in v0.1.8

func CheckRLSRuntimeRole(ctx context.Context, pool *pgxpool.Pool) error

CheckRLSRuntimeRole reports whether the role the pool connects as can actually be constrained by Row Level Security. RLS is bypassed entirely by SUPERUSER and BYPASSRLS roles regardless of FORCE ROW LEVEL SECURITY, so if the runtime role holds either attribute the whole tenant isolation guarantee is silently a no-op. It returns a non-nil error naming the offending attribute when the role would bypass RLS.

This is intended to gate multi-tenant enablement (fail-closed): the caller that turns on RLS-protected tables must refuse to serve if this returns an error. It is exported and separate so that path can enforce it at startup, while single-tenant deployments that connect as a superuser and use no RLS-protected table are not forced to change their role.

func Connect

func Connect(ctx context.Context, dsn string) (*pgxpool.Pool, error)

Connect opens a pgx pool with default sizing (back-compat wrapper).

func ConnectPool

func ConnectPool(ctx context.Context, dsn string, pc PoolConfig) (*pgxpool.Pool, error)

ConnectPool opens a sized pgx pool and verifies connectivity.

func GrantRuntimePrivileges added in v0.1.8

func GrantRuntimePrivileges(ctx context.Context, adminDSN, runtimeDSN string) error

GrantRuntimePrivileges grants the runtime role the DML privileges required by the application after migrations have completed under the separate owner credential.

func Migrate

func Migrate(ctx context.Context, dsn string) error

Migrate applies all pending goose migrations (idempotent; tracked in goose_db_version).

func ValidateMigrationRoleSeparation added in v0.1.8

func ValidateMigrationRoleSeparation(migrationDSN, runtimeDSN string) error

ValidateMigrationRoleSeparation ensures migrations cannot run as the runtime role.

func WithContextTenant added in v0.1.8

func WithContextTenant(ctx context.Context, pool *pgxpool.Pool, fn func(pgx.Tx) error) error

WithContextTenant runs fn under the immutable tenant previously bound to ctx.

func WithGlobalRead added in v0.1.8

func WithGlobalRead(ctx context.Context, pool *pgxpool.Pool, fn func(pgx.Tx) error) error

func WithGlobalWrite added in v0.1.8

func WithGlobalWrite(ctx context.Context, pool *pgxpool.Pool, fn func(pgx.Tx) error) error

func WithTenant added in v0.1.8

func WithTenant(ctx context.Context, pool *pgxpool.Pool, tenantID string, fn func(pgx.Tx) error) (err error)

WithTenant runs fn inside a transaction whose `app.current_tenant` session variable is set to tenantID for the life of that transaction only. Stores of Row-Level-Security-protected tables (see migration 0057 and its synapse_enable_tenant_rls procedure) MUST route reads and writes through this helper: the policy denies every row when the tenant resolves to NULL, so a query that runs outside WithTenant sees nothing rather than leaking across tenants. The setting is applied with set_config(..., is_local => true), which is transaction-scoped.

Fail-closed semantics: an empty tenantID resolves (via synapse_current_tenant's NULLIF) to NULL and therefore matches no row. Under RLS the empty string is DENY, not the default tenant, so callers of RLS-protected tables must pass a non-empty tenant id. This closes the placeholder-GUC reset hazard: app.current_tenant reverts to the empty string (not "unset") after a transaction, and mapping the empty string to NULL means a connection reused outside WithTenant still denies rather than exposing default-tenant rows.

Types

type AITriageReviewRepository added in v0.1.8

type AITriageReviewRepository struct {
	// contains filtered or unexported fields
}

func NewAITriageReviewRepository added in v0.1.8

func NewAITriageReviewRepository(pool *pgxpool.Pool) *AITriageReviewRepository

func (*AITriageReviewRepository) Get added in v0.1.8

func (*AITriageReviewRepository) List added in v0.1.8

func (*AITriageReviewRepository) SaveDecision added in v0.1.8

func (r *AITriageReviewRepository) SaveDecision(ctx context.Context, review aitriagereview.Review, expectedVersion int) error

func (*AITriageReviewRepository) SaveOwner added in v0.1.8

func (r *AITriageReviewRepository) SaveOwner(ctx context.Context, review aitriagereview.Review, expectedVersion int) error

func (*AITriageReviewRepository) UpsertPending added in v0.1.8

func (r *AITriageReviewRepository) UpsertPending(ctx context.Context, review aitriagereview.Review) error

type AUPStore

type AUPStore struct {
	// contains filtered or unexported fields
}

AUPStore persists Acceptable-Use-Policy acceptances to PostgreSQL.

func NewAUPStore

func NewAUPStore(pool *pgxpool.Pool) *AUPStore

NewAUPStore returns an AUP store backed by the given pool.

func (*AUPStore) Accepted

func (s *AUPStore) Accepted(ctx context.Context, version string) (bool, error)

Accepted reports whether the given policy version has been accepted.

func (*AUPStore) Save

func (s *AUPStore) Save(ctx context.Context, a aup.Acceptance) error

Save records an acceptance, idempotent per (actor, version) – this keeps per-actor history (the file dev sink keeps one record per version; both gate identically via Accepted's EXISTS-by-version). RBAC is enforced at the API edge. Should actor identifiers ever become attacker- influenced (e.g. a future external OIDC subject), key idempotency on a UNIQUE(actor, policy_version) constraint instead of a concatenated id.

type AdvisoryMaterializer added in v0.1.8

type AdvisoryMaterializer struct {
	// contains filtered or unexported fields
}

func NewAdvisoryMaterializer added in v0.1.8

func NewAdvisoryMaterializer(pool *pgxpool.Pool) *AdvisoryMaterializer

func (*AdvisoryMaterializer) AdvisoryRevisionAt added in v0.1.8

func (r *AdvisoryMaterializer) AdvisoryRevisionAt(ctx context.Context, advisoryID string, snapshotAt time.Time) (ports.AdvisoryRevisionRef, error)

func (*AdvisoryMaterializer) ByCPE added in v0.1.8

func (r *AdvisoryMaterializer) ByCPE(ctx context.Context, part, vendor, product string) ([]advisory.Advisory, error)

func (*AdvisoryMaterializer) ByPackage added in v0.1.8

func (r *AdvisoryMaterializer) ByPackage(ctx context.Context, ecosystem, packageName string) ([]advisory.Advisory, error)

func (*AdvisoryMaterializer) CountVulnerabilityAdvisoriesChangedSince added in v0.1.8

func (r *AdvisoryMaterializer) CountVulnerabilityAdvisoriesChangedSince(ctx context.Context, since time.Time) (int64, error)

func (*AdvisoryMaterializer) CurrentRevision added in v0.1.8

func (r *AdvisoryMaterializer) CurrentRevision(ctx context.Context, id string) (int64, error)

func (*AdvisoryMaterializer) CurrentSourceRecordIDs added in v0.1.8

func (r *AdvisoryMaterializer) CurrentSourceRecordIDs(ctx context.Context, sourceID string, yield func(string) error) error

func (*AdvisoryMaterializer) GetCanonical added in v0.1.8

func (r *AdvisoryMaterializer) GetCanonical(ctx context.Context, id string) (advisory.Canonical, error)

func (*AdvisoryMaterializer) GetCanonicalAtRevision added in v0.1.8

func (r *AdvisoryMaterializer) GetCanonicalAtRevision(ctx context.Context, id string, revision int64) (advisory.Canonical, error)

func (*AdvisoryMaterializer) ListAdvisoryRevisions added in v0.1.8

func (r *AdvisoryMaterializer) ListAdvisoryRevisions(ctx context.Context, after string, snapshotAt time.Time, limit int) (ports.AdvisoryRevisionPage, error)

func (*AdvisoryMaterializer) ListVulnerabilityAdvisories added in v0.1.8

func (r *AdvisoryMaterializer) ListVulnerabilityAdvisories(ctx context.Context, tenantID shared.ID, query vulnerabilityintel.AdvisoryQuery) (vulnerabilityintel.AdvisoryPage, error)

func (*AdvisoryMaterializer) ListVulnerabilityAdvisoryRevisions added in v0.1.8

func (*AdvisoryMaterializer) ListVulnerabilitySyncRunRevisions added in v0.1.8

func (r *AdvisoryMaterializer) ListVulnerabilitySyncRunRevisions(ctx context.Context, runIDs []shared.ID, limitPerRun int) (map[shared.ID]vulnerabilityintel.AdvisoryRevisionLinkPage, error)

func (*AdvisoryMaterializer) MarkAdvisoryEvaluated added in v0.1.8

func (r *AdvisoryMaterializer) MarkAdvisoryEvaluated(ctx context.Context, tenantID shared.ID, advisoryID string, revision int64, evaluatedAt time.Time) error

func (*AdvisoryMaterializer) Materialize added in v0.1.8

func (*AdvisoryMaterializer) OldestUnevaluatedAdvisory added in v0.1.8

func (r *AdvisoryMaterializer) OldestUnevaluatedAdvisory(ctx context.Context, tenantID shared.ID) (*vulnerabilityintel.EvaluationLag, error)

type AdvisoryRepository

type AdvisoryRepository struct {
	// contains filtered or unexported fields
}

AdvisoryRepository persists the OWNED normalized-advisory store to PostgreSQL. It is GLOBAL reference data (NOT tenant-scoped): the full advisory is a JSONB blob in `advisories`, with one `advisory_affects` row per affected (ecosystem, package) for the indexed ByPackage lookup.

func NewAdvisoryRepository

func NewAdvisoryRepository(pool *pgxpool.Pool) *AdvisoryRepository

NewAdvisoryRepository returns a repository backed by the given pool.

func (*AdvisoryRepository) ByCPE added in v0.1.8

func (r *AdvisoryRepository) ByCPE(ctx context.Context, part, vendor, product string) ([]advisory.Advisory, error)

func (*AdvisoryRepository) ByPackage

func (r *AdvisoryRepository) ByPackage(ctx context.Context, ecosystem, name string) ([]advisory.Advisory, error)

ByPackage returns the advisories that affect (ecosystem, name), decoded from their JSONB blobs. The caller runs advisory.Match to decide which actually hit the component's version. Deterministic id order.

func (*AdvisoryRepository) Upsert

Upsert inserts or replaces an advisory by id and rebuilds its (ecosystem, package) index rows, in one transaction. Idempotent – advisories are re-syncable reference data (a re-ingest REPLACES in place), not an append-only ledger. The affected (ecosystem, package) keys must be ingester-normalized per the ports.AdvisoryStore KEY CONTRACT. The full domain advisory round-trips through the JSONB `data` blob.

type AgentDecisionStore

type AgentDecisionStore struct {
	// contains filtered or unexported fields
}

AgentDecisionStore is the durable ports.DecisionStore on PostgreSQL (migration 0032). seq is a monotonic per-session counter (MAX+1). Idempotency is enforced by partial unique indexes – one row per (session_id, action_id) for steps and one stop per session – so a redelivered drive that re-records a decision is a no-op (ON CONFLICT DO NOTHING), never a forked log. Decisions are written by a single driver under the session run lock, so the MAX+1 read/insert pair is race-free.

func NewAgentDecisionStore

func NewAgentDecisionStore(pool *pgxpool.Pool) *AgentDecisionStore

NewAgentDecisionStore returns a Postgres-backed decision store.

func (*AgentDecisionStore) AppendDecision

func (s *AgentDecisionStore) AppendDecision(ctx context.Context, d agent.AgentDecision) error

func (*AgentDecisionStore) ListBySession

func (s *AgentDecisionStore) ListBySession(ctx context.Context, sessionID shared.ID) ([]agent.AgentDecision, error)

type AgentPlanStore

type AgentPlanStore struct {
	// contains filtered or unexported fields
}

AgentPlanStore is the durable ports.PlanStore on PostgreSQL (migration 0031). One plan per session (session_id UNIQUE → CreatePlan on a redelivery hits a unique violation → ErrConflict, preventing a forked second plan). SavePlan is a guarded UPDATE (… WHERE revision=$expected) that bumps the revision, so a node claim is an atomic compare-and-swap; a lost CAS (0 rows) returns ErrConflict.

func NewAgentPlanStore

func NewAgentPlanStore(pool *pgxpool.Pool) *AgentPlanStore

NewAgentPlanStore returns a Postgres-backed plan store.

func (*AgentPlanStore) CreatePlan

func (s *AgentPlanStore) CreatePlan(ctx context.Context, p agent.Plan) error

func (*AgentPlanStore) GetBySession

func (s *AgentPlanStore) GetBySession(ctx context.Context, sessionID shared.ID) (agent.Plan, bool, error)

func (*AgentPlanStore) SavePlan

func (s *AgentPlanStore) SavePlan(ctx context.Context, p agent.Plan) error

type AgentSessionStore

type AgentSessionStore struct {
	// contains filtered or unexported fields
}

AgentSessionStore is the durable ports.AgentSessionStore on PostgreSQL: agent_sessions + agent_messages (migration 0027). The (session_id, seq) primary key is the transcript fork-guard – a duplicate seq is a unique violation → ErrConflict.

func NewAgentSessionStore

func NewAgentSessionStore(pool *pgxpool.Pool) *AgentSessionStore

NewAgentSessionStore returns a Postgres-backed agent session store.

func (*AgentSessionStore) AppendMessage

func (s *AgentSessionStore) AppendMessage(ctx context.Context, sessionID shared.ID, seq int, m agent.Message) error

func (*AgentSessionStore) GetSession

func (s *AgentSessionStore) GetSession(ctx context.Context, id shared.ID) (agent.Session, error)

func (*AgentSessionStore) ListByEngagement

func (s *AgentSessionStore) ListByEngagement(ctx context.Context, engagementID shared.ID) ([]agent.Session, error)

func (*AgentSessionStore) ListResumable

func (s *AgentSessionStore) ListResumable(ctx context.Context, staleFor time.Duration, now time.Time, limit int) ([]agent.Session, error)

func (*AgentSessionStore) Messages

func (s *AgentSessionStore) Messages(ctx context.Context, sessionID shared.ID) ([]agent.Message, error)

func (*AgentSessionStore) SaveSession

func (s *AgentSessionStore) SaveSession(ctx context.Context, e agent.Session) error

type ApprovalStore

type ApprovalStore struct {
	// contains filtered or unexported fields
}

ApprovalStore is the durable ports.ApprovalStore on PostgreSQL: the HITL approval queue (migration 0028). Decide is a guarded UPDATE (… WHERE decision_state= 'pending') so the first decision wins; a second hits 0 rows and returns ErrConflict.

func NewApprovalStore

func NewApprovalStore(pool *pgxpool.Pool) *ApprovalStore

NewApprovalStore returns a Postgres-backed approval store.

func (*ApprovalStore) Consume added in v0.1.8

func (s *ApprovalStore) Consume(ctx context.Context, actionID shared.ID) error

func (*ApprovalStore) Decide

func (*ApprovalStore) EngagementsWithPending

func (s *ApprovalStore) EngagementsWithPending(ctx context.Context) ([]shared.ID, error)

func (*ApprovalStore) Enqueue

func (*ApprovalStore) Get

func (*ApprovalStore) Pending

func (s *ApprovalStore) Pending(ctx context.Context, engagementID shared.ID) ([]agent.ProposedAction, error)

type AssetRepository added in v0.1.8

type AssetRepository struct {
	// contains filtered or unexported fields
}

AssetRepository is the Postgres-backed fleet asset model. It is the first store to route every operation through WithTenant, so the Row Level Security policies on fleet_assets, fleet_asset_edges and fleet_business_services (migration 0058, using the 0057 procedure) enforce tenant isolation at the database. A query that bypassed WithTenant would resolve the tenant to NULL and see nothing.

func NewAssetRepository added in v0.1.8

func NewAssetRepository(pool *pgxpool.Pool) *AssetRepository

NewAssetRepository constructs the Postgres asset repository.

func (*AssetRepository) AssignEngagementBusinessAsset added in v0.1.8

func (r *AssetRepository) AssignEngagementBusinessAsset(ctx context.Context, tenantID, engagementID, assetID shared.ID) error

func (*AssetRepository) CreateBusinessAsset added in v0.1.8

func (r *AssetRepository) CreateBusinessAsset(ctx context.Context, a *asset.BusinessAsset) error

func (*AssetRepository) GetAssetByKey added in v0.1.8

func (r *AssetRepository) GetAssetByKey(ctx context.Context, tenantID shared.ID, kind asset.Kind, key string) (*asset.Asset, error)

GetAssetByKey returns the asset for (tenantID, kind, key) or shared.ErrNotFound.

func (*AssetRepository) GetBusinessAssetByID added in v0.1.8

func (r *AssetRepository) GetBusinessAssetByID(ctx context.Context, tenantID, id shared.ID) (*asset.BusinessAsset, error)

func (*AssetRepository) GetBusinessAssetByKey added in v0.1.8

func (r *AssetRepository) GetBusinessAssetByKey(ctx context.Context, tenantID shared.ID, key string) (*asset.BusinessAsset, error)

func (*AssetRepository) ListAssets added in v0.1.8

func (r *AssetRepository) ListAssets(ctx context.Context, tenantID shared.ID) ([]*asset.Asset, error)

ListAssets returns the tenant's assets ordered by (kind, key).

func (*AssetRepository) ListBusinessAssetProjects added in v0.1.8

func (r *AssetRepository) ListBusinessAssetProjects(ctx context.Context, tenantID, assetID shared.ID) ([]asset.ComponentMembership, error)

func (*AssetRepository) ListBusinessAssetTechnicalAssets added in v0.1.8

func (r *AssetRepository) ListBusinessAssetTechnicalAssets(ctx context.Context, tenantID, assetID shared.ID) ([]asset.ComponentMembership, error)

func (*AssetRepository) ListBusinessAssets added in v0.1.8

func (r *AssetRepository) ListBusinessAssets(ctx context.Context, tenantID shared.ID) ([]*asset.BusinessAsset, error)

func (*AssetRepository) ListEdges added in v0.1.8

func (r *AssetRepository) ListEdges(ctx context.Context, tenantID shared.ID) ([]*asset.Edge, error)

ListEdges returns the tenant's edges ordered by (from, to, kind, provenance).

func (*AssetRepository) ListEngagementsByBusinessAsset added in v0.1.8

func (r *AssetRepository) ListEngagementsByBusinessAsset(ctx context.Context, tenantID, assetID shared.ID) ([]*engagement.Engagement, error)

func (*AssetRepository) ReplaceBusinessAssetProjects added in v0.1.8

func (r *AssetRepository) ReplaceBusinessAssetProjects(ctx context.Context, tenantID, assetID shared.ID, links []asset.ComponentMembership) error

func (*AssetRepository) ReplaceBusinessAssetTechnicalAssets added in v0.1.8

func (r *AssetRepository) ReplaceBusinessAssetTechnicalAssets(ctx context.Context, tenantID, assetID shared.ID, links []asset.ComponentMembership) error

func (*AssetRepository) UpdateBusinessAsset added in v0.1.8

func (r *AssetRepository) UpdateBusinessAsset(ctx context.Context, a *asset.BusinessAsset, expectedVersion int) error

func (*AssetRepository) UpsertAsset added in v0.1.8

func (r *AssetRepository) UpsertAsset(ctx context.Context, a *asset.Asset) error

UpsertAsset inserts or updates by the (tenant_id, kind, key) natural key, preserving the id and created_at of an existing row so re-observation does not churn identity.

func (*AssetRepository) UpsertEdge added in v0.1.8

func (r *AssetRepository) UpsertEdge(ctx context.Context, e *asset.Edge) error

UpsertEdge inserts the edge idempotently by its full natural key.

type AttackPathStore added in v0.1.8

type AttackPathStore struct {
	// contains filtered or unexported fields
}

AttackPathStore persists derived asset-to-finding links behind PostgreSQL RLS.

func NewAttackPathStore added in v0.1.8

func NewAttackPathStore(pool *pgxpool.Pool) *AttackPathStore

func (*AttackPathStore) ListBindings added in v0.1.8

func (s *AttackPathStore) ListBindings(ctx context.Context, tenantID shared.ID) ([]attackpath.Binding, error)

func (*AttackPathStore) ReplaceBindings added in v0.1.8

func (s *AttackPathStore) ReplaceBindings(ctx context.Context, tenantID, engagementID, producer shared.ID, bindings []attackpath.Binding) error

type AuditLog

type AuditLog struct {
	// contains filtered or unexported fields
}

AuditLog is an append-only, attributable audit log on PostgreSQL.

func NewAuditLog

func NewAuditLog(pool *pgxpool.Pool) *AuditLog

NewAuditLog returns an audit log backed by the given pool.

func (*AuditLog) List

func (l *AuditLog) List(ctx context.Context, limit int) (out []ports.AuditEntry, err error)

List returns the calling tenant's most recent audit entries (newest first), capped at limit.

func (*AuditLog) Record

func (l *AuditLog) Record(ctx context.Context, e ports.AuditEntry) error

func (*AuditLog) RecordOnce added in v0.1.8

func (l *AuditLog) RecordOnce(ctx context.Context, e ports.AuditEntry) error

Record appends an immutable audit entry (INSERT only – never update or delete), chaining it to the previous row. A transaction-scoped advisory lock serializes the read-head/insert so concurrent writers cannot fork the chain. The fork-guard unique index (migration 0033) is defense-in-depth on top of the lock: if the lock is ever bypassed, a concurrent append yields a 23505 unique violation – Record then re-reads the advanced head and re-chains (bounded), parity with the evidence store, rather than surfacing an opaque error. On the normal locked path the conflict is unreachable and the loop runs once. RecordOnce implements ports.IdempotentAuditLogger. Entries with a deterministic metadata idempotency_key are recovered without adding a second chain link.

func (*AuditLog) Verify

func (l *AuditLog) Verify(ctx context.Context) (audit.Report, error)

Verify examines only the calling tenant's rows. The historical audit chain is global, so omitted links make a tenant-only result unavailable rather than falsely claiming a complete integrity check. Server maintenance code may use VerifyGlobal.

func (*AuditLog) VerifyGlobal added in v0.1.8

func (l *AuditLog) VerifyGlobal(ctx context.Context) (audit.Report, error)

VerifyGlobal re-derives the complete, globally linked audit chain. It deliberately bypasses tenant visibility and must only be called by server-side maintenance code, never by a tenant HTTP endpoint.

type CloudObservationStore added in v0.1.8

type CloudObservationStore struct {
	// contains filtered or unexported fields
}

CloudObservationStore atomically replaces producer ownership only after a complete target snapshot.

func NewCloudObservationStore added in v0.1.8

func NewCloudObservationStore(pool *pgxpool.Pool) *CloudObservationStore

func (*CloudObservationStore) ReconcileCloudObservations added in v0.1.8

func (s *CloudObservationStore) ReconcileCloudObservations(ctx context.Context, tenantID, engagementID shared.ID, producer string, evidenceID shared.ID, assets, findings []shared.ID, edges []string, complete bool) error

type CloudRunStore added in v0.1.8

type CloudRunStore struct {
	// contains filtered or unexported fields
}

CloudRunStore persists the tenant-scoped CSPM lifecycle under RLS.

func NewCloudRunStore added in v0.1.8

func NewCloudRunStore(pool *pgxpool.Pool) *CloudRunStore

func (*CloudRunStore) EnqueueCloudRun added in v0.1.8

func (s *CloudRunStore) EnqueueCloudRun(ctx context.Context, run cloudposture.Run, kind string, payload []byte) error

func (*CloudRunStore) GetCloudRun added in v0.1.8

func (s *CloudRunStore) GetCloudRun(ctx context.Context, tenantID, id shared.ID) (out cloudposture.Run, err error)

func (*CloudRunStore) SaveCloudRun added in v0.1.8

func (s *CloudRunStore) SaveCloudRun(ctx context.Context, run cloudposture.Run) error

type CommentRepository

type CommentRepository struct {
	// contains filtered or unexported fields
}

CommentRepository persists the per-finding comment thread to PostgreSQL.

func NewCommentRepository

func NewCommentRepository(pool *pgxpool.Pool) *CommentRepository

NewCommentRepository returns a repository backed by the given pool.

func (*CommentRepository) Add

Add inserts a comment (append-only; comments are not edited or deleted in app code).

func (*CommentRepository) ListByEngagementFinding

func (r *CommentRepository) ListByEngagementFinding(ctx context.Context, engagementID, findingID shared.ID) (out []finding.Comment, err error)

ListByEngagementFinding returns a finding's comments oldest-first, scoped to the engagement (no cross-engagement read).

type ComponentInventoryStore added in v0.1.8

type ComponentInventoryStore struct {
	// contains filtered or unexported fields
}

func NewComponentInventoryStore added in v0.1.8

func NewComponentInventoryStore(pool *pgxpool.Pool) *ComponentInventoryStore

func (*ComponentInventoryStore) ListCurrentComponents added in v0.1.8

func (s *ComponentInventoryStore) ListCurrentComponents(ctx context.Context, query sbom.ComponentQuery) (sbom.ComponentPage, error)

type DetectionRecordRepository added in v0.1.8

type DetectionRecordRepository struct {
	// contains filtered or unexported fields
}

DetectionRecordRepository persists the detection-ledger projection (migration 0074), tenant-scoped via WithTenant so RLS isolates one tenant's detections from another's. The evidence-chain link each row references is the permanent ledger; these rows are the queryable, retention-bounded projection.

func NewDetectionRecordRepository added in v0.1.8

func NewDetectionRecordRepository(pool *pgxpool.Pool) *DetectionRecordRepository

NewDetectionRecordRepository constructs the repository.

func (*DetectionRecordRepository) AppendDetection added in v0.1.8

func (r *DetectionRecordRepository) AppendDetection(ctx context.Context, rec detection.Record) error

AppendDetection stores one projection row, idempotent on (tenant_id, id): a row is immutable once written (provenance), so a re-delivery of the same detection does not overwrite it.

func (*DetectionRecordRepository) ExpireDetections added in v0.1.8

func (r *DetectionRecordRepository) ExpireDetections(ctx context.Context, engagementID shared.ID, cutoff time.Time) ([]shared.ID, error)

ExpireDetections deletes the engagement's records whose expiry has elapsed at cutoff and returns their ids for auditing. Rows with a NULL expires_at are never removed. Deleting a projection row leaves its evidence-chain link intact — the ledger is permanent, only the queryable projection ages out.

func (*DetectionRecordRepository) HasDetection added in v0.1.8

func (r *DetectionRecordRepository) HasDetection(ctx context.Context, id shared.ID) (bool, error)

HasDetection reports whether a record with this id already exists in the ctx tenant.

func (*DetectionRecordRepository) LastBatchSequence added in v0.1.8

func (r *DetectionRecordRepository) LastBatchSequence(ctx context.Context, agentID shared.ID) (uint64, error)

LastBatchSequence returns the highest batch sequence for an agent in the ctx tenant (0 = none yet).

func (*DetectionRecordRepository) ListDetections added in v0.1.8

func (r *DetectionRecordRepository) ListDetections(ctx context.Context, engagementID shared.ID) ([]detection.Record, error)

ListDetections returns the non-expired records for an engagement, oldest first, tenant-scoped by RLS.

type EmulationRunRepository added in v0.1.8

type EmulationRunRepository struct {
	// contains filtered or unexported fields
}

EmulationRunRepository persists emulation runs and coverage (migration 0073), tenant-scoped via WithTenant so RLS isolates one tenant's coverage from another's.

func NewEmulationRunRepository added in v0.1.8

func NewEmulationRunRepository(pool *pgxpool.Pool) *EmulationRunRepository

NewEmulationRunRepository constructs the repository.

func (*EmulationRunRepository) SaveRun added in v0.1.8

func (r *EmulationRunRepository) SaveRun(ctx context.Context, run demu.Run) error

SaveRun writes the run and all its coverage rows in one transaction, so a partially-written run can never present as complete coverage.

type EngagementRepository

type EngagementRepository struct {
	// contains filtered or unexported fields
}

EngagementRepository persists engagements and their scope to PostgreSQL.

func NewEngagementRepository

func NewEngagementRepository(pool *pgxpool.Pool) *EngagementRepository

NewEngagementRepository returns a repository backed by the given pool.

func (*EngagementRepository) Create

Create inserts the engagement and its scope targets in one transaction.

func (*EngagementRepository) Delete

func (r *EngagementRepository) Delete(ctx context.Context, id shared.ID) error

Delete removes an engagement; ON DELETE CASCADE drops its scope, findings, comments, evidence, recon runs, and retests. Idempotent (no error if absent). Used to roll back a partially-materialized import.

func (*EngagementRepository) GetByID

func (r *EngagementRepository) GetByID(ctx context.Context, id shared.ID) (out *engagement.Engagement, err error)

GetByID returns the engagement with its full scope WITHOUT a tenant predicate. It is the INTERNAL execution-gate read (see ports.EngagementRepository.GetByID): the scope/window/RoE guard and the worker/agent execution paths, which act on an engagement a queued/authorized run already belongs to. User-facing access uses GetByIDInTenant (below), which adds the tenant predicate that blocks cross-tenant reads.

func (*EngagementRepository) GetByIDInTenant

func (r *EngagementRepository) GetByIDInTenant(ctx context.Context, tenantID, id shared.ID) (out *engagement.Engagement, err error)

GetByIDInTenant loads an engagement scoped to tenantID. Empty input normalizes to the non-empty default tenant and never becomes a wildcard; cross-tenant access returns ErrNotFound.

func (*EngagementRepository) GetByProjectID

func (r *EngagementRepository) GetByProjectID(ctx context.Context, tenantID, projectID shared.ID) (out *engagement.Engagement, err error)

func (*EngagementRepository) List

func (r *EngagementRepository) List(ctx context.Context, tenantID shared.ID) (out []*engagement.Engagement, err error)

List returns the tenant's engagements, each with its scope loaded (consistent with the in-memory repository; the UI and the scope gate both rely on scope).

func (*EngagementRepository) ListProjectEngagements added in v0.1.8

func (r *EngagementRepository) ListProjectEngagements(ctx context.Context, tenantID shared.ID) (out []*engagement.Engagement, err error)

ListProjectEngagements returns the tenant's hidden Project analysis contexts for operational aggregation. Normal engagement lists remain unchanged and continue to hide these rows.

func (*EngagementRepository) ListPromotionReconciliationScopes added in v0.1.8

func (r *EngagementRepository) ListPromotionReconciliationScopes(ctx context.Context) ([]ports.PromotionReconciliationScope, error)

ListPromotionReconciliationScopes returns every non-project tenant and engagement pair for process-local recovery. Each engagement query remains RLS-scoped.

func (*EngagementRepository) ListReconciliationEngagements added in v0.1.8

func (r *EngagementRepository) ListReconciliationEngagements(ctx context.Context, tenantID, after shared.ID, snapshotAt time.Time, limit int) (ports.ReconciliationEngagementPage, error)

func (*EngagementRepository) ListTenantIDs added in v0.1.8

func (r *EngagementRepository) ListTenantIDs(ctx context.Context) ([]shared.ID, error)

func (*EngagementRepository) ProjectContexts

func (r *EngagementRepository) ProjectContexts(ctx context.Context, tenantID shared.ID, projectIDs []shared.ID) (out map[shared.ID]*engagement.Engagement, err error)

Update persists an existing engagement aggregate: the engagement row and its full scope target set, replaced atomically in one transaction (E1 scope CRUD + lifecycle). Returns shared.ErrNotFound if the engagement does not exist. Unlike Create's deterministic scope PKs, the replace path uses generated IDs.

func (*EngagementRepository) Update

type EvidenceStore

type EvidenceStore struct {
	// contains filtered or unexported fields
}

EvidenceStore persists the per-engagement hash-chained evidence ledger.

func NewEvidenceStore

func NewEvidenceStore(pool *pgxpool.Pool) *EvidenceStore

NewEvidenceStore returns a store backed by the given pool.

func (*EvidenceStore) Append

func (r *EvidenceStore) Append(ctx context.Context, items []evidence.Evidence) error

Append inserts sealed evidence items in order, in one transaction (append-only).

func (*EvidenceStore) Head

func (r *EvidenceStore) Head(ctx context.Context, engagementID shared.ID) (string, error)

Head returns the most recent sealed hash for an engagement ("" if the chain is empty). A real query error is returned (NOT swallowed as "empty") so the caller never forks the append-only chain on a transient DB failure.

func (*EvidenceStore) ListByEngagement

func (r *EvidenceStore) ListByEngagement(ctx context.Context, engagementID shared.ID) (out []evidence.Evidence, err error)

ListByEngagement returns the engagement's evidence in chain order (oldest first).

func (*EvidenceStore) LookupSealedForFinding added in v0.1.8

func (r *EvidenceStore) LookupSealedForFinding(ctx context.Context, engagementID, findingID shared.ID, kind string) (evidence.Evidence, bool, error)

LookupSealedForFinding returns the most recent sealed evidence link of the given kind for the specified finding, or (zero, false, nil) if none exists.

type ExploitationChainRepository added in v0.1.8

type ExploitationChainRepository struct {
	// contains filtered or unexported fields
}

ExploitationChainRepository persists attack chains and their steps (migration 0072). Every method runs through WithTenant so Row Level Security isolates one tenant's chains from another's.

func NewExploitationChainRepository added in v0.1.8

func NewExploitationChainRepository(pool *pgxpool.Pool) *ExploitationChainRepository

NewExploitationChainRepository constructs the repository.

func (*ExploitationChainRepository) SaveChain added in v0.1.8

func (r *ExploitationChainRepository) SaveChain(ctx context.Context, chain *dexploit.Chain) error

SaveChain upserts the chain and replaces its steps atomically. The whole chain — cursor, state, and every step's state and evidence — is written in one transaction, so a halt or a crash cannot leave the persisted chain disagreeing with itself about which steps still owe cleanup.

type FindingRepository

type FindingRepository struct {
	// contains filtered or unexported fields
}

FindingRepository persists findings to PostgreSQL, deduped per engagement.

func NewFindingRepository

func NewFindingRepository(pool *pgxpool.Pool) *FindingRepository

NewFindingRepository returns a repository backed by the given pool.

func (*FindingRepository) ClaimFindingProjection added in v0.1.8

func (r *FindingRepository) ClaimFindingProjection(ctx context.Context, tenantID, engagementID, judgmentID shared.ID, mode ports.FindingProjectionMode) error

ClaimFindingProjection atomically reserves the SAST or legacy runtime DAST projection mode for a judgment.

func (*FindingRepository) GetByEngagementAndID added in v0.1.8

func (r *FindingRepository) GetByEngagementAndID(ctx context.Context, engagementID, findingID shared.ID) (finding.Finding, error)

GetByEngagementAndID loads a single finding by engagement and finding ID. Returns shared.ErrNotFound if no such finding exists in the engagement.

func (*FindingRepository) ListByEngagement

func (r *FindingRepository) ListByEngagement(ctx context.Context, engagementID shared.ID) (out []finding.Finding, err error)

ListByEngagement returns the engagement's findings, highest risk first (CISA KEV, then EPSS x CVSS, then severity).

func (*FindingRepository) ListPublishableByEngagement

func (r *FindingRepository) ListPublishableByEngagement(ctx context.Context, engagementID shared.ID) ([]finding.Finding, error)

ListPublishableByEngagement returns only the engagement's findings that clear the evidence gate. It reuses ListByEngagement and the single domain rule finding.Publishable, so the publishability policy lives in exactly one place (the domain) rather than being re-encoded in SQL.

func (*FindingRepository) SetAssignee

func (r *FindingRepository) SetAssignee(ctx context.Context, engagementID, findingID shared.ID, assignee string, expectedVersion int) (out finding.Finding, err error)

SetAssignee sets the assignee with the same optimistic-concurrency guard.

func (*FindingRepository) SetEvidenceScore

func (r *FindingRepository) SetEvidenceScore(ctx context.Context, engagementID, findingID shared.ID, score, expectedVersion int) (out finding.Finding, err error)

SetEvidenceScore sets a finding's evidence score with the same optimistic-concurrency guard as UpdateStatus (the adversarial-verdict path): the row is updated only if version matches, then version is bumped. Note the Upsert ON CONFLICT set deliberately omits evidence_score, so this is the only path that moves it for an already-stored finding.

func (*FindingRepository) UpdateStatus

func (r *FindingRepository) UpdateStatus(ctx context.Context, engagementID, findingID shared.ID, status finding.Status, expectedVersion int) (out finding.Finding, err error)

UpdateStatus sets the triage status with optimistic concurrency: the row is updated only if version matches expectedVersion, then version is bumped. On a miss it distinguishes ErrConflict (exists, version moved) from ErrNotFound.

func (*FindingRepository) Upsert

func (r *FindingRepository) Upsert(ctx context.Context, findings []finding.Finding) error

Upsert inserts or updates findings, deduped on (engagement_id, dedup_key). On conflict it updates machine-owned data, preserves id, status (triage), assignee, and created_at, and bumps version only when machine-owned data changes.

type FleetAgentRepository added in v0.1.8

type FleetAgentRepository struct {
	// contains filtered or unexported fields
}

FleetAgentRepository is the Postgres-backed fleet agent identity store. Every method runs through WithTenant so Row Level Security (migration 0060 via the 0057 procedure) isolates by tenant. The auth lookup is tenant-scoped because the agent credential carries a non-secret tenant prefix.

func NewFleetAgentRepository added in v0.1.8

func NewFleetAgentRepository(pool *pgxpool.Pool) *FleetAgentRepository

NewFleetAgentRepository constructs the Postgres fleet agent repository.

func (*FleetAgentRepository) ConsumeEnrolToken added in v0.1.8

func (r *FleetAgentRepository) ConsumeEnrolToken(ctx context.Context, tenantID shared.ID, hash string, now time.Time) (*fleetagent.EnrolToken, error)

ConsumeEnrolToken atomically marks a usable token used and returns it; shared.ErrNotFound if no unused, unexpired token with that hash exists for the tenant.

func (*FleetAgentRepository) CreateAgent added in v0.1.8

func (r *FleetAgentRepository) CreateAgent(ctx context.Context, a *fleetagent.Agent) error

func (*FleetAgentRepository) CreateEnrolToken added in v0.1.8

func (r *FleetAgentRepository) CreateEnrolToken(ctx context.Context, t *fleetagent.EnrolToken) error

func (*FleetAgentRepository) Decommission added in v0.1.8

func (r *FleetAgentRepository) Decommission(ctx context.Context, tenantID, id shared.ID, now time.Time) error

Decommission marks the agent decommissioned on its own report (#412). It is an idempotent no-op on a REVOKED agent: an operator revocation is the stronger terminal state and a self-report must not overwrite it (the CASE leaves a revoked row untouched but still matches it, so only a truly missing agent yields ErrNotFound — matching the memory store's contract).

func (*FleetAgentRepository) GetAgent added in v0.1.8

func (r *FleetAgentRepository) GetAgent(ctx context.Context, tenantID, id shared.ID) (*fleetagent.Agent, error)

func (*FleetAgentRepository) Heartbeat added in v0.1.8

func (r *FleetAgentRepository) Heartbeat(ctx context.Context, tenantID, id shared.ID, platform, osVersion, agentVersion string, capabilities []string, now time.Time) error

func (*FleetAgentRepository) ListAgents added in v0.1.8

func (r *FleetAgentRepository) ListAgents(ctx context.Context, tenantID shared.ID) ([]*fleetagent.Agent, error)

func (*FleetAgentRepository) Revoke added in v0.1.8

func (r *FleetAgentRepository) Revoke(ctx context.Context, tenantID, id, by shared.ID, reason string, now time.Time) error

func (*FleetAgentRepository) SetFingerprint added in v0.1.8

func (r *FleetAgentRepository) SetFingerprint(ctx context.Context, tenantID, id shared.ID, fingerprint string, now time.Time) error

type FleetRolloutRepository added in v0.1.8

type FleetRolloutRepository struct {
	// contains filtered or unexported fields
}

FleetRolloutRepository persists operator update-rollout plans (migration 0065).

Durability is the point: a plan held only in memory stops offering updates the moment the control plane restarts, and says nothing about why. Every method runs through WithTenant, so Row Level Security isolates by tenant — one tenant must never be able to read, still less move, another tenant's fleet.

func NewFleetRolloutRepository added in v0.1.8

func NewFleetRolloutRepository(pool *pgxpool.Pool) *FleetRolloutRepository

NewFleetRolloutRepository constructs the Postgres rollout repository.

func (*FleetRolloutRepository) Get added in v0.1.8

func (r *FleetRolloutRepository) Get(ctx context.Context, tenantID shared.ID, channel string) (*fleetrollout.Plan, error)

Get returns the plan for a channel, or shared.ErrNotFound when none is configured.

"No row" is a legitimate resting state, not an error condition: it means no rollout is in progress and therefore no agent is offered anything.

func (*FleetRolloutRepository) Put added in v0.1.8

Put upserts the plan for (tenant, channel).

It is an upsert rather than an insert-or-update decision at the call site because a plan is a single current STATE, not a history: "the fleet is moving to 1.4.0, canary first" replaces whatever came before it. The audit log is what carries the history of who decided what.

created_at is preserved on conflict so the plan keeps the moment the rollout began, while updated_at moves with each operator action.

type ImportedFindingRepository added in v0.1.8

type ImportedFindingRepository struct {
	// contains filtered or unexported fields
}

ImportedFindingRepository persists third-party findings (migration 0064) to PostgreSQL.

Durability is not incidental here: the ingest writes an append-only audit entry claiming that N external results entered an engagement, and that claim is only true if the rows survive a restart. Every method runs through WithTenant so Row Level Security isolates by tenant.

func NewImportedFindingRepository added in v0.1.8

func NewImportedFindingRepository(pool *pgxpool.Pool) *ImportedFindingRepository

NewImportedFindingRepository constructs the Postgres imported-finding repository.

func (*ImportedFindingRepository) ExistsDigest added in v0.1.8

func (r *ImportedFindingRepository) ExistsDigest(ctx context.Context, tenantID, engagementID shared.ID, digest string) (bool, error)

ExistsDigest reports whether this tenant's engagement already ingested a document with this digest.

func (*ImportedFindingRepository) ListByEngagement added in v0.1.8

func (r *ImportedFindingRepository) ListByEngagement(ctx context.Context, tenantID, engagementID shared.ID) ([]importedfinding.ImportedFinding, error)

ListByEngagement returns the engagement's imported findings in a deterministic order.

func (*ImportedFindingRepository) Save added in v0.1.8

Save persists a batch ATOMICALLY: one transaction, so a failure anywhere leaves no partially ingested report and no recorded digest that would make a retry look like a clean deduplicated ingest.

Each row is inserted with ON CONFLICT DO NOTHING against the idempotency index, so re-posting the same document is a no-op rather than a duplicate, and the accepted/deduplicated split the caller reports is the database's own answer rather than a guess.

type ImportedSBOMStore

type ImportedSBOMStore struct {
	// contains filtered or unexported fields
}

ImportedSBOMStore persists the active imported SBOM per engagement.

func NewImportedSBOMStore

func NewImportedSBOMStore(pool *pgxpool.Pool) *ImportedSBOMStore

NewImportedSBOMStore returns a Postgres-backed imported-SBOM store.

func (*ImportedSBOMStore) LatestByEngagement

func (s *ImportedSBOMStore) LatestByEngagement(ctx context.Context, tenantID, engagementID shared.ID) (importedsbom.Record, error)

LatestByEngagement returns the active imported SBOM for a tenant-scoped engagement.

func (*ImportedSBOMStore) SaveActive

func (s *ImportedSBOMStore) SaveActive(ctx context.Context, record importedsbom.Record) error

SaveActive upserts the active imported SBOM for an engagement.

type JobQueue

type JobQueue struct {
	// contains filtered or unexported fields
}

JobQueue is the durable PostgreSQL queue with tenant-bound at-least-once delivery.

func NewJobQueue

func NewJobQueue(pool *pgxpool.Pool, ids ports.IDGenerator) *JobQueue

func (*JobQueue) Claim

func (q *JobQueue) Claim(ctx context.Context, visibility time.Duration, kinds ...string) (*ports.QueuedJob, error)

func (*JobQueue) Complete

func (q *JobQueue) Complete(ctx context.Context, id string) error

func (*JobQueue) Deadletter

func (q *JobQueue) Deadletter(ctx context.Context, id string) error

func (*JobQueue) Depth

func (q *JobQueue) Depth(ctx context.Context, kinds ...string) (count int, err error)

func (*JobQueue) Enqueue

func (q *JobQueue) Enqueue(ctx context.Context, kind string, payload []byte) (string, error)

func (*JobQueue) Fail

func (q *JobQueue) Fail(ctx context.Context, id string, retryIn time.Duration) error

func (*JobQueue) Heartbeat

func (q *JobQueue) Heartbeat(ctx context.Context, id string, extend time.Duration) error

func (*JobQueue) JobStatus added in v0.1.8

func (q *JobQueue) JobStatus(ctx context.Context, id string) (status ports.JobStatus, err error)

func (*JobQueue) Stats added in v0.1.8

func (q *JobQueue) Stats(ctx context.Context, kinds ...string) (stats ports.JobStats, err error)

type JudgmentRepository

type JudgmentRepository struct {
	// contains filtered or unexported fields
}

JudgmentRepository persists AI judgments to PostgreSQL, engagement-scoped. All operations route through WithContextTenant so tenant isolation is enforced at the database level. Save validates that the engagement belongs to the context tenant before writing.

func NewJudgmentRepository

func NewJudgmentRepository(pool *pgxpool.Pool) *JudgmentRepository

NewJudgmentRepository returns a repository backed by the given pool.

func (*JudgmentRepository) AcknowledgeJudgmentAudit added in v0.1.8

func (r *JudgmentRepository) AcknowledgeJudgmentAudit(ctx context.Context, kind ports.JudgmentAuditKind, judgmentID shared.ID, version int) error

func (*JudgmentRepository) ListByEngagement

func (r *JudgmentRepository) ListByEngagement(ctx context.Context, engagementID shared.ID) ([]judgment.Judgment, error)

ListByEngagement returns the engagement's judgments, oldest first (deterministic order). RLS scopes the query to the context tenant.

func (*JudgmentRepository) ListBySubject

func (r *JudgmentRepository) ListBySubject(ctx context.Context, engagementID, subjectID shared.ID) ([]judgment.Judgment, error)

ListBySubject returns the engagement's judgments about a given subject id, oldest first. RLS scopes the query to the context tenant.

func (*JudgmentRepository) ListPendingJudgmentAudits added in v0.1.8

func (r *JudgmentRepository) ListPendingJudgmentAudits(ctx context.Context, engagementID shared.ID) (out []ports.PendingJudgmentAudit, err error)

func (*JudgmentRepository) Save

Save inserts a proposed judgment (idempotent by id; never clobbers an existing row – score/state move only via SetScoreState). The typed claim is stored as its fail-closed discriminated envelope (JSONB). The tenant_id is resolved from context, and the engagement is validated to belong to that tenant before the insert.

func (*JudgmentRepository) SaveWithProposalAudit added in v0.1.8

func (r *JudgmentRepository) SaveWithProposalAudit(ctx context.Context, j judgment.Judgment, entry ports.AuditEntry) error

SaveWithProposalAudit persists a proposal with its immutable pending audit entry.

func (*JudgmentRepository) SetScoreState

func (r *JudgmentRepository) SetScoreState(ctx context.Context, engagementID, id shared.ID, score int, state judgment.State, expectedVersion int) (judgment.Judgment, error)

SetScoreState moves a judgment's evidence score + state under optimistic concurrency (the verify/accept path): the row updates only if version matches expectedVersion, then version is bumped. This is the ONLY path that moves a stored judgment's score/state, and it is deliberately off the broad ports.JudgmentStore interface (a read-only consumer cannot reach it). On a miss it distinguishes ErrConflict (exists, version moved) from ErrNotFound. RLS scopes the operation to the context tenant.

func (*JudgmentRepository) SetVerdictState added in v0.1.8

func (r *JudgmentRepository) SetVerdictState(ctx context.Context, engagementID, id shared.ID, score int, state judgment.State, verifiedBy, verdictRationale string, expectedVersion int) (judgment.Judgment, error)

SetVerdictState persists a verdict's sealed verifier and rationale with its score transition.

func (*JudgmentRepository) SetVerdictStateWithAudit added in v0.1.8

func (r *JudgmentRepository) SetVerdictStateWithAudit(ctx context.Context, engagementID, id shared.ID, score int, state judgment.State, verifiedBy, rationale string, expectedVersion int, entry ports.AuditEntry) (out judgment.Judgment, err error)

SetVerdictStateWithAudit commits the verdict and immutable pending audit entry atomically.

type LeaderStore added in v0.1.8

type LeaderStore struct {
	// contains filtered or unexported fields
}

LeaderStore is the Postgres fenced-lease implementation of leader election. It is global control-plane state (no tenant scoping, no RLS; see migration 0061).

func NewLeaderStore added in v0.1.8

func NewLeaderStore(pool *pgxpool.Pool) *LeaderStore

NewLeaderStore constructs the Postgres leader store.

func (*LeaderStore) Acquire added in v0.1.8

func (s *LeaderStore) Acquire(ctx context.Context, resource, holder string, term time.Duration, now time.Time) (bool, int64, error)

Acquire atomically takes or renews leadership in a single upsert: it takes the lease when the row is absent, already held by holder (renewal), or expired (takeover, which bumps the fence), and otherwise leaves a live foreign lease untouched. held is true iff holder owns it afterwards.

func (*LeaderStore) Resign added in v0.1.8

func (s *LeaderStore) Resign(ctx context.Context, resource, holder string, now time.Time) error

Resign releases the lease if held by holder. It EXPIRES the lease (clears the holder and sets the term to now) rather than deleting the row, so the fence survives: a graceful handover keeps the fence monotonic (the next acquirer takes over an expired row and bumps it), which a fresh INSERT with fence=1 would not. A challenger sees the expired row immediately and can take over without waiting out the term.

type LeaseRunLock

type LeaseRunLock struct {
	// contains filtered or unexported fields
}

LeaseRunLock implements ports.RunLocker via a jobs_run_lock ROW lease instead of a session advisory lock. Unlike RunLock it does NOT hold a pooled connection for the duration of the run – each operation borrows a connection transiently – so N concurrent runs cannot starve the pool (the ≤8-default-pool hazard the hostile review flagged). A background renewer extends the lease while the run is live; a crash lets the lease expire so another worker can reclaim it. Used for RECON; the agent SESSION lock stays the advisory RunLock (it must not expire mid-LLM-loop). owner is a per-process id so renew/release are owner-scoped.

func NewLeaseRunLock

func NewLeaseRunLock(pool *pgxpool.Pool, owner string, lease time.Duration) *LeaseRunLock

NewLeaseRunLock returns a row-lease run locker. lease is the claim TTL (set it comfortably above the longest run, e.g. ReconTimeout + a minute); the renewer ticks at lease/4 so several renews fall inside one TTL.

func (*LeaseRunLock) TryLock

func (l *LeaseRunLock) TryLock(ctx context.Context, runID string) (func(), bool, error)

TryLock claims the lease (see TryLockLeased) and discards the lease-loss context – for callers that don't observe lease loss (e.g. the stale-run sweeper's liveness probe).

func (*LeaseRunLock) TryLockLeased

func (l *LeaseRunLock) TryLockLeased(ctx context.Context, runID string) (context.Context, func(), bool, error)

TryLockLeased claims the lease for runID if it is free or expired. On success it starts a renewer and returns: a leaseCtx cancelled when the lease is LOST (so the caller aborts the in-flight run), plus a release that stops the renewer, cancels leaseCtx, and deletes the owner's row. A claim held by a live owner returns ok=false (the at-least-once queue retries).

type PoolConfig

type PoolConfig struct {
	MaxConns          int32
	MinConns          int32
	MaxConnLifetime   time.Duration
	MaxConnIdleTime   time.Duration
	HealthCheckPeriod time.Duration
}

PoolConfig sizes the pgx connection pool. Zero values get sane defaults. Sizing the pool explicitly (the default pgx cap is max(4, NumCPU) ≈ 8) is required now that the durable agent path holds a connection-bearing advisory lock per active run – an unsized pool would starve HTTP handlers at low-tens concurrency.

type ProjectAnalysisStore

type ProjectAnalysisStore struct {
	// contains filtered or unexported fields
}

ProjectAnalysisStore persists immutable Project analysis snapshots.

func NewProjectAnalysisStore

func NewProjectAnalysisStore(pool *pgxpool.Pool) *ProjectAnalysisStore

func (*ProjectAnalysisStore) AttachSourceWithAudit added in v0.1.8

func (r *ProjectAnalysisStore) AttachSourceWithAudit(ctx context.Context, tenantID, projectID, analysisID shared.ID, capture projectanalysis.SourceCapture, audit ports.AuditEntry) error

func (*ProjectAnalysisStore) CurrentAnalysisHotspotSummary

func (r *ProjectAnalysisStore) CurrentAnalysisHotspotSummary(ctx context.Context, tenantID, projectID, analysisID shared.ID, lens hotspot.Lens) (hotspot.Summary, error)

func (*ProjectAnalysisStore) CurrentFindingStatuses added in v0.1.8

func (r *ProjectAnalysisStore) CurrentFindingStatuses(ctx context.Context, tenantID, projectID shared.ID, keys []string) (map[string]string, error)

func (*ProjectAnalysisStore) Get

func (r *ProjectAnalysisStore) Get(ctx context.Context, tenantID, projectID, analysisID shared.ID) (projectanalysis.Analysis, error)

func (*ProjectAnalysisStore) GetHotspot

func (r *ProjectAnalysisStore) GetHotspot(ctx context.Context, tenantID, projectID, hotspotID shared.ID) (hotspot.Hotspot, error)

func (*ProjectAnalysisStore) GetIssue

func (r *ProjectAnalysisStore) GetIssue(ctx context.Context, tenantID, projectID, issueID shared.ID) (issue.Issue, error)

func (*ProjectAnalysisStore) HotspotHistory

func (r *ProjectAnalysisStore) HotspotHistory(ctx context.Context, tenantID, projectID, hotspotID shared.ID) ([]hotspot.ReviewEvent, error)

func (*ProjectAnalysisStore) IssueHistory

func (r *ProjectAnalysisStore) IssueHistory(ctx context.Context, tenantID, projectID, issueID shared.ID) ([]issue.ReviewEvent, error)

func (*ProjectAnalysisStore) LatestForProjects

func (r *ProjectAnalysisStore) LatestForProjects(ctx context.Context, tenantID shared.ID, projectIDs []shared.ID) (map[shared.ID]projectanalysis.Analysis, error)

func (*ProjectAnalysisStore) LatestWithResult

func (r *ProjectAnalysisStore) LatestWithResult(ctx context.Context, tenantID, projectID shared.ID) (projectanalysis.Analysis, []byte, error)

func (*ProjectAnalysisStore) List

func (r *ProjectAnalysisStore) List(ctx context.Context, tenantID, projectID shared.ID, limit int, beforeCreatedAt time.Time, beforeID shared.ID) ([]projectanalysis.Analysis, bool, error)

func (*ProjectAnalysisStore) ListAnalysisHotspots

func (r *ProjectAnalysisStore) ListAnalysisHotspots(ctx context.Context, tenantID, projectID, analysisID shared.ID, lens hotspot.Lens, filter hotspot.ListFilter) (hotspot.Page, hotspot.Summary, error)

func (*ProjectAnalysisStore) ListHotspots

func (r *ProjectAnalysisStore) ListHotspots(ctx context.Context, tenantID, projectID shared.ID, filter hotspot.ListFilter) (hotspot.Page, error)

func (*ProjectAnalysisStore) ListIssues

func (r *ProjectAnalysisStore) ListIssues(ctx context.Context, tenantID, projectID shared.ID, filter issue.ListFilter) (issue.Page, error)

func (*ProjectAnalysisStore) ResolvedIssueKeys

func (r *ProjectAnalysisStore) ResolvedIssueKeys(ctx context.Context, tenantID, projectID shared.ID) (map[string]bool, error)

func (*ProjectAnalysisStore) Save

func (*ProjectAnalysisStore) SaveWithResult

func (r *ProjectAnalysisStore) SaveWithResult(ctx context.Context, analysis projectanalysis.Analysis, result []byte) error

func (*ProjectAnalysisStore) SaveWithResultAndHotspots

func (r *ProjectAnalysisStore) SaveWithResultAndHotspots(ctx context.Context, analysis projectanalysis.Analysis, result []byte, candidates []hotspot.Candidate) error

SaveWithResultAndHotspots commits the immutable analysis and its Security Hotspot projection in one PostgreSQL transaction. It delegates to SaveWithResultAndProjections with no issue projection, so both write paths share the same single-transaction body: a projection write failure rolls the analysis back, and the scan worker cannot publish a successful analysis without its projections.

func (*ProjectAnalysisStore) SaveWithResultAndProjections

func (r *ProjectAnalysisStore) SaveWithResultAndProjections(ctx context.Context, analysis projectanalysis.Analysis, result []byte, hotspots []hotspot.Candidate, issues []issue.Candidate) error

SaveWithResultAndProjections commits the immutable analysis, its Security Hotspot projection, and its code-quality issue projection in a single PostgreSQL transaction. A projection write failure rolls the analysis back, so the scan worker cannot publish a successful analysis without both projections.

func (*ProjectAnalysisStore) TransitionHotspot

func (*ProjectAnalysisStore) TransitionIssue

type ProjectRepository

type ProjectRepository struct {
	// contains filtered or unexported fields
}

func NewProjectRepository

func NewProjectRepository(pool *pgxpool.Pool) *ProjectRepository

func (*ProjectRepository) AssignProfile

func (r *ProjectRepository) AssignProfile(ctx context.Context, tenantID shared.ID, projectKey, language, profileKey string) error

AssignProfile sets or clears the quality profile for a language in the project's JSONB default_profile_by_lang map, atomically at the column level (no read-modify-write race).

func (*ProjectRepository) CountByGate

func (r *ProjectRepository) CountByGate(ctx context.Context, tenantID shared.ID, gateID string) (int, error)

func (*ProjectRepository) Create

func (*ProjectRepository) DeleteByKey

func (r *ProjectRepository) DeleteByKey(ctx context.Context, tenantID shared.ID, key string) error

func (*ProjectRepository) GetByID

func (r *ProjectRepository) GetByID(ctx context.Context, tenantID, projectID shared.ID) (*project.Project, error)

func (*ProjectRepository) GetByKey

func (r *ProjectRepository) GetByKey(ctx context.Context, tenantID shared.ID, key string) (*project.Project, error)

func (*ProjectRepository) List

func (r *ProjectRepository) List(ctx context.Context, tenantID shared.ID) ([]*project.Project, error)

func (*ProjectRepository) UpdateGate

func (r *ProjectRepository) UpdateGate(ctx context.Context, tenantID shared.ID, key, gateID string) error

type PromotionStore added in v0.1.8

type PromotionStore struct {
	// contains filtered or unexported fields
}

PromotionStore persists promotion lifecycle events to PostgreSQL. Every operation runs inside a single RLS-scoped transaction via WithContextTenant so tenant isolation is enforced at the database level.

Apply atomically:

  1. Acquires sorted advisory transaction locks on (judgment, fingerprint) to serialize concurrent idempotency checks (prevents deadlocks).
  2. Checks judgment-level idempotency (tenant+judgmentID).
  3. Checks fingerprint-level idempotency (tenant+fingerprint).
  4. Locks the finding FOR UPDATE, verifies CAS (priority + version).
  5. Binds command metadata to CAS state.
  6. Validates exact reversal for corroborating_signal_loss.
  7. Constructs and validates the PromotionEvent.
  8. Mutates the finding (priority + version) for escalating/de-escalating effects.
  9. Appends the event to the append-only table.

func NewPromotionStore added in v0.1.8

func NewPromotionStore(pool *pgxpool.Pool) (*PromotionStore, error)

NewPromotionStore returns a repository backed by the given pool.

func (*PromotionStore) Apply added in v0.1.8

func (r *PromotionStore) Apply(ctx context.Context, engagementID, findingID shared.ID, cmd ports.PromotionCommand) (out finding.Finding, err error)

Apply constructs a PromotionEvent from the command, persists it, and atomically moves the finding's priority. Returns the existing event on exact replay (same judgmentID), or shared.ErrConflict on semantic conflicts.

func (*PromotionStore) FindByJudgment added in v0.1.8

func (r *PromotionStore) FindByJudgment(ctx context.Context, engagementID, findingID, judgmentID shared.ID) (evt promotion.PromotionEvent, ok bool, err error)

FindByJudgment returns an event scoped to its tenant, engagement, and finding.

func (*PromotionStore) LatestByFinding added in v0.1.8

func (r *PromotionStore) LatestByFinding(ctx context.Context, engagementID, findingID shared.ID) (evt promotion.PromotionEvent, ok bool, err error)

LatestByFinding returns the most recent promotion event for a finding, or (zero, false) if none exist.

func (*PromotionStore) ListByFinding added in v0.1.8

func (r *PromotionStore) ListByFinding(ctx context.Context, engagementID, findingID shared.ID) (out []promotion.PromotionEvent, err error)

ListByFinding returns all promotion events for a finding, oldest first.

func (*PromotionStore) ListPendingAudits added in v0.1.8

func (r *PromotionStore) ListPendingAudits(ctx context.Context, engagementID shared.ID) (out []promotion.PromotionEvent, err error)

ListPendingAudits returns applied events whose required audit record has not been acknowledged. The status row is created atomically with each event.

func (*PromotionStore) MarkAuditComplete added in v0.1.8

func (r *PromotionStore) MarkAuditComplete(ctx context.Context, eventID shared.ID) error

MarkAuditComplete acknowledges an event's required audit record. Repeating the acknowledgement is idempotent.

type PurpleRepository added in v0.1.8

type PurpleRepository struct {
	// contains filtered or unexported fields
}

PurpleRepository persists purple-team coverage (migration 0077), tenant-scoped via WithTenant/RLS so one tenant's coverage is never visible to another.

func NewPurpleRepository added in v0.1.8

func NewPurpleRepository(pool *pgxpool.Pool) *PurpleRepository

NewPurpleRepository constructs the repository.

func (*PurpleRepository) ListByEngagement added in v0.1.8

func (r *PurpleRepository) ListByEngagement(ctx context.Context, engagementID shared.ID) ([]pcdom.Coverage, error)

ListByEngagement returns all coverage for an engagement in the ctx tenant, oldest first, so a trend across runs is queryable.

func (*PurpleRepository) ListByRun added in v0.1.8

func (r *PurpleRepository) ListByRun(ctx context.Context, runID shared.ID) ([]pcdom.Coverage, error)

ListByRun returns one run's coverage in the ctx tenant, ordered by technique.

func (*PurpleRepository) SaveCoverage added in v0.1.8

func (r *PurpleRepository) SaveCoverage(ctx context.Context, records []pcdom.Coverage) error

SaveCoverage upserts a run's coverage under the authenticated tenant, keyed (run, technique), in one transaction so a re-computation of a run is atomic.

type QualityGateMutator

type QualityGateMutator struct {
	// contains filtered or unexported fields
}

QualityGateMutator commits managed-gate writes and their audit records together.

func NewQualityGateMutator

func NewQualityGateMutator(pool *pgxpool.Pool) *QualityGateMutator

func (*QualityGateMutator) AssignProjectGate

func (m *QualityGateMutator) AssignProjectGate(ctx context.Context, tenantID shared.ID, projectKey, gateID string, audit ports.AuditEntry) error

func (*QualityGateMutator) CreateGate

func (m *QualityGateMutator) CreateGate(ctx context.Context, tenantID shared.ID, gate qualitygate.Gate, audit ports.AuditEntry) error

func (*QualityGateMutator) CreateProjectWithGate

func (m *QualityGateMutator) CreateProjectWithGate(ctx context.Context, p *project.Project) error

func (*QualityGateMutator) DeleteGate

func (m *QualityGateMutator) DeleteGate(ctx context.Context, tenantID shared.ID, key string, audit ports.AuditEntry) error

func (*QualityGateMutator) UpdateGate

func (m *QualityGateMutator) UpdateGate(ctx context.Context, tenantID shared.ID, gate qualitygate.Gate, audit ports.AuditEntry) error

type QualityGateStore

type QualityGateStore struct {
	// contains filtered or unexported fields
}

QualityGateStore persists tenant-scoped custom quality gates.

func NewQualityGateStore

func NewQualityGateStore(pool *pgxpool.Pool) *QualityGateStore

func (*QualityGateStore) Create

func (s *QualityGateStore) Create(ctx context.Context, tenantID shared.ID, gate qualitygate.Gate) error

func (*QualityGateStore) Delete

func (s *QualityGateStore) Delete(ctx context.Context, tenantID shared.ID, key string) error

func (*QualityGateStore) DeleteIfUnassigned

func (s *QualityGateStore) DeleteIfUnassigned(ctx context.Context, tenantID shared.ID, key string) error

func (*QualityGateStore) Get

func (s *QualityGateStore) Get(ctx context.Context, tenantID shared.ID, key string) (qualitygate.Gate, error)

func (*QualityGateStore) List

func (s *QualityGateStore) List(ctx context.Context, tenantID shared.ID) ([]qualitygate.Gate, error)

func (*QualityGateStore) Update

func (s *QualityGateStore) Update(ctx context.Context, tenantID shared.ID, gate qualitygate.Gate) error

type QualityProfileStore

type QualityProfileStore struct {
	// contains filtered or unexported fields
}

QualityProfileStore persists tenant-scoped custom quality profiles. Built-in profiles are generated from the rule catalog and never stored here.

func NewQualityProfileStore

func NewQualityProfileStore(pool *pgxpool.Pool) *QualityProfileStore

func (*QualityProfileStore) Create

func (s *QualityProfileStore) Create(ctx context.Context, tenantID shared.ID, profile qualityprofile.Profile) error

func (*QualityProfileStore) Delete

func (s *QualityProfileStore) Delete(ctx context.Context, tenantID shared.ID, key string) error

func (*QualityProfileStore) Get

func (*QualityProfileStore) List

func (*QualityProfileStore) Update

func (s *QualityProfileStore) Update(ctx context.Context, tenantID shared.ID, profile qualityprofile.Profile) error

type ReconRunStore

type ReconRunStore struct {
	// contains filtered or unexported fields
}

ReconRunStore persists recon-run records.

func NewReconRunStore

func NewReconRunStore(pool *pgxpool.Pool) *ReconRunStore

NewReconRunStore returns a store backed by the given pool.

func (*ReconRunStore) Get

func (r *ReconRunStore) Get(ctx context.Context, id shared.ID) (recon.Run, error)

Get returns a run by id, or shared.ErrNotFound.

func (*ReconRunStore) ListByEngagement

func (r *ReconRunStore) ListByEngagement(ctx context.Context, engagementID shared.ID) ([]recon.Run, error)

ListByEngagement returns an engagement's runs, newest first.

func (*ReconRunStore) ListStaleRunning

func (r *ReconRunStore) ListStaleRunning(ctx context.Context, olderThan time.Time, limit int) ([]recon.Run, error)

ListStaleRunning returns runs still 'running' that started before olderThan (≤ limit), oldest first – the stale-run sweeper's input.

func (*ReconRunStore) Save

func (r *ReconRunStore) Save(ctx context.Context, run recon.Run) error

Save upserts a run (used on create and on every stage/status update).

type ResponseRepository added in v0.1.8

type ResponseRepository struct {
	// contains filtered or unexported fields
}

ResponseRepository persists governed response actions (migration 0076), tenant-scoped via WithTenant/RLS so one tenant's actions are never visible to another.

func NewResponseRepository added in v0.1.8

func NewResponseRepository(pool *pgxpool.Pool) *ResponseRepository

NewResponseRepository constructs the repository.

func (*ResponseRepository) Get added in v0.1.8

Get returns the record for an id in the ctx tenant.

func (*ResponseRepository) ListByState added in v0.1.8

func (r *ResponseRepository) ListByState(ctx context.Context, state rdom.State) ([]rdom.Record, error)

ListByState returns the ctx tenant's records in a state, deterministically ordered by id.

func (*ResponseRepository) Put added in v0.1.8

Put upserts a response record under the authenticated tenant.

type RetestRepository

type RetestRepository struct {
	// contains filtered or unexported fields
}

RetestRepository persists the per-finding retest history to PostgreSQL.

func NewRetestRepository

func NewRetestRepository(pool *pgxpool.Pool) *RetestRepository

NewRetestRepository returns a repository backed by the given pool.

func (*RetestRepository) Add

Add inserts a retest (append-only; retests are not edited or deleted in app code).

func (*RetestRepository) ListByEngagementFinding

func (r *RetestRepository) ListByEngagementFinding(ctx context.Context, engagementID, findingID shared.ID) (out []finding.Retest, err error)

ListByEngagementFinding returns a finding's retests oldest-first, scoped to the engagement (no cross-engagement read).

type RunLock

type RunLock struct {
	// contains filtered or unexported fields
}

RunLock implements ports.RunLocker with a PostgreSQL session advisory lock keyed by the run id (F9). The lock is held on a dedicated pooled connection for the duration of the execution and released (with the connection) afterwards – so across the API + the worker, at most one delivery of a given run executes at a time. A redelivery that finds the lock held gets ok=false and skips, preventing a duplicate live scan.

func NewRunLock

func NewRunLock(pool *pgxpool.Pool) *RunLock

NewRunLock returns a Postgres-backed run locker.

func (*RunLock) TryLock

func (l *RunLock) TryLock(ctx context.Context, runID string) (func(), bool, error)

type SLAStore added in v0.1.8

type SLAStore struct {
	// contains filtered or unexported fields
}

SLAStore is the PostgreSQL adapter for versioned SLA policy, immutable assessments, and the human remediation lifecycle. Every operation is routed through WithTenant so RLS remains the final isolation boundary even when an application-level predicate regresses.

func NewSLAStore added in v0.1.8

func NewSLAStore(pool *pgxpool.Pool) *SLAStore

func (*SLAStore) ActivePolicy added in v0.1.8

func (s *SLAStore) ActivePolicy(ctx context.Context, tenantID shared.ID) (sla.Policy, error)

func (*SLAStore) AssessmentHistory added in v0.1.8

func (s *SLAStore) AssessmentHistory(ctx context.Context, tenantID, engagementID, findingID shared.ID) ([]sla.Assessment, error)

func (*SLAStore) Current added in v0.1.8

func (s *SLAStore) Current(ctx context.Context, tenantID, engagementID, findingID shared.ID) (sla.Current, error)

func (*SLAStore) LifecycleEvents added in v0.1.8

func (s *SLAStore) LifecycleEvents(ctx context.Context, tenantID, engagementID, findingID shared.ID) ([]sla.LifecycleEvent, error)

func (*SLAStore) ListCurrent added in v0.1.8

func (s *SLAStore) ListCurrent(ctx context.Context, tenantID, engagementID shared.ID) ([]sla.Current, error)

func (*SLAStore) PolicyHistory added in v0.1.8

func (s *SLAStore) PolicyHistory(ctx context.Context, tenantID shared.ID) ([]sla.Policy, error)

func (*SLAStore) PutPolicy added in v0.1.8

func (s *SLAStore) PutPolicy(ctx context.Context, policy sla.Policy, activate bool) (bool, error)

func (*SLAStore) SaveTransition added in v0.1.8

func (s *SLAStore) SaveTransition(ctx context.Context, next sla.Lifecycle, event sla.LifecycleEvent) error

func (*SLAStore) UpsertAssessment added in v0.1.8

func (s *SLAStore) UpsertAssessment(ctx context.Context, assessment sla.Assessment) (sla.AssessmentUpsertResult, error)

type ScanJobStore

type ScanJobStore struct {
	// contains filtered or unexported fields
}

ScanJobStore persists asynchronous scan-job status.

func NewScanJobStore

func NewScanJobStore(pool *pgxpool.Pool) *ScanJobStore

NewScanJobStore returns a store backed by the given pool.

func (*ScanJobStore) CreateRunning

func (r *ScanJobStore) CreateRunning(ctx context.Context, j ports.ScanJob) error

func (*ScanJobStore) GetJob

func (r *ScanJobStore) GetJob(ctx context.Context, id string) (ports.ScanJob, error)

GetJob returns a scan job by its own id, or ErrNotFound.

func (*ScanJobStore) LatestForEngagement

func (r *ScanJobStore) LatestForEngagement(ctx context.Context, engagementID shared.ID) (ports.ScanJob, error)

func (*ScanJobStore) LatestForEngagements

func (r *ScanJobStore) LatestForEngagements(ctx context.Context, engagementIDs []shared.ID) (map[shared.ID]ports.ScanJob, error)

LatestForEngagement returns the engagement's most recent scan job, or ErrNotFound.

func (*ScanJobStore) ListStaleRunning

func (r *ScanJobStore) ListStaleRunning(ctx context.Context, olderThan time.Time, limit int) ([]ports.ScanJob, error)

ListStaleRunning returns scan jobs still 'running' that started before olderThan (≤ limit), oldest first – the stale-scan sweeper's input.

func (*ScanJobStore) Save

func (r *ScanJobStore) Save(ctx context.Context, j ports.ScanJob) error

Save upserts a scan job (used on create and on every stage/status update).

type ScanRepository

type ScanRepository struct {
	// contains filtered or unexported fields
}

ScanRepository persists SCA scans (SBOM + components + vulnerabilities).

func NewScanRepository

func NewScanRepository(pool *pgxpool.Pool) *ScanRepository

NewScanRepository returns a repository backed by the given pool.

func (*ScanRepository) SaveScan

func (r *ScanRepository) SaveScan(ctx context.Context, engagementID shared.ID, doc *sbom.SBOM, vulns []vulnerability.Vulnerability, snap ports.ScanSnapshot) (int, error)

SaveScan stores the SBOM, its components, and the vulnerabilities found against them in one transaction – a new immutable snapshot per scan. It returns the number of vulns that could not be linked to a component in this SBOM (skipped, never orphaned); the caller surfaces a non-zero count on the audit log so a dropped advisory is never invisible on a chain-of-custody tool.

type ScanResultStore

type ScanResultStore struct {
	// contains filtered or unexported fields
}

ScanResultStore caches the latest full scan result (JSON) per engagement.

func NewScanResultStore

func NewScanResultStore(pool *pgxpool.Pool) *ScanResultStore

NewScanResultStore returns a store backed by the given pool.

func (*ScanResultStore) LatestResult

func (r *ScanResultStore) LatestResult(ctx context.Context, engagementID shared.ID) ([]byte, error)

LatestResult returns the engagement's cached scan result, or shared.ErrNotFound.

func (*ScanResultStore) SaveResult

func (r *ScanResultStore) SaveResult(ctx context.Context, engagementID shared.ID, result []byte) error

SaveResult upserts the engagement's latest scan result JSON.

type ScanRunStore

type ScanRunStore struct {
	// contains filtered or unexported fields
}

ScanRunStore persists scan-run manifests + finding keys.

func NewScanRunStore

func NewScanRunStore(pool *pgxpool.Pool) *ScanRunStore

NewScanRunStore returns a store backed by the given pool.

func (*ScanRunStore) Get

func (r *ScanRunStore) Get(ctx context.Context, runID string) (ports.ScanRun, error)

func (*ScanRunStore) List

func (r *ScanRunStore) List(ctx context.Context, engagementID shared.ID) ([]ports.ScanRun, error)

func (*ScanRunStore) Save

func (r *ScanRunStore) Save(ctx context.Context, run ports.ScanRun) error

type ScannedImageStore added in v0.1.8

type ScannedImageStore struct {
	// contains filtered or unexported fields
}

ScannedImageStore is the Postgres-backed scanned-image digest index (#446). Every operation routes through WithTenant so the Row Level Security policy on scanned_image (migration 0063) enforces tenant isolation at the database — a query that bypassed WithTenant would resolve the tenant to NULL and see nothing.

func NewScannedImageStore added in v0.1.8

func NewScannedImageStore(pool *pgxpool.Pool) *ScannedImageStore

NewScannedImageStore constructs the Postgres scanned-image store.

func (*ScannedImageStore) MarkScanned added in v0.1.8

func (s *ScannedImageStore) MarkScanned(ctx context.Context, tenantID shared.ID, digest string, at time.Time) error

MarkScanned records digest as scanned for the tenant. Idempotent by (tenant, digest): a repeat keeps the earliest first_scanned_at.

func (*ScannedImageStore) ScannedDigests added in v0.1.8

func (s *ScannedImageStore) ScannedDigests(ctx context.Context, tenantID shared.ID) (map[string]bool, error)

ScannedDigests returns the set of scanned digests for the tenant.

type SyncRunStore added in v0.1.8

type SyncRunStore struct {
	// contains filtered or unexported fields
}

func NewSyncRunStore added in v0.1.8

func NewSyncRunStore(pool *pgxpool.Pool, ids ports.IDGenerator) *SyncRunStore

func (*SyncRunStore) Advance added in v0.1.8

func (s *SyncRunStore) Advance(ctx context.Context, id shared.ID, expectedCheckpoint, nextCheckpoint []byte, counts vulnerabilitysync.Counts, errors []string) (vulnerabilitysync.Run, error)

func (*SyncRunStore) Finish added in v0.1.8

func (*SyncRunStore) Get added in v0.1.8

func (*SyncRunStore) GetByDurableJobID added in v0.1.8

func (s *SyncRunStore) GetByDurableJobID(ctx context.Context, jobID string) (vulnerabilitysync.Run, error)

func (*SyncRunStore) GetVulnerabilitySyncRun added in v0.1.8

func (s *SyncRunStore) GetVulnerabilitySyncRun(ctx context.Context, tenantID, id shared.ID) (vulnerabilityintel.SyncRunItem, error)

func (*SyncRunStore) LatestForSource added in v0.1.8

func (s *SyncRunStore) LatestForSource(ctx context.Context, sourceID shared.ID, states []vulnerabilitysync.State) (vulnerabilitysync.Run, error)

func (*SyncRunStore) LatestSuccessfulVulnerabilitySync added in v0.1.8

func (s *SyncRunStore) LatestSuccessfulVulnerabilitySync(ctx context.Context, tenantID shared.ID) (*time.Time, error)

func (*SyncRunStore) ListStale added in v0.1.8

func (s *SyncRunStore) ListStale(ctx context.Context, olderThan time.Time, limit int) ([]vulnerabilitysync.Run, error)

func (*SyncRunStore) ListVulnerabilitySyncRuns added in v0.1.8

func (s *SyncRunStore) ListVulnerabilitySyncRuns(ctx context.Context, query vulnerabilityintel.SyncRunQuery) (vulnerabilityintel.SyncRunPage, error)

func (*SyncRunStore) MarkRunning added in v0.1.8

func (s *SyncRunStore) MarkRunning(ctx context.Context, id shared.ID) error

func (*SyncRunStore) RecoverStale added in v0.1.8

func (s *SyncRunStore) RecoverStale(ctx context.Context, staleRunID shared.ID, staleBefore time.Time, request ports.SyncRunStart) (vulnerabilitysync.Run, bool, error)

func (*SyncRunStore) Start added in v0.1.8

func (*SyncRunStore) Supersede added in v0.1.8

func (s *SyncRunStore) Supersede(ctx context.Context, id shared.ID) error

type TelemetryRepository added in v0.1.8

type TelemetryRepository struct {
	// contains filtered or unexported fields
}

TelemetryRepository is the CE columnar-tier store (migration 0075) behind ports.TelemetryStore, tenant-scoped via WithTenant/RLS. It is reached only through the port and appears in no domain type.

func NewTelemetryRepository added in v0.1.8

func NewTelemetryRepository(pool *pgxpool.Pool, hot, warm time.Duration) *TelemetryRepository

NewTelemetryRepository constructs the store with the hot/warm tier boundaries (ADR 0001 config).

func (*TelemetryRepository) Footprint added in v0.1.8

Footprint reports the GLOBAL store size — an operator spend metric across all tenants, not a per-tenant figure. Both numbers are global and coherent: an estimated row count from the planner statistics (pg_class.reltuples) paired with the real on-disk size (pg_total_relation_size). These are catalog reads, unaffected by RLS, and carry only counts/bytes — never tenant data — so a global scope leaks nothing. reltuples is an estimate (refreshed by ANALYZE/autovacuum), which is the right shape for "predict spend, don't discover it".

func (*TelemetryRepository) Ingest added in v0.1.8

Ingest bulk-inserts the batch's events, idempotent on (tenant, host, class, seq, idx).

func (*TelemetryRepository) LastSequence added in v0.1.8

func (r *TelemetryRepository) LastSequence(ctx context.Context, hostID shared.ID, class detection.Class) (uint64, error)

LastSequence returns the highest stored seq for a (host, class) in the ctx tenant.

func (*TelemetryRepository) Query added in v0.1.8

Query runs a retro-hunt over a window and reports completeness honestly (sampled + sequence gaps).

func (*TelemetryRepository) RetentionSweep added in v0.1.8

func (r *TelemetryRepository) RetentionSweep(ctx context.Context, now time.Time) (ports.SweepReport, error)

RetentionSweep down-samples the warm window (drops 1-in-2 by idx for reduced resolution) and expires the past-warm window, for the ctx tenant. Returns the counts so the caller can audit the expiry.

type TenantTransactionRunner added in v0.1.8

type TenantTransactionRunner struct {
	// contains filtered or unexported fields
}

func NewTenantTransactionRunner added in v0.1.8

func NewTenantTransactionRunner(pool *pgxpool.Pool) *TenantTransactionRunner

func (*TenantTransactionRunner) Run added in v0.1.8

func (runner *TenantTransactionRunner) Run(ctx context.Context, tenantID shared.ID, fn func(context.Context) error) error

type ThreatModelRepository

type ThreatModelRepository struct {
	// contains filtered or unexported fields
}

ThreatModelRepository persists the architecture-input threat model per engagement to PostgreSQL: one row per engagement, the validated domain model stored as a JSONB blob. Tenant-scoped customer data (tenant_id recorded for the row-scoping sweep); reads are engagement-scoped (the tenant gate runs upstream at the child route).

func NewThreatModelRepository

func NewThreatModelRepository(pool *pgxpool.Pool) *ThreatModelRepository

NewThreatModelRepository returns a repository backed by the given pool.

func (*ThreatModelRepository) Get

func (r *ThreatModelRepository) Get(ctx context.Context, engagementID shared.ID) (threatmodel.Model, bool, error)

Get decodes the engagement's model from its JSONB blob; ok=false when none has been ingested.

func (*ThreatModelRepository) Save

func (r *ThreatModelRepository) Save(ctx context.Context, engagementID, tenantID shared.ID, m threatmodel.Model) error

Save upserts the engagement's model (the usecase has already bounded size + validated it), bumping version on each re-ingest. The model round-trips through the JSONB `data` blob.

type TimestampStore

type TimestampStore struct {
	// contains filtered or unexported fields
}

TimestampStore persists external RFC-3161 tokens for chain heads on PostgreSQL, out-of-band from the report. One token per (chain, engagement, head).

func NewTimestampStore

func NewTimestampStore(pool *pgxpool.Pool) *TimestampStore

NewTimestampStore returns a timestamp store backed by the given pool.

func (*TimestampStore) Get

func (s *TimestampStore) Get(ctx context.Context, chain string, eng shared.ID, head string) (*ports.TimestampToken, error)

Get returns the stored token for a head, or nil if it is not yet anchored.

func (*TimestampStore) LatestHead

func (s *TimestampStore) LatestHead(ctx context.Context, chain string, eng shared.ID) (string, bool, error)

LatestHead returns the most-recently-anchored head for a chain (ok=false if none) – the retained head for out-of-band tail-truncation detection.

func (*TimestampStore) Put

func (s *TimestampStore) Put(ctx context.Context, chain string, eng shared.ID, head string, token ports.TimestampToken) error

Put stores a token for a head, idempotent per (chain, engagement, head).

type UserRepository

type UserRepository struct {
	// contains filtered or unexported fields
}

UserRepository persists operator identities to PostgreSQL.

func NewUserRepository

func NewUserRepository(pool *pgxpool.Pool) *UserRepository

NewUserRepository returns a repository backed by the given pool.

func (*UserRepository) Create

func (r *UserRepository) Create(ctx context.Context, u *user.User) error

func (*UserRepository) GetByAPIKeyHash

func (r *UserRepository) GetByAPIKeyHash(ctx context.Context, hash string) (*user.User, error)

func (*UserRepository) GetByID

func (r *UserRepository) GetByID(ctx context.Context, id shared.ID) (*user.User, error)

func (*UserRepository) List

func (r *UserRepository) List(ctx context.Context) ([]*user.User, error)

func (*UserRepository) Upsert

func (r *UserRepository) Upsert(ctx context.Context, u *user.User) error

type VulnerabilityActionStore added in v0.1.8

type VulnerabilityActionStore struct {
	// contains filtered or unexported fields
}

func NewVulnerabilityActionStore added in v0.1.8

func NewVulnerabilityActionStore(pool *pgxpool.Pool) *VulnerabilityActionStore

func (*VulnerabilityActionStore) AcknowledgeAction added in v0.1.8

func (s *VulnerabilityActionStore) AcknowledgeAction(ctx context.Context, tenantID, actionID shared.ID, actor string, at time.Time) (vulnerabilityaction.Action, error)

func (*VulnerabilityActionStore) ClaimOutbox added in v0.1.8

func (s *VulnerabilityActionStore) ClaimOutbox(ctx context.Context, tenantID shared.ID, now, lockedUntil time.Time, limit int) ([]vulnerabilityaction.OutboxEvent, error)

func (*VulnerabilityActionStore) CompleteOutbox added in v0.1.8

func (s *VulnerabilityActionStore) CompleteOutbox(ctx context.Context, tenantID, eventID shared.ID, at time.Time) error

func (*VulnerabilityActionStore) CountPendingVulnerabilityActions added in v0.1.8

func (s *VulnerabilityActionStore) CountPendingVulnerabilityActions(ctx context.Context, tenantID shared.ID) (int64, error)

func (*VulnerabilityActionStore) GetAction added in v0.1.8

func (s *VulnerabilityActionStore) GetAction(ctx context.Context, tenantID, actionID shared.ID) (vulnerabilityaction.Action, error)

func (*VulnerabilityActionStore) ListActions added in v0.1.8

func (*VulnerabilityActionStore) ListVulnerabilityTransitions added in v0.1.8

func (*VulnerabilityActionStore) RecordChange added in v0.1.8

func (*VulnerabilityActionStore) ResolveAction added in v0.1.8

func (s *VulnerabilityActionStore) ResolveAction(ctx context.Context, tenantID, actionID shared.ID, actor string, at time.Time) (vulnerabilityaction.Action, error)

func (*VulnerabilityActionStore) RetryOutbox added in v0.1.8

func (s *VulnerabilityActionStore) RetryOutbox(ctx context.Context, tenantID, eventID shared.ID, at, availableAt time.Time, lastError string, terminal bool) error

func (*VulnerabilityActionStore) SummarizeVulnerabilityActions added in v0.1.8

func (s *VulnerabilityActionStore) SummarizeVulnerabilityActions(ctx context.Context, tenantID shared.ID, advisoryIDs []string) (map[string]vulnerabilityintel.AdvisoryActionSummary, error)

type VulnerabilityOccurrenceStore added in v0.1.8

type VulnerabilityOccurrenceStore struct {
	// contains filtered or unexported fields
}

func NewVulnerabilityOccurrenceStore added in v0.1.8

func NewVulnerabilityOccurrenceStore(pool *pgxpool.Pool) *VulnerabilityOccurrenceStore

func (*VulnerabilityOccurrenceStore) CountActiveVulnerabilityOccurrences added in v0.1.8

func (s *VulnerabilityOccurrenceStore) CountActiveVulnerabilityOccurrences(ctx context.Context, tenantID shared.ID, advisoryID string) (int64, error)

func (*VulnerabilityOccurrenceStore) Get added in v0.1.8

func (s *VulnerabilityOccurrenceStore) Get(ctx context.Context, tenantID, engagementID shared.ID, advisoryID, componentFingerprint string) (vulnerabilityoccurrence.Occurrence, error)

func (*VulnerabilityOccurrenceStore) ListByEngagement added in v0.1.8

func (s *VulnerabilityOccurrenceStore) ListByEngagement(ctx context.Context, tenantID, engagementID shared.ID, states []vulnerabilityoccurrence.State) ([]vulnerabilityoccurrence.Occurrence, error)

func (*VulnerabilityOccurrenceStore) ListEvents added in v0.1.8

func (s *VulnerabilityOccurrenceStore) ListEvents(ctx context.Context, tenantID, occurrenceID shared.ID) ([]vulnerabilityoccurrence.Event, error)

func (*VulnerabilityOccurrenceStore) ListUnreconciled added in v0.1.8

func (s *VulnerabilityOccurrenceStore) ListUnreconciled(ctx context.Context, tenantID, runID shared.ID, advisoryID string, after shared.ID, snapshotAt time.Time, limit int) (ports.VulnerabilityOccurrenceReconciliationPage, error)

func (*VulnerabilityOccurrenceStore) ListVulnerabilityOccurrences added in v0.1.8

func (*VulnerabilityOccurrenceStore) SummarizeVulnerabilityOccurrences added in v0.1.8

func (s *VulnerabilityOccurrenceStore) SummarizeVulnerabilityOccurrences(ctx context.Context, tenantID shared.ID, advisoryIDs []string, affectedAsset string, states []vulnerabilityoccurrence.State) (map[string]vulnerabilityintel.AdvisoryOccurrenceSummary, error)

func (*VulnerabilityOccurrenceStore) Upsert added in v0.1.8

type VulnerabilityReconcileRunStore added in v0.1.8

type VulnerabilityReconcileRunStore struct {
	// contains filtered or unexported fields
}

func NewVulnerabilityReconcileRunStore added in v0.1.8

func NewVulnerabilityReconcileRunStore(pool *pgxpool.Pool, ids ports.IDGenerator) *VulnerabilityReconcileRunStore

func (*VulnerabilityReconcileRunStore) Advance added in v0.1.8

func (s *VulnerabilityReconcileRunStore) Advance(ctx context.Context, id shared.ID, expectedCheckpoint, nextCheckpoint []byte, counts vulnerabilityreconcile.Counts, samples []string) (vulnerabilityreconcile.Run, error)

func (*VulnerabilityReconcileRunStore) Finish added in v0.1.8

func (*VulnerabilityReconcileRunStore) Get added in v0.1.8

func (*VulnerabilityReconcileRunStore) GetByDurableJobID added in v0.1.8

func (*VulnerabilityReconcileRunStore) HasReconciliationMatch added in v0.1.8

func (s *VulnerabilityReconcileRunStore) HasReconciliationMatch(ctx context.Context, tenantID, runID, engagementID shared.ID, advisoryID, componentFingerprint string) (bool, error)

func (*VulnerabilityReconcileRunStore) ListReconciliationDiffs added in v0.1.8

func (*VulnerabilityReconcileRunStore) MarkRunning added in v0.1.8

func (s *VulnerabilityReconcileRunStore) MarkRunning(ctx context.Context, id shared.ID) error

func (*VulnerabilityReconcileRunStore) RecordReconciliationDiff added in v0.1.8

func (s *VulnerabilityReconcileRunStore) RecordReconciliationDiff(ctx context.Context, diff vulnerabilityreconcile.Diff) (bool, error)

func (*VulnerabilityReconcileRunStore) Start added in v0.1.8

func (*VulnerabilityReconcileRunStore) SummarizeReconciliationDiffs added in v0.1.8

func (s *VulnerabilityReconcileRunStore) SummarizeReconciliationDiffs(ctx context.Context, tenantID, runID shared.ID) (vulnerabilityreconcile.Counts, error)

type VulnerabilityRiskAssessmentStore added in v0.1.8

type VulnerabilityRiskAssessmentStore struct {
	// contains filtered or unexported fields
}

func NewVulnerabilityRiskAssessmentStore added in v0.1.8

func NewVulnerabilityRiskAssessmentStore(pool *pgxpool.Pool) *VulnerabilityRiskAssessmentStore

func (*VulnerabilityRiskAssessmentStore) CountOpenHighCriticalVulnerabilityExposure added in v0.1.8

func (s *VulnerabilityRiskAssessmentStore) CountOpenHighCriticalVulnerabilityExposure(ctx context.Context, tenantID shared.ID) (int64, error)

func (*VulnerabilityRiskAssessmentStore) Current added in v0.1.8

func (s *VulnerabilityRiskAssessmentStore) Current(ctx context.Context, tenantID, occurrenceID shared.ID) (vulnerabilityrisk.Assessment, error)

func (*VulnerabilityRiskAssessmentStore) History added in v0.1.8

func (s *VulnerabilityRiskAssessmentStore) History(ctx context.Context, tenantID, occurrenceID shared.ID) ([]vulnerabilityrisk.Assessment, error)

func (*VulnerabilityRiskAssessmentStore) ListVulnerabilityAssessments added in v0.1.8

func (*VulnerabilityRiskAssessmentStore) SummarizeVulnerabilityRisk added in v0.1.8

func (s *VulnerabilityRiskAssessmentStore) SummarizeVulnerabilityRisk(ctx context.Context, tenantID shared.ID, advisoryIDs []string) (map[string]vulnerabilityintel.AdvisoryRiskSummary, error)

func (*VulnerabilityRiskAssessmentStore) Upsert added in v0.1.8

type VulnerabilitySourceStore added in v0.1.8

type VulnerabilitySourceStore struct {
	// contains filtered or unexported fields
}

func NewVulnerabilitySourceStore added in v0.1.8

func NewVulnerabilitySourceStore(pool *pgxpool.Pool) *VulnerabilitySourceStore

func (*VulnerabilitySourceStore) Archive added in v0.1.8

func (s *VulnerabilitySourceStore) Archive(ctx context.Context, id shared.ID, expectedVersion int) error

func (*VulnerabilitySourceStore) Create added in v0.1.8

func (*VulnerabilitySourceStore) Get added in v0.1.8

func (*VulnerabilitySourceStore) List added in v0.1.8

func (s *VulnerabilitySourceStore) List(ctx context.Context, includeArchived bool) ([]vulnerabilitysource.Source, error)

func (*VulnerabilitySourceStore) SetEnabled added in v0.1.8

func (s *VulnerabilitySourceStore) SetEnabled(ctx context.Context, id shared.ID, enabled bool, expectedVersion int) (vulnerabilitysource.Source, error)

func (*VulnerabilitySourceStore) Update added in v0.1.8

func (s *VulnerabilitySourceStore) Update(ctx context.Context, source vulnerabilitysource.Source, expectedVersion int) error

type WorkOrderRepository added in v0.1.8

type WorkOrderRepository struct {
	// contains filtered or unexported fields
}

WorkOrderRepository is the Postgres-backed fleet work order store. Every method runs through WithTenant so Row Level Security (migration 0059 via the 0057 procedure) isolates by tenant.

func NewWorkOrderRepository added in v0.1.8

func NewWorkOrderRepository(pool *pgxpool.Pool) *WorkOrderRepository

NewWorkOrderRepository constructs the Postgres work order repository.

func (*WorkOrderRepository) CancelForAgent added in v0.1.8

func (r *WorkOrderRepository) CancelForAgent(ctx context.Context, tenantID, agentID shared.ID, reason string, now time.Time) (int, error)

CancelForAgent cancels every live order addressed to agentID (used on agent revocation).

func (*WorkOrderRepository) Claim added in v0.1.8

func (r *WorkOrderRepository) Claim(ctx context.Context, tenantID, agentID shared.ID, max int, now time.Time) ([]*workorder.WorkOrder, error)

Claim atomically moves up to max unexpired issued orders addressed to agentID into claimed and returns them, using FOR UPDATE SKIP LOCKED so concurrent claimers never double-claim.

func (*WorkOrderRepository) GetByID added in v0.1.8

func (r *WorkOrderRepository) GetByID(ctx context.Context, tenantID, id shared.ID) (*workorder.WorkOrder, error)

GetByID returns the order for (tenantID, id) or shared.ErrNotFound.

func (*WorkOrderRepository) Issue added in v0.1.8

Issue inserts wo. It is idempotent by (tenant, idempotency key): a duplicate returns the existing order. A second LIVE order for the same (tenant, asset, capability, time bucket) returns shared.ErrConflict (the partial unique index).

func (*WorkOrderRepository) ListByTenant added in v0.1.8

func (r *WorkOrderRepository) ListByTenant(ctx context.Context, tenantID shared.ID) ([]*workorder.WorkOrder, error)

ListByTenant returns every work order for the tenant, ordered deterministically. Read-only, used by the coverage projection (#413); routed through WithTenant so RLS scopes it to the tenant.

func (*WorkOrderRepository) Transition added in v0.1.8

func (r *WorkOrderRepository) Transition(ctx context.Context, tenantID, id shared.ID, to workorder.State, reason string, expected workorder.State, now time.Time) error

Transition applies to with an optimistic expected-state check. It returns shared.ErrConflict when no row matched (the state changed concurrently or the order does not exist under this tenant).

type WriteupDraftRepository

type WriteupDraftRepository struct {
	// contains filtered or unexported fields
}

WriteupDraftRepository persists AI-proposed, human-gated finding write-up drafts to PostgreSQL, engagement-scoped.

func NewWriteupDraftRepository

func NewWriteupDraftRepository(pool *pgxpool.Pool) *WriteupDraftRepository

NewWriteupDraftRepository returns a repository backed by the given pool.

func (*WriteupDraftRepository) Get

func (r *WriteupDraftRepository) Get(ctx context.Context, engagementID, id shared.ID) (out writeupdraft.Draft, err error)

Get returns the engagement's draft by id, or shared.ErrNotFound.

func (*WriteupDraftRepository) ListByEngagement

func (r *WriteupDraftRepository) ListByEngagement(ctx context.Context, engagementID shared.ID) (out []writeupdraft.Draft, err error)

ListByEngagement returns the engagement's drafts, oldest first (deterministic order).

func (*WriteupDraftRepository) Save

Save upserts a draft by id. Unlike a Judgment (insert-only), a Draft is mutable working data, so on conflict the mutable fields (text, state, decided_by, updated_at) are replaced; the immutable fields (engagement_id, finding_id, proposed_by, created_at) are never moved. tenant_id is written as the empty-string default tenant (mirrors judgments/findings); reads are tenant-isolated via the engagement gate, and the column is present so the P5/E22 row-scoping sweep covers it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL