letopis 2.0.0 → 3.0.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.
Files changed (83) hide show
  1. package/AGENT-CHEATSHEET.md +491 -414
  2. package/CHANGELOG.md +361 -0
  3. package/LICENSE +75 -21
  4. package/LICENSE-COMMERCIAL.md +49 -0
  5. package/README.md +969 -210
  6. package/api-contract.json +385 -0
  7. package/dist/acl.d.ts +7 -2
  8. package/dist/acl.js +19 -2
  9. package/dist/admin.d.ts +7 -1
  10. package/dist/admin.js +16 -9
  11. package/dist/auth.d.ts +47 -11
  12. package/dist/auth.js +129 -15
  13. package/dist/chain.d.ts +52 -25
  14. package/dist/chain.js +445 -64
  15. package/dist/cli.js +72 -20
  16. package/dist/errors.d.ts +23 -6
  17. package/dist/errors.js +52 -13
  18. package/dist/index.d.ts +32 -15
  19. package/dist/index.js +209 -68
  20. package/dist/internal.d.ts +2 -0
  21. package/dist/load.d.ts +10 -6
  22. package/dist/load.js +30 -11
  23. package/dist/log.d.ts +121 -0
  24. package/dist/log.js +654 -0
  25. package/dist/mcp.d.ts +1 -1
  26. package/dist/mcp.js +34 -16
  27. package/dist/messages/access.d.ts +10 -0
  28. package/dist/messages/access.js +13 -0
  29. package/dist/messages/api.d.ts +76 -0
  30. package/dist/messages/api.js +109 -7
  31. package/dist/messages/boot.d.ts +5 -0
  32. package/dist/messages/boot.js +11 -4
  33. package/dist/messages/codes.js +4 -4
  34. package/dist/messages/index.d.ts +5 -2
  35. package/dist/messages/index.js +5 -2
  36. package/dist/messages/log.d.ts +232 -0
  37. package/dist/messages/log.js +105 -0
  38. package/dist/messages/mcp.js +7 -7
  39. package/dist/messages/model.d.ts +40 -1
  40. package/dist/messages/model.js +52 -3
  41. package/dist/messages/query.d.ts +10 -0
  42. package/dist/messages/query.js +13 -0
  43. package/dist/messages/sql-core.d.ts +15 -1
  44. package/dist/messages/sql-core.js +20 -3
  45. package/dist/messages/sql-time.d.ts +15 -0
  46. package/dist/messages/sql-time.js +23 -0
  47. package/dist/messages/write.d.ts +52 -0
  48. package/dist/messages/write.js +77 -6
  49. package/dist/model.d.ts +10 -2
  50. package/dist/model.js +28 -6
  51. package/dist/registry.d.ts +6 -0
  52. package/dist/registry.js +6 -0
  53. package/dist/sql.d.ts +2 -0
  54. package/dist/sql.js +77 -25
  55. package/dist/sync.d.ts +4 -5
  56. package/dist/sync.js +53 -4
  57. package/dist/tx.d.ts +10 -1
  58. package/dist/tx.js +44 -2
  59. package/dist/typed.d.ts +18 -14
  60. package/dist/types.d.ts +91 -8
  61. package/dist/types.js +8 -3
  62. package/dist/up.d.ts +8 -5
  63. package/dist/up.js +71 -23
  64. package/dist/uuid.d.ts +8 -0
  65. package/dist/uuid.js +9 -1
  66. package/dist/validate.js +24 -7
  67. package/dist/watch.d.ts +5 -0
  68. package/dist/watch.js +13 -0
  69. package/dist/write.d.ts +84 -3
  70. package/dist/write.js +427 -40
  71. package/llms.txt +38 -17
  72. package/package.json +12 -6
  73. package/sql/20-context.sql +11 -3
  74. package/sql/30-validate.sql +51 -10
  75. package/sql/40-class.sql +21 -1
  76. package/sql/60-write.sql +18 -6
  77. package/sql/80-auth.sql +88 -17
  78. package/sql/90-time.sql +34 -6
  79. package/sql/99-revision.sql +5 -1
  80. package/sql/migrate/0009-restore-rev.tenants-name.sql +10 -0
  81. package/sql/migrate/0010-impersonation-no-account.sql +5 -0
  82. package/sql/migrate/0011-key-lock.otp-enabled.sql +7 -0
  83. package/sql/migrate/README.md +5 -1
