Extract ExtendGrantTx, RevokeGrantTx, and ExpireGrantTx into internal/entitlements so the operator handlers, expiry activity, and demo seed all run the same conferral path. The seed plays a five-grant history on Carlos's org; pin-timestamps now uses dense-rank offsets and transition list ordering ties break by transition_id.
217 lines
6.8 KiB
Go
217 lines
6.8 KiB
Go
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
|
|
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
|
|
|
|
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.29.0
|
|
// source: pool_provision_transitions.sql
|
|
|
|
package entitlements
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
const listRecentTransitions = `-- name: ListRecentTransitions :many
|
|
SELECT
|
|
t.transition_id,
|
|
t.pool_id,
|
|
rp.org_id,
|
|
t.transition_type,
|
|
t.actor_type,
|
|
t.actor_id,
|
|
t.from_rank,
|
|
t.to_rank,
|
|
t.effective_at
|
|
FROM core.pool_provision_transitions t
|
|
JOIN core.resource_pools rp ON rp.pool_id = t.pool_id
|
|
ORDER BY t.effective_at DESC
|
|
LIMIT $1
|
|
`
|
|
|
|
type ListRecentTransitionsRow struct {
|
|
TransitionID string `json:"transition_id"`
|
|
PoolID string `json:"pool_id"`
|
|
OrgID string `json:"org_id"`
|
|
TransitionType string `json:"transition_type"`
|
|
ActorType string `json:"actor_type"`
|
|
ActorID uuid.NullUUID `json:"actor_id"`
|
|
FromRank sql.NullInt32 `json:"from_rank"`
|
|
ToRank sql.NullInt32 `json:"to_rank"`
|
|
EffectiveAt time.Time `json:"effective_at"`
|
|
}
|
|
|
|
// Recent transitions for the operator landing activity timeline. Joins to
|
|
// core.resource_pools to resolve the org_id (same schema, safe for
|
|
// sqlc). Operator-actor transitions carry actor_id; system/webhook actors
|
|
// don't, so person_id is nullable in the result.
|
|
func (q *Queries) ListRecentTransitions(ctx context.Context, limit int32) ([]ListRecentTransitionsRow, error) {
|
|
rows, err := q.db.QueryContext(ctx, listRecentTransitions, limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListRecentTransitionsRow{}
|
|
for rows.Next() {
|
|
var i ListRecentTransitionsRow
|
|
if err := rows.Scan(
|
|
&i.TransitionID,
|
|
&i.PoolID,
|
|
&i.OrgID,
|
|
&i.TransitionType,
|
|
&i.ActorType,
|
|
&i.ActorID,
|
|
&i.FromRank,
|
|
&i.ToRank,
|
|
&i.EffectiveAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listTransitionsByPool = `-- name: ListTransitionsByPool :many
|
|
|
|
SELECT
|
|
t.transition_id, t.pool_id, t.provision_id, t.plan_ladder_id, t.from_rank, t.to_rank, t.transition_type, t.actor_type, t.actor_id, t.reason, t.effective_at, t.created_at, t.provision_ladder_id,
|
|
p.grant_id,
|
|
g.grant_reason AS grant_reason,
|
|
g.description AS grant_description
|
|
FROM core.pool_provision_transitions t
|
|
LEFT JOIN core.pool_provisions p ON p.provision_id = t.provision_id
|
|
LEFT JOIN core.grants g ON g.grant_id = p.grant_id
|
|
WHERE t.pool_id = $1
|
|
ORDER BY t.effective_at DESC, t.created_at DESC, t.transition_id DESC
|
|
`
|
|
|
|
type ListTransitionsByPoolRow struct {
|
|
TransitionID string `json:"transition_id"`
|
|
PoolID string `json:"pool_id"`
|
|
ProvisionID uuid.NullUUID `json:"provision_id"`
|
|
PlanLadderID string `json:"plan_ladder_id"`
|
|
FromRank sql.NullInt32 `json:"from_rank"`
|
|
ToRank sql.NullInt32 `json:"to_rank"`
|
|
TransitionType string `json:"transition_type"`
|
|
ActorType string `json:"actor_type"`
|
|
ActorID uuid.NullUUID `json:"actor_id"`
|
|
Reason sql.NullString `json:"reason"`
|
|
EffectiveAt time.Time `json:"effective_at"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
ProvisionLadderID uuid.NullUUID `json:"provision_ladder_id"`
|
|
GrantID uuid.NullUUID `json:"grant_id"`
|
|
GrantReason sql.NullString `json:"grant_reason"`
|
|
GrantDescription sql.NullString `json:"grant_description"`
|
|
}
|
|
|
|
// Writes to core.pool_provision_transitions go through the conferral functions
|
|
// (queries/conferral.sql) alone; core_writer holds no direct DML on this
|
|
// table (migration 00005 enclosure). Only read paths live here.
|
|
// Joins to core.pool_provisions (on provision_id) and core.grants (on
|
|
// grant_id) so the Tier changes view can tell a grant-backed row from any
|
|
// other: a row whose provision came from a grant renders that grant's
|
|
// reason and description (tier-changes-ledger design D4) instead of the
|
|
// transition's own reason string, which Issue grant and Extend fill with
|
|
// the operator's free-text note. Both joins are LEFT JOINs -- a
|
|
// subscription- or purchase-sourced provision has no grant, and an
|
|
// expiry/revocation/cancellation's restorative row has no provision_id at
|
|
// all in some paths -- so a row with neither join match still renders via
|
|
// its own reason. The transition id (uuidv7, rising with insertion) ends the
|
|
// ORDER BY so the order is total: the two rows one act writes share
|
|
// effective_at and created_at (one transaction), and the snapshot's clock
|
|
// pin keeps such ties tied; the later-written row (a restoration after an
|
|
// end) lists first.
|
|
func (q *Queries) ListTransitionsByPool(ctx context.Context, poolID string) ([]ListTransitionsByPoolRow, error) {
|
|
rows, err := q.db.QueryContext(ctx, listTransitionsByPool, poolID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListTransitionsByPoolRow{}
|
|
for rows.Next() {
|
|
var i ListTransitionsByPoolRow
|
|
if err := rows.Scan(
|
|
&i.TransitionID,
|
|
&i.PoolID,
|
|
&i.ProvisionID,
|
|
&i.PlanLadderID,
|
|
&i.FromRank,
|
|
&i.ToRank,
|
|
&i.TransitionType,
|
|
&i.ActorType,
|
|
&i.ActorID,
|
|
&i.Reason,
|
|
&i.EffectiveAt,
|
|
&i.CreatedAt,
|
|
&i.ProvisionLadderID,
|
|
&i.GrantID,
|
|
&i.GrantReason,
|
|
&i.GrantDescription,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listTransitionsByProvision = `-- name: ListTransitionsByProvision :many
|
|
SELECT transition_id, pool_id, provision_id, plan_ladder_id, from_rank, to_rank, transition_type, actor_type, actor_id, reason, effective_at, created_at, provision_ladder_id FROM core.pool_provision_transitions
|
|
WHERE provision_id = $1
|
|
ORDER BY effective_at ASC, created_at ASC, transition_id ASC
|
|
`
|
|
|
|
func (q *Queries) ListTransitionsByProvision(ctx context.Context, provisionID uuid.NullUUID) ([]PoolProvisionTransition, error) {
|
|
rows, err := q.db.QueryContext(ctx, listTransitionsByProvision, provisionID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []PoolProvisionTransition{}
|
|
for rows.Next() {
|
|
var i PoolProvisionTransition
|
|
if err := rows.Scan(
|
|
&i.TransitionID,
|
|
&i.PoolID,
|
|
&i.ProvisionID,
|
|
&i.PlanLadderID,
|
|
&i.FromRank,
|
|
&i.ToRank,
|
|
&i.TransitionType,
|
|
&i.ActorType,
|
|
&i.ActorID,
|
|
&i.Reason,
|
|
&i.EffectiveAt,
|
|
&i.CreatedAt,
|
|
&i.ProvisionLadderID,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|