mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-10 18:36:45 +08:00
* feat(billing): notify workspaces about autopilot quota Co-authored-by: multica-agent <[email protected]> * fix(billing): isolate and throttle quota notices Co-authored-by: multica-agent <[email protected]> * fix(billing): tighten quota notice delivery Co-authored-by: multica-agent <[email protected]> * fix(billing): reuse quota admission snapshot Co-authored-by: multica-agent <[email protected]> * fix(autopilot): make quota notices actionable Co-authored-by: multica-agent <[email protected]> * fix(billing): align quota recovery notifications Co-authored-by: multica-agent <[email protected]> * fix(billing): align quota notice recipients Co-authored-by: multica-agent <[email protected]> * fix(billing): finish quota notice review Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> Co-authored-by: Sol-Boy <[email protected]>
433 lines
14 KiB
Go
433 lines
14 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: autopilot_quota.sql
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const consumeAutopilotQuotaReservation = `-- name: ConsumeAutopilotQuotaReservation :one
|
|
WITH locked AS (
|
|
SELECT qr.id, qr.workspace_id, qr.period_start, qr.period_end, qr.policy_revision, qr.subscription_version, qr.source, qr.idempotency_key, qr.state, qr.created_at, qr.finalized_at FROM autopilot_quota_reservation qr
|
|
WHERE qr.id = $1 AND qr.state = 'reserved'
|
|
FOR UPDATE
|
|
), changed AS (
|
|
UPDATE autopilot_quota_reservation AS r
|
|
SET state = 'consumed', finalized_at = now()
|
|
FROM locked
|
|
WHERE r.id = locked.id
|
|
AND EXISTS (
|
|
SELECT 1 FROM autopilot_quota_period p
|
|
WHERE p.workspace_id = locked.workspace_id
|
|
AND p.period_start = locked.period_start
|
|
AND p.period_end = locked.period_end
|
|
)
|
|
RETURNING locked.workspace_id, locked.period_start, locked.period_end
|
|
)
|
|
UPDATE autopilot_quota_period AS p
|
|
SET reserved_count = reserved_count - 1,
|
|
used_count = used_count + 1,
|
|
updated_at = now()
|
|
FROM changed
|
|
WHERE p.workspace_id = changed.workspace_id
|
|
AND p.period_start = changed.period_start
|
|
AND p.period_end = changed.period_end
|
|
RETURNING p.workspace_id, p.period_start, p.period_end, p.used_count, p.reserved_count, p.blocked_counts, p.created_at, p.updated_at, p.rejection_notified_at
|
|
`
|
|
|
|
// used_count is monotonic within a period: consuming a reserved slot is the
|
|
// only write that changes it, and no release path decrements it.
|
|
func (q *Queries) ConsumeAutopilotQuotaReservation(ctx context.Context, reservationID pgtype.UUID) (AutopilotQuotaPeriod, error) {
|
|
row := q.db.QueryRow(ctx, consumeAutopilotQuotaReservation, reservationID)
|
|
var i AutopilotQuotaPeriod
|
|
err := row.Scan(
|
|
&i.WorkspaceID,
|
|
&i.PeriodStart,
|
|
&i.PeriodEnd,
|
|
&i.UsedCount,
|
|
&i.ReservedCount,
|
|
&i.BlockedCounts,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RejectionNotifiedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const createAutopilotQuotaReservation = `-- name: CreateAutopilotQuotaReservation :one
|
|
INSERT INTO autopilot_quota_reservation (
|
|
workspace_id, period_start, period_end, policy_revision,
|
|
subscription_version, source, idempotency_key
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
RETURNING id, workspace_id, period_start, period_end, policy_revision, subscription_version, source, idempotency_key, state, created_at, finalized_at
|
|
`
|
|
|
|
type CreateAutopilotQuotaReservationParams struct {
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
PeriodStart pgtype.Timestamptz `json:"period_start"`
|
|
PeriodEnd pgtype.Timestamptz `json:"period_end"`
|
|
PolicyRevision int64 `json:"policy_revision"`
|
|
SubscriptionVersion int64 `json:"subscription_version"`
|
|
Source string `json:"source"`
|
|
IdempotencyKey string `json:"idempotency_key"`
|
|
}
|
|
|
|
func (q *Queries) CreateAutopilotQuotaReservation(ctx context.Context, arg CreateAutopilotQuotaReservationParams) (AutopilotQuotaReservation, error) {
|
|
row := q.db.QueryRow(ctx, createAutopilotQuotaReservation,
|
|
arg.WorkspaceID,
|
|
arg.PeriodStart,
|
|
arg.PeriodEnd,
|
|
arg.PolicyRevision,
|
|
arg.SubscriptionVersion,
|
|
arg.Source,
|
|
arg.IdempotencyKey,
|
|
)
|
|
var i AutopilotQuotaReservation
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.PeriodStart,
|
|
&i.PeriodEnd,
|
|
&i.PolicyRevision,
|
|
&i.SubscriptionVersion,
|
|
&i.Source,
|
|
&i.IdempotencyKey,
|
|
&i.State,
|
|
&i.CreatedAt,
|
|
&i.FinalizedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const ensureAutopilotQuotaPeriod = `-- name: EnsureAutopilotQuotaPeriod :one
|
|
INSERT INTO autopilot_quota_period (workspace_id, period_start, period_end)
|
|
VALUES ($1, $2, $3)
|
|
ON CONFLICT (workspace_id, period_start, period_end) DO UPDATE
|
|
SET updated_at = autopilot_quota_period.updated_at
|
|
RETURNING workspace_id, period_start, period_end, used_count, reserved_count, blocked_counts, created_at, updated_at, rejection_notified_at
|
|
`
|
|
|
|
type EnsureAutopilotQuotaPeriodParams struct {
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
PeriodStart pgtype.Timestamptz `json:"period_start"`
|
|
PeriodEnd pgtype.Timestamptz `json:"period_end"`
|
|
}
|
|
|
|
// The no-op update is intentional: ON CONFLICT UPDATE locks the period row for
|
|
// the caller's transaction, serialising admission for one workspace/period.
|
|
func (q *Queries) EnsureAutopilotQuotaPeriod(ctx context.Context, arg EnsureAutopilotQuotaPeriodParams) (AutopilotQuotaPeriod, error) {
|
|
row := q.db.QueryRow(ctx, ensureAutopilotQuotaPeriod, arg.WorkspaceID, arg.PeriodStart, arg.PeriodEnd)
|
|
var i AutopilotQuotaPeriod
|
|
err := row.Scan(
|
|
&i.WorkspaceID,
|
|
&i.PeriodStart,
|
|
&i.PeriodEnd,
|
|
&i.UsedCount,
|
|
&i.ReservedCount,
|
|
&i.BlockedCounts,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RejectionNotifiedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getAutopilotQuotaPeriod = `-- name: GetAutopilotQuotaPeriod :one
|
|
SELECT workspace_id, period_start, period_end, used_count, reserved_count, blocked_counts, created_at, updated_at, rejection_notified_at FROM autopilot_quota_period
|
|
WHERE workspace_id = $1 AND period_start = $2 AND period_end = $3
|
|
`
|
|
|
|
type GetAutopilotQuotaPeriodParams struct {
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
PeriodStart pgtype.Timestamptz `json:"period_start"`
|
|
PeriodEnd pgtype.Timestamptz `json:"period_end"`
|
|
}
|
|
|
|
func (q *Queries) GetAutopilotQuotaPeriod(ctx context.Context, arg GetAutopilotQuotaPeriodParams) (AutopilotQuotaPeriod, error) {
|
|
row := q.db.QueryRow(ctx, getAutopilotQuotaPeriod, arg.WorkspaceID, arg.PeriodStart, arg.PeriodEnd)
|
|
var i AutopilotQuotaPeriod
|
|
err := row.Scan(
|
|
&i.WorkspaceID,
|
|
&i.PeriodStart,
|
|
&i.PeriodEnd,
|
|
&i.UsedCount,
|
|
&i.ReservedCount,
|
|
&i.BlockedCounts,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RejectionNotifiedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getAutopilotQuotaReservationByKey = `-- name: GetAutopilotQuotaReservationByKey :one
|
|
SELECT id, workspace_id, period_start, period_end, policy_revision, subscription_version, source, idempotency_key, state, created_at, finalized_at FROM autopilot_quota_reservation
|
|
WHERE workspace_id = $1
|
|
AND period_start = $2
|
|
AND period_end = $3
|
|
AND idempotency_key = $4
|
|
AND state <> 'released'
|
|
`
|
|
|
|
type GetAutopilotQuotaReservationByKeyParams struct {
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
PeriodStart pgtype.Timestamptz `json:"period_start"`
|
|
PeriodEnd pgtype.Timestamptz `json:"period_end"`
|
|
IdempotencyKey string `json:"idempotency_key"`
|
|
}
|
|
|
|
func (q *Queries) GetAutopilotQuotaReservationByKey(ctx context.Context, arg GetAutopilotQuotaReservationByKeyParams) (AutopilotQuotaReservation, error) {
|
|
row := q.db.QueryRow(ctx, getAutopilotQuotaReservationByKey,
|
|
arg.WorkspaceID,
|
|
arg.PeriodStart,
|
|
arg.PeriodEnd,
|
|
arg.IdempotencyKey,
|
|
)
|
|
var i AutopilotQuotaReservation
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.PeriodStart,
|
|
&i.PeriodEnd,
|
|
&i.PolicyRevision,
|
|
&i.SubscriptionVersion,
|
|
&i.Source,
|
|
&i.IdempotencyKey,
|
|
&i.State,
|
|
&i.CreatedAt,
|
|
&i.FinalizedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const incrementAutopilotQuotaBlocked = `-- name: IncrementAutopilotQuotaBlocked :one
|
|
UPDATE autopilot_quota_period
|
|
SET blocked_counts = jsonb_set(
|
|
blocked_counts,
|
|
ARRAY[$1::text],
|
|
to_jsonb(COALESCE((blocked_counts ->> $1::text)::bigint, 0) + 1),
|
|
true
|
|
),
|
|
updated_at = now()
|
|
WHERE workspace_id = $2
|
|
AND period_start = $3
|
|
AND period_end = $4
|
|
RETURNING workspace_id, period_start, period_end, used_count, reserved_count, blocked_counts, created_at, updated_at, rejection_notified_at
|
|
`
|
|
|
|
type IncrementAutopilotQuotaBlockedParams struct {
|
|
Source string `json:"source"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
PeriodStart pgtype.Timestamptz `json:"period_start"`
|
|
PeriodEnd pgtype.Timestamptz `json:"period_end"`
|
|
}
|
|
|
|
func (q *Queries) IncrementAutopilotQuotaBlocked(ctx context.Context, arg IncrementAutopilotQuotaBlockedParams) (AutopilotQuotaPeriod, error) {
|
|
row := q.db.QueryRow(ctx, incrementAutopilotQuotaBlocked,
|
|
arg.Source,
|
|
arg.WorkspaceID,
|
|
arg.PeriodStart,
|
|
arg.PeriodEnd,
|
|
)
|
|
var i AutopilotQuotaPeriod
|
|
err := row.Scan(
|
|
&i.WorkspaceID,
|
|
&i.PeriodStart,
|
|
&i.PeriodEnd,
|
|
&i.UsedCount,
|
|
&i.ReservedCount,
|
|
&i.BlockedCounts,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RejectionNotifiedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const incrementAutopilotQuotaReserved = `-- name: IncrementAutopilotQuotaReserved :one
|
|
UPDATE autopilot_quota_period
|
|
SET reserved_count = reserved_count + 1,
|
|
updated_at = now()
|
|
WHERE workspace_id = $1 AND period_start = $2 AND period_end = $3
|
|
RETURNING workspace_id, period_start, period_end, used_count, reserved_count, blocked_counts, created_at, updated_at, rejection_notified_at
|
|
`
|
|
|
|
type IncrementAutopilotQuotaReservedParams struct {
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
PeriodStart pgtype.Timestamptz `json:"period_start"`
|
|
PeriodEnd pgtype.Timestamptz `json:"period_end"`
|
|
}
|
|
|
|
func (q *Queries) IncrementAutopilotQuotaReserved(ctx context.Context, arg IncrementAutopilotQuotaReservedParams) (AutopilotQuotaPeriod, error) {
|
|
row := q.db.QueryRow(ctx, incrementAutopilotQuotaReserved, arg.WorkspaceID, arg.PeriodStart, arg.PeriodEnd)
|
|
var i AutopilotQuotaPeriod
|
|
err := row.Scan(
|
|
&i.WorkspaceID,
|
|
&i.PeriodStart,
|
|
&i.PeriodEnd,
|
|
&i.UsedCount,
|
|
&i.ReservedCount,
|
|
&i.BlockedCounts,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RejectionNotifiedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const listRecoverableAutopilotQuotaReservations = `-- name: ListRecoverableAutopilotQuotaReservations :many
|
|
SELECT r.id, r.workspace_id, r.period_start, r.period_end, r.policy_revision, r.subscription_version, r.source, r.idempotency_key, r.state, r.created_at, r.finalized_at
|
|
FROM autopilot_quota_reservation r
|
|
LEFT JOIN autopilot_run ar ON ar.quota_reservation_id = r.id
|
|
WHERE r.state = 'reserved'
|
|
AND (
|
|
(
|
|
r.created_at < $1
|
|
AND (ar.id IS NULL OR ar.status IN ('completed', 'failed', 'skipped'))
|
|
)
|
|
OR (
|
|
-- Manual/API requests have no durable retry owner. Reclaim only an
|
|
-- hours-old partial run with neither a linked side effect nor even
|
|
-- an unlinked task row; schedule and webhook retries repair their
|
|
-- own partial state and are deliberately excluded.
|
|
r.created_at < $2
|
|
AND ar.source IN ('manual', 'api')
|
|
AND (
|
|
ar.status = 'pending'
|
|
OR (ar.status = 'issue_created' AND ar.issue_id IS NULL)
|
|
OR (ar.status = 'running' AND ar.task_id IS NULL)
|
|
)
|
|
AND NOT EXISTS (
|
|
SELECT 1
|
|
FROM agent_task_queue task
|
|
WHERE task.autopilot_run_id = ar.id
|
|
)
|
|
)
|
|
)
|
|
ORDER BY r.created_at
|
|
LIMIT $3
|
|
`
|
|
|
|
type ListRecoverableAutopilotQuotaReservationsParams struct {
|
|
TerminalCreatedBefore pgtype.Timestamptz `json:"terminal_created_before"`
|
|
PartialCreatedBefore pgtype.Timestamptz `json:"partial_created_before"`
|
|
RowLimit int32 `json:"row_limit"`
|
|
}
|
|
|
|
func (q *Queries) ListRecoverableAutopilotQuotaReservations(ctx context.Context, arg ListRecoverableAutopilotQuotaReservationsParams) ([]AutopilotQuotaReservation, error) {
|
|
rows, err := q.db.Query(ctx, listRecoverableAutopilotQuotaReservations, arg.TerminalCreatedBefore, arg.PartialCreatedBefore, arg.RowLimit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []AutopilotQuotaReservation{}
|
|
for rows.Next() {
|
|
var i AutopilotQuotaReservation
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.PeriodStart,
|
|
&i.PeriodEnd,
|
|
&i.PolicyRevision,
|
|
&i.SubscriptionVersion,
|
|
&i.Source,
|
|
&i.IdempotencyKey,
|
|
&i.State,
|
|
&i.CreatedAt,
|
|
&i.FinalizedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const markAutopilotQuotaRejectionNotified = `-- name: MarkAutopilotQuotaRejectionNotified :one
|
|
UPDATE autopilot_quota_period
|
|
SET rejection_notified_at = COALESCE(rejection_notified_at, now()),
|
|
updated_at = now()
|
|
WHERE workspace_id = $1
|
|
AND period_start = $2
|
|
AND period_end = $3
|
|
RETURNING workspace_id, period_start, period_end, used_count, reserved_count, blocked_counts, created_at, updated_at, rejection_notified_at
|
|
`
|
|
|
|
type MarkAutopilotQuotaRejectionNotifiedParams struct {
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
PeriodStart pgtype.Timestamptz `json:"period_start"`
|
|
PeriodEnd pgtype.Timestamptz `json:"period_end"`
|
|
}
|
|
|
|
func (q *Queries) MarkAutopilotQuotaRejectionNotified(ctx context.Context, arg MarkAutopilotQuotaRejectionNotifiedParams) (AutopilotQuotaPeriod, error) {
|
|
row := q.db.QueryRow(ctx, markAutopilotQuotaRejectionNotified, arg.WorkspaceID, arg.PeriodStart, arg.PeriodEnd)
|
|
var i AutopilotQuotaPeriod
|
|
err := row.Scan(
|
|
&i.WorkspaceID,
|
|
&i.PeriodStart,
|
|
&i.PeriodEnd,
|
|
&i.UsedCount,
|
|
&i.ReservedCount,
|
|
&i.BlockedCounts,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RejectionNotifiedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const releaseAutopilotQuotaReservation = `-- name: ReleaseAutopilotQuotaReservation :one
|
|
WITH locked AS (
|
|
SELECT qr.id, qr.workspace_id, qr.period_start, qr.period_end, qr.policy_revision, qr.subscription_version, qr.source, qr.idempotency_key, qr.state, qr.created_at, qr.finalized_at FROM autopilot_quota_reservation qr
|
|
WHERE qr.id = $1 AND qr.state = 'reserved'
|
|
FOR UPDATE
|
|
), changed AS (
|
|
UPDATE autopilot_quota_reservation AS r
|
|
SET state = 'released', finalized_at = now()
|
|
FROM locked
|
|
WHERE r.id = locked.id
|
|
AND EXISTS (
|
|
SELECT 1 FROM autopilot_quota_period p
|
|
WHERE p.workspace_id = locked.workspace_id
|
|
AND p.period_start = locked.period_start
|
|
AND p.period_end = locked.period_end
|
|
)
|
|
RETURNING locked.workspace_id, locked.period_start, locked.period_end
|
|
)
|
|
UPDATE autopilot_quota_period AS p
|
|
SET reserved_count = reserved_count - 1,
|
|
updated_at = now()
|
|
FROM changed
|
|
WHERE p.workspace_id = changed.workspace_id
|
|
AND p.period_start = changed.period_start
|
|
AND p.period_end = changed.period_end
|
|
RETURNING p.workspace_id, p.period_start, p.period_end, p.used_count, p.reserved_count, p.blocked_counts, p.created_at, p.updated_at, p.rejection_notified_at
|
|
`
|
|
|
|
// Release is intentionally limited to still-reserved work. A consumed
|
|
// create_issue slot remains counted after cancellation, blocking, or deletion.
|
|
func (q *Queries) ReleaseAutopilotQuotaReservation(ctx context.Context, reservationID pgtype.UUID) (AutopilotQuotaPeriod, error) {
|
|
row := q.db.QueryRow(ctx, releaseAutopilotQuotaReservation, reservationID)
|
|
var i AutopilotQuotaPeriod
|
|
err := row.Scan(
|
|
&i.WorkspaceID,
|
|
&i.PeriodStart,
|
|
&i.PeriodEnd,
|
|
&i.UsedCount,
|
|
&i.ReservedCount,
|
|
&i.BlockedCounts,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RejectionNotifiedAt,
|
|
)
|
|
return i, err
|
|
}
|