Two coordinated additions:
1. paliad.approval_requests gets requester_kind text NOT NULL DEFAULT
'user' CHECK ('user','agent') + agent_turn_id uuid REFERENCES
paliadin_turns(turn_id) ON DELETE SET NULL. The xor-check pins
(kind='agent' ↔ agent_turn_id IS NOT NULL) so agent rows can't lose
provenance and user rows can't accidentally pick up a turn id.
Existing rows backfill cleanly via the DEFAULT.
2. paliad.paliadin_turns.context jsonb — structured page-context
payload (route_name + primary_entity + selection + view hints) the
inline widget submits with every turn. Old page_origin column stays
as the cosmetic URL field.
Idempotent — every ALTER uses IF NOT EXISTS and the constraints/index
are guarded by DO blocks. Verified live via BEGIN..ROLLBACK on the
production DB: cols + 3 constraints + index land cleanly, second apply
is a no-op, xor-check rejects ('agent', NULL).
Skipped the optional PaliadinRelay interface extraction per the design
doc's own §6.4 caveat: paliadin.go + paliadin_remote.go already share
the paliadinDB substrate cleanly; introducing an interface now would
duplicate without removing duplication. Reserves the seam for the
future API cutover without paying its cost today.
Refs: docs/design-paliadin-inline-2026-05-08.md §7.1, §4.2, §6.4.
107 lines
4.4 KiB
SQL
107 lines
4.4 KiB
SQL
-- t-paliad-161: Inline Paliadin chat modal + agent-suggested write path.
|
|
--
|
|
-- Design: docs/design-paliadin-inline-2026-05-08.md §7.1 + §4.2.
|
|
--
|
|
-- Two coordinated additions:
|
|
--
|
|
-- 1. paliad.approval_requests gets two new columns marking which requests
|
|
-- were drafted by Paliadin on a user's behalf:
|
|
-- - requester_kind text — 'user' (direct create) | 'agent' (Paliadin
|
|
-- suggestion awaiting the user's review)
|
|
-- - agent_turn_id uuid — links back to the paliadin_turns row that
|
|
-- produced the suggestion. ON DELETE SET NULL
|
|
-- so audit rows survive turn-row purges.
|
|
-- The xor-check pins (kind='agent' ↔ agent_turn_id IS NOT NULL) so we
|
|
-- can't lose provenance on agent rows or accidentally tag user rows
|
|
-- with a turn id.
|
|
--
|
|
-- 2. paliad.paliadin_turns.context jsonb — the structured page-context
|
|
-- payload (route_name + primary_entity_type + primary_entity_id +
|
|
-- user_selection_text + view hints) the inline widget submits with
|
|
-- every turn. Old page_origin column stays as the cosmetic URL field.
|
|
--
|
|
-- Idempotent — every ALTER uses IF NOT EXISTS and the constraints/index
|
|
-- are guarded by DO blocks. Re-applying after a partial failure leaves
|
|
-- the same end state as a fresh run.
|
|
|
|
-- ============================================================================
|
|
-- 1. paliad.approval_requests.requester_kind + agent_turn_id
|
|
-- ============================================================================
|
|
|
|
ALTER TABLE paliad.approval_requests
|
|
ADD COLUMN IF NOT EXISTS requester_kind text NOT NULL DEFAULT 'user';
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_constraint
|
|
WHERE conname = 'approval_requests_requester_kind_check'
|
|
AND conrelid = 'paliad.approval_requests'::regclass
|
|
) THEN
|
|
ALTER TABLE paliad.approval_requests
|
|
ADD CONSTRAINT approval_requests_requester_kind_check
|
|
CHECK (requester_kind IN ('user', 'agent'));
|
|
END IF;
|
|
END$$;
|
|
|
|
ALTER TABLE paliad.approval_requests
|
|
ADD COLUMN IF NOT EXISTS agent_turn_id uuid;
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_constraint
|
|
WHERE conname = 'approval_requests_agent_turn_fk'
|
|
AND conrelid = 'paliad.approval_requests'::regclass
|
|
) THEN
|
|
ALTER TABLE paliad.approval_requests
|
|
ADD CONSTRAINT approval_requests_agent_turn_fk
|
|
FOREIGN KEY (agent_turn_id) REFERENCES paliad.paliadin_turns(turn_id)
|
|
ON DELETE SET NULL;
|
|
END IF;
|
|
END$$;
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_constraint
|
|
WHERE conname = 'approval_requests_agent_xor'
|
|
AND conrelid = 'paliad.approval_requests'::regclass
|
|
) THEN
|
|
ALTER TABLE paliad.approval_requests
|
|
ADD CONSTRAINT approval_requests_agent_xor
|
|
CHECK (
|
|
(requester_kind = 'agent' AND agent_turn_id IS NOT NULL)
|
|
OR (requester_kind = 'user' AND agent_turn_id IS NULL)
|
|
);
|
|
END IF;
|
|
END$$;
|
|
|
|
CREATE INDEX IF NOT EXISTS approval_requests_agent_turn_idx
|
|
ON paliad.approval_requests (agent_turn_id)
|
|
WHERE agent_turn_id IS NOT NULL;
|
|
|
|
COMMENT ON COLUMN paliad.approval_requests.requester_kind IS
|
|
'Who originated the request: ''user'' (direct user create) or '
|
|
'''agent'' (Paliadin drafted it from a chat turn, awaiting user '
|
|
'approval). Default ''user'' so existing audit rows backfill cleanly.';
|
|
|
|
COMMENT ON COLUMN paliad.approval_requests.agent_turn_id IS
|
|
'When requester_kind=''agent'', the paliadin_turns row the suggestion '
|
|
'came from. NULL otherwise. ON DELETE SET NULL so the audit record '
|
|
'survives if the turn row is later purged.';
|
|
|
|
-- ============================================================================
|
|
-- 2. paliad.paliadin_turns.context — structured page payload
|
|
-- ============================================================================
|
|
|
|
ALTER TABLE paliad.paliadin_turns
|
|
ADD COLUMN IF NOT EXISTS context jsonb;
|
|
|
|
COMMENT ON COLUMN paliad.paliadin_turns.context IS
|
|
'Structured page-context payload from the inline widget: route_name + '
|
|
'primary_entity_type + primary_entity_id + user_selection_text + '
|
|
'view_mode + filter_summary. NULL for legacy turns (PoC standalone '
|
|
'page predates the structured payload — page_origin is the only field '
|
|
'they carry). Design: docs/design-paliadin-inline-2026-05-08.md §4.';
|