Introduce extends_grant_id on grants (models, CreateGrant param and SQL), add GetGrantLineage recursive query, and enforce same-org/immutability guards via migration triggers. Implement TransitionTarget.Extend with validation and an extend path that issues a chained grant + provision. Update docs, specs and tests accordingly.
47 lines
1.8 KiB
SQL
47 lines
1.8 KiB
SQL
-- name: CreateGrant :one
|
|
INSERT INTO entitlements.grants (product_id, entitlement_set_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, $10)
|
|
RETURNING *;
|
|
|
|
-- name: GetGrantByID :one
|
|
SELECT * FROM entitlements.grants
|
|
WHERE grant_id = $1;
|
|
|
|
-- name: ListGrantsByOrgID :many
|
|
SELECT * FROM entitlements.grants
|
|
WHERE granted_to_org_id = $1
|
|
ORDER BY created_at DESC;
|
|
|
|
-- name: ListAllGrants :many
|
|
SELECT * FROM entitlements.grants
|
|
ORDER BY created_at DESC;
|
|
|
|
-- name: RevokeGrant :one
|
|
UPDATE entitlements.grants
|
|
SET status = 'revoked', revoked_at = NOW(), revoked_by_person_id = $2, revocation_reason = $3
|
|
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 entitlements.grants g
|
|
WHERE g.grant_id = $1
|
|
UNION ALL
|
|
SELECT g.*, l.depth + 1 FROM entitlements.grants g
|
|
JOIN lineage l ON g.grant_id = l.extends_grant_id
|
|
)
|
|
SELECT lineage.grant_id, lineage.product_id, lineage.entitlement_set_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;
|