create-quorum-router 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -2
- package/bin/create-quorum-router.js +25 -4
- package/package.json +1 -1
- package/templates/basic/README.md +64 -6
- package/templates/basic/deno.json +1 -0
- package/templates/basic/router.config.example.json +11 -0
- package/templates/basic/src/auth_session.ts +1 -0
- package/templates/basic/src/best_route.ts +10 -1
- package/templates/basic/src/cli.ts +14 -2
- package/templates/basic/src/cost_aware.ts +150 -0
- package/templates/basic/src/provider_registry.ts +14 -7
- package/templates/basic/src/schema.ts +87 -0
- package/templates/basic/src/supabase.ts +462 -0
- package/templates/basic/src/trace.ts +3 -0
- package/templates/basic/src/wrapper_client.ts +46 -13
- package/templates/basic/supabase/migrations/20260701130000_workflow_access_audit.sql +125 -0
- package/templates/basic/supabase/migrations/20260712211500_workflow_access_audit_limits.sql +94 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
create extension if not exists pgcrypto with schema extensions;
|
|
2
|
+
|
|
3
|
+
create table if not exists public.workflow_access_audit (
|
|
4
|
+
id uuid primary key default extensions.gen_random_uuid(),
|
|
5
|
+
org_id text not null,
|
|
6
|
+
actor_id uuid not null,
|
|
7
|
+
actor_type text not null default 'ai_assistant'
|
|
8
|
+
check (actor_type in ('ai_assistant', 'user', 'system')),
|
|
9
|
+
event_type text not null check (length(event_type) > 0),
|
|
10
|
+
workflow_id text,
|
|
11
|
+
route text,
|
|
12
|
+
decision text not null check (decision in ('allow', 'deny', 'error')),
|
|
13
|
+
reason text,
|
|
14
|
+
metadata jsonb not null default '{}'::jsonb,
|
|
15
|
+
created_at timestamptz not null default now()
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
alter table public.workflow_access_audit enable row level security;
|
|
19
|
+
|
|
20
|
+
revoke all privileges on table public.workflow_access_audit from public;
|
|
21
|
+
revoke all privileges on table public.workflow_access_audit from anon;
|
|
22
|
+
revoke all privileges on table public.workflow_access_audit from authenticated;
|
|
23
|
+
|
|
24
|
+
create or replace function public.prevent_workflow_access_audit_mutation()
|
|
25
|
+
returns trigger
|
|
26
|
+
language plpgsql
|
|
27
|
+
set search_path = public, pg_temp
|
|
28
|
+
as $$
|
|
29
|
+
begin
|
|
30
|
+
raise exception 'workflow_access_audit is append-only';
|
|
31
|
+
end;
|
|
32
|
+
$$;
|
|
33
|
+
|
|
34
|
+
revoke all privileges on function public.prevent_workflow_access_audit_mutation()
|
|
35
|
+
from public;
|
|
36
|
+
|
|
37
|
+
drop trigger if exists workflow_access_audit_no_update_delete
|
|
38
|
+
on public.workflow_access_audit;
|
|
39
|
+
create trigger workflow_access_audit_no_update_delete
|
|
40
|
+
before update or delete on public.workflow_access_audit
|
|
41
|
+
for each row
|
|
42
|
+
execute function public.prevent_workflow_access_audit_mutation();
|
|
43
|
+
|
|
44
|
+
create or replace function public.insert_workflow_access_audit_batch(records jsonb)
|
|
45
|
+
returns void
|
|
46
|
+
language plpgsql
|
|
47
|
+
security definer
|
|
48
|
+
set search_path = public, pg_temp
|
|
49
|
+
as $$
|
|
50
|
+
declare
|
|
51
|
+
rec jsonb;
|
|
52
|
+
claim_org_id text;
|
|
53
|
+
claim_actor_id uuid;
|
|
54
|
+
event_type text;
|
|
55
|
+
decision text;
|
|
56
|
+
begin
|
|
57
|
+
claim_actor_id := auth.uid();
|
|
58
|
+
if claim_actor_id is null then
|
|
59
|
+
raise exception 'missing authenticated user'
|
|
60
|
+
using errcode = '28000';
|
|
61
|
+
end if;
|
|
62
|
+
|
|
63
|
+
claim_org_id := nullif(auth.jwt() ->> 'org_id', '');
|
|
64
|
+
if claim_org_id is null then
|
|
65
|
+
raise exception 'missing org_id claim'
|
|
66
|
+
using errcode = '28000';
|
|
67
|
+
end if;
|
|
68
|
+
|
|
69
|
+
if records is null or jsonb_typeof(records) <> 'array' then
|
|
70
|
+
raise exception 'records must be a JSON array'
|
|
71
|
+
using errcode = '22023';
|
|
72
|
+
end if;
|
|
73
|
+
|
|
74
|
+
for rec in select value from jsonb_array_elements(records) as entry(value) loop
|
|
75
|
+
event_type := nullif(rec ->> 'event_type', '');
|
|
76
|
+
decision := nullif(rec ->> 'decision', '');
|
|
77
|
+
|
|
78
|
+
if event_type is null then
|
|
79
|
+
raise exception 'audit event_type is required'
|
|
80
|
+
using errcode = '22023';
|
|
81
|
+
end if;
|
|
82
|
+
|
|
83
|
+
if decision is null then
|
|
84
|
+
raise exception 'audit decision is required'
|
|
85
|
+
using errcode = '22023';
|
|
86
|
+
end if;
|
|
87
|
+
|
|
88
|
+
insert into public.workflow_access_audit (
|
|
89
|
+
org_id,
|
|
90
|
+
actor_id,
|
|
91
|
+
actor_type,
|
|
92
|
+
event_type,
|
|
93
|
+
workflow_id,
|
|
94
|
+
route,
|
|
95
|
+
decision,
|
|
96
|
+
reason,
|
|
97
|
+
metadata,
|
|
98
|
+
created_at
|
|
99
|
+
) values (
|
|
100
|
+
claim_org_id,
|
|
101
|
+
claim_actor_id,
|
|
102
|
+
coalesce(nullif(rec ->> 'actor_type', ''), 'ai_assistant'),
|
|
103
|
+
event_type,
|
|
104
|
+
nullif(rec ->> 'workflow_id', ''),
|
|
105
|
+
nullif(rec ->> 'route', ''),
|
|
106
|
+
decision,
|
|
107
|
+
nullif(rec ->> 'reason', ''),
|
|
108
|
+
case
|
|
109
|
+
when jsonb_typeof(rec -> 'metadata') = 'object' then rec -> 'metadata'
|
|
110
|
+
else '{}'::jsonb
|
|
111
|
+
end,
|
|
112
|
+
now()
|
|
113
|
+
);
|
|
114
|
+
end loop;
|
|
115
|
+
end;
|
|
116
|
+
$$;
|
|
117
|
+
|
|
118
|
+
revoke all privileges on function public.insert_workflow_access_audit_batch(jsonb)
|
|
119
|
+
from public;
|
|
120
|
+
revoke all privileges on function public.insert_workflow_access_audit_batch(jsonb)
|
|
121
|
+
from anon;
|
|
122
|
+
revoke all privileges on function public.insert_workflow_access_audit_batch(jsonb)
|
|
123
|
+
from authenticated;
|
|
124
|
+
grant execute on function public.insert_workflow_access_audit_batch(jsonb)
|
|
125
|
+
to authenticated;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
-- Harden the BYO audit RPC for both existing and fresh projects.
|
|
2
|
+
-- Every user owns their own Supabase project; no central database or shared
|
|
3
|
+
-- tenant-routing claim is used. Runtime callers remain authenticated JWT users.
|
|
4
|
+
|
|
5
|
+
create or replace function public.insert_workflow_access_audit_batch(records jsonb)
|
|
6
|
+
returns void
|
|
7
|
+
language plpgsql
|
|
8
|
+
security definer
|
|
9
|
+
set search_path = public, pg_temp
|
|
10
|
+
as $$
|
|
11
|
+
declare
|
|
12
|
+
rec jsonb;
|
|
13
|
+
claim_actor_id uuid;
|
|
14
|
+
workflow_id text;
|
|
15
|
+
route text;
|
|
16
|
+
decision text;
|
|
17
|
+
metadata jsonb;
|
|
18
|
+
begin
|
|
19
|
+
claim_actor_id := auth.uid();
|
|
20
|
+
if claim_actor_id is null then
|
|
21
|
+
raise exception 'missing authenticated user' using errcode = '28000';
|
|
22
|
+
end if;
|
|
23
|
+
|
|
24
|
+
if records is null or jsonb_typeof(records) <> 'array' then
|
|
25
|
+
raise exception 'records must be a JSON array' using errcode = '22023';
|
|
26
|
+
end if;
|
|
27
|
+
if jsonb_array_length(records) < 1 or jsonb_array_length(records) > 100 then
|
|
28
|
+
raise exception 'records batch size must be between 1 and 100' using errcode = '22023';
|
|
29
|
+
end if;
|
|
30
|
+
if pg_column_size(records) > 262144 then
|
|
31
|
+
raise exception 'records payload exceeds 256 KiB' using errcode = '22023';
|
|
32
|
+
end if;
|
|
33
|
+
|
|
34
|
+
for rec in select value from jsonb_array_elements(records) as entry(value) loop
|
|
35
|
+
if jsonb_typeof(rec) <> 'object' then
|
|
36
|
+
raise exception 'each audit record must be an object' using errcode = '22023';
|
|
37
|
+
end if;
|
|
38
|
+
if exists (
|
|
39
|
+
select 1 from jsonb_object_keys(rec) as key
|
|
40
|
+
where key not in ('workflow_id', 'route', 'decision', 'metadata')
|
|
41
|
+
) then
|
|
42
|
+
raise exception 'audit record contains unsupported fields' using errcode = '22023';
|
|
43
|
+
end if;
|
|
44
|
+
|
|
45
|
+
workflow_id := nullif(rec ->> 'workflow_id', '');
|
|
46
|
+
route := nullif(rec ->> 'route', '');
|
|
47
|
+
decision := nullif(rec ->> 'decision', '');
|
|
48
|
+
metadata := coalesce(rec -> 'metadata', '{}'::jsonb);
|
|
49
|
+
|
|
50
|
+
if workflow_id is null or length(workflow_id) > 256 then
|
|
51
|
+
raise exception 'audit workflow_id is required and limited to 256 characters' using errcode = '22023';
|
|
52
|
+
end if;
|
|
53
|
+
if route is not null and length(route) > 256 then
|
|
54
|
+
raise exception 'audit route exceeds 256 characters' using errcode = '22023';
|
|
55
|
+
end if;
|
|
56
|
+
if decision not in ('allow', 'error') then
|
|
57
|
+
raise exception 'invalid route outcome decision' using errcode = '22023';
|
|
58
|
+
end if;
|
|
59
|
+
if jsonb_typeof(metadata) <> 'object' or pg_column_size(metadata) > 16384 then
|
|
60
|
+
raise exception 'audit metadata must be an object no larger than 16 KiB' using errcode = '22023';
|
|
61
|
+
end if;
|
|
62
|
+
if exists (
|
|
63
|
+
select 1 from jsonb_object_keys(metadata) as key
|
|
64
|
+
where key not in (
|
|
65
|
+
'command', 'mode', 'auth_mode', 'provider_selection_honored',
|
|
66
|
+
'fallback_used', 'schema_valid'
|
|
67
|
+
)
|
|
68
|
+
) then
|
|
69
|
+
raise exception 'audit metadata contains unsupported fields' using errcode = '22023';
|
|
70
|
+
end if;
|
|
71
|
+
|
|
72
|
+
insert into public.workflow_access_audit (
|
|
73
|
+
org_id, actor_id, actor_type, event_type, workflow_id, route,
|
|
74
|
+
decision, reason, metadata, created_at
|
|
75
|
+
) values (
|
|
76
|
+
claim_actor_id::text,
|
|
77
|
+
claim_actor_id,
|
|
78
|
+
'ai_assistant',
|
|
79
|
+
'route.outcome',
|
|
80
|
+
workflow_id,
|
|
81
|
+
route,
|
|
82
|
+
decision,
|
|
83
|
+
case when decision = 'allow' then 'route completed' else 'route validation failed' end,
|
|
84
|
+
metadata,
|
|
85
|
+
now()
|
|
86
|
+
);
|
|
87
|
+
end loop;
|
|
88
|
+
end;
|
|
89
|
+
$$;
|
|
90
|
+
|
|
91
|
+
revoke all privileges on function public.insert_workflow_access_audit_batch(jsonb) from public;
|
|
92
|
+
revoke all privileges on function public.insert_workflow_access_audit_batch(jsonb) from anon;
|
|
93
|
+
revoke all privileges on function public.insert_workflow_access_audit_batch(jsonb) from authenticated;
|
|
94
|
+
grant execute on function public.insert_workflow_access_audit_batch(jsonb) to authenticated;
|