package phasea import ( "context" "crypto/rand" "database/sql" _ "embed" "encoding/hex" "encoding/json" "errors" "fmt" "regexp" "strings" "time" "github.com/jackc/pgx/v5/pgconn" _ "github.com/jackc/pgx/v5/stdlib" ) //go:embed migrations/001_phase_a.sql var migration001 string var ( ErrConflict = errors.New("resource conflicts with existing state") ErrInvalid = errors.New("invalid phase A input") ErrNotFound = errors.New("resource not found") idPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,31}$`) refPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$`) credentialKeyPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]{0,63}/[A-Za-z0-9][A-Za-z0-9._/-]{0,126}$`) ) type Store struct{ db *sql.DB } type Account struct { ID string `json:"id"` CredentialReferenceID string `json:"credential_reference_id"` CredentialProvider string `json:"credential_provider"` CredentialKey string `json:"-"` ProfileID string `json:"profile_id"` } type RuntimeBinding struct { ID string `json:"id"` AccountID string `json:"account_id"` RuntimeID string `json:"runtime_id"` LeaseUntil time.Time `json:"lease_until"` } type Draft struct { ID string `json:"id"` AccountID string `json:"account_id"` Version int64 `json:"version"` Content string `json:"content"` } type Confirmation struct { ID string `json:"id"` AccountID string `json:"account_id"` AccountVersion int64 `json:"account_version"` DraftID string `json:"draft_id"` DraftVersion int64 `json:"draft_version"` Version int64 `json:"version"` } type Task struct { ID string `json:"id"` IdempotencyKey string `json:"idempotency_key"` AccountID string `json:"account_id"` AccountVersion int64 `json:"account_version"` DraftID string `json:"draft_id"` DraftVersion int64 `json:"draft_version"` ConfirmationID string `json:"confirmation_id"` ConfirmationVersion int64 `json:"confirmation_version"` State string `json:"state"` } type Execution struct { TaskID string `json:"task_id"` AttemptID string `json:"attempt_id"` AccountID string `json:"account_id"` ConfirmationID string `json:"confirmation_id"` ConfirmationVersion int64 `json:"confirmation_version"` State string `json:"state"` WasClaimed bool `json:"was_claimed"` } type AuditEvent struct { ID int64 `json:"id"` EventType string `json:"event_type"` AccountID string `json:"account_id,omitempty"` ConfirmationID string `json:"confirmation_id,omitempty"` ConfirmationVersion int64 `json:"confirmation_version,omitempty"` AttemptID string `json:"attempt_id,omitempty"` TaskID string `json:"task_id,omitempty"` Details json.RawMessage `json:"details"` CreatedAt time.Time `json:"created_at"` } func Open(ctx context.Context, databaseURL string) (*Store, error) { db, err := sql.Open("pgx", databaseURL) if err != nil { return nil, errors.New("open phase A database") } db.SetMaxOpenConns(10) db.SetMaxIdleConns(2) db.SetConnMaxIdleTime(5 * time.Minute) if err := db.PingContext(ctx); err != nil { db.Close() return nil, errors.New("connect to phase A database") } store := &Store{db: db} if err := store.migrate(ctx); err != nil { db.Close() return nil, err } return store, nil } func (s *Store) Close() error { return s.db.Close() } func (s *Store) migrate(ctx context.Context) error { tx, err := s.db.BeginTx(ctx, nil) if err != nil { return errors.New("begin schema migration") } defer tx.Rollback() if _, err := tx.ExecContext(ctx, `SELECT pg_advisory_xact_lock(1542738011)`); err != nil { return errors.New("lock schema migration") } if _, err := tx.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS schema_migration (version integer PRIMARY KEY, applied_at timestamptz NOT NULL DEFAULT now())`); err != nil { return errors.New("create schema migration table") } var applied bool if err := tx.QueryRowContext(ctx, `SELECT EXISTS (SELECT 1 FROM schema_migration WHERE version = 1)`).Scan(&applied); err != nil { return errors.New("read schema migration state") } if !applied { if _, err := tx.ExecContext(ctx, migration001); err != nil { return fmt.Errorf("apply schema migration 1: %w", err) } if _, err := tx.ExecContext(ctx, `INSERT INTO schema_migration (version) VALUES (1)`); err != nil { return errors.New("record schema migration 1") } } if err := tx.Commit(); err != nil { return errors.New("commit schema migration") } return nil } func (s *Store) CreateAccount(ctx context.Context, account Account) error { if !idPattern.MatchString(account.ID) || !refPattern.MatchString(account.CredentialReferenceID) || !credentialKeyPattern.MatchString(account.CredentialKey) || !refPattern.MatchString(account.ProfileID) || (account.CredentialProvider != "os_keyring" && account.CredentialProvider != "secret_manager") { return ErrInvalid } tx, err := s.db.BeginTx(ctx, nil) if err != nil { return errors.New("begin account transaction") } defer tx.Rollback() if _, err := tx.ExecContext(ctx, `INSERT INTO credential_reference (id, provider, reference_key) VALUES ($1, $2, $3)`, account.CredentialReferenceID, account.CredentialProvider, account.CredentialKey); err != nil { return publicDatabaseError(err) } if _, err := tx.ExecContext(ctx, `INSERT INTO social_account (id, credential_reference_id, profile_id) VALUES ($1, $2, $3)`, account.ID, account.CredentialReferenceID, account.ProfileID); err != nil { return publicDatabaseError(err) } if err := appendAudit(ctx, tx, "account_created", account.ID, "", 0, "", "", map[string]string{"profile_id": account.ProfileID}); err != nil { return err } return commit(tx) } func (s *Store) BindRuntime(ctx context.Context, binding RuntimeBinding) error { if !refPattern.MatchString(binding.ID) || !idPattern.MatchString(binding.AccountID) || !refPattern.MatchString(binding.RuntimeID) || !binding.LeaseUntil.After(time.Now()) { return ErrInvalid } tx, err := s.db.BeginTx(ctx, nil) if err != nil { return errors.New("begin runtime transaction") } defer tx.Rollback() if _, err := tx.ExecContext(ctx, `UPDATE runtime_instance SET released_at = now() WHERE released_at IS NULL AND lease_until < now()`); err != nil { return errors.New("expire runtime bindings") } var accountID string if err := tx.QueryRowContext(ctx, ` INSERT INTO runtime_instance (id, account_id, runtime_id, lease_until) SELECT $1, id, $3, $4 FROM social_account WHERE id = $2 AND status = 'active' RETURNING account_id`, binding.ID, binding.AccountID, binding.RuntimeID, binding.LeaseUntil).Scan(&accountID); err != nil { if errors.Is(err, sql.ErrNoRows) { return ErrConflict } return rowError(err) } if err := appendAudit(ctx, tx, "runtime_bound", accountID, "", 0, "", "", map[string]string{"runtime_id": binding.RuntimeID}); err != nil { return err } return commit(tx) } func (s *Store) ReleaseRuntime(ctx context.Context, id string) error { if !refPattern.MatchString(id) { return ErrInvalid } tx, err := s.db.BeginTx(ctx, nil) if err != nil { return errors.New("begin runtime release") } defer tx.Rollback() var accountID string if err := tx.QueryRowContext(ctx, `UPDATE runtime_instance SET released_at = now() WHERE id = $1 AND released_at IS NULL RETURNING account_id`, id).Scan(&accountID); err != nil { return rowError(err) } if err := appendAudit(ctx, tx, "runtime_released", accountID, "", 0, "", "", map[string]string{"binding_id": id}); err != nil { return err } return commit(tx) } func (s *Store) CreateDraft(ctx context.Context, draft Draft) error { if !refPattern.MatchString(draft.ID) || !idPattern.MatchString(draft.AccountID) || draft.Version < 1 || strings.TrimSpace(draft.Content) == "" { return ErrInvalid } _, err := s.db.ExecContext(ctx, `INSERT INTO content_draft (id, account_id, version, content) VALUES ($1, $2, $3, $4)`, draft.ID, draft.AccountID, draft.Version, draft.Content) return publicDatabaseError(err) } func (s *Store) Confirm(ctx context.Context, confirmation Confirmation) error { if !refPattern.MatchString(confirmation.ID) || !idPattern.MatchString(confirmation.AccountID) || !refPattern.MatchString(confirmation.DraftID) || confirmation.AccountVersion < 1 || confirmation.DraftVersion < 1 || confirmation.Version < 1 { return ErrInvalid } tx, err := s.db.BeginTx(ctx, nil) if err != nil { return errors.New("begin confirmation transaction") } defer tx.Rollback() var valid bool if err := tx.QueryRowContext(ctx, ` SELECT EXISTS ( SELECT 1 FROM social_account a JOIN content_draft d ON d.account_id = a.id WHERE a.id = $1 AND a.version = $2 AND d.id = $3 AND d.version = $4 )`, confirmation.AccountID, confirmation.AccountVersion, confirmation.DraftID, confirmation.DraftVersion).Scan(&valid); err != nil { return errors.New("validate confirmation versions") } if !valid { return ErrConflict } if _, err := tx.ExecContext(ctx, ` INSERT INTO confirmation (id, account_id, account_version, draft_id, draft_version, version) VALUES ($1, $2, $3, $4, $5, $6)`, confirmation.ID, confirmation.AccountID, confirmation.AccountVersion, confirmation.DraftID, confirmation.DraftVersion, confirmation.Version); err != nil { return publicDatabaseError(err) } if err := appendAudit(ctx, tx, "draft_confirmed", confirmation.AccountID, confirmation.ID, confirmation.Version, "", "", map[string]any{ "account_version": confirmation.AccountVersion, "draft_id": confirmation.DraftID, "draft_version": confirmation.DraftVersion, }); err != nil { return err } return commit(tx) } func (s *Store) Enqueue(ctx context.Context, task Task) (Task, bool, error) { if !refPattern.MatchString(task.ID) || !refPattern.MatchString(task.IdempotencyKey) || !idPattern.MatchString(task.AccountID) || !refPattern.MatchString(task.DraftID) || task.AccountVersion < 1 || task.DraftVersion < 1 || ((task.ConfirmationID == "") != (task.ConfirmationVersion == 0)) || (task.ConfirmationID != "" && (!refPattern.MatchString(task.ConfirmationID) || task.ConfirmationVersion < 1)) { return Task{}, false, ErrInvalid } tx, err := s.db.BeginTx(ctx, nil) if err != nil { return Task{}, false, errors.New("begin task transaction") } defer tx.Rollback() var insertedID string err = tx.QueryRowContext(ctx, ` INSERT INTO operation_task (id, idempotency_key, account_id, account_version, draft_id, draft_version, confirmation_id, confirmation_version) VALUES ($1, $2, $3, $4, $5, $6, NULLIF($7, ''), NULLIF($8, 0)) ON CONFLICT (idempotency_key) DO NOTHING RETURNING id`, task.ID, task.IdempotencyKey, task.AccountID, task.AccountVersion, task.DraftID, task.DraftVersion, task.ConfirmationID, task.ConfirmationVersion).Scan(&insertedID) if err != nil && !errors.Is(err, sql.ErrNoRows) { return Task{}, false, publicDatabaseError(err) } if insertedID != "" { task.State = "queued" if err := appendAudit(ctx, tx, "task_queued", task.AccountID, task.ConfirmationID, task.ConfirmationVersion, "", task.ID, nil); err != nil { return Task{}, false, err } if err := commit(tx); err != nil { return Task{}, false, err } return task, true, nil } var existing Task var confirmationID sql.NullString var confirmationVersion sql.NullInt64 err = tx.QueryRowContext(ctx, ` SELECT id, idempotency_key, account_id, account_version, draft_id, draft_version, confirmation_id, confirmation_version, state FROM operation_task WHERE idempotency_key = $1`, task.IdempotencyKey).Scan(&existing.ID, &existing.IdempotencyKey, &existing.AccountID, &existing.AccountVersion, &existing.DraftID, &existing.DraftVersion, &confirmationID, &confirmationVersion, &existing.State) if err != nil { return Task{}, false, rowError(err) } existing.ConfirmationID = confirmationID.String existing.ConfirmationVersion = confirmationVersion.Int64 if existing.AccountID != task.AccountID || existing.AccountVersion != task.AccountVersion || existing.DraftID != task.DraftID || existing.DraftVersion != task.DraftVersion || existing.ConfirmationID != task.ConfirmationID || existing.ConfirmationVersion != task.ConfirmationVersion { return Task{}, false, ErrConflict } if err := commit(tx); err != nil { return Task{}, false, err } return existing, false, nil } func (s *Store) PauseAccount(ctx context.Context, accountID string) error { if !idPattern.MatchString(accountID) { return ErrInvalid } tx, err := s.db.BeginTx(ctx, nil) if err != nil { return errors.New("begin pause transaction") } defer tx.Rollback() var version int64 var status string if err := tx.QueryRowContext(ctx, `SELECT version, status FROM social_account WHERE id = $1 FOR UPDATE`, accountID).Scan(&version, &status); err != nil { return rowError(err) } if status == "paused" { return commit(tx) } if err := tx.QueryRowContext(ctx, ` UPDATE social_account SET status = 'paused', paused_at = now(), version = version + 1, updated_at = now() WHERE id = $1 RETURNING version`, accountID).Scan(&version); err != nil { return errors.New("pause account") } result, err := tx.ExecContext(ctx, ` UPDATE operation_task SET state = 'policy_hold', updated_at = now() WHERE account_id = $1 AND state = 'queued'`, accountID) if err != nil { return errors.New("hold queued account tasks") } held, _ := result.RowsAffected() if err := appendAudit(ctx, tx, "account_paused", accountID, "", 0, "", "", map[string]any{"account_version": version, "tasks_held": held}); err != nil { return err } return commit(tx) } func (s *Store) CancelTask(ctx context.Context, taskID string) error { if !refPattern.MatchString(taskID) { return ErrInvalid } tx, err := s.db.BeginTx(ctx, nil) if err != nil { return errors.New("begin cancel transaction") } defer tx.Rollback() var accountID, state string var attemptID sql.NullString var confirmationID sql.NullString var confirmationVersion sql.NullInt64 err = tx.QueryRowContext(ctx, ` UPDATE operation_task SET state = CASE WHEN state = 'executing' THEN 'needs_confirmation' ELSE 'cancelled' END, lease_owner = NULL, lease_until = NULL, updated_at = now() WHERE id = $1 AND state IN ('queued', 'executing', 'needs_confirmation', 'policy_hold') RETURNING account_id, state, current_attempt_id, confirmation_id, confirmation_version`, taskID).Scan( &accountID, &state, &attemptID, &confirmationID, &confirmationVersion) if err != nil { return rowError(err) } if attemptID.Valid { if _, err := tx.ExecContext(ctx, ` UPDATE execution_attempt SET finished_at = now(), outcome = 'uncertain' WHERE id = $1 AND finished_at IS NULL`, attemptID.String); err != nil { return errors.New("mark cancelled attempt uncertain") } } if err := appendAudit(ctx, tx, "task_cancel_requested", accountID, confirmationID.String, confirmationVersion.Int64, attemptID.String, taskID, map[string]string{"state": state}); err != nil { return err } return commit(tx) } func (s *Store) ExecuteMock(ctx context.Context, workerID, outcome string) (Execution, error) { if !refPattern.MatchString(workerID) || (outcome != "succeeded" && outcome != "failed" && outcome != "uncertain" && outcome != "policy_hold") { return Execution{}, ErrInvalid } execution, err := s.claim(ctx, workerID) if err != nil || !execution.WasClaimed { return execution, err } return s.complete(ctx, workerID, execution, outcome) } func (s *Store) claim(ctx context.Context, workerID string) (Execution, error) { tx, err := s.db.BeginTx(ctx, nil) if err != nil { return Execution{}, errors.New("begin claim transaction") } defer tx.Rollback() if err := quarantineExpired(ctx, tx); err != nil { return Execution{}, err } if err := quarantineInvalid(ctx, tx); err != nil { return Execution{}, err } var execution Execution err = tx.QueryRowContext(ctx, ` WITH candidate AS ( SELECT t.id FROM operation_task t JOIN social_account a ON a.id = t.account_id JOIN content_draft d ON d.id = t.draft_id JOIN confirmation c ON c.id = t.confirmation_id WHERE t.state = 'queued' AND a.status = 'active' AND a.version = t.account_version AND d.account_id = t.account_id AND d.version = t.draft_version AND c.account_id = t.account_id AND c.account_version = t.account_version AND c.draft_id = t.draft_id AND c.draft_version = t.draft_version AND c.version = t.confirmation_version ORDER BY t.created_at, t.id FOR UPDATE OF t SKIP LOCKED LIMIT 1 ) UPDATE operation_task t SET state = 'executing', lease_owner = $1, lease_until = now() + interval '1 minute', updated_at = now() FROM candidate WHERE t.id = candidate.id RETURNING t.id, t.account_id, t.confirmation_id, t.confirmation_version`, workerID).Scan( &execution.TaskID, &execution.AccountID, &execution.ConfirmationID, &execution.ConfirmationVersion) if errors.Is(err, sql.ErrNoRows) { if err := commit(tx); err != nil { return Execution{}, err } return Execution{}, nil } if err != nil { return Execution{}, errors.New("claim task") } execution.AttemptID = newID() execution.State = "executing" execution.WasClaimed = true if _, err := tx.ExecContext(ctx, `INSERT INTO execution_attempt (id, task_id) VALUES ($1, $2)`, execution.AttemptID, execution.TaskID); err != nil { return Execution{}, publicDatabaseError(err) } if _, err := tx.ExecContext(ctx, `UPDATE operation_task SET current_attempt_id = $1 WHERE id = $2`, execution.AttemptID, execution.TaskID); err != nil { return Execution{}, errors.New("attach execution attempt") } if err := appendAudit(ctx, tx, "task_claimed", execution.AccountID, execution.ConfirmationID, execution.ConfirmationVersion, execution.AttemptID, execution.TaskID, map[string]string{"worker_id": workerID}); err != nil { return Execution{}, err } if err := commit(tx); err != nil { return Execution{}, err } return execution, nil } func (s *Store) complete(ctx context.Context, workerID string, execution Execution, outcome string) (Execution, error) { tx, err := s.db.BeginTx(ctx, nil) if err != nil { return Execution{}, errors.New("begin completion transaction") } defer tx.Rollback() var leaseUntil time.Time err = tx.QueryRowContext(ctx, ` SELECT lease_until FROM operation_task WHERE id = $1 AND state = 'executing' AND lease_owner = $2 AND current_attempt_id = $3 FOR UPDATE`, execution.TaskID, workerID, execution.AttemptID).Scan(&leaseUntil) if err != nil { return Execution{}, rowError(err) } state := map[string]string{"succeeded": "succeeded", "failed": "failed", "uncertain": "needs_confirmation", "policy_hold": "policy_hold"}[outcome] if leaseUntil.Before(time.Now()) { outcome, state = "uncertain", "needs_confirmation" } result, _ := json.Marshal(map[string]string{"mock_outcome": outcome}) if _, err := tx.ExecContext(ctx, `UPDATE execution_attempt SET finished_at = now(), outcome = $1, result = $2 WHERE id = $3`, outcome, result, execution.AttemptID); err != nil { return Execution{}, errors.New("finish execution attempt") } if _, err := tx.ExecContext(ctx, ` UPDATE operation_task SET state = $1, lease_owner = NULL, lease_until = NULL, updated_at = now() WHERE id = $2`, state, execution.TaskID); err != nil { return Execution{}, errors.New("finish task") } if err := appendAudit(ctx, tx, "task_finished", execution.AccountID, execution.ConfirmationID, execution.ConfirmationVersion, execution.AttemptID, execution.TaskID, map[string]string{"state": state}); err != nil { return Execution{}, err } if err := commit(tx); err != nil { return Execution{}, err } execution.State = state return execution, nil } func quarantineExpired(ctx context.Context, tx *sql.Tx) error { rows, err := tx.QueryContext(ctx, ` UPDATE operation_task SET state = 'needs_confirmation', lease_owner = NULL, lease_until = NULL, updated_at = now() WHERE state = 'executing' AND lease_until < now() RETURNING id, account_id, current_attempt_id, confirmation_id, confirmation_version`) if err != nil { return errors.New("quarantine expired tasks") } type expiredTask struct { taskID, accountID, attemptID, confirmationID string confirmationVersion int64 } var expired []expiredTask for rows.Next() { var taskID, accountID string var attemptID sql.NullString var confirmationID sql.NullString var confirmationVersion sql.NullInt64 if err := rows.Scan(&taskID, &accountID, &attemptID, &confirmationID, &confirmationVersion); err != nil { rows.Close() return errors.New("read expired task") } expired = append(expired, expiredTask{taskID, accountID, attemptID.String, confirmationID.String, confirmationVersion.Int64}) } if err := rows.Close(); err != nil { return errors.New("close expired task rows") } for _, task := range expired { if task.attemptID != "" { if _, err := tx.ExecContext(ctx, `UPDATE execution_attempt SET finished_at = now(), outcome = 'uncertain' WHERE id = $1 AND finished_at IS NULL`, task.attemptID); err != nil { return errors.New("mark expired attempt uncertain") } } if err := appendAudit(ctx, tx, "task_lease_expired", task.accountID, task.confirmationID, task.confirmationVersion, task.attemptID, task.taskID, nil); err != nil { return err } } return nil } func quarantineInvalid(ctx context.Context, tx *sql.Tx) error { rows, err := tx.QueryContext(ctx, ` UPDATE operation_task t SET state = CASE WHEN EXISTS ( SELECT 1 FROM social_account paused WHERE paused.id = t.account_id AND paused.status = 'paused' ) THEN 'policy_hold' ELSE 'needs_confirmation' END, updated_at = now() WHERE t.state = 'queued' AND NOT EXISTS ( SELECT 1 FROM social_account a JOIN content_draft d ON d.id = t.draft_id JOIN confirmation c ON c.id = t.confirmation_id WHERE a.id = t.account_id AND a.status = 'active' AND a.version = t.account_version AND d.account_id = t.account_id AND d.version = t.draft_version AND c.account_id = t.account_id AND c.account_version = t.account_version AND c.draft_id = t.draft_id AND c.draft_version = t.draft_version AND c.version = t.confirmation_version ) RETURNING t.id, t.account_id, t.confirmation_id, t.confirmation_version, t.state`) if err != nil { return errors.New("quarantine invalid tasks") } type invalidTask struct { taskID, accountID, confirmationID, state string confirmationVersion int64 } var invalid []invalidTask for rows.Next() { var taskID, accountID, state string var confirmationID sql.NullString var confirmationVersion sql.NullInt64 if err := rows.Scan(&taskID, &accountID, &confirmationID, &confirmationVersion, &state); err != nil { rows.Close() return errors.New("read quarantined task") } invalid = append(invalid, invalidTask{taskID, accountID, confirmationID.String, state, confirmationVersion.Int64}) } if err := rows.Close(); err != nil { return errors.New("close quarantined task rows") } for _, task := range invalid { if err := appendAudit(ctx, tx, "task_quarantined", task.accountID, task.confirmationID, task.confirmationVersion, "", task.taskID, map[string]string{"state": task.state}); err != nil { return err } } return nil } func (s *Store) Audit(ctx context.Context) ([]AuditEvent, error) { rows, err := s.db.QueryContext(ctx, ` SELECT id, event_type, account_id, confirmation_id, confirmation_version, attempt_id, task_id, details, created_at FROM audit_event ORDER BY id`) if err != nil { return nil, errors.New("read audit events") } defer rows.Close() var events []AuditEvent for rows.Next() { var event AuditEvent var accountID, confirmationID, attemptID, taskID sql.NullString var confirmationVersion sql.NullInt64 if err := rows.Scan(&event.ID, &event.EventType, &accountID, &confirmationID, &confirmationVersion, &attemptID, &taskID, &event.Details, &event.CreatedAt); err != nil { return nil, errors.New("decode audit event") } event.AccountID, event.ConfirmationID, event.ConfirmationVersion = accountID.String, confirmationID.String, confirmationVersion.Int64 event.AttemptID, event.TaskID = attemptID.String, taskID.String events = append(events, event) } return events, rows.Err() } func appendAudit(ctx context.Context, tx *sql.Tx, eventType, accountID, confirmationID string, confirmationVersion int64, attemptID, taskID string, details any) error { if details == nil { details = map[string]any{} } encoded, err := json.Marshal(details) if err != nil { return errors.New("encode audit details") } _, err = tx.ExecContext(ctx, ` INSERT INTO audit_event (event_type, account_id, confirmation_id, confirmation_version, attempt_id, task_id, details) VALUES ($1, NULLIF($2, ''), NULLIF($3, ''), NULLIF($4, 0), NULLIF($5, ''), NULLIF($6, ''), $7)`, eventType, accountID, confirmationID, confirmationVersion, attemptID, taskID, encoded) if err != nil { return errors.New("append audit event") } return nil } func newID() string { var value [16]byte _, _ = rand.Read(value[:]) return hex.EncodeToString(value[:]) } func commit(tx *sql.Tx) error { if err := tx.Commit(); err != nil { return errors.New("commit transaction") } return nil } func rowError(err error) error { if errors.Is(err, sql.ErrNoRows) { return ErrNotFound } return publicDatabaseError(err) } func publicDatabaseError(err error) error { if err == nil { return nil } var postgresError *pgconn.PgError if errors.As(err, &postgresError) && (postgresError.Code == "23505" || postgresError.Code == "23503" || postgresError.Code == "23514") { return ErrConflict } return errors.New("phase A persistence operation failed") }