-- name: CreateGrant :one INSERT INTO core.grants (product_id, granted_to_org_id, granted_by_person_id, grant_reason, description, quantity, valid_from, valid_until, extends_grant_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *; -- name: GetGrantByID :one SELECT * FROM core.grants WHERE grant_id = $1; -- name: ListGrantsByOrgID :many SELECT * FROM core.grants WHERE granted_to_org_id = $1 ORDER BY created_at DESC; -- name: ListGrantsWithDelivery :many -- The one shared delivery-state derivation (ux-honest-surfaces design -- decision 1): every operator surface that lists grants renders from this -- query so the grants index and the org-detail composite can never -- disagree about what "live" / "superseded" / "inactive" mean. -- -- live -> at least one linked pool_provision is status='active' -- superseded -> not live, AND a later grant actually names this one as -- its extends_grant_id ancestor (a replacement was issued -- -- see replaced_by_grant_id below) -- inactive -> not live and not superseded: revoked/expired with no -- recorded successor, or a grant that never delivered -- -- This is deliberately narrower than "not live" alone: a grant whose -- provision simply ended (revoked, expired) with no replacement grant on -- record is "inactive", not "superseded" -- "superseded" is reserved for -- the case the UI can point at a specific replacing grant (the lineage -- requirement: a superseded row must be able to name what replaced it). -- -- replaced_by_grant_id is the most recently created grant (if any) whose -- extends_grant_id points at this row; NULL when nothing replaced it. -- extends_grant_id (this grant's own ancestor pointer) is passed through -- unchanged so the UI can walk lineage in either direction. -- -- Off-ladder grants (add-ons / usage / one-time) have no ladder attachment, -- so activated_at / ended_at may be NULL even when delivery_state='live'. -- Ordered live-first, then created_at DESC so the operationally-relevant -- row appears at the top with history beneath. -- -- sqlc.narg(org_id): NULL returns every grant system-wide (the grants -- index, /operator/grants); set, scopes to one organization (org-detail). SELECT g.grant_id, g.granted_to_org_id, g.product_id, g.grant_reason, g.quantity, g.status AS grant_status, g.created_at, g.extends_grant_id, CASE WHEN BOOL_OR(p.status = 'active') THEN 'live' WHEN EXISTS ( SELECT 1 FROM core.grants child WHERE child.extends_grant_id = g.grant_id ) THEN 'superseded' ELSE 'inactive' END AS delivery_state, MAX(( SELECT child.grant_id::text FROM core.grants child WHERE child.extends_grant_id = g.grant_id ORDER BY child.created_at DESC LIMIT 1 )) AS replaced_by_grant_id, MAX(l.activated_at) AS activated_at, MAX(CASE WHEN l.status = 'ended' THEN l.ended_at END) AS ended_at FROM core.grants g LEFT JOIN core.pool_provisions p ON p.grant_id = g.grant_id LEFT JOIN core.pool_provision_ladders l ON l.provision_id = p.provision_id WHERE sqlc.narg(org_id)::uuid IS NULL OR g.granted_to_org_id = sqlc.narg(org_id)::uuid GROUP BY g.grant_id, g.granted_to_org_id, g.product_id, g.grant_reason, g.quantity, g.status, g.created_at, g.extends_grant_id ORDER BY CASE WHEN BOOL_OR(p.status = 'active') THEN 0 ELSE 1 END, g.created_at DESC; -- name: ListGrantsWithDeliveryPage :many -- Paginated, searchable, filterable system-wide variant of -- ListGrantsWithDelivery for /operator/grants (operator-list-scale, -- design D4). Same derivation, join shape, and ordering as -- ListGrantsWithDelivery above -- do not let the two drift, and never -- scope this one to an org_id; the org-detail composite keeps calling -- ListGrantsWithDelivery unpaged. -- -- sqlc.narg(q): NULL means search is inactive and every row matches. -- Non-NULL matches a grant when the granted-to organization's name -- ILIKE's the term, OR the grant's product_id is present in -- sqlc.narg(product_ids) -- the grants module cannot query the billing -- schema directly, so the caller (Go) pre-resolves product IDs whose name -- matches the search term and passes them here; a NULL or empty array -- with a non-NULL q simply means the q criterion rides on org name alone. -- -- sqlc.narg(delivery_state): NULL returns every delivery state; otherwise -- only rows whose derived state equals the value. The HAVING clause below -- repeats the exact CASE expression from the SELECT list (HAVING cannot -- reference a SELECT-list alias) so the filter can never disagree with -- what the SELECT list -- and therefore the Go derivation reading it -- -- renders as that row's state (a mixed-fixture test pins this agreement). -- -- count(*) OVER() is evaluated after GROUP BY/HAVING, so with the GROUP -- BY below it counts grouped (one-per-grant) rows that passed WHERE and -- HAVING -- the true total of matching grants, not a join-multiplied -- count of the underlying pool_provisions/pool_provision_ladders rows. SELECT g.grant_id, g.granted_to_org_id, g.product_id, g.grant_reason, g.quantity, g.status AS grant_status, g.created_at, g.extends_grant_id, CASE WHEN BOOL_OR(p.status = 'active') THEN 'live' WHEN EXISTS ( SELECT 1 FROM core.grants child WHERE child.extends_grant_id = g.grant_id ) THEN 'superseded' ELSE 'inactive' END AS delivery_state, MAX(( SELECT child.grant_id::text FROM core.grants child WHERE child.extends_grant_id = g.grant_id ORDER BY child.created_at DESC LIMIT 1 )) AS replaced_by_grant_id, MAX(l.activated_at) AS activated_at, MAX(CASE WHEN l.status = 'ended' THEN l.ended_at END) AS ended_at, count(*) OVER() AS total_count FROM core.grants g LEFT JOIN core.pool_provisions p ON p.grant_id = g.grant_id LEFT JOIN core.pool_provision_ladders l ON l.provision_id = p.provision_id LEFT JOIN core.organizations org ON org.org_id = g.granted_to_org_id WHERE sqlc.narg(q)::text IS NULL OR org.name ILIKE ('%' || sqlc.narg(q)::text || '%') OR g.product_id = ANY(sqlc.narg(product_ids)::uuid[]) GROUP BY g.grant_id, g.granted_to_org_id, g.product_id, g.grant_reason, g.quantity, g.status, g.created_at, g.extends_grant_id HAVING sqlc.narg(delivery_state)::text IS NULL OR ( CASE WHEN BOOL_OR(p.status = 'active') THEN 'live' WHEN EXISTS ( SELECT 1 FROM core.grants child WHERE child.extends_grant_id = g.grant_id ) THEN 'superseded' ELSE 'inactive' END ) = sqlc.narg(delivery_state)::text ORDER BY CASE WHEN BOOL_OR(p.status = 'active') THEN 0 ELSE 1 END, g.created_at DESC LIMIT sqlc.arg(page_limit) OFFSET sqlc.arg(page_offset); -- name: ListDeliveringGrantsByOrgID :many -- Returns only grants currently delivering entitlements to a pool in the -- given org — i.e. grants with at least one provision in status='active'. -- Used by the member-facing Sources panel where audit history would be -- noise (members only need to see what they actually have right now). -- DISTINCT collapses multi-provision grants to one row. SELECT DISTINCT g.grant_id, g.product_id, g.granted_to_billing_account_id, g.granted_to_org_id, g.granted_to_person_id, g.granted_by_person_id, g.grant_reason, g.description, g.quantity, g.valid_from, g.valid_until, g.status, g.revoked_at, g.revoked_by_person_id, g.revocation_reason, g.metadata, g.created_at, g.updated_at, g.extends_grant_id FROM core.grants g INNER JOIN core.pool_provisions p ON p.grant_id = g.grant_id WHERE g.granted_to_org_id = $1 AND p.status = 'active' ORDER BY g.created_at DESC; -- name: ListRecentGrants :many -- Recent grants for the operator landing activity timeline. Returns the -- raw row data with org_id + granted_by_person_id; Go-side merger resolves -- those UUIDs to display names via batch lookups (avoids cross-schema -- joins that don't fit per-module sqlc scope). Only org-targeted grants — -- person-targeted and billing-account-targeted grants don't show up on -- the org-centric operator timeline. SELECT grant_id, product_id, granted_to_org_id, granted_by_person_id, grant_reason, quantity, created_at FROM core.grants WHERE granted_to_org_id IS NOT NULL ORDER BY created_at DESC LIMIT $1; -- name: CountDeliveringOperatorGrants :one -- Operator overview headline: grants someone deliberately issued that are -- delivering right now. Signup defaults are excluded — conferral mints one -- per org, so they drown the number an operator can act on; their count -- rides the tile's caption (CountDeliveringDefaultGrants). Non-default is -- exactly the operator-authored set per chk_grants_default_iff_system_authored. -- -- grants.status is an issuance LEDGER — it records what was written and -- whether it was later revoked, not whether service is flowing today. The -- current-delivery fact lives on core.pool_provisions.status, so this counts -- distinct grants that own at least one active provision rather than -- filtering grants.status alone. SELECT COUNT(DISTINCT g.grant_id) FROM core.grants g JOIN core.pool_provisions pp ON pp.grant_id = g.grant_id WHERE pp.status = 'active' AND g.grant_reason <> 'default'; -- name: CountDeliveringDefaultGrants :one -- The delivering-grants caption: how many signup defaults are delivering, -- excluded from the headline above. Same provision join as -- CountDeliveringOperatorGrants so the two can never disagree about what -- "delivering" means. SELECT COUNT(DISTINCT g.grant_id) FROM core.grants g JOIN core.pool_provisions pp ON pp.grant_id = g.grant_id WHERE pp.status = 'active' AND g.grant_reason = 'default'; -- name: ListAllGrants :many SELECT * FROM core.grants ORDER BY created_at DESC; -- name: RevokeGrant :one UPDATE core.grants SET status = 'revoked', revoked_at = NOW(), revoked_by_person_id = $2, revocation_reason = $3 WHERE grant_id = $1 AND status = 'active' RETURNING *; -- name: ExpireGrant :one -- Marks a grant expired at its valid_until bound. Enacting the position end is -- end_conferral's job; this is the decree half (design D5). UPDATE core.grants SET status = 'expired' WHERE grant_id = $1 AND status = 'active' RETURNING *; -- name: GetGrantLineage :many -- Walks the ancestry chain of the focal grant via extends_grant_id, returning -- the focal grant first followed by each ancestor (parent, grandparent, ...) -- in chain-walk order. Revoked or expired ancestors are included; lineage is -- about who-came-before, not current validity. WITH RECURSIVE lineage AS ( SELECT g.*, 0 AS depth FROM core.grants g WHERE g.grant_id = $1 UNION ALL SELECT g.*, l.depth + 1 FROM core.grants g JOIN lineage l ON g.grant_id = l.extends_grant_id ) SELECT lineage.grant_id, lineage.product_id, lineage.granted_to_billing_account_id, lineage.granted_to_org_id, lineage.granted_to_person_id, lineage.granted_by_person_id, lineage.grant_reason, lineage.description, lineage.quantity, lineage.valid_from, lineage.valid_until, lineage.status, lineage.revoked_at, lineage.revoked_by_person_id, lineage.revocation_reason, lineage.metadata, lineage.created_at, lineage.updated_at, lineage.extends_grant_id FROM lineage ORDER BY lineage.depth ASC;