letopis 1.3.0 → 1.4.1
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 +14 -6
- package/AGENT-CHEATSHEET.md +18 -10
- package/CHANGELOG.md +247 -0
- package/README.en.md +146 -58
- package/README.md +129 -51
- package/dist/acl.d.ts +11 -5
- package/dist/admin.d.ts +30 -14
- package/dist/admin.js +42 -16
- package/dist/auth.d.ts +61 -17
- package/dist/auth.js +16 -2
- package/dist/cache.d.ts +5 -1
- package/dist/chain.d.ts +91 -15
- package/dist/chain.js +47 -4
- package/dist/cli.js +6 -0
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +5 -5
- package/dist/import.js +7 -0
- package/dist/index.d.ts +74 -45
- package/dist/index.js +18 -4
- package/dist/load.d.ts +10 -3
- package/dist/load.js +19 -4
- package/dist/mcp.d.ts +20 -11
- package/dist/mcp.js +4 -4
- package/dist/model.d.ts +4 -1
- package/dist/ops.d.ts +58 -18
- package/dist/ops.js +58 -18
- package/dist/project.d.ts +44 -12
- package/dist/project.js +90 -8
- package/dist/registry.d.ts +4 -1
- package/dist/registry.js +4 -1
- package/dist/sql.d.ts +4 -1
- package/dist/sql.js +12 -4
- package/dist/sync.js +11 -5
- package/dist/tx.js +10 -1
- package/dist/types.d.ts +14 -7
- package/dist/types.js +13 -6
- package/dist/up.d.ts +65 -25
- package/dist/up.js +152 -40
- package/dist/validate.d.ts +9 -2
- package/dist/validate.js +19 -3
- package/dist/watch.d.ts +17 -5
- package/dist/watch.js +40 -8
- package/dist/write.d.ts +3 -2
- package/dist/write.js +29 -10
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/sql/10-core.sql +6 -1
- package/sql/50-acl.sql +5 -1
- package/sql/60-write.sql +77 -22
- package/sql/80-auth.sql +39 -2
- package/sql/88-derived.sql +82 -1
- package/sql/90-time.sql +75 -38
- package/sql/95-seed.system.sql +6 -3
- package/sql/99-revision.sql +3 -2
- package/sql/README.md +2 -0
- package/sql/migrate/0007-secret-account-fk.sql +12 -0
package/sql/90-time.sql
CHANGED
|
@@ -80,13 +80,22 @@ begin
|
|
|
80
80
|
return false;
|
|
81
81
|
end $$;
|
|
82
82
|
|
|
83
|
+
-- Действующая роль сеанса, под которой вызывающий работает вне функций владельца: роль SET ROLE (и параметра запуска
|
|
84
|
+
-- role), иначе роль входа. Внутри функции владельца current_user — владелец, а session_user — роль входа, даже если
|
|
85
|
+
-- поверх неё поставлена роль приложения: привилегированный вход с ролью приложения выглядел бы администратором
|
|
86
|
+
-- (автоматическое ревью коммита 1734d21). Подменить её нельзя: SET ROLE проверяет членство.
|
|
87
|
+
create or replace function @SCHEMA@.session_role() returns name
|
|
88
|
+
language sql stable
|
|
89
|
+
set search_path = pg_catalog, @SCHEMA@, pg_temp
|
|
90
|
+
return coalesce(nullif(current_setting('role'), 'none'), session_user)::name;
|
|
91
|
+
|
|
83
92
|
-- Роль подключения — администраторская (суперпользователь или может стать владельцем): verify,
|
|
84
93
|
-- reset и maintain доступны ей и без сессии (CLI, обслуживание по расписанию).
|
|
85
94
|
create or replace function @SCHEMA@.admin_role() returns boolean
|
|
86
95
|
language sql stable
|
|
87
96
|
set search_path = pg_catalog, @SCHEMA@, pg_temp
|
|
88
|
-
return exists (select 1 from pg_roles where rolname =
|
|
89
|
-
or pg_has_role(
|
|
97
|
+
return exists (select 1 from pg_roles where rolname = @SCHEMA@.session_role() and rolsuper)
|
|
98
|
+
or pg_has_role(@SCHEMA@.session_role(), '@OWNER_NAME@', 'SET');
|
|
90
99
|
|
|
91
100
|
-- Право letopis.* актора сессии по правилам System или администраторская роль подключения.
|
|
92
101
|
create or replace function @SCHEMA@.letopis_allowed(p_endpoint text) returns boolean
|
|
@@ -209,6 +218,9 @@ begin
|
|
|
209
218
|
end if;
|
|
210
219
|
perform @SCHEMA@.family_lock(cls, false);
|
|
211
220
|
cls := @SCHEMA@.class_row(l.class, l.tenant);
|
|
221
|
+
if cls.id is null then
|
|
222
|
+
perform @SCHEMA@.lt_fail('invalid_class', format('класс %s удалён', l.class));
|
|
223
|
+
end if;
|
|
212
224
|
res := cls.data->'resolved';
|
|
213
225
|
if coalesce((res->>'abstract')::boolean, false) then
|
|
214
226
|
perform @SCHEMA@.lt_fail('invalid_class', format('класс %s абстрактный', l.class));
|
|
@@ -256,12 +268,16 @@ end $$;
|
|
|
256
268
|
-- все версии удаляются, а на место версии 1 встаёт запись purge с числом стёртых версий и хэшем
|
|
257
269
|
-- последней. Право DELETE на класс по последнему снимку. История системных классов — аудит прав и
|
|
258
270
|
-- схемы: её не стирают.
|
|
259
|
-
-- Проектор источника ещё не прочёл
|
|
260
|
-
--
|
|
261
|
-
--
|
|
262
|
-
--
|
|
263
|
-
--
|
|
264
|
-
|
|
271
|
+
-- Проектор источника ещё не прочёл одну из версий p_id арендатора p_tenant с номером до p_rev включительно (null — все
|
|
272
|
+
-- версии): курсор не прошёл её позицию (tx, seq), и снимок перестройки её не учёл (ревью безопасности №3, №5 — purge;
|
|
273
|
+
-- хвост ревью №3 — обрезка истории). Проверяется каждая версия (хвост ревью доработок после 1.2.0): у более поздней
|
|
274
|
+
-- версии бывает меньший xid, а снимок перестройки учитывает транзакции, зафиксированные до него, — непрочитанной может
|
|
275
|
+
-- оказаться и версия между самой поздней по (tx, seq) и последней. Проектор касается арендатора, у которого есть его
|
|
276
|
+
-- производный класс (derivedBy — имя проектора): брошенный проектор с удалёнными производными классами не держит, — и
|
|
277
|
+
-- строки, у которой хоть одна из этих версий — в классах его источника: уход из семейства источников сменой класса он
|
|
278
|
+
-- тоже читает. Курсор «tx:seq~снимок»; чекпоинт без from (до 1.2.0) касается всех классов. Записи purge, trim и reset —
|
|
279
|
+
-- не версии.
|
|
280
|
+
create or replace function @SCHEMA@.projector_unread(p_tenant uuid, p_id uuid, p_rev int) returns boolean
|
|
265
281
|
language sql stable
|
|
266
282
|
set search_path = pg_catalog, @SCHEMA@, pg_temp
|
|
267
283
|
return exists (select 1 from @SCHEMA@.secret s
|
|
@@ -272,11 +288,18 @@ return exists (select 1 from @SCHEMA@.secret s
|
|
|
272
288
|
and exists (select 1 from @SCHEMA@.entity x where x.class = 'Class' and x.tenant = p_tenant
|
|
273
289
|
and x.data->'resolved'->>'derivedBy' = s.ident)
|
|
274
290
|
and (not s.meta ? 'from'
|
|
275
|
-
or exists (select 1 from
|
|
276
|
-
|
|
291
|
+
or exists (select 1 from (select distinct v.class from @SCHEMA@.log v
|
|
292
|
+
where v.id = p_id and (p_rev is null or v.rev <= p_rev)
|
|
293
|
+
and v.op not in ('purge', 'trim', 'reset')) c
|
|
294
|
+
where coalesce((@SCHEMA@.class_row(c.class, p_tenant)).data->'resolved'->'ancestors', jsonb_build_array(c.class))
|
|
277
295
|
?| array(select jsonb_array_elements_text(s.meta->'from'))))
|
|
278
|
-
and
|
|
279
|
-
|
|
296
|
+
and exists (select 1 from @SCHEMA@.log v
|
|
297
|
+
where v.id = p_id and (p_rev is null or v.rev <= p_rev) and v.op not in ('purge', 'trim', 'reset')
|
|
298
|
+
and (v.tx, v.seq) > (split_part(k.head, ':', 1)::xid8, split_part(k.head, ':', 2)::bigint)
|
|
299
|
+
and not coalesce(pg_visible_in_snapshot(v.tx, k.snap::pg_snapshot), false)));
|
|
300
|
+
|
|
301
|
+
-- до 1.4 проверялась одна позиция: надгробие у purge, самая поздняя по (tx, seq) версия у стыка обрезки
|
|
302
|
+
drop function if exists @SCHEMA@.projector_behind(uuid, text[], xid8, bigint);
|
|
280
303
|
|
|
281
304
|
create or replace function @SCHEMA@.purge(p_id uuid, p_confirm boolean default false) returns jsonb
|
|
282
305
|
language plpgsql volatile security definer
|
|
@@ -301,9 +324,9 @@ begin
|
|
|
301
324
|
if not @SCHEMA@.acl_row_ok(@SCHEMA@.acl_plan('DELETE'), l.class, l.id, l.owner, l.tenant, l.tags, l.data, l.links) then
|
|
302
325
|
perform @SCHEMA@.lt_fail('acl_denied', format('нет права DELETE на класс %s', l.class));
|
|
303
326
|
end if;
|
|
304
|
-
-- проектор источника,
|
|
305
|
-
-- узнал бы — вклад строки остался бы в итогах навсегда (ревью безопасности №3, №5)
|
|
306
|
-
if @SCHEMA@.
|
|
327
|
+
-- проектор источника, который ещё не прочёл какую-то версию строки (в том числе надгробие этой транзакции), об
|
|
328
|
+
-- удалении не узнал бы — вклад строки остался бы в итогах навсегда (ревью безопасности №3, №5)
|
|
329
|
+
if @SCHEMA@.projector_unread(l.tenant, p_id, null) then
|
|
307
330
|
perform @SCHEMA@.lt_fail('projector_pending', 'проектор источника ещё не прочёл удаление: purge — после его run()');
|
|
308
331
|
end if;
|
|
309
332
|
select count(*)::int into n from @SCHEMA@.log x where x.id = p_id;
|
|
@@ -340,7 +363,6 @@ declare
|
|
|
340
363
|
r @SCHEMA@.log;
|
|
341
364
|
first @SCHEMA@.log;
|
|
342
365
|
nxt record;
|
|
343
|
-
late record;
|
|
344
366
|
j int;
|
|
345
367
|
h bytea;
|
|
346
368
|
k int;
|
|
@@ -385,15 +407,11 @@ begin
|
|
|
385
407
|
select x.* into first from @SCHEMA@.log x where x.id = r.id order by x.rev limit 1;
|
|
386
408
|
-- до момента — только сама запись trim: стирать нечего
|
|
387
409
|
continue when j = 1 and first.op = 'trim';
|
|
388
|
-
-- проектор источника,
|
|
389
|
-
-- before, а её уже не будет — свёртка прибавила бы новую, не вычитая прежнюю (хвост ревью безопасности
|
|
390
|
-
--
|
|
391
|
-
select x.rev
|
|
392
|
-
|
|
393
|
-
-- меньший xid (автоматическое ревью)
|
|
394
|
-
select x.tx, x.seq into late from @SCHEMA@.log x where x.id = r.id and x.rev <= nxt.rev order by x.tx desc, x.seq desc limit 1;
|
|
395
|
-
if @SCHEMA@.projector_behind(r.tenant, array[(select x.class from @SCHEMA@.log x where x.id = r.id and x.rev = j), nxt.class],
|
|
396
|
-
late.tx, late.seq) then
|
|
410
|
+
-- проектор источника, который ещё не прочёл стираемые версии или первую оставшуюся: читая её, он возьмёт прошлую
|
|
411
|
+
-- версию для before, а её уже не будет — свёртка прибавила бы новую, не вычитая прежнюю (хвост ревью безопасности
|
|
412
|
+
-- №3); каждая версия до первой оставшейся — у более поздней версии бывает меньший xid
|
|
413
|
+
select x.rev into nxt from @SCHEMA@.log x where x.id = r.id and x.rev > j order by x.rev limit 1;
|
|
414
|
+
if @SCHEMA@.projector_unread(r.tenant, r.id, nxt.rev) then
|
|
397
415
|
perform @SCHEMA@.lt_fail('projector_pending', 'проектор источника ещё не прочёл версии: trimHistory — после его run()');
|
|
398
416
|
end if;
|
|
399
417
|
select x.hash into h from @SCHEMA@.log x where x.id = r.id and x.rev = j;
|
|
@@ -456,6 +474,9 @@ begin
|
|
|
456
474
|
cls := @SCHEMA@.class_row(o.class, o.tenant);
|
|
457
475
|
perform @SCHEMA@.family_lock(cls, false);
|
|
458
476
|
cls := @SCHEMA@.class_row(o.class, o.tenant);
|
|
477
|
+
if cls.id is null then
|
|
478
|
+
perform @SCHEMA@.lt_fail('invalid_class', format('класс %s удалён', o.class));
|
|
479
|
+
end if;
|
|
459
480
|
res := cls.data->'resolved';
|
|
460
481
|
if res ? 'derivedBy' then
|
|
461
482
|
perform @SCHEMA@.lt_fail('derived_class', format('класс %s производный: его строки пишет проектор %s', o.class, res->>'derivedBy'));
|
|
@@ -601,7 +622,7 @@ end $$;
|
|
|
601
622
|
-- последнего включённого владельца. Данные и журнал арендатора-цели стираются физически; в чужих
|
|
602
623
|
-- арендаторах членства снимаются (надгробия), владение строками переходит арендаторам (§2.10);
|
|
603
624
|
-- остались ссылки на аккаунт — delete_restricted. Строка Account удаляется с надгробием (история
|
|
604
|
-
-- аккаунтов — аудит), сессии
|
|
625
|
+
-- аккаунтов — аудит), сессии гаснут с сигналом, строки secret аккаунта стирает каскад внешнего ключа.
|
|
605
626
|
create or replace function @SCHEMA@.account_purge(p_account uuid) returns jsonb
|
|
606
627
|
language plpgsql volatile security definer
|
|
607
628
|
set search_path = pg_catalog, @SCHEMA@, pg_temp
|
|
@@ -637,6 +658,14 @@ begin
|
|
|
637
658
|
if a.id is null or p_account = '@SYSTEM@'::uuid then
|
|
638
659
|
perform @SCHEMA@.lt_fail('target_not_found', 'аккаунт не найден');
|
|
639
660
|
end if;
|
|
661
|
+
-- владелец стирает арендатора, в котором работает: его членство уйдёт вместе с данными, а надгробия и
|
|
662
|
+
-- передача владения ниже перечитывают ctx() — остаток операции идёт в личном арендаторе вызывающего,
|
|
663
|
+
-- и его сессия больше не указывает на стёртого арендатора (иначе tenant_denied и откат всего вызова)
|
|
664
|
+
if c.tenant = p_account then
|
|
665
|
+
perform set_config('letopis.tenant', c.account::text, true);
|
|
666
|
+
update @SCHEMA@.secret s set meta = s.meta - 'tenant'
|
|
667
|
+
where s.id = c.session and s.kind = 'SESSION' and s.meta->>'tenant' = p_account::text;
|
|
668
|
+
end if;
|
|
640
669
|
-- последний владелец: в каждом чужом арендаторе, где цель — владелец, должен остаться другой
|
|
641
670
|
for t in select x.tenant from @SCHEMA@.entity x
|
|
642
671
|
where x.class = 'member' and x.links->>'User' = p_account::text and x.tenant <> p_account
|
|
@@ -658,7 +687,7 @@ begin
|
|
|
658
687
|
perform @SCHEMA@.tombstone(m);
|
|
659
688
|
n_mem := n_mem + 1;
|
|
660
689
|
end loop;
|
|
661
|
-
for t in select distinct x.tenant from @SCHEMA@.entity x where x.owner = p_account and x.owner <> x.tenant loop
|
|
690
|
+
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
|
|
662
691
|
n_own := n_own + @SCHEMA@.owner_to_tenant(t.tenant, p_account);
|
|
663
692
|
end loop;
|
|
664
693
|
select coalesce(jsonb_agg(jsonb_build_object('id', x.id, 'class', x.class, 'tenant', x.tenant)), '[]'::jsonb) into blockers
|
|
@@ -666,10 +695,10 @@ begin
|
|
|
666
695
|
if blockers <> '[]'::jsonb then
|
|
667
696
|
perform @SCHEMA@.lt_fail('delete_restricted', 'на аккаунт ссылаются другие строки', @SCHEMA@.visible_detail(blockers, c.tenant));
|
|
668
697
|
end if;
|
|
698
|
+
-- сессии аккаунта гасит с сигналом триггер до удаления (trg_entity_bd_account, срабатывает и для владельца), а его
|
|
699
|
+
-- строки secret — сессии и креды — стирает каскад внешнего ключа secret.account (ревизия 7)
|
|
669
700
|
delete from @SCHEMA@.entity x where x.id = p_account;
|
|
670
701
|
perform @SCHEMA@.tombstone(a);
|
|
671
|
-
perform @SCHEMA@.auth_kill(p_account, null);
|
|
672
|
-
delete from @SCHEMA@.secret s where s.account = p_account;
|
|
673
702
|
-- данные арендатора-цели — его парами; Account и member — классы прав, их сигнал сбрасывает всё
|
|
674
703
|
perform @SCHEMA@.signal(classes || array['Account', 'member'],
|
|
675
704
|
array(select p_account from unnest(classes)) || array['@SYSTEM@'::uuid, '@SYSTEM@'::uuid]);
|
|
@@ -876,7 +905,9 @@ begin
|
|
|
876
905
|
with d as (delete from @SCHEMA@.secret x where x.kind = 'SESSION'
|
|
877
906
|
and (x.deleted_at is not null or x.expires_at <= clock_timestamp()) returning 1)
|
|
878
907
|
select count(*)::int into s from d;
|
|
879
|
-
|
|
908
|
+
-- коды всех видов, которые выпускает auth_issue_otp: OTP и OTP_<имя> (тот же шаблон вида), — использованные,
|
|
909
|
+
-- заменённые, исчерпавшие попытки (deleted_at) и истёкшие
|
|
910
|
+
with d as (delete from @SCHEMA@.secret x where x.kind ~ '^OTP(_[A-Z0-9]{1,27})?$'
|
|
880
911
|
and (x.deleted_at is not null or x.expires_at <= clock_timestamp()) returning 1)
|
|
881
912
|
select count(*)::int into o from d;
|
|
882
913
|
return jsonb_build_object('sessions', s, 'codes', o, 'queue', pg_notification_queue_usage())
|
|
@@ -1061,7 +1092,10 @@ end $$;
|
|
|
1061
1092
|
-- окна p_upto — последняя строка полной пачки watch_read), а строки — синтетические надгробия прежней версии: класс,
|
|
1062
1093
|
-- концы и данные прежние; номер, время, автор, tx и seq — версии смены класса; вид delete; агента и причины нет. Только
|
|
1063
1094
|
-- если нового класса нет в p_classes, а прежнюю версию актор видит (row_visible с его планом READ): о строке, которой он
|
|
1064
|
-
-- не читал, подписчик не узнает. Функция владельца: версию с новым классом актору видеть не нужно.
|
|
1095
|
+
-- не читал, подписчик не узнает. Функция владельца: версию с новым классом актору видеть не нужно. Подключение в обход
|
|
1096
|
+
-- RLS (суперпользователь, BYPASSRLS, член роли-владельца с наследованием — connect() пускает его только с
|
|
1097
|
+
-- allowBypassRls) видит в watch_read журнал всех арендаторов, поэтому и события ухода получает все, с автором (хвост
|
|
1098
|
+
-- ревью доработок после 1.2.0). Роль — действующая роль сеанса (session_role): та, к которой watch_read применяет RLS.
|
|
1065
1099
|
create or replace function @SCHEMA@.watch_leaving(p_tx xid8, p_seq bigint, p_classes text[], p_limit int,
|
|
1066
1100
|
p_snap pg_snapshot default null, p_upto_tx xid8 default null, p_upto_seq bigint default null)
|
|
1067
1101
|
returns setof @SCHEMA@.log
|
|
@@ -1071,12 +1105,15 @@ as $$
|
|
|
1071
1105
|
declare
|
|
1072
1106
|
horizon xid8 := pg_snapshot_xmin(pg_current_snapshot());
|
|
1073
1107
|
a jsonb;
|
|
1108
|
+
bypass boolean;
|
|
1074
1109
|
begin
|
|
1075
1110
|
if p_classes is null then
|
|
1076
1111
|
return;
|
|
1077
1112
|
end if;
|
|
1078
|
-
|
|
1079
|
-
|
|
1113
|
+
bypass := exists (select 1 from pg_roles r where r.rolname = @SCHEMA@.session_role() and (r.rolsuper or r.rolbypassrls))
|
|
1114
|
+
or pg_has_role(@SCHEMA@.session_role(), '@OWNER_NAME@', 'USAGE');
|
|
1115
|
+
a := case when not bypass then @SCHEMA@.acl_actor('READ') end;
|
|
1116
|
+
if a is null and not bypass then
|
|
1080
1117
|
return;
|
|
1081
1118
|
end if;
|
|
1082
1119
|
-- новую версию актор не видит — надгробие при любом p_classes и без автора: перебором классов подписки он узнал бы её
|
|
@@ -1086,15 +1123,15 @@ begin
|
|
|
1086
1123
|
from @SCHEMA@.log l
|
|
1087
1124
|
cross join lateral (select x.* from @SCHEMA@.log x where x.id = l.id and x.rev < l.rev and x.op not in ('purge', 'trim', 'reset')
|
|
1088
1125
|
order by x.rev desc limit 1) p
|
|
1089
|
-
cross join lateral (select @SCHEMA@.row_visible(l.tenant, l.class, l.id, l.owner, l.tags, l.data, l.links,
|
|
1126
|
+
cross join lateral (select bypass or @SCHEMA@.row_visible(l.tenant, l.class, l.id, l.owner, l.tags, l.data, l.links,
|
|
1090
1127
|
(a->>'tenant')::uuid, (a->>'account')::uuid, a->'plan') as seen) v
|
|
1091
1128
|
where (l.tx, l.seq) > (p_tx, p_seq) and l.tx < horizon
|
|
1092
1129
|
and (p_upto_tx is null or (l.tx, l.seq) <= (p_upto_tx, p_upto_seq))
|
|
1093
1130
|
and l.op = 'reclass' and l.moved is null and (not (l.class = any (p_classes)) or not v.seen)
|
|
1094
1131
|
and p.class = any (p_classes) and p.op <> 'delete'
|
|
1095
1132
|
and (p_snap is null or not pg_visible_in_snapshot(l.tx, p_snap))
|
|
1096
|
-
and @SCHEMA@.row_visible(p.tenant, p.class, p.id, p.owner, p.tags, p.data, p.links,
|
|
1097
|
-
|
|
1133
|
+
and (bypass or @SCHEMA@.row_visible(p.tenant, p.class, p.id, p.owner, p.tags, p.data, p.links,
|
|
1134
|
+
(a->>'tenant')::uuid, (a->>'account')::uuid, a->'plan'))
|
|
1098
1135
|
order by l.tx, l.seq limit p_limit;
|
|
1099
1136
|
end $$;
|
|
1100
1137
|
|
|
@@ -1116,8 +1153,8 @@ declare f text;
|
|
|
1116
1153
|
begin
|
|
1117
1154
|
foreach f in array array[
|
|
1118
1155
|
'history_default()', 'history_policy(jsonb)', 'history_iv(text)', 'history_horizon(jsonb)',
|
|
1119
|
-
'history_keep(jsonb, timestamp, timestamp, timestamp)', 'admin_role()', 'letopis_allowed(text)', 'session_expires()', 'signal_mode()', 'signal_key()', 'class_stamp()',
|
|
1120
|
-
'last_version_visible(uuid, uuid, jsonb)', 'restore(uuid)', '
|
|
1156
|
+
'history_keep(jsonb, timestamp, timestamp, timestamp)', 'session_role()', 'admin_role()', 'letopis_allowed(text)', 'session_expires()', 'signal_mode()', 'signal_key()', 'class_stamp()',
|
|
1157
|
+
'last_version_visible(uuid, uuid, jsonb)', 'restore(uuid)', 'projector_unread(uuid, uuid, int)', 'purge(uuid, boolean)', 'trim_history(uuid[], text[], timestamptz)',
|
|
1121
1158
|
'rekey(uuid, jsonb, jsonb)', 'rekey_class(text, jsonb)', 'account_purge(uuid)', 'reset_mark(uuid, text, uuid)',
|
|
1122
1159
|
'reset(text, text, uuid, int)', 'thin_batch(timestamptz, int)', 'maintain(timestamptz, int)', 'verify(boolean)',
|
|
1123
1160
|
'watch_read(xid8, bigint, timestamptz, text[], int, pg_snapshot, boolean)', 'watch_leaving(xid8, bigint, text[], int, pg_snapshot, xid8, bigint)', 'watch_head()'] loop
|
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,3 +1,4 @@
|
|
|
1
1
|
-- letopis 1.0 — штамп ревизии движка (план, §2.2, этап 1, п. 11): последний оператор наката.
|
|
2
|
-
-- Номер совпадает с DDL_REVISION в lib/src/types.ts (проверяет check-docs).
|
|
3
|
-
|
|
2
|
+
-- Номер совпадает с DDL_REVISION в lib/src/types.ts (проверяет check-docs). С 1.4 в штампе ещё версия пакета и хэш
|
|
3
|
+
-- файлов ядра (engine, sql): connect() предупреждает, если функции схемы ставила другая версия движка той же ревизии.
|
|
4
|
+
comment on schema @SCHEMA@ is 'letopis ddl_revision=7 engine=@ENGINE_VERSION@ sql=@ENGINE_SQL@';
|
package/sql/README.md
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
| `@HISTORY_DEFAULT@` | политика хранения по умолчанию `up({ history })` литералом JSON или `'null'` — константа установки |
|
|
18
18
|
| `@RESERVED_NAMES@` | методы цепочки — `RESERVED_CLASS_NAMES` из `lib/src/types.ts`, литералами через запятую |
|
|
19
19
|
| `@SIGNALS@` | режим сигналов `class`, `tenant`, `opaque` или `none` (план 1.1, §2.2): опция `up({ signals })`, иначе сохранённый в `meta.signals` строки ключа `secret`, иначе `class` — константа установки |
|
|
20
|
+
| `@ENGINE_VERSION@` | версия пакета (`lib/package.json`) — штамп движка в комментарии схемы (`99-revision.sql`) |
|
|
21
|
+
| `@ENGINE_SQL@` | 16 знаков sha256 исходного текста файлов `NN-*.sql` до подстановок (переводы строк — LF): `connect()` сверяет его с файлами своего пакета |
|
|
20
22
|
|
|
21
23
|
## Колонки снимка версии
|
|
22
24
|
|
|
@@ -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 $$;
|