package/sql/90-time.sql CHANGED
@@ -109,8 +109,13 @@ end $$;
109
109
 
110
110
  -- Восстановление удалённого объекта (этап 6, п. 2): последний снимок новой версией с видом restore.
111
111
  -- Снимок перепроверяется по текущему описанию класса (данные, концы с видимостью целей, ключ), право
112
- -- WRITE — на восстановленные значения. id прежний, в том числе у класса без ключа.
113
- create or replace function @SCHEMA@.restore(p_id uuid) returns uuid
112
+ -- WRITE — на восстановленные значения. id прежний, в том числе у класса без ключа. p_rev (ревизия 9, план 2.1, A1) —
113
+ -- ожидаемый номер последней записи журнала (надгробия из ответа delete или withDeleted): другой — отказ restore_stale,
114
+ -- проверка под той же блокировкой записи журнала, что у параллельного restore и воскрешения. Прежняя restore(uuid)
115
+ -- снимается миграцией 0009: вызов restore(id) попадает сюда с p_rev = null. Отказ — по записи журнала за последним
116
+ -- надгробием (второй раунд 2.1, A2): restore — restore_already, create — restore_id_taken (объект живой); иначе с p_rev —
117
+ -- restore_stale с after (вид записи сразу за p_rev), deleted и live, без p_rev — restore_not_deleted.
118
+ create or replace function @SCHEMA@.restore(p_id uuid, p_rev int default null) returns uuid
114
119
  language plpgsql volatile security definer
115
120
  set search_path = pg_catalog, @SCHEMA@, pg_temp
116
121
  as $$
@@ -127,6 +132,9 @@ declare
127
132
  own uuid;
128
133
  ts timestamptz;
129
134
  n @SCHEMA@.entity;
135
+ v_live boolean;
136
+ v_tomb int;
137
+ v_next text;
130
138
  begin
131
139
  select * into c from @SCHEMA@.ctx();
132
140
  if c.account is null then
@@ -134,8 +142,28 @@ begin
134
142
  end if;
135
143
  rd := @SCHEMA@.acl_actor('READ');
136
144
  l := @SCHEMA@.last_version_visible(p_id, c.tenant, rd);
137
- if l.op <> 'delete' or exists (select 1 from @SCHEMA@.entity e where e.id = p_id) then
138
- perform @SCHEMA@.lt_fail('sql-time.restore_not_deleted');
145
+ -- проигравший гонку: блокировка дождалась чужой фиксации, а запись l прочитана до неё. Новая версия видна следующему
146
+ -- оператору (read committed) — тогда последняя запись берётся заново, под той же блокировкой и проверкой видимости
147
+ if exists (select 1 from @SCHEMA@.log x where x.id = p_id and x.tenant = c.tenant and x.rev > l.rev) then
148
+ l := @SCHEMA@.last_version_visible(p_id, c.tenant, rd);
149
+ end if;
150
+ v_live := exists (select 1 from @SCHEMA@.entity e where e.id = p_id);
151
+ if l.op <> 'delete' or v_live or (p_rev is not null and l.rev <> p_rev) then
152
+ -- что за последним надгробием: restore — уже восстановлен, create — id занял новый объект с тем же ключом
153
+ select max(x.rev) into v_tomb from @SCHEMA@.log x where x.id = p_id and x.tenant = c.tenant and x.op = 'delete';
154
+ select x.op into v_next from @SCHEMA@.log x where x.id = p_id and x.tenant = c.tenant and x.rev > v_tomb order by x.rev limit 1;
155
+ if v_live and v_next = 'restore' then
156
+ perform @SCHEMA@.lt_fail('sql-time.restore_already', jsonb_build_object('id', p_id), jsonb_build_object('id', p_id, 'rev', l.rev));
157
+ elsif v_live and v_next = 'create' then
158
+ perform @SCHEMA@.lt_fail('sql-time.restore_id_taken', jsonb_build_object('id', p_id), jsonb_build_object('id', p_id, 'rev', l.rev));
159
+ elsif p_rev is null then
160
+ perform @SCHEMA@.lt_fail('sql-time.restore_not_deleted');
161
+ end if;
162
+ -- удалён снова, не удалялся, гонка: что было сразу после показанного удаления
163
+ select x.op into v_next from @SCHEMA@.log x where x.id = p_id and x.tenant = c.tenant and x.rev > p_rev order by x.rev limit 1;
164
+ perform @SCHEMA@.lt_fail('sql-time.restore_stale',
165
+ jsonb_build_object('id', p_id, 'rev', l.rev, 'expected', p_rev, 'after', coalesce(v_next, '—')),
166
+ jsonb_build_object('id', p_id, 'rev', l.rev, 'expected', p_rev, 'after', v_next, 'deleted', l.op = 'delete', 'live', v_live));
139
167
  end if;
