letopis 1.4.0 → 1.4.2
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/AGENT-CHEATSHEET.en.md +15 -7
- package/AGENT-CHEATSHEET.md +19 -11
- package/CHANGELOG.md +299 -1
- package/README.en.md +180 -62
- package/README.md +165 -55
- package/dist/acl.d.ts +11 -5
- package/dist/admin.d.ts +30 -14
- package/dist/admin.js +53 -18
- package/dist/auth.d.ts +61 -17
- package/dist/auth.js +16 -2
- package/dist/cache.d.ts +5 -1
- package/dist/cache.js +3 -2
- package/dist/chain.d.ts +92 -15
- package/dist/chain.js +56 -7
- package/dist/cli.js +16 -2
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +5 -5
- package/dist/import.js +7 -0
- package/dist/index.d.ts +73 -45
- package/dist/index.js +69 -14
- package/dist/internal.d.ts +7 -0
- package/dist/internal.js +5 -0
- package/dist/load.d.ts +10 -3
- package/dist/load.js +39 -11
- package/dist/mcp.d.ts +20 -11
- package/dist/mcp.js +8 -6
- package/dist/model.d.ts +4 -1
- package/dist/model.js +7 -1
- package/dist/ops.d.ts +58 -18
- package/dist/ops.js +58 -18
- package/dist/project.d.ts +39 -11
- package/dist/project.js +75 -15
- package/dist/registry.d.ts +4 -1
- package/dist/registry.js +4 -1
- package/dist/signals.d.ts +4 -4
- package/dist/signals.js +4 -4
- package/dist/sql.d.ts +8 -4
- package/dist/sql.js +110 -25
- package/dist/sync.js +11 -4
- package/dist/tx.d.ts +15 -0
- package/dist/tx.js +22 -4
- package/dist/types.d.ts +21 -7
- package/dist/types.js +13 -6
- package/dist/up.d.ts +48 -25
- package/dist/up.js +94 -41
- package/dist/validate.d.ts +9 -2
- package/dist/validate.js +19 -3
- package/dist/watch.d.ts +29 -5
- package/dist/watch.js +62 -13
- package/dist/write.d.ts +3 -2
- package/dist/write.js +52 -19
- package/package.json +8 -4
- package/sql/10-core.sql +6 -1
- package/sql/60-write.sql +47 -18
- package/sql/70-read.sql +13 -7
- package/sql/75-history.sql +106 -28
- package/sql/80-auth.sql +41 -3
- package/sql/88-derived.sql +3 -1
- package/sql/90-time.sql +118 -30
- package/sql/95-seed.system.sql +6 -3
- package/sql/99-revision.sql +1 -1
- package/sql/migrate/0007-secret-account-fk.sql +12 -0
package/sql/90-time.sql
CHANGED
|
@@ -389,9 +389,12 @@ begin
|
|
|
389
389
|
end if;
|
|
390
390
|
end loop;
|
|
391
391
|
end if;
|
|
392
|
+
-- по классам: последняя версия id — любого класса, класс проверяется у неё (план 2.0, 7.1 №1)
|
|
392
393
|
for r in select distinct on (l.id) l.* from @SCHEMA@.log l
|
|
393
|
-
where l.tenant = c.tenant and (case when p_ids is not null then l.id = any (p_ids)
|
|
394
|
+
where l.tenant = c.tenant and (case when p_ids is not null then l.id = any (p_ids)
|
|
395
|
+
else l.id in (select x.id from @SCHEMA@.log x where x.tenant = c.tenant and x.class = any (p_classes)) end)
|
|
394
396
|
order by l.id, l.rev desc loop
|
|
397
|
+
continue when p_ids is null and not (r.class = any (p_classes));
|
|
395
398
|
continue when r.op in ('purge', 'reset')
|
|
396
399
|
or not @SCHEMA@.row_visible(r.tenant, r.class, r.id, r.owner, r.tags, r.data, r.links, c.tenant, c.account, rd->'plan');
|
|
397
400
|
if r.class in ('Class', 'Account', 'member', 'Resource', 'rule') then
|
|
@@ -622,7 +625,7 @@ end $$;
|
|
|
622
625
|
-- последнего включённого владельца. Данные и журнал арендатора-цели стираются физически; в чужих
|
|
623
626
|
-- арендаторах членства снимаются (надгробия), владение строками переходит арендаторам (§2.10);
|
|
624
627
|
-- остались ссылки на аккаунт — delete_restricted. Строка Account удаляется с надгробием (история
|
|
625
|
-
-- аккаунтов — аудит), сессии
|
|
628
|
+
-- аккаунтов — аудит), сессии гаснут с сигналом, строки secret аккаунта стирает каскад внешнего ключа.
|
|
626
629
|
create or replace function @SCHEMA@.account_purge(p_account uuid) returns jsonb
|
|
627
630
|
language plpgsql volatile security definer
|
|
628
631
|
set search_path = pg_catalog, @SCHEMA@, pg_temp
|
|
@@ -658,6 +661,14 @@ begin
|
|
|
658
661
|
if a.id is null or p_account = '@SYSTEM@'::uuid then
|
|
659
662
|
perform @SCHEMA@.lt_fail('target_not_found', 'аккаунт не найден');
|
|
660
663
|
end if;
|
|
664
|
+
-- владелец стирает арендатора, в котором работает: его членство уйдёт вместе с данными, а надгробия и
|
|
665
|
+
-- передача владения ниже перечитывают ctx() — остаток операции идёт в личном арендаторе вызывающего,
|
|
666
|
+
-- и его сессия больше не указывает на стёртого арендатора (иначе tenant_denied и откат всего вызова)
|
|
667
|
+
if c.tenant = p_account then
|
|
668
|
+
perform set_config('letopis.tenant', c.account::text, true);
|
|
669
|
+
update @SCHEMA@.secret s set meta = s.meta - 'tenant'
|
|
670
|
+
where s.id = c.session and s.kind = 'SESSION' and s.meta->>'tenant' = p_account::text;
|
|
671
|
+
end if;
|
|
661
672
|
-- последний владелец: в каждом чужом арендаторе, где цель — владелец, должен остаться другой
|
|
662
673
|
for t in select x.tenant from @SCHEMA@.entity x
|
|
663
674
|
where x.class = 'member' and x.links->>'User' = p_account::text and x.tenant <> p_account
|
|
@@ -679,7 +690,7 @@ begin
|
|
|
679
690
|
perform @SCHEMA@.tombstone(m);
|
|
680
691
|
n_mem := n_mem + 1;
|
|
681
692
|
end loop;
|
|
682
|
-
for t in select distinct x.tenant from @SCHEMA@.entity x where x.owner = p_account and x.owner <> x.tenant loop
|
|
693
|
+
for t in select distinct x.tenant from @SCHEMA@.entity x where x.owner = p_account and x.owner <> x.tenant order by x.tenant loop
|
|
683
694
|
n_own := n_own + @SCHEMA@.owner_to_tenant(t.tenant, p_account);
|
|
684
695
|
end loop;
|
|
685
696
|
select coalesce(jsonb_agg(jsonb_build_object('id', x.id, 'class', x.class, 'tenant', x.tenant)), '[]'::jsonb) into blockers
|
|
@@ -687,10 +698,10 @@ begin
|
|
|
687
698
|
if blockers <> '[]'::jsonb then
|
|
688
699
|
perform @SCHEMA@.lt_fail('delete_restricted', 'на аккаунт ссылаются другие строки', @SCHEMA@.visible_detail(blockers, c.tenant));
|
|
689
700
|
end if;
|
|
701
|
+
-- сессии аккаунта гасит с сигналом триггер до удаления (trg_entity_bd_account, срабатывает и для владельца), а его
|
|
702
|
+
-- строки secret — сессии и креды — стирает каскад внешнего ключа secret.account (ревизия 7)
|
|
690
703
|
delete from @SCHEMA@.entity x where x.id = p_account;
|
|
691
704
|
perform @SCHEMA@.tombstone(a);
|
|
692
|
-
perform @SCHEMA@.auth_kill(p_account, null);
|
|
693
|
-
delete from @SCHEMA@.secret s where s.account = p_account;
|
|
694
705
|
-- данные арендатора-цели — его парами; Account и member — классы прав, их сигнал сбрасывает всё
|
|
695
706
|
perform @SCHEMA@.signal(classes || array['Account', 'member'],
|
|
696
707
|
array(select p_account from unnest(classes)) || array['@SYSTEM@'::uuid, '@SYSTEM@'::uuid]);
|
|
@@ -789,7 +800,9 @@ end $$;
|
|
|
789
800
|
-- ========================= обслуживание =========================
|
|
790
801
|
|
|
791
802
|
-- Одна пачка прореживания журнала (§2.11): версии, которые не нужны действующей политике хранения
|
|
792
|
-
-- своего
|
|
803
|
+
-- своего класса — как его резолвит class_row: свой класс арендатора, иначе System; у арендатора со своим одноимённым
|
|
804
|
+
-- классом политика класса System не действует (план 2.0, 7.3 №1: класс System, заведённый позже, дублировал строки
|
|
805
|
+
-- журнала в отборе и стирал промежуточные версии). Не трогаются версия 1, текущая и версии особых видов (create, delete, reclass,
|
|
793
806
|
-- rekey, restore, purge, reset, trim); история удалённого объекта, надгробие которого старше
|
|
794
807
|
-- последнего яруса со сроком, стирается целиком. Пачка — до 100 объектов после курсора (meta
|
|
795
808
|
-- строки ключа: участки по порядку id), не больше p_limit версий; объект, не вошедший целиком,
|
|
@@ -819,32 +832,60 @@ begin
|
|
|
819
832
|
)
|
|
820
833
|
select array_agg(q.id order by q.id) into ids from (
|
|
821
834
|
select distinct l.id from @SCHEMA@.log l
|
|
822
|
-
join pol on pol.p is not null and pol.cls = l.class
|
|
835
|
+
join pol on pol.p is not null and pol.cls = l.class
|
|
836
|
+
and (pol.ct = l.tenant
|
|
837
|
+
or pol.ct = '@SYSTEM@'::uuid
|
|
838
|
+
and not exists (select 1 from @SCHEMA@.entity y
|
|
839
|
+
where y.class = 'Class' and y.tenant = l.tenant and y.data->>'name' = l.class))
|
|
823
840
|
where l.at < p_now - @SCHEMA@.history_iv(pol.p->>'all') and (cur is null or l.id > cur)
|
|
824
841
|
order by l.id limit 100) q;
|
|
825
842
|
if ids is null then
|
|
826
843
|
update @SCHEMA@.secret s set meta = s.meta - 'thin' where s.id = '@SYSTEM@'::uuid;
|
|
827
844
|
return jsonb_build_object('thinned', 0, 'more', false);
|
|
828
845
|
end if;
|
|
846
|
+
-- последняя запись каждого id пачки — for update: restore(), purge() и новая версия того же id берут её же (инвариант
|
|
847
|
+
-- M-TIME); отбор ниже видит то, что они успели зафиксировать, и не стирает историю объекта, который только что
|
|
848
|
+
-- восстановили (план 2.0, 7.3 №3)
|
|
849
|
+
perform 1 from @SCHEMA@.log l
|
|
850
|
+
where l.id = any (ids) and not exists (select 1 from @SCHEMA@.log z where z.id = l.id and z.rev > l.rev)
|
|
851
|
+
order by l.id
|
|
852
|
+
for update of l;
|
|
829
853
|
with pol as (
|
|
830
854
|
select x.data->>'name' as cls, x.tenant as ct, @SCHEMA@.history_policy(x.data->'resolved') as p
|
|
831
855
|
from @SCHEMA@.entity x where x.class = 'Class'
|
|
832
856
|
), v as (
|
|
833
|
-
|
|
857
|
+
-- все версии id, любого класса: последняя версия и следующая по времени — по всей цепочке (план 2.0, 7.3 №2: после
|
|
858
|
+
-- reclass версии прежнего класса без политики выпадали из отбора и переживали стирание истории удалённого)
|
|
859
|
+
select l.id, l.rev, l.at, l.op, l.class, l.tenant,
|
|
834
860
|
max(l.rev) over (partition by l.id) as last_rev,
|
|
835
861
|
first_value(l.op) over (partition by l.id order by l.rev desc) as last_op,
|
|
836
862
|
first_value(l.at) over (partition by l.id order by l.rev desc) as last_at,
|
|
863
|
+
first_value(l.class) over (partition by l.id order by l.rev desc) as last_class,
|
|
837
864
|
lead(l.at) over (partition by l.id order by l.rev) as next_at
|
|
838
865
|
from @SCHEMA@.log l
|
|
839
|
-
join pol on pol.p is not null and pol.cls = l.class and (pol.ct = l.tenant or pol.ct = '@SYSTEM@'::uuid)
|
|
840
866
|
where l.id = any (ids)
|
|
867
|
+
), vp as (
|
|
868
|
+
-- прореживание версии — по политике её класса; стирание удалённого целиком — по политике класса надгробия
|
|
869
|
+
select v.*, pv.p, coalesce(pv.p->>'tz', 'UTC') as tz, pl.p as lp, coalesce(pl.p->>'tz', 'UTC') as ltz
|
|
870
|
+
from v
|
|
871
|
+
left join pol pv on pv.cls = v.class
|
|
872
|
+
and (pv.ct = v.tenant
|
|
873
|
+
or pv.ct = '@SYSTEM@'::uuid
|
|
874
|
+
and not exists (select 1 from @SCHEMA@.entity y
|
|
875
|
+
where y.class = 'Class' and y.tenant = v.tenant and y.data->>'name' = v.class))
|
|
876
|
+
left join pol pl on pl.cls = v.last_class
|
|
877
|
+
and (pl.ct = v.tenant
|
|
878
|
+
or pl.ct = '@SYSTEM@'::uuid
|
|
879
|
+
and not exists (select 1 from @SCHEMA@.entity y
|
|
880
|
+
where y.class = 'Class' and y.tenant = v.tenant and y.data->>'name' = v.last_class))
|
|
841
881
|
)
|
|
842
|
-
select array_agg(
|
|
843
|
-
from
|
|
844
|
-
where (
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
and not
|
|
882
|
+
select array_agg(vp.id order by vp.id, vp.rev), array_agg(vp.rev order by vp.id, vp.rev) into vid, vrev
|
|
883
|
+
from vp
|
|
884
|
+
where (vp.last_op = 'delete' and vp.lp is not null
|
|
885
|
+
and timezone(vp.ltz, vp.last_at) <= timezone(vp.ltz, p_now) - @SCHEMA@.history_horizon(vp.lp))
|
|
886
|
+
or (vp.p is not null and vp.rev <> 1 and vp.rev <> vp.last_rev
|
|
887
|
+
and vp.op not in ('create', 'delete', 'reclass', 'rekey', 'restore', 'purge', 'reset', 'trim')
|
|
888
|
+
and not @SCHEMA@.history_keep(vp.p, timezone(vp.tz, vp.at), timezone(vp.tz, p_now), timezone(vp.tz, vp.next_at)));
|
|
848
889
|
total := coalesce(cardinality(vid), 0);
|
|
849
890
|
k := least(total, p_limit);
|
|
850
891
|
if k > 0 then
|
|
@@ -867,11 +908,15 @@ begin
|
|
|
867
908
|
return jsonb_build_object('thinned', k, 'more', more);
|
|
868
909
|
end $$;
|
|
869
910
|
|
|
870
|
-
-- Обслуживание (этап 6, п. 8): истёкшие и погашенные сессии и одноразовые коды, одна
|
|
871
|
-
-- прореживания журнала, заполнение очереди сигналов. Вызывает системный администратор, роль с
|
|
911
|
+
-- Обслуживание (этап 6, п. 8): истёкшие и погашенные сессии и одноразовые коды, строки-замки удалённых семейств, одна
|
|
912
|
+
-- пачка прореживания журнала, заполнение очереди сигналов. Вызывает системный администратор, роль с
|
|
872
913
|
-- правом letopis.maintain или администраторское подключение. p_now — момент «сейчас» для
|
|
873
|
-
-- прореживания, только при allow_reset (тесты): иначе одним вызовом можно было бы проредить всю историю.
|
|
874
|
-
|
|
914
|
+
-- прореживания, только при allow_reset (тесты): иначе одним вызовом можно было бы проредить всю историю. p_cleanup —
|
|
915
|
+
-- удалять ли истёкшие сессии, коды и строки-замки: библиотека передаёт true только в первой пачке вызова, иначе каждая
|
|
916
|
+
-- из ≤100 пачек заново просматривала бы secret (план 2.0, 7.3 №4); прежний вызов без третьего аргумента чистит, как раньше.
|
|
917
|
+
drop function if exists @SCHEMA@.maintain(timestamptz, int);
|
|
918
|
+
create or replace function @SCHEMA@.maintain(p_now timestamptz default null, p_limit int default 10000,
|
|
919
|
+
p_cleanup boolean default true) returns jsonb
|
|
875
920
|
language plpgsql volatile security definer
|
|
876
921
|
set search_path = pg_catalog, @SCHEMA@, pg_temp
|
|
877
922
|
as $$
|
|
@@ -879,6 +924,7 @@ declare
|
|
|
879
924
|
ts timestamptz := clock_timestamp();
|
|
880
925
|
s int;
|
|
881
926
|
o int;
|
|
927
|
+
fam text := '@SCHEMA_NAME@:family:';
|
|
882
928
|
begin
|
|
883
929
|
if not @SCHEMA@.letopis_allowed('letopis.maintain') then
|
|
884
930
|
perform @SCHEMA@.lt_fail('acl_denied', 'нет права letopis.maintain');
|
|
@@ -894,12 +940,41 @@ begin
|
|
|
894
940
|
end if;
|
|
895
941
|
ts := p_now;
|
|
896
942
|
end if;
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
943
|
+
s := 0;
|
|
944
|
+
o := 0;
|
|
945
|
+
if coalesce(p_cleanup, true) then
|
|
946
|
+
with d as (delete from @SCHEMA@.secret x where x.kind = 'SESSION'
|
|
947
|
+
and (x.deleted_at is not null or x.expires_at <= clock_timestamp()) returning 1)
|
|
948
|
+
select count(*)::int into s from d;
|
|
949
|
+
-- коды всех видов, которые выпускает auth_issue_otp: OTP и OTP_<имя> (тот же шаблон вида), — использованные,
|
|
950
|
+
-- заменённые, исчерпавшие попытки (deleted_at) и истёкшие
|
|
951
|
+
with d as (delete from @SCHEMA@.secret x where x.kind ~ '^OTP(_[A-Z0-9]{1,27})?$'
|
|
952
|
+
and (x.deleted_at is not null or x.expires_at <= clock_timestamp()) returning 1)
|
|
953
|
+
select count(*)::int into o from d;
|
|
954
|
+
-- строки-замки семейств, чьего корня больше нет — удалён класс или его арендатор (план 2.0, 7.7 №8, долгий
|
|
955
|
+
-- прогон): их заранее заводит запись строки Class (family_rows), а сами они не уходили — заведение и удаление
|
|
956
|
+
-- классов копило их без предела. Корень жив — строка Class с этим именем у арендатора семейства (family_name;
|
|
957
|
+
-- в имени класса двоеточия нет). Занятую строку не трогаем (skip locked), а строку-замок семейства — только вместе
|
|
958
|
+
-- с её шлюзом или без шлюза: исключительная блокировка берёт шлюз первым, и строка-замок, до которой она ещё не
|
|
959
|
+
-- дошла, у неё не пропадёт. Пропавшую строку lock_row и family_lock заводят заново, если класс с этим именем
|
|
960
|
+
-- появится снова.
|
|
961
|
+
with stale as (
|
|
962
|
+
select l.id, l.meta->>'name' as n
|
|
963
|
+
from @SCHEMA@.secret l
|
|
964
|
+
where l.kind = 'LOCK' and l.account = '@SYSTEM@'::uuid and l.deleted_at is null
|
|
965
|
+
and left(l.meta->>'name', length(fam)) = fam
|
|
966
|
+
and not exists (
|
|
967
|
+
select 1 from @SCHEMA@.entity c
|
|
968
|
+
where c.class = 'Class'
|
|
969
|
+
and c.data->>'name' = split_part(substr(l.meta->>'name', length(fam) + 1), ':', 2)
|
|
970
|
+
and c.tenant::text = split_part(substr(l.meta->>'name', length(fam) + 1), ':', 1))
|
|
971
|
+
for update of l skip locked)
|
|
972
|
+
delete from @SCHEMA@.secret x using stale st
|
|
973
|
+
where x.id = st.id
|
|
974
|
+
and (st.n like '%:gate'
|
|
975
|
+
or exists (select 1 from stale g where g.n = st.n || ':gate')
|
|
976
|
+
or not exists (select 1 from @SCHEMA@.secret y where y.id = @SCHEMA@.lock_id(st.n || ':gate')));
|
|
977
|
+
end if;
|
|
903
978
|
return jsonb_build_object('sessions', s, 'codes', o, 'queue', pg_notification_queue_usage())
|
|
904
979
|
|| @SCHEMA@.thin_batch(ts, greatest(coalesce(p_limit, 10000), 1));
|
|
905
980
|
end $$;
|
|
@@ -959,8 +1034,12 @@ begin
|
|
|
959
1034
|
select jsonb_build_object('id', id, 'rev', want_rev, 'problem', 'разрыв: нет версий ' || want_rev || '…' || (rev - 1))
|
|
960
1035
|
from exp
|
|
961
1036
|
where rev > want_rev
|
|
962
|
-
and not exists (select 1 from pol where pol.cls = exp.class and
|
|
963
|
-
and
|
|
1037
|
+
and not exists (select 1 from pol where pol.cls = exp.class and exp.prev_at < ts - pol.all_iv
|
|
1038
|
+
and (pol.ct = exp.tenant
|
|
1039
|
+
or pol.ct = '@SYSTEM@'::uuid
|
|
1040
|
+
and not exists (select 1 from @SCHEMA@.entity y
|
|
1041
|
+
where y.class = 'Class' and y.tenant = exp.tenant
|
|
1042
|
+
and y.data->>'name' = exp.class)))
|
|
964
1043
|
union all
|
|
965
1044
|
select jsonb_build_object('id', id, 'rev', rev, 'problem', 'номер версии меньше ожидаемого после обрезки')
|
|
966
1045
|
from exp where rev < want_rev
|
|
@@ -1054,11 +1133,20 @@ as $$
|
|
|
1054
1133
|
declare
|
|
1055
1134
|
horizon xid8 := pg_snapshot_xmin(pg_current_snapshot());
|
|
1056
1135
|
lim interval;
|
|
1136
|
+
t uuid := @SCHEMA@.ctx_tenant();
|
|
1057
1137
|
begin
|
|
1058
1138
|
if p_at is not null then
|
|
1139
|
+
-- ярус all классов, которые видит подписка: арендатора сессии и System (кроме одноимённых своих); политика класса
|
|
1140
|
+
-- чужого арендатора с тем же именем курсор не просрочивает (план 2.0, 7.3 №1). Без арендатора в контексте
|
|
1141
|
+
-- (администраторское подключение видит всех) — все классы
|
|
1059
1142
|
select min(@SCHEMA@.history_iv(p.p->>'all')) into lim
|
|
1060
1143
|
from @SCHEMA@.entity x cross join lateral (select @SCHEMA@.history_policy(x.data->'resolved') as p) p
|
|
1061
|
-
where x.class = 'Class' and p.p is not null and (p_classes is null or x.data->>'name' = any (p_classes))
|
|
1144
|
+
where x.class = 'Class' and p.p is not null and (p_classes is null or x.data->>'name' = any (p_classes))
|
|
1145
|
+
and (t is null
|
|
1146
|
+
or x.tenant = t
|
|
1147
|
+
or x.tenant = '@SYSTEM@'::uuid
|
|
1148
|
+
and not exists (select 1 from @SCHEMA@.entity y
|
|
1149
|
+
where y.class = 'Class' and y.tenant = t and y.data->>'name' = x.data->>'name'));
|
|
1062
1150
|
if lim is not null and p_at < clock_timestamp() - lim then
|
|
1063
1151
|
raise exception using errcode = 'LT016',
|
|
1064
1152
|
message = 'letopis: cursor_expired: курсор подписки старше яруса all — события могли проредиться, перечитайте текущее состояние';
|
|
@@ -1146,7 +1234,7 @@ begin
|
|
|
1146
1234
|
'history_keep(jsonb, timestamp, timestamp, timestamp)', 'session_role()', 'admin_role()', 'letopis_allowed(text)', 'session_expires()', 'signal_mode()', 'signal_key()', 'class_stamp()',
|
|
1147
1235
|
'last_version_visible(uuid, uuid, jsonb)', 'restore(uuid)', 'projector_unread(uuid, uuid, int)', 'purge(uuid, boolean)', 'trim_history(uuid[], text[], timestamptz)',
|
|
1148
1236
|
'rekey(uuid, jsonb, jsonb)', 'rekey_class(text, jsonb)', 'account_purge(uuid)', 'reset_mark(uuid, text, uuid)',
|
|
1149
|
-
'reset(text, text, uuid, int)', 'thin_batch(timestamptz, int)', 'maintain(timestamptz, int)', 'verify(boolean)',
|
|
1237
|
+
'reset(text, text, uuid, int)', 'thin_batch(timestamptz, int)', 'maintain(timestamptz, int, boolean)', 'verify(boolean)',
|
|
1150
1238
|
'watch_read(xid8, bigint, timestamptz, text[], int, pg_snapshot, boolean)', 'watch_leaving(xid8, bigint, text[], int, pg_snapshot, xid8, bigint)', 'watch_head()'] loop
|
|
1151
1239
|
execute format('alter function @SCHEMA@.%s owner to @OWNER@', f);
|
|
1152
1240
|
execute format('revoke all on function @SCHEMA@.%s from public', f);
|
|
@@ -1155,7 +1243,7 @@ end $$;
|
|
|
1155
1243
|
-- приложению: функции владельца этапа 6 (проверяют права сами), подписка (под RLS) и чистые помощники политики
|
|
1156
1244
|
grant execute on function @SCHEMA@.restore(uuid), @SCHEMA@.purge(uuid, boolean), @SCHEMA@.trim_history(uuid[], text[], timestamptz),
|
|
1157
1245
|
@SCHEMA@.rekey(uuid, jsonb, jsonb), @SCHEMA@.rekey_class(text, jsonb), @SCHEMA@.account_purge(uuid),
|
|
1158
|
-
@SCHEMA@.reset(text, text, uuid, int), @SCHEMA@.maintain(timestamptz, int), @SCHEMA@.verify(boolean),
|
|
1246
|
+
@SCHEMA@.reset(text, text, uuid, int), @SCHEMA@.maintain(timestamptz, int, boolean), @SCHEMA@.verify(boolean),
|
|
1159
1247
|
@SCHEMA@.session_expires(), @SCHEMA@.watch_read(xid8, bigint, timestamptz, text[], int, pg_snapshot, boolean), @SCHEMA@.watch_leaving(xid8, bigint, text[], int, pg_snapshot, xid8, bigint), @SCHEMA@.watch_head(),
|
|
1160
1248
|
@SCHEMA@.history_default(), @SCHEMA@.history_policy(jsonb), @SCHEMA@.history_iv(text),
|
|
1161
1249
|
@SCHEMA@.signal_mode(), @SCHEMA@.signal_key(), @SCHEMA@.class_stamp() to @APP@;
|
package/sql/95-seed.system.sql
CHANGED
|
@@ -78,6 +78,12 @@ declare
|
|
|
78
78
|
o_signals uuid;
|
|
79
79
|
op text;
|
|
80
80
|
begin
|
|
81
|
+
-- аккаунт System — раньше строки ключа: служебные строки secret (KEY, LOCK, PROJECTOR) принадлежат ему, а внешний
|
|
82
|
+
-- ключ secret.account (ревизия 7) требует строку аккаунта в entity. Счётчик прав sys_create повышает, только когда
|
|
83
|
+
-- строка ключа уже есть, — на свежей установке и после reset({ all }) ей неоткуда взяться, и начальный v — 0
|
|
84
|
+
perform @SCHEMA@.sys_create('@SYSTEM@'::uuid, 'Account', '@SYSTEM@'::uuid, '{}',
|
|
85
|
+
'{"name": "System", "categories": ["System"], "enabled": true}');
|
|
86
|
+
|
|
81
87
|
-- Ключ подписи пропуска прав и счётчик изменений прав (50-acl.sql: acl_token, acl_bump); от ключа же
|
|
82
88
|
-- выводится ключ сигналов. Режим сигналов (up({ signals })) хранится в meta этой строки: установщик
|
|
83
89
|
-- читает его перед накатом, поэтому повторный up() без опции режим не меняет, а reset({ all }) кладёт
|
|
@@ -87,9 +93,6 @@ begin
|
|
|
87
93
|
on conflict (id) do update set meta = k.meta || '{"signals": "@SIGNALS@"}'
|
|
88
94
|
where k.meta->>'signals' is distinct from '@SIGNALS@';
|
|
89
95
|
|
|
90
|
-
perform @SCHEMA@.sys_create('@SYSTEM@'::uuid, 'Account', '@SYSTEM@'::uuid, '{}',
|
|
91
|
-
'{"name": "System", "categories": ["System"], "enabled": true}');
|
|
92
|
-
|
|
93
96
|
-- Строка Class, описывающая саму себя, и системные классы. Полная метасхема и развёрнутое
|
|
94
97
|
-- описание resolved — в 40-class.sql (этап 1, п. 6).
|
|
95
98
|
perform @SCHEMA@.sys_create(@SCHEMA@.v5_id('@SYSTEM@'::uuid, 'Class', array['Class']), 'Class', '@SYSTEM@'::uuid, '{}',
|
package/sql/99-revision.sql
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
-- letopis 1.0 — штамп ревизии движка (план, §2.2, этап 1, п. 11): последний оператор наката.
|
|
2
2
|
-- Номер совпадает с DDL_REVISION в lib/src/types.ts (проверяет check-docs). С 1.4 в штампе ещё версия пакета и хэш
|
|
3
3
|
-- файлов ядра (engine, sql): connect() предупреждает, если функции схемы ставила другая версия движка той же ревизии.
|
|
4
|
-
comment on schema @SCHEMA@ is 'letopis ddl_revision=
|
|
4
|
+
comment on schema @SCHEMA@ is 'letopis ddl_revision=7 engine=@ENGINE_VERSION@ sql=@ENGINE_SQL@';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- Ревизия 7 (1.4.1-alpha.0, решение владельца): строки secret принадлежат аккаунту — строке entity. Удалённый аккаунт
|
|
2
|
+
-- уносит свои строки каскадом внешнего ключа: сессии, хэши паролей и ключей, секреты TOTP, внешние учётные записи. До
|
|
3
|
+
-- ревизии 7 удаление аккаунта лишь помечало их deleted_at, а maintain() убирает только сессии и коды OTP — остальное
|
|
4
|
+
-- хранилось вечно. Сначала стираются строки, чьих аккаунтов в entity уже нет, затем ставится ключ (как в 10-core.sql).
|
|
5
|
+
delete from @SCHEMA@.secret s where not exists (select 1 from @SCHEMA@.entity e where e.id = s.account);
|
|
6
|
+
do $$
|
|
7
|
+
begin
|
|
8
|
+
if not exists (select 1 from pg_constraint where conrelid = '@SCHEMA@.secret'::regclass and conname = 'secret_account_fkey') then
|
|
9
|
+
alter table @SCHEMA@.secret add constraint secret_account_fkey
|
|
10
|
+
foreign key (account) references @SCHEMA@.entity (id) on delete cascade;
|
|
11
|
+
end if;
|
|
12
|
+
end $$;
|