140
168
  if l.class = 'Class' then
141
169
  perform @SCHEMA@.lt_fail('sql-time.restore_class_row');
@@ -370,13 +398,13 @@ declare f text;
370
398
  begin
371
399
  foreach f in array array[
372
400
  'session_role()', 'admin_role()', 'letopis_allowed(text)', 'session_expires()', 'signal_mode()', 'signal_key()', 'class_stamp()',
373
- 'last_version_visible(uuid, uuid, jsonb)', 'restore(uuid)', 'account_purge(uuid)', 'maintain()',
401
+ 'last_version_visible(uuid, uuid, jsonb)', 'restore(uuid, int)', 'account_purge(uuid)', 'maintain()',
374
402
  'watch_read(xid8, bigint, text[], int, pg_snapshot)', 'watch_head()'] loop
375
403
  execute format('alter function @SCHEMA@.%s owner to @OWNER@', f);
376
404
  execute format('revoke all on function @SCHEMA@.%s from public', f);
377
405
  end loop;
378
406
  end $$;
379
407
  -- приложению: функции владельца этапа 6 (проверяют права сами) и подписка (под RLS)
380
- grant execute on function @SCHEMA@.restore(uuid), @SCHEMA@.account_purge(uuid), @SCHEMA@.maintain(),
408
+ grant execute on function @SCHEMA@.restore(uuid, int), @SCHEMA@.account_purge(uuid), @SCHEMA@.maintain(),
381
409
  @SCHEMA@.session_expires(), @SCHEMA@.watch_read(xid8, bigint, text[], int, pg_snapshot), @SCHEMA@.watch_head(),
382
410
  @SCHEMA@.signal_mode(), @SCHEMA@.signal_key(), @SCHEMA@.class_stamp() to @APP@;
@@ -2,4 +2,8 @@
2
2
  -- Номер совпадает с DDL_REVISION в lib/src/types.ts (проверяет check-docs). С 1.4 в штампе ещё версия пакета и хэш
3
3
  -- файлов ядра (engine, sql): connect() предупреждает, если функции схемы ставила другая версия движка той же ревизии.
4
4
  -- Ревизия 8 — первая у 2.0 (план 2.0, редакция 2): схему ниже неё (letopis 1.x) up() и connect() отклоняют.
5
- comment on schema @SCHEMA@ is 'letopis ddl_revision=8 engine=@ENGINE_VERSION@ sql=@ENGINE_SQL@';
5
+ -- Ревизия 9 — 2.1.0: restore(id, rev), auth_logout_token(), auth_tenants() с именем, validate_row(); миграция 0009.
6
+ -- Ревизия 10 — 3.0.0: ctx() — имперсонация несуществующего аккаунта — «не найден» (target_not_found); миграция 0010.
7
+ -- Ревизия 11 — 3.0.0: вход по ключу и отзыв ключа — под блокировкой строки ключа, отключённые OTP и TOTP не
8
+ -- принимаются; миграция 0011.
9
+ comment on schema @SCHEMA@ is 'letopis ddl_revision=11 engine=@ENGINE_VERSION@ sql=@ENGINE_SQL@';
@@ -0,0 +1,10 @@
1
+ -- letopis 2.1.0 — ревизия 9 (план 2.1, A1, A3, A5). Миграция снимает функции, чью сигнатуру или вид результата меняет
2
+ -- ревизия: create or replace их не заменит (новый аргумент дал бы вторую перегрузку и неоднозначный вызов restore(id),
3
+ -- новый вид результата PostgreSQL не принимает). Новые функции и права ставит повторный накат файлов ядра сразу после
4
+ -- миграции (90-time.sql, 80-auth.sql); новая auth_logout_token(text) и validate_row(text, jsonb) — тоже он.
5
+ -- Второй раунд 2.1 (ревизия та же, 9): новые тела restore(uuid, int) — отказы по смыслу restore_already, restore_id_taken,
6
+ -- restore_stale с after, deleted, live — и validate_data(jsonb, jsonpath, jsonb, jsonb) — граница в тексте длины строки
7
+ -- (sql-core.issue_string_too_short, issue_string_too_long) — сигнатур не меняют: их ставит тот же повторный накат, а
8
+ -- тексты новых ключей — каталог сообщений (message_catalog), который накат пишет заново. Снимать здесь нечего.
9
+ drop function if exists @SCHEMA@.restore(uuid);
10
+ drop function if exists @SCHEMA@.auth_tenants();
@@ -0,0 +1,5 @@
1
+ -- letopis 3.0.0 — ревизия 10. Новое тело ctx() (20-context.sql): имперсонация аккаунта, которого нет, — отказ
2
+ -- sql-core.impersonation_no_account (target_not_found) вместо sql-core.impersonation_denied (acl_denied). Сигнатура та же:
3
+ -- тело ставит повторный накат файлов ядра сразу после миграции, текст ключа — каталог сообщений (message_catalog),
4
+ -- который накат пишет заново. Снимать и переносить нечего; оператор ниже — чтобы файл не был пустым запросом.
5
+ select 1;
@@ -0,0 +1,7 @@
1
+ -- letopis 3.0.0 — ревизия 11. Новые тела функций входа и кредов (20-context.sql, 80-auth.sql): вход по ключу
2
+ -- (login_apikey, auth_login_apikey, login_keysecret) берёт строку ключа for share, отзыв (auth_revoke_credential) —
3
+ -- for update: сессия, открытая параллельно с отзывом, гаснет вместе с ключом; отключённые (meta.enabled = false) OTP
4
+ -- и TOTP не принимаются (login_otp, auth_verify_totp, auth_totp_enabled); отключение креда (auth_set_flags) берёт его
5
+ -- строку for update и гасит его сессии, последний ключ сервиса не отключается. Сигнатуры те же: тела ставит повторный
6
+ -- накат файлов ядра сразу после миграции. Снимать и переносить нечего; оператор ниже — чтобы файл не был пустым запросом.
7
+ select 1;
@@ -11,4 +11,8 @@
11
11
  у файлов ядра (`@SCHEMA@`, `@OWNER@` и другие — таблица в `lib/sql/README.md`).
12
12
  - В миграцию идёт только то, чего не догонит повторный накат: колонка или ограничение существующей таблицы, перенос
13
13
  данных. Функции, индексы, триггеры и политики ставят файлы ядра.
14
- - У 2.0 (ревизия 8) миграций нет.
14
+ - У 2.0 (ревизия 8) миграций нет. Ревизия 9 (2.1.0) — `0009-restore-rev.tenants-name.sql`: снимает `restore(uuid)` и
15
+ `auth_tenants()` прежнего вида, новые ставит повторный накат. Ревизия 10 (3.0.0) — `0010-impersonation-no-account.sql`:
16
+ новое тело `ctx()` ставит повторный накат, миграции переносить нечего. Ревизия 11 (3.0.0) —
17
+ `0011-key-lock.otp-enabled.sql`: новые тела функций входа по ключу, отзыва креда, OTP и TOTP ставит повторный накат,
18
+ миграции переносить нечего.