@coffre/db 0.14.4 → 0.15.0

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 (218) hide show
  1. package/README.md +2 -1
  2. package/dist/change-records-_fKvahvs.js +55 -0
  3. package/dist/connect.d.ts +22 -10
  4. package/dist/connect.js +74 -12
  5. package/dist/database-CVteLhp1.d.ts +410 -0
  6. package/dist/{database-BadNeeN-.js → database-D55ENPX-.js} +46 -25
  7. package/dist/dialect.d.ts +4 -3
  8. package/dist/dialect.js +14 -5
  9. package/dist/grants.d.ts +4 -4
  10. package/dist/grants.js +4 -4
  11. package/dist/hyperdrive.d.ts +48 -23
  12. package/dist/hyperdrive.js +168 -91
  13. package/dist/index.d.ts +3 -3
  14. package/dist/index.js +1 -1
  15. package/dist/log-DaD7fyUq.d.ts +722 -0
  16. package/dist/log.d.ts +2 -596
  17. package/dist/log.js +224 -10
  18. package/dist/migrate.d.ts +43 -3
  19. package/dist/migrate.js +149 -29
  20. package/dist/migrations/postgres/0021_prune_vault_releases.sql +19 -0
  21. package/dist/migrations/postgres/0022_log_015.sql +672 -0
  22. package/dist/migrations/postgres/0023_change_path.sql +153 -0
  23. package/dist/migrations/postgres/0024_use_015.sql +556 -0
  24. package/dist/migrations/postgres/0025_app_change.sql +221 -0
  25. package/dist/migrations/postgres/0026_issue_lock.sql +106 -0
  26. package/dist/migrations/postgres/0027_retention.sql +118 -0
  27. package/dist/migrations/postgres/0028_use_stalls.sql +594 -0
  28. package/dist/migrations/postgres/0029_use_objects.sql +887 -0
  29. package/dist/migrations/postgres/0030_unarchive_restores.sql +259 -0
  30. package/dist/migrations/postgres/0031_app_read.sql +65 -0
  31. package/dist/migrations/postgres/0032_secrets_live_index.sql +4 -0
  32. package/dist/migrations/postgres/0033_use_generations.sql +582 -0
  33. package/dist/migrations/postgres/0034_vault_credentials.sql +153 -0
  34. package/dist/migrations/postgres/0035_first_days.sql +3 -0
  35. package/dist/migrations/postgres/0036_vault_issues.sql +91 -0
  36. package/dist/migrations/postgres/0037_vault_bindings.sql +271 -0
  37. package/dist/migrations/postgres/0040_binding_origin.sql +159 -0
  38. package/dist/migrations/postgres/0041_cutover.sql +132 -0
  39. package/dist/migrations/postgres/0042_sign_in_index.sql +7 -0
  40. package/dist/migrations/postgres/0043_issue_revoke_index.sql +4 -0
  41. package/dist/migrations/postgres/0044_use_by_name.sql +705 -0
  42. package/dist/migrations/postgres/0045_decide_first.sql +878 -0
  43. package/dist/migrations/postgres/0046_way_in_uses.sql +1149 -0
  44. package/dist/migrations/postgres/0047_cutover_finish.sql +268 -0
  45. package/dist/migrations/postgres/0048_use_batch.sql +59 -0
  46. package/dist/migrations/postgres/0049_reads.sql +171 -0
  47. package/dist/migrations/postgres/meta/0021_snapshot.json +3334 -0
  48. package/dist/migrations/postgres/meta/0022_snapshot.json +3831 -0
  49. package/dist/migrations/postgres/meta/0023_snapshot.json +3831 -0
  50. package/dist/migrations/postgres/meta/0024_snapshot.json +3831 -0
  51. package/dist/migrations/postgres/meta/0025_snapshot.json +3831 -0
  52. package/dist/migrations/postgres/meta/0026_snapshot.json +3831 -0
  53. package/dist/migrations/postgres/meta/0027_snapshot.json +3853 -0
  54. package/dist/migrations/postgres/meta/0028_snapshot.json +3853 -0
  55. package/dist/migrations/postgres/meta/0029_snapshot.json +3859 -0
  56. package/dist/migrations/postgres/meta/0030_snapshot.json +3859 -0
  57. package/dist/migrations/postgres/meta/0031_snapshot.json +3859 -0
  58. package/dist/migrations/postgres/meta/0032_snapshot.json +3875 -0
  59. package/dist/migrations/postgres/meta/0033_snapshot.json +3875 -0
  60. package/dist/migrations/postgres/meta/0034_snapshot.json +3875 -0
  61. package/dist/migrations/postgres/meta/0035_snapshot.json +3875 -0
  62. package/dist/migrations/postgres/meta/0036_snapshot.json +3875 -0
  63. package/dist/migrations/postgres/meta/0037_snapshot.json +3875 -0
  64. package/dist/migrations/postgres/meta/0038_snapshot.json +3881 -0
  65. package/dist/migrations/postgres/meta/0039_snapshot.json +3881 -0
  66. package/dist/migrations/postgres/meta/0040_snapshot.json +3931 -0
  67. package/dist/migrations/postgres/meta/0041_snapshot.json +3981 -0
  68. package/dist/migrations/postgres/meta/0042_snapshot.json +3981 -0
  69. package/dist/migrations/postgres/meta/0043_snapshot.json +3981 -0
  70. package/dist/migrations/postgres/meta/0044_snapshot.json +3991 -0
  71. package/dist/migrations/postgres/meta/0045_snapshot.json +3997 -0
  72. package/dist/migrations/postgres/meta/0046_snapshot.json +3997 -0
  73. package/dist/migrations/postgres/meta/0047_snapshot.json +4234 -0
  74. package/dist/migrations/postgres/meta/_journal.json +189 -0
  75. package/dist/migrations/sqlite/0018_log_015.sql +84 -0
  76. package/dist/migrations/sqlite/0019_retention.sql +2 -0
  77. package/dist/migrations/sqlite/0020_ended_references.sql +1 -0
  78. package/dist/migrations/sqlite/0021_secrets_live_index.sql +1 -0
  79. package/dist/migrations/sqlite/0022_binding_origin.sql +1 -0
  80. package/dist/migrations/sqlite/0023_sign_in_index.sql +2 -0
  81. package/dist/migrations/sqlite/0024_issue_revoke_index.sql +2 -0
  82. package/dist/migrations/sqlite/0025_way_in_uses.sql +42 -0
  83. package/dist/migrations/sqlite/0026_cutover_finish.sql +11 -0
  84. package/dist/migrations/sqlite/0027_reads.sql +38 -0
  85. package/dist/migrations/sqlite/meta/0018_snapshot.json +3529 -0
  86. package/dist/migrations/sqlite/meta/0019_snapshot.json +3538 -0
  87. package/dist/migrations/sqlite/meta/0020_snapshot.json +3545 -0
  88. package/dist/migrations/sqlite/meta/0021_snapshot.json +3553 -0
  89. package/dist/migrations/sqlite/meta/0022_snapshot.json +3560 -0
  90. package/dist/migrations/sqlite/meta/0023_snapshot.json +3579 -0
  91. package/dist/migrations/sqlite/meta/0024_snapshot.json +3598 -0
  92. package/dist/migrations/sqlite/meta/0025_snapshot.json +3609 -0
  93. package/dist/migrations/sqlite/meta/0026_snapshot.json +3616 -0
  94. package/dist/migrations/sqlite/meta/0027_snapshot.json +3834 -0
  95. package/dist/migrations/sqlite/meta/_journal.json +70 -0
  96. package/dist/platform-Y3gICK6n.d.ts +58 -0
  97. package/dist/portable.d.ts +2 -2
  98. package/dist/postgres-BI3xTnyw.js +1718 -0
  99. package/dist/postgres-platform-node.d.ts +14 -0
  100. package/dist/postgres-platform-node.js +21 -0
  101. package/dist/postgres-platform-workerd.d.ts +61 -0
  102. package/dist/postgres-platform-workerd.js +163 -0
  103. package/dist/{schema-z8GcN_Xu.d.ts → schema-DgoPqqsI.d.ts} +1329 -9
  104. package/dist/schema-sqlite.d.ts +1308 -8
  105. package/dist/schema-sqlite.js +156 -20
  106. package/dist/schema-version.d.ts +1 -1
  107. package/dist/schema-version.js +261 -2
  108. package/dist/schema.d.ts +2 -2
  109. package/dist/schema.js +229 -22
  110. package/package.json +13 -5
  111. package/src/active-subject.ts +9 -0
  112. package/src/change-records.ts +38 -0
  113. package/src/connect.ts +67 -13
  114. package/src/database.ts +67 -51
  115. package/src/dialect.ts +17 -8
  116. package/src/grants.ts +3 -3
  117. package/src/hyperdrive.ts +181 -104
  118. package/src/log.ts +244 -11
  119. package/src/migrate.ts +157 -30
  120. package/src/migrations/postgres/0021_prune_vault_releases.sql +19 -0
  121. package/src/migrations/postgres/0022_log_015.sql +672 -0
  122. package/src/migrations/postgres/0023_change_path.sql +153 -0
  123. package/src/migrations/postgres/0024_use_015.sql +556 -0
  124. package/src/migrations/postgres/0025_app_change.sql +221 -0
  125. package/src/migrations/postgres/0026_issue_lock.sql +106 -0
  126. package/src/migrations/postgres/0027_retention.sql +118 -0
  127. package/src/migrations/postgres/0028_use_stalls.sql +594 -0
  128. package/src/migrations/postgres/0029_use_objects.sql +887 -0
  129. package/src/migrations/postgres/0030_unarchive_restores.sql +259 -0
  130. package/src/migrations/postgres/0031_app_read.sql +65 -0
  131. package/src/migrations/postgres/0032_secrets_live_index.sql +4 -0
  132. package/src/migrations/postgres/0033_use_generations.sql +582 -0
  133. package/src/migrations/postgres/0034_vault_credentials.sql +153 -0
  134. package/src/migrations/postgres/0035_first_days.sql +3 -0
  135. package/src/migrations/postgres/0036_vault_issues.sql +91 -0
  136. package/src/migrations/postgres/0037_vault_bindings.sql +271 -0
  137. package/src/migrations/postgres/0040_binding_origin.sql +159 -0
  138. package/src/migrations/postgres/0041_cutover.sql +132 -0
  139. package/src/migrations/postgres/0042_sign_in_index.sql +7 -0
  140. package/src/migrations/postgres/0043_issue_revoke_index.sql +4 -0
  141. package/src/migrations/postgres/0044_use_by_name.sql +705 -0
  142. package/src/migrations/postgres/0045_decide_first.sql +878 -0
  143. package/src/migrations/postgres/0046_way_in_uses.sql +1149 -0
  144. package/src/migrations/postgres/0047_cutover_finish.sql +268 -0
  145. package/src/migrations/postgres/0048_use_batch.sql +59 -0
  146. package/src/migrations/postgres/0049_reads.sql +171 -0
  147. package/src/migrations/postgres/meta/0021_snapshot.json +3334 -0
  148. package/src/migrations/postgres/meta/0022_snapshot.json +3831 -0
  149. package/src/migrations/postgres/meta/0023_snapshot.json +3831 -0
  150. package/src/migrations/postgres/meta/0024_snapshot.json +3831 -0
  151. package/src/migrations/postgres/meta/0025_snapshot.json +3831 -0
  152. package/src/migrations/postgres/meta/0026_snapshot.json +3831 -0
  153. package/src/migrations/postgres/meta/0027_snapshot.json +3853 -0
  154. package/src/migrations/postgres/meta/0028_snapshot.json +3853 -0
  155. package/src/migrations/postgres/meta/0029_snapshot.json +3859 -0
  156. package/src/migrations/postgres/meta/0030_snapshot.json +3859 -0
  157. package/src/migrations/postgres/meta/0031_snapshot.json +3859 -0
  158. package/src/migrations/postgres/meta/0032_snapshot.json +3875 -0
  159. package/src/migrations/postgres/meta/0033_snapshot.json +3875 -0
  160. package/src/migrations/postgres/meta/0034_snapshot.json +3875 -0
  161. package/src/migrations/postgres/meta/0035_snapshot.json +3875 -0
  162. package/src/migrations/postgres/meta/0036_snapshot.json +3875 -0
  163. package/src/migrations/postgres/meta/0037_snapshot.json +3875 -0
  164. package/src/migrations/postgres/meta/0038_snapshot.json +3881 -0
  165. package/src/migrations/postgres/meta/0039_snapshot.json +3881 -0
  166. package/src/migrations/postgres/meta/0040_snapshot.json +3931 -0
  167. package/src/migrations/postgres/meta/0041_snapshot.json +3981 -0
  168. package/src/migrations/postgres/meta/0042_snapshot.json +3981 -0
  169. package/src/migrations/postgres/meta/0043_snapshot.json +3981 -0
  170. package/src/migrations/postgres/meta/0044_snapshot.json +3991 -0
  171. package/src/migrations/postgres/meta/0045_snapshot.json +3997 -0
  172. package/src/migrations/postgres/meta/0046_snapshot.json +3997 -0
  173. package/src/migrations/postgres/meta/0047_snapshot.json +4234 -0
  174. package/src/migrations/postgres/meta/_journal.json +189 -0
  175. package/src/migrations/sqlite/0018_log_015.sql +84 -0
  176. package/src/migrations/sqlite/0019_retention.sql +2 -0
  177. package/src/migrations/sqlite/0020_ended_references.sql +1 -0
  178. package/src/migrations/sqlite/0021_secrets_live_index.sql +1 -0
  179. package/src/migrations/sqlite/0022_binding_origin.sql +1 -0
  180. package/src/migrations/sqlite/0023_sign_in_index.sql +2 -0
  181. package/src/migrations/sqlite/0024_issue_revoke_index.sql +2 -0
  182. package/src/migrations/sqlite/0025_way_in_uses.sql +42 -0
  183. package/src/migrations/sqlite/0026_cutover_finish.sql +11 -0
  184. package/src/migrations/sqlite/0027_reads.sql +38 -0
  185. package/src/migrations/sqlite/meta/0018_snapshot.json +3529 -0
  186. package/src/migrations/sqlite/meta/0019_snapshot.json +3538 -0
  187. package/src/migrations/sqlite/meta/0020_snapshot.json +3545 -0
  188. package/src/migrations/sqlite/meta/0021_snapshot.json +3553 -0
  189. package/src/migrations/sqlite/meta/0022_snapshot.json +3560 -0
  190. package/src/migrations/sqlite/meta/0023_snapshot.json +3579 -0
  191. package/src/migrations/sqlite/meta/0024_snapshot.json +3598 -0
  192. package/src/migrations/sqlite/meta/0025_snapshot.json +3609 -0
  193. package/src/migrations/sqlite/meta/0026_snapshot.json +3616 -0
  194. package/src/migrations/sqlite/meta/0027_snapshot.json +3834 -0
  195. package/src/migrations/sqlite/meta/_journal.json +70 -0
  196. package/src/postgres-js/README.md +183 -0
  197. package/src/postgres-js/UNLICENSE +24 -0
  198. package/src/postgres-js/bytes.ts +89 -0
  199. package/src/postgres-js/connection.ts +882 -0
  200. package/src/postgres-js/errors.ts +84 -0
  201. package/src/postgres-js/index.ts +516 -0
  202. package/src/postgres-js/platform.node.ts +29 -0
  203. package/src/postgres-js/platform.ts +60 -0
  204. package/src/postgres-js/platform.workerd.ts +225 -0
  205. package/src/postgres-js/query.ts +123 -0
  206. package/src/postgres-js/queue.ts +42 -0
  207. package/src/postgres-js/result.ts +46 -0
  208. package/src/postgres-js/types.ts +52 -0
  209. package/src/postgres.ts +375 -26
  210. package/src/schema-version.ts +2 -2
  211. package/src/schema.sqlite.ts +200 -9
  212. package/src/schema.ts +294 -8
  213. package/src/verify-runtime.ts +18 -29
  214. package/LICENSE +0 -21
  215. package/dist/database-CFhTnnzM.d.ts +0 -73
  216. package/dist/postgres-Bmar8y0q.js +0 -29
  217. package/dist/relations-BfR3JjT1.js +0 -38
  218. package/src/relations.ts +0 -40
@@ -0,0 +1,887 @@
1
+ -- 0.15's use, correct on three inputs before the switch (docs/design/0.15-plan.md, "use_credential correctness"; the spec: formal/SealedObjects.tla). It only adds: a nullable column, and functions nothing calls before 0.15's switch; 0.14's code serves on it as before.
2
+ -- - A reference's end in a row (T125). 0.14 ends a reference with its vault's `reference.end` entry alone, which 0.15's use never read: its holder kept reading the source, the end logged nowhere 0.15 looks. Now `secret_references.ended_at`, written by `change_input` in the vault's decision that appends the entry, as a change of the holder's object (`secret.write`, source null), and `use_credential` takes a source only from a newest reference row that has not ended.
3
+ -- - A project's archival on its environments (T126). 0.14 takes an environment of an archived project as archived; 0.15's object held the environment's own `archived_at` only, so a use through a fork of an archived project passed `throughOk`. Now each environment's object holds its project's archival apart from its own (`projectArchived`: one bit for both would let a writer clear the environment's own under a valid seal while its project is archived), the app logs `project.archive` on each environment when a project is archived or restored, and `use_credential` reads it.
4
+ -- - A plain value's text under its seal (T129). Its object held no part of the text, so a writer who edited `secret_versions.plaintext` changed the value under a valid seal. Now the object holds its digest, and `use_credential` answers the text, which the vault hashes against it.
5
+ --> statement-breakpoint
6
+ -- Its newest reference row's end (EndInRow): what the holder's object reads, and `change_input` writes in the vault's decision.
7
+ ALTER TABLE "secret_references" ADD COLUMN "ended_at" timestamp with time zone;
8
+ --> statement-breakpoint
9
+ -- The vault reads the objects it changes, a reference's holder's included, whose source is its newest reference row's: reads only, as of every place it decides on (0000's grants).
10
+ GRANT SELECT ON secret_references TO coffre_vault;
11
+ --> statement-breakpoint
12
+ -- The use's row gains the value's text, when plain (its object's digest), each environment's project's archival, apart from its own (ArchivedApart), and the reference row the key and its source read through (ReferenceInObject).
13
+ ALTER TYPE use_row ADD ATTRIBUTE v_plaintext text, ADD ATTRIBUTE e1_project_archived boolean, ADD ATTRIBUTE e2_project_archived boolean, ADD ATTRIBUTE e3_project_archived boolean, ADD ATTRIBUTE e4_project_archived boolean, ADD ATTRIBUTE s_reference uuid, ADD ATTRIBUTE x_reference uuid;
14
+ --> statement-breakpoint
15
+ -- `use_credential` as 0028 made it (its comments and 0024's say how), but for what it reads: a reference's source, and the id of the row it is read through, only while its newest row has not ended; each environment's project's archival, a scalar subquery each, one primary-key probe of `projects` (as left joins of the statement they cost about 0.2 ms of Postgres CPU a use, measured; as subqueries about 0.02 ms); and a plain value's text. And no JIT, ever (`jit = off`): the statement's generic plan, the one the hot login keeps (`plan_cache_mode`), is estimated near `jit_above_cost` (100,000 by default; 0028's at about 97,000 on hot-path-one.md's rig, this one's at about 102,000 with the project reads), and the estimate grows with the tables. Past it, Postgres compiles the plan's expressions at every execution: about 130 ms a use, measured, where the use costs under 2 ms. A setting of the function's, not the login's, so that no deployment's server default can bring it back.
16
+ CREATE OR REPLACE FUNCTION use_credential(
17
+ p_credential uuid, p_secret uuid, p_request bigint, p_at bigint, p_purpose smallint, p_place uuid, p_mac bytea, p_deadline bigint,
18
+ p_cover bigint DEFAULT NULL, p_cover_at bigint DEFAULT NULL, p_cover_request bigint DEFAULT NULL, p_known bigint DEFAULT NULL, p_known_mac bytea DEFAULT NULL,
19
+ p_cap_count integer DEFAULT NULL, p_cap_window bigint DEFAULT NULL
20
+ ) RETURNS json
21
+ LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog, pg_temp SET bytea_output = 'hex' SET jit = off AS $$
22
+ DECLARE
23
+ r public.use_row;
24
+ -- The principal's lock held: exclusive (`locked`), to enter its key in the cap's set; shared (`moving`), to move it on.
25
+ locked boolean := false;
26
+ moving boolean := false;
27
+ probe boolean := p_known IS NULL OR p_cap_count IS NULL OR p_cap_window IS NULL;
28
+ -- A use through a place, or of a key with no value of its own (a reference), reads every input; the others, most
29
+ -- uses, the plain shape: no place, no source, none of their joins.
30
+ whole boolean := p_place IS NOT NULL;
31
+ -- The cached forms' refresh and period, from the cap the isolate passes; the probing forms work theirs out from the cap they read.
32
+ c_refresh bigint := greatest(1, least(60000, p_cap_window / 10));
33
+ c_period bigint := p_at - ((p_at % c_refresh) + c_refresh) % c_refresh;
34
+ -- Whether this session has yet to plan the four statements (T131), and the form the warm-up is at.
35
+ dry boolean := current_setting('coffre.use_planned', true) IS DISTINCT FROM '0029';
36
+ form integer := 0;
37
+ first_probe boolean;
38
+ first_whole boolean;
39
+ -- The period this call writes, if it writes the cap's row, and the lock that elects the one use that moves it.
40
+ b_refresh bigint;
41
+ b_period bigint;
42
+ b_lock bigint;
43
+ b_round integer := 0;
44
+ BEGIN
45
+ IF p_deadline IS NULL OR p_deadline > p_at + public.deadline_max() THEN r.state := 'late'; RETURN to_json(r); END IF;
46
+ -- Its Worker's clock ahead by over a minute (0022 says why): checked here at no cost, and again in the statement.
47
+ IF p_at > (extract(epoch FROM clock_timestamp()) * 1000)::bigint + 60000 THEN r.state := 'ahead'; RETURN to_json(r); END IF;
48
+ IF dry THEN
49
+ -- Each form first runs dry, outside the fence, so that its plan is made before this call holds the fence.
50
+ first_probe := probe;
51
+ first_whole := whole;
52
+ probe := true;
53
+ whole := false;
54
+ ELSE
55
+ PERFORM pg_advisory_xact_lock_shared(1668244070, 0);
56
+ END IF;
57
+ LOOP
58
+ IF probe AND NOT whole THEN
59
+ WITH i AS MATERIALIZED (
60
+ SELECT n.number AS newest, n.mac AS newest_mac, coalesce(n.number = p_known AND n.mac = p_known_mac, false) AS cached,
61
+ f.at_floor, (lpol.payload::jsonb #>> '{value,bulk,count}')::integer AS cap_count, (lpol.payload::jsonb #>> '{value,bulk,windowMs}')::bigint AS cap_window,
62
+ lpol.number AS lpol_number, lpol.seal AS p_last,
63
+ c.principal AS p, c.kind AS c_kind, c.scope AS c_scope, c.revoked_at IS NOT NULL AS c_revoked,
64
+ floor(extract(epoch FROM least(c.expires_at, c.not_after)) * 1000)::bigint AS c_expires, c.seal AS c_seal,
65
+ m.status AS m_status, m.owner AS m_owner, m.role AS m_role, m.scope AS m_scope, m.seal AS m_seal,
66
+ cfg.r_value, cfg.r_seal, cfg.p_value, cfg.p_seal,
67
+ s.project_id AS s_project, s.environment_id AS s_environment, s.current_version_id AS s_version, NULL::uuid AS s_source, NULL::uuid AS s_reference, s.seal AS s_seal,
68
+ NULL::uuid AS x_project, NULL::uuid AS x_environment, NULL::uuid AS x_version, NULL::uuid AS x_source, NULL::uuid AS x_reference, NULL::bytea AS x_seal,
69
+ sv.envelope_version, sv.ciphertext, sv.iv, sv.auth_tag, sv.wrapped_dek, sv.kek_provider, sv.kek_id, sv.kek_version, sv.sensitive, sv.plaintext IS NOT NULL AS v_plain, sv.plaintext AS v_plaintext,
70
+ NULL::uuid AS e1_id, NULL::uuid AS e1_project, NULL::text AS e1_slug, NULL::text AS e1_owner, NULL::uuid AS e1_parent, NULL::boolean AS e1_protected, NULL::boolean AS e1_archived, NULL::boolean AS e1_project_archived, NULL::bytea AS e1_seal,
71
+ s.environment_id AS e2_id, e2.project_id AS e2_project, e2.slug AS e2_slug, e2.owner AS e2_owner, e2.parent_id AS e2_parent, e2.protected AS e2_protected, e2.archived_at IS NOT NULL AS e2_archived, (SELECT q.archived_at IS NOT NULL FROM public.projects q WHERE q.id = e2.project_id) AS e2_project_archived, e2.seal AS e2_seal,
72
+ NULL::uuid AS e3_id, NULL::uuid AS e3_project, NULL::text AS e3_slug, NULL::text AS e3_owner, NULL::uuid AS e3_parent, NULL::boolean AS e3_protected, NULL::boolean AS e3_archived, NULL::boolean AS e3_project_archived, NULL::bytea AS e3_seal,
73
+ CASE WHEN e2.owner IS NOT NULL THEN e2.parent_id END AS e4_id, e4.project_id AS e4_project, e4.slug AS e4_slug, e4.owner AS e4_owner, e4.parent_id AS e4_parent, e4.protected AS e4_protected, e4.archived_at IS NOT NULL AS e4_archived, (SELECT q.archived_at IS NOT NULL FROM public.projects q WHERE q.id = e4.project_id) AS e4_project_archived, e4.seal AS e4_seal,
74
+ b.last AS bulk_last,
75
+ -- Each input's object key, computed once: a key in a probe's own condition is computed again for every row it reads (0022, decision 3).
76
+ public.log_object('credential', p_credential::text) AS k_credential,
77
+ public.log_object('principal', c.principal) AS k_member,
78
+ public.log_object('secret', p_secret::text) AS k_secret,
79
+ public.log_object('environment', s.environment_id::text) AS k_e2,
80
+ public.log_object('environment', (CASE WHEN e2.owner IS NOT NULL THEN e2.parent_id END)::text) AS k_e4
81
+ FROM (SELECT 1) one
82
+ LEFT JOIN LATERAL (SELECT x.number, x.mac FROM public.log_changes x ORDER BY x.number DESC LIMIT 1) n ON true
83
+ LEFT JOIN public.log_fence f ON f.only_row
84
+ -- The roles and the policy, both rows in one read of their one-page table.
85
+ LEFT JOIN LATERAL (
86
+ SELECT max(g.value) FILTER (WHERE g.name = 'roles') AS r_value, (array_agg(g.seal) FILTER (WHERE g.name = 'roles'))[1] AS r_seal,
87
+ max(g.value) FILTER (WHERE g.name = 'policy') AS p_value, (array_agg(g.seal) FILTER (WHERE g.name = 'policy'))[1] AS p_seal
88
+ FROM public.log_config g
89
+ ) cfg ON true
90
+ -- The cap as the policy's newest change says it (CapSealed): every policy change logs the whole policy as its `value`.
91
+ LEFT JOIN LATERAL (
92
+ SELECT x.number, x.seal, x.payload FROM public.log_changes x WHERE x.object = public.log_object('policy', 'instance') ORDER BY x.number DESC LIMIT 1
93
+ ) lpol ON true
94
+ LEFT JOIN public.credentials c ON c.id = p_credential
95
+ LEFT JOIN public.vault_members m ON m.principal = c.principal
96
+ -- An archived key is gone (`secret.delete`): no object.
97
+ LEFT JOIN public.secrets s ON s.id = p_secret AND s.archived_at IS NULL
98
+ -- The key's own version, its own environment, and that environment's parent when it is a personal config.
99
+ LEFT JOIN public.secret_versions sv ON sv.id = s.current_version_id
100
+ LEFT JOIN public.environments e2 ON e2.id = s.environment_id
101
+ LEFT JOIN public.environments e4 ON e4.id = e2.parent_id AND e2.owner IS NOT NULL
102
+ LEFT JOIN public.log_bulk b ON b.principal = c.principal AND b.secret = p_secret
103
+ WHERE NOT dry
104
+ ),
105
+ v AS MATERIALIZED (
106
+ SELECT i.*, cap.refresh, cap.period,
107
+ CASE WHEN i.s_project IS NOT NULL AND i.s_version IS NULL THEN 'whole'
108
+ WHEN i.at_floor IS NULL OR p_at < i.at_floor OR (extract(epoch FROM clock_timestamp()) * 1000)::bigint > p_deadline THEN 'late'
109
+ WHEN p_at > (extract(epoch FROM clock_timestamp()) * 1000)::bigint + 60000 THEN 'ahead'
110
+ WHEN i.cap_count IS NULL OR i.cap_window IS NULL THEN 'unconfigured'
111
+ WHEN i.p IS NOT NULL AND NOT locked AND NOT coalesce(i.bulk_last > p_at - i.cap_window - cap.refresh, false) THEN 'lock'
112
+ WHEN i.p IS NOT NULL AND NOT locked AND NOT moving AND i.bulk_last < cap.period THEN 'share' END AS state,
113
+ greatest(lc.number, lm.number, lr.number, i.lpol_number, ls.number, l2.number, l4.number) AS inputs,
114
+ lc.seal AS c_last, lm.seal AS m_last, (lm.payload::jsonb ->> 'old')::bigint AS m_old, lr.seal AS r_last, ls.seal AS s_last, NULL::bytea AS x_last,
115
+ NULL::bytea AS e1_last, l2.seal AS e2_last, NULL::bytea AS e3_last, l4.seal AS e4_last,
116
+ coalesce(i.bulk_last > p_at - i.cap_window - cap.refresh, false) AS is_in,
117
+ -- The other keys in the set, up to the cap: read only under the principal's lock, for a key not in it.
118
+ CASE WHEN locked AND i.p IS NOT NULL AND NOT coalesce(i.bulk_last > p_at - i.cap_window - cap.refresh, false) THEN (
119
+ SELECT count(*)::integer FROM (
120
+ SELECT 1 FROM public.log_bulk o
121
+ WHERE o.principal = i.p AND o.secret <> p_secret AND o.last > p_at - i.cap_window - cap.refresh - public.deadline_max() - 60000
122
+ LIMIT i.cap_count
123
+ ) z
124
+ ) ELSE 0 END AS n_others,
125
+ -- A repeat rides its cover only while every part of this holds: never on a key whose bulk row is missing (CoverFresh).
126
+ coalesce(p_cover IS NOT NULL AND i.bulk_last >= cap.period AND p_cover > coalesce(greatest(lc.number, lm.number, lr.number, i.lpol_number, ls.number, l2.number, l4.number), 0)
127
+ AND EXISTS (SELECT 1 FROM public.log_uses u WHERE u.at = p_cover_at AND u.request_id = p_cover_request AND u.number = p_cover
128
+ AND u.credential = p_credential AND u.secret = p_secret AND u.purpose = p_purpose AND u.place IS NOT DISTINCT FROM p_place), false) AS is_covered
129
+ FROM i
130
+ CROSS JOIN LATERAL (SELECT greatest(1, least(60000, i.cap_window / 10)) AS refresh) r0
131
+ CROSS JOIN LATERAL (SELECT r0.refresh, p_at - ((p_at % r0.refresh) + r0.refresh) % r0.refresh AS period) cap
132
+ -- Each input's newest change: one index-only probe each (log_changes_object_idx holds the seal), the principal's
133
+ -- with its payload, which names the 0.14 entry it accounts for.
134
+ LEFT JOIN LATERAL (SELECT x.number, x.seal FROM public.log_changes x WHERE x.object = i.k_credential ORDER BY x.number DESC LIMIT 1) lc ON true
135
+ LEFT JOIN LATERAL (SELECT x.number, x.seal, x.payload FROM public.log_changes x WHERE x.object = i.k_member ORDER BY x.number DESC LIMIT 1) lm ON i.k_member IS NOT NULL
136
+ LEFT JOIN LATERAL (SELECT x.number, x.seal FROM public.log_changes x WHERE x.object = public.log_object('roles', 'instance') ORDER BY x.number DESC LIMIT 1) lr ON true
137
+ LEFT JOIN LATERAL (SELECT x.number, x.seal FROM public.log_changes x WHERE x.object = i.k_secret ORDER BY x.number DESC LIMIT 1) ls ON true
138
+ LEFT JOIN LATERAL (SELECT x.number, x.seal FROM public.log_changes x WHERE x.object = i.k_e2 ORDER BY x.number DESC LIMIT 1) l2 ON i.k_e2 IS NOT NULL
139
+ LEFT JOIN LATERAL (SELECT x.number, x.seal FROM public.log_changes x WHERE x.object = i.k_e4 ORDER BY x.number DESC LIMIT 1) l4 ON i.k_e4 IS NOT NULL
140
+ ),
141
+ w AS (
142
+ INSERT INTO public.log_uses (number, at, request_id, credential, secret, purpose, mac, place)
143
+ SELECT nextval('public.log_number'), p_at, p_request, p_credential, p_secret, p_purpose, p_mac, p_place
144
+ FROM v WHERE v.state IS NULL AND NOT v.is_covered
145
+ RETURNING log_uses.number
146
+ )
147
+ SELECT v.state, (SELECT w.number FROM w), CASE WHEN v.state IS NULL AND v.is_covered THEN p_cover END, v.newest, v.newest_mac, v.inputs, true,
148
+ v.p, v.c_kind, v.c_scope, v.c_revoked, v.c_expires, v.c_seal, v.c_last,
149
+ v.m_status, v.m_owner, v.m_role, v.m_scope, v.m_seal, v.m_last, v.m_old,
150
+ (SELECT a.seq FROM public.audit_log a WHERE a.author = 'vault' AND a.subject_principal = v.p AND a.decision = 'allow'
151
+ AND a.action IN ('member.add', 'member.restore', 'member.owner', 'member.role', 'member.remove', 'access.grant', 'access.revoke') ORDER BY a.seq DESC LIMIT 1),
152
+ gr.g_project, gr.g_environment, gr.g_slug, gr.g_role, gr.g_expires,
153
+ v.r_value, v.r_seal, v.r_last,
154
+ v.p_value, v.p_seal, v.p_last, v.cap_count, v.cap_window,
155
+ v.s_project, v.s_environment, v.s_version, v.s_source, v.s_seal, v.s_last,
156
+ v.x_project, v.x_environment, v.x_version, v.x_source, v.x_seal, v.x_last,
157
+ v.envelope_version, v.ciphertext, v.iv, v.auth_tag, v.wrapped_dek, v.kek_provider, v.kek_id, v.kek_version, v.sensitive, v.v_plain,
158
+ v.e1_id, v.e1_project, v.e1_slug, v.e1_owner, v.e1_parent, v.e1_protected, v.e1_archived, v.e1_seal, v.e1_last,
159
+ v.e2_id, v.e2_project, v.e2_slug, v.e2_owner, v.e2_parent, v.e2_protected, v.e2_archived, v.e2_seal, v.e2_last,
160
+ v.e3_id, v.e3_project, v.e3_slug, v.e3_owner, v.e3_parent, v.e3_protected, v.e3_archived, v.e3_seal, v.e3_last,
161
+ v.e4_id, v.e4_project, v.e4_slug, v.e4_owner, v.e4_parent, v.e4_protected, v.e4_archived, v.e4_seal, v.e4_last,
162
+ v.is_in, v.n_others, v.v_plaintext, v.e1_project_archived, v.e2_project_archived, v.e3_project_archived, v.e4_project_archived, v.s_reference, v.x_reference
163
+ INTO r
164
+ FROM v
165
+ -- The principal's grants as five lists, one entry per grant, lapsed ones included; an environment's grant with its project, which its row finds through `environments`.
166
+ LEFT JOIN LATERAL (
167
+ SELECT array_agg(coalesce(g.project_id, ge.project_id)) AS g_project, array_agg(g.environment_id) AS g_environment,
168
+ array_agg(g.environment_slug) AS g_slug, array_agg(g.role) AS g_role, array_agg(g.expires_at) AS g_expires
169
+ FROM public.vault_grants g LEFT JOIN public.environments ge ON ge.id = g.environment_id
170
+ WHERE g.principal = v.p
171
+ ) gr ON v.m_status IS NOT NULL;
172
+ ELSIF probe THEN
173
+ WITH i AS MATERIALIZED (
174
+ SELECT n.number AS newest, n.mac AS newest_mac, coalesce(n.number = p_known AND n.mac = p_known_mac, false) AS cached,
175
+ f.at_floor, (lpol.payload::jsonb #>> '{value,bulk,count}')::integer AS cap_count, (lpol.payload::jsonb #>> '{value,bulk,windowMs}')::bigint AS cap_window,
176
+ lpol.number AS lpol_number, lpol.seal AS p_last,
177
+ c.principal AS p, c.kind AS c_kind, c.scope AS c_scope, c.revoked_at IS NOT NULL AS c_revoked,
178
+ floor(extract(epoch FROM least(c.expires_at, c.not_after)) * 1000)::bigint AS c_expires, c.seal AS c_seal,
179
+ m.status AS m_status, m.owner AS m_owner, m.role AS m_role, m.scope AS m_scope, m.seal AS m_seal,
180
+ cfg.r_value, cfg.r_seal, cfg.p_value, cfg.p_seal,
181
+ s.project_id AS s_project, s.environment_id AS s_environment, s.current_version_id AS s_version, ref.source_secret_id AS s_source, ref.reference_id AS s_reference, s.seal AS s_seal,
182
+ src.project_id AS x_project, src.environment_id AS x_environment, src.current_version_id AS x_version, xref.source_secret_id AS x_source, xref.reference_id AS x_reference, src.seal AS x_seal,
183
+ sv.envelope_version, sv.ciphertext, sv.iv, sv.auth_tag, sv.wrapped_dek, sv.kek_provider, sv.kek_id, sv.kek_version, sv.sensitive, sv.plaintext IS NOT NULL AS v_plain, sv.plaintext AS v_plaintext,
184
+ ids.e1 AS e1_id, e1.project_id AS e1_project, e1.slug AS e1_slug, e1.owner AS e1_owner, e1.parent_id AS e1_parent, e1.protected AS e1_protected, e1.archived_at IS NOT NULL AS e1_archived, (SELECT q.archived_at IS NOT NULL FROM public.projects q WHERE q.id = e1.project_id) AS e1_project_archived, e1.seal AS e1_seal,
185
+ s.environment_id AS e2_id, e2.project_id AS e2_project, e2.slug AS e2_slug, e2.owner AS e2_owner, e2.parent_id AS e2_parent, e2.protected AS e2_protected, e2.archived_at IS NOT NULL AS e2_archived, (SELECT q.archived_at IS NOT NULL FROM public.projects q WHERE q.id = e2.project_id) AS e2_project_archived, e2.seal AS e2_seal,
186
+ ids.e3 AS e3_id, e3.project_id AS e3_project, e3.slug AS e3_slug, e3.owner AS e3_owner, e3.parent_id AS e3_parent, e3.protected AS e3_protected, e3.archived_at IS NOT NULL AS e3_archived, (SELECT q.archived_at IS NOT NULL FROM public.projects q WHERE q.id = e3.project_id) AS e3_project_archived, e3.seal AS e3_seal,
187
+ CASE WHEN ve.owner IS NOT NULL THEN ve.parent END AS e4_id, e4.project_id AS e4_project, e4.slug AS e4_slug, e4.owner AS e4_owner, e4.parent_id AS e4_parent, e4.protected AS e4_protected, e4.archived_at IS NOT NULL AS e4_archived, (SELECT q.archived_at IS NOT NULL FROM public.projects q WHERE q.id = e4.project_id) AS e4_project_archived, e4.seal AS e4_seal,
188
+ b.last AS bulk_last,
189
+ -- Each input's object key, computed once: a key in a probe's own condition is computed again for every row it reads (0022, decision 3).
190
+ public.log_object('credential', p_credential::text) AS k_credential,
191
+ public.log_object('principal', c.principal) AS k_member,
192
+ public.log_object('secret', p_secret::text) AS k_secret,
193
+ public.log_object('secret', ref.source_secret_id::text) AS k_source,
194
+ public.log_object('environment', ids.e1::text) AS k_e1,
195
+ public.log_object('environment', s.environment_id::text) AS k_e2,
196
+ public.log_object('environment', ids.e3::text) AS k_e3,
197
+ public.log_object('environment', (CASE WHEN ve.owner IS NOT NULL THEN ve.parent END)::text) AS k_e4
198
+ FROM (SELECT 1) one
199
+ LEFT JOIN LATERAL (SELECT x.number, x.mac FROM public.log_changes x ORDER BY x.number DESC LIMIT 1) n ON true
200
+ LEFT JOIN public.log_fence f ON f.only_row
201
+ -- The roles and the policy, both rows in one read of their one-page table.
202
+ LEFT JOIN LATERAL (
203
+ SELECT max(g.value) FILTER (WHERE g.name = 'roles') AS r_value, (array_agg(g.seal) FILTER (WHERE g.name = 'roles'))[1] AS r_seal,
204
+ max(g.value) FILTER (WHERE g.name = 'policy') AS p_value, (array_agg(g.seal) FILTER (WHERE g.name = 'policy'))[1] AS p_seal
205
+ FROM public.log_config g
206
+ ) cfg ON true
207
+ -- The cap as the policy's newest change says it (CapSealed): every policy change logs the whole policy as its `value`.
208
+ LEFT JOIN LATERAL (
209
+ SELECT x.number, x.seal, x.payload FROM public.log_changes x WHERE x.object = public.log_object('policy', 'instance') ORDER BY x.number DESC LIMIT 1
210
+ ) lpol ON true
211
+ LEFT JOIN public.credentials c ON c.id = p_credential
212
+ LEFT JOIN public.vault_members m ON m.principal = c.principal
213
+ -- An archived key is gone (`secret.delete`): no object.
214
+ LEFT JOIN public.secrets s ON s.id = p_secret AND s.archived_at IS NULL
215
+ -- A key with no version of its own is a reference while its newest reference row names a source and has not ended (`ended_at`): an ended one's holder reads as no value, no source (`ended`).
216
+ LEFT JOIN LATERAL (
217
+ SELECT CASE WHEN y.ended_at IS NULL THEN y.source_secret_id END AS source_secret_id, CASE WHEN y.ended_at IS NULL THEN y.id END AS reference_id FROM public.secret_references y WHERE y.secret_id = s.id ORDER BY y.created_seq DESC LIMIT 1
218
+ ) ref ON s.current_version_id IS NULL
219
+ LEFT JOIN public.secrets src ON src.id = ref.source_secret_id AND src.archived_at IS NULL
220
+ LEFT JOIN LATERAL (
221
+ SELECT CASE WHEN y.ended_at IS NULL THEN y.source_secret_id END AS source_secret_id, CASE WHEN y.ended_at IS NULL THEN y.id END AS reference_id FROM public.secret_references y WHERE y.secret_id = src.id ORDER BY y.created_seq DESC LIMIT 1
222
+ ) xref ON src.current_version_id IS NULL
223
+ -- The value's version: the key's own, or its source's.
224
+ LEFT JOIN public.secret_versions sv ON sv.id = CASE WHEN ref.source_secret_id IS NULL THEN s.current_version_id ELSE src.current_version_id END
225
+ -- The environments `useEnvironments` names, each by the id the rows name, whether or not its row is there: e1 the
226
+ -- place read through, e2 the key's own, e3 its value's (a reference's source's) where that is another, and e4
227
+ -- the parent of its value's when that is a personal config.
228
+ LEFT JOIN LATERAL (
229
+ SELECT CASE WHEN p_place IS DISTINCT FROM s.environment_id THEN p_place END AS e1,
230
+ CASE WHEN src.environment_id IS DISTINCT FROM s.environment_id THEN src.environment_id END AS e3
231
+ ) ids ON true
232
+ LEFT JOIN public.environments e1 ON e1.id = ids.e1
233
+ LEFT JOIN public.environments e2 ON e2.id = s.environment_id
234
+ LEFT JOIN public.environments e3 ON e3.id = ids.e3
235
+ -- The value's environment's row: the key's own, unless it is a reference, whose source is elsewhere or gone.
236
+ LEFT JOIN LATERAL (
237
+ SELECT CASE WHEN ref.source_secret_id IS NULL OR (src.id IS NOT NULL AND ids.e3 IS NULL) THEN e2.owner ELSE e3.owner END AS owner,
238
+ CASE WHEN ref.source_secret_id IS NULL OR (src.id IS NOT NULL AND ids.e3 IS NULL) THEN e2.parent_id ELSE e3.parent_id END AS parent
239
+ ) ve ON true
240
+ LEFT JOIN public.environments e4 ON e4.id = ve.parent AND ve.owner IS NOT NULL
241
+ LEFT JOIN public.log_bulk b ON b.principal = c.principal AND b.secret = p_secret
242
+ WHERE NOT dry
243
+ ),
244
+ v AS MATERIALIZED (
245
+ SELECT i.*, cap.refresh, cap.period,
246
+ CASE WHEN i.at_floor IS NULL OR p_at < i.at_floor OR (extract(epoch FROM clock_timestamp()) * 1000)::bigint > p_deadline THEN 'late'
247
+ WHEN p_at > (extract(epoch FROM clock_timestamp()) * 1000)::bigint + 60000 THEN 'ahead'
248
+ WHEN i.cap_count IS NULL OR i.cap_window IS NULL THEN 'unconfigured'
249
+ WHEN i.p IS NOT NULL AND NOT locked AND NOT coalesce(i.bulk_last > p_at - i.cap_window - cap.refresh, false) THEN 'lock'
250
+ WHEN i.p IS NOT NULL AND NOT locked AND NOT moving AND i.bulk_last < cap.period THEN 'share' END AS state,
251
+ greatest(lc.number, lm.number, lr.number, i.lpol_number, ls.number, lx.number, l1.number, l2.number, l3.number, l4.number) AS inputs,
252
+ lc.seal AS c_last, lm.seal AS m_last, (lm.payload::jsonb ->> 'old')::bigint AS m_old, lr.seal AS r_last, ls.seal AS s_last, lx.seal AS x_last,
253
+ l1.seal AS e1_last, l2.seal AS e2_last, l3.seal AS e3_last, l4.seal AS e4_last,
254
+ coalesce(i.bulk_last > p_at - i.cap_window - cap.refresh, false) AS is_in,
255
+ -- The other keys in the set, up to the cap: read only under the principal's lock, for a key not in it.
256
+ CASE WHEN locked AND i.p IS NOT NULL AND NOT coalesce(i.bulk_last > p_at - i.cap_window - cap.refresh, false) THEN (
257
+ SELECT count(*)::integer FROM (
258
+ SELECT 1 FROM public.log_bulk o
259
+ WHERE o.principal = i.p AND o.secret <> p_secret AND o.last > p_at - i.cap_window - cap.refresh - public.deadline_max() - 60000
260
+ LIMIT i.cap_count
261
+ ) z
262
+ ) ELSE 0 END AS n_others,
263
+ -- A repeat rides its cover only while every part of this holds: never on a key whose bulk row is missing (CoverFresh).
264
+ coalesce(p_cover IS NOT NULL AND i.bulk_last >= cap.period AND p_cover > coalesce(greatest(lc.number, lm.number, lr.number, i.lpol_number, ls.number, lx.number, l1.number, l2.number, l3.number, l4.number), 0)
265
+ AND EXISTS (SELECT 1 FROM public.log_uses u WHERE u.at = p_cover_at AND u.request_id = p_cover_request AND u.number = p_cover
266
+ AND u.credential = p_credential AND u.secret = p_secret AND u.purpose = p_purpose AND u.place IS NOT DISTINCT FROM p_place), false) AS is_covered
267
+ FROM i
268
+ CROSS JOIN LATERAL (SELECT greatest(1, least(60000, i.cap_window / 10)) AS refresh) r0
269
+ CROSS JOIN LATERAL (SELECT r0.refresh, p_at - ((p_at % r0.refresh) + r0.refresh) % r0.refresh AS period) cap
270
+ -- Each input's newest change: one index-only probe each (log_changes_object_idx holds the seal), the principal's
271
+ -- with its payload, which names the 0.14 entry it accounts for.
272
+ LEFT JOIN LATERAL (SELECT x.number, x.seal FROM public.log_changes x WHERE x.object = i.k_credential ORDER BY x.number DESC LIMIT 1) lc ON true
273
+ LEFT JOIN LATERAL (SELECT x.number, x.seal, x.payload FROM public.log_changes x WHERE x.object = i.k_member ORDER BY x.number DESC LIMIT 1) lm ON i.k_member IS NOT NULL
274
+ LEFT JOIN LATERAL (SELECT x.number, x.seal FROM public.log_changes x WHERE x.object = public.log_object('roles', 'instance') ORDER BY x.number DESC LIMIT 1) lr ON true
275
+ LEFT JOIN LATERAL (SELECT x.number, x.seal FROM public.log_changes x WHERE x.object = i.k_secret ORDER BY x.number DESC LIMIT 1) ls ON true
276
+ LEFT JOIN LATERAL (SELECT x.number, x.seal FROM public.log_changes x WHERE x.object = i.k_source ORDER BY x.number DESC LIMIT 1) lx ON i.k_source IS NOT NULL
277
+ LEFT JOIN LATERAL (SELECT x.number, x.seal FROM public.log_changes x WHERE x.object = i.k_e1 ORDER BY x.number DESC LIMIT 1) l1 ON i.k_e1 IS NOT NULL
278
+ LEFT JOIN LATERAL (SELECT x.number, x.seal FROM public.log_changes x WHERE x.object = i.k_e2 ORDER BY x.number DESC LIMIT 1) l2 ON i.k_e2 IS NOT NULL
279
+ LEFT JOIN LATERAL (SELECT x.number, x.seal FROM public.log_changes x WHERE x.object = i.k_e3 ORDER BY x.number DESC LIMIT 1) l3 ON i.k_e3 IS NOT NULL
280
+ LEFT JOIN LATERAL (SELECT x.number, x.seal FROM public.log_changes x WHERE x.object = i.k_e4 ORDER BY x.number DESC LIMIT 1) l4 ON i.k_e4 IS NOT NULL
281
+ ),
282
+ w AS (
283
+ INSERT INTO public.log_uses (number, at, request_id, credential, secret, purpose, mac, place)
284
+ SELECT nextval('public.log_number'), p_at, p_request, p_credential, p_secret, p_purpose, p_mac, p_place
285
+ FROM v WHERE v.state IS NULL AND NOT v.is_covered
286
+ RETURNING log_uses.number
287
+ )
288
+ SELECT v.state, (SELECT w.number FROM w), CASE WHEN v.state IS NULL AND v.is_covered THEN p_cover END, v.newest, v.newest_mac, v.inputs, true,
289
+ v.p, v.c_kind, v.c_scope, v.c_revoked, v.c_expires, v.c_seal, v.c_last,
290
+ v.m_status, v.m_owner, v.m_role, v.m_scope, v.m_seal, v.m_last, v.m_old,
291
+ (SELECT a.seq FROM public.audit_log a WHERE a.author = 'vault' AND a.subject_principal = v.p AND a.decision = 'allow'
292
+ AND a.action IN ('member.add', 'member.restore', 'member.owner', 'member.role', 'member.remove', 'access.grant', 'access.revoke') ORDER BY a.seq DESC LIMIT 1),
293
+ gr.g_project, gr.g_environment, gr.g_slug, gr.g_role, gr.g_expires,
294
+ v.r_value, v.r_seal, v.r_last,
295
+ v.p_value, v.p_seal, v.p_last, v.cap_count, v.cap_window,
296
+ v.s_project, v.s_environment, v.s_version, v.s_source, v.s_seal, v.s_last,
297
+ v.x_project, v.x_environment, v.x_version, v.x_source, v.x_seal, v.x_last,
298
+ v.envelope_version, v.ciphertext, v.iv, v.auth_tag, v.wrapped_dek, v.kek_provider, v.kek_id, v.kek_version, v.sensitive, v.v_plain,
299
+ v.e1_id, v.e1_project, v.e1_slug, v.e1_owner, v.e1_parent, v.e1_protected, v.e1_archived, v.e1_seal, v.e1_last,
300
+ v.e2_id, v.e2_project, v.e2_slug, v.e2_owner, v.e2_parent, v.e2_protected, v.e2_archived, v.e2_seal, v.e2_last,
301
+ v.e3_id, v.e3_project, v.e3_slug, v.e3_owner, v.e3_parent, v.e3_protected, v.e3_archived, v.e3_seal, v.e3_last,
302
+ v.e4_id, v.e4_project, v.e4_slug, v.e4_owner, v.e4_parent, v.e4_protected, v.e4_archived, v.e4_seal, v.e4_last,
303
+ v.is_in, v.n_others, v.v_plaintext, v.e1_project_archived, v.e2_project_archived, v.e3_project_archived, v.e4_project_archived, v.s_reference, v.x_reference
304
+ INTO r
305
+ FROM v
306
+ -- The principal's grants as five lists, one entry per grant, lapsed ones included; an environment's grant with its project, which its row finds through `environments`.
307
+ LEFT JOIN LATERAL (
308
+ SELECT array_agg(coalesce(g.project_id, ge.project_id)) AS g_project, array_agg(g.environment_id) AS g_environment,
309
+ array_agg(g.environment_slug) AS g_slug, array_agg(g.role) AS g_role, array_agg(g.expires_at) AS g_expires
310
+ FROM public.vault_grants g LEFT JOIN public.environments ge ON ge.id = g.environment_id
311
+ WHERE g.principal = v.p
312
+ ) gr ON v.m_status IS NOT NULL;
313
+ ELSIF NOT whole THEN
314
+ -- The isolate's seals, and its cap, read at the change it names: no probe, only whether a change came since
315
+ -- (then nothing is written: 'reprobe').
316
+ WITH i AS MATERIALIZED (
317
+ SELECT n.number AS newest, n.mac AS newest_mac, coalesce(n.number = p_known AND n.mac = p_known_mac, false) AS cached,
318
+ f.at_floor,
319
+ c.principal AS p, c.kind AS c_kind, c.scope AS c_scope, c.revoked_at IS NOT NULL AS c_revoked,
320
+ floor(extract(epoch FROM least(c.expires_at, c.not_after)) * 1000)::bigint AS c_expires, c.seal AS c_seal,
321
+ m.status AS m_status, m.owner AS m_owner, m.role AS m_role, m.scope AS m_scope, m.seal AS m_seal,
322
+ cfg.r_value, cfg.r_seal, cfg.p_value, cfg.p_seal,
323
+ s.project_id AS s_project, s.environment_id AS s_environment, s.current_version_id AS s_version, NULL::uuid AS s_source, NULL::uuid AS s_reference, s.seal AS s_seal,
324
+ NULL::uuid AS x_project, NULL::uuid AS x_environment, NULL::uuid AS x_version, NULL::uuid AS x_source, NULL::uuid AS x_reference, NULL::bytea AS x_seal,
325
+ sv.envelope_version, sv.ciphertext, sv.iv, sv.auth_tag, sv.wrapped_dek, sv.kek_provider, sv.kek_id, sv.kek_version, sv.sensitive, sv.plaintext IS NOT NULL AS v_plain, sv.plaintext AS v_plaintext,
326
+ NULL::uuid AS e1_id, NULL::uuid AS e1_project, NULL::text AS e1_slug, NULL::text AS e1_owner, NULL::uuid AS e1_parent, NULL::boolean AS e1_protected, NULL::boolean AS e1_archived, NULL::boolean AS e1_project_archived, NULL::bytea AS e1_seal,
327
+ s.environment_id AS e2_id, e2.project_id AS e2_project, e2.slug AS e2_slug, e2.owner AS e2_owner, e2.parent_id AS e2_parent, e2.protected AS e2_protected, e2.archived_at IS NOT NULL AS e2_archived, (SELECT q.archived_at IS NOT NULL FROM public.projects q WHERE q.id = e2.project_id) AS e2_project_archived, e2.seal AS e2_seal,
328
+ NULL::uuid AS e3_id, NULL::uuid AS e3_project, NULL::text AS e3_slug, NULL::text AS e3_owner, NULL::uuid AS e3_parent, NULL::boolean AS e3_protected, NULL::boolean AS e3_archived, NULL::boolean AS e3_project_archived, NULL::bytea AS e3_seal,
329
+ CASE WHEN e2.owner IS NOT NULL THEN e2.parent_id END AS e4_id, e4.project_id AS e4_project, e4.slug AS e4_slug, e4.owner AS e4_owner, e4.parent_id AS e4_parent, e4.protected AS e4_protected, e4.archived_at IS NOT NULL AS e4_archived, (SELECT q.archived_at IS NOT NULL FROM public.projects q WHERE q.id = e4.project_id) AS e4_project_archived, e4.seal AS e4_seal,
330
+ b.last AS bulk_last
331
+ FROM (SELECT 1) one
332
+ LEFT JOIN LATERAL (SELECT x.number, x.mac FROM public.log_changes x ORDER BY x.number DESC LIMIT 1) n ON true
333
+ LEFT JOIN public.log_fence f ON f.only_row
334
+ -- The roles and the policy, both rows in one read of their one-page table.
335
+ LEFT JOIN LATERAL (
336
+ SELECT max(g.value) FILTER (WHERE g.name = 'roles') AS r_value, (array_agg(g.seal) FILTER (WHERE g.name = 'roles'))[1] AS r_seal,
337
+ max(g.value) FILTER (WHERE g.name = 'policy') AS p_value, (array_agg(g.seal) FILTER (WHERE g.name = 'policy'))[1] AS p_seal
338
+ FROM public.log_config g
339
+ ) cfg ON true
340
+ LEFT JOIN public.credentials c ON c.id = p_credential
341
+ LEFT JOIN public.vault_members m ON m.principal = c.principal
342
+ -- An archived key is gone (`secret.delete`): no object.
343
+ LEFT JOIN public.secrets s ON s.id = p_secret AND s.archived_at IS NULL
344
+ -- The key's own version, its own environment, and that environment's parent when it is a personal config.
345
+ LEFT JOIN public.secret_versions sv ON sv.id = s.current_version_id
346
+ LEFT JOIN public.environments e2 ON e2.id = s.environment_id
347
+ LEFT JOIN public.environments e4 ON e4.id = e2.parent_id AND e2.owner IS NOT NULL
348
+ LEFT JOIN public.log_bulk b ON b.principal = c.principal AND b.secret = p_secret
349
+ WHERE NOT dry
350
+ ),
351
+ v AS MATERIALIZED (
352
+ SELECT i.*, c_refresh AS refresh, c_period AS period,
353
+ CASE WHEN NOT i.cached THEN 'reprobe'
354
+ WHEN i.s_project IS NOT NULL AND i.s_version IS NULL THEN 'whole'
355
+ WHEN i.at_floor IS NULL OR p_at < i.at_floor OR (extract(epoch FROM clock_timestamp()) * 1000)::bigint > p_deadline THEN 'late'
356
+ WHEN p_at > (extract(epoch FROM clock_timestamp()) * 1000)::bigint + 60000 THEN 'ahead'
357
+ WHEN i.p IS NOT NULL AND NOT locked AND NOT coalesce(i.bulk_last > p_at - p_cap_window - c_refresh, false) THEN 'lock'
358
+ WHEN i.p IS NOT NULL AND NOT locked AND NOT moving AND i.bulk_last < c_period THEN 'share' END AS state,
359
+ coalesce(i.bulk_last > p_at - p_cap_window - c_refresh, false) AS is_in,
360
+ -- The other keys in the set, up to the cap: read only under the principal's lock, for a key not in it.
361
+ CASE WHEN locked AND i.p IS NOT NULL AND NOT coalesce(i.bulk_last > p_at - p_cap_window - c_refresh, false) THEN (
362
+ SELECT count(*)::integer FROM (
363
+ SELECT 1 FROM public.log_bulk o
364
+ WHERE o.principal = i.p AND o.secret <> p_secret AND o.last > p_at - p_cap_window - c_refresh - public.deadline_max() - 60000
365
+ LIMIT p_cap_count
366
+ ) z
367
+ ) ELSE 0 END AS n_others,
368
+ -- A repeat rides its cover only while every part of this holds: never on a key whose bulk row is missing (CoverFresh).
369
+ coalesce(p_cover IS NOT NULL AND i.bulk_last >= c_period
370
+ AND EXISTS (SELECT 1 FROM public.log_uses u WHERE u.at = p_cover_at AND u.request_id = p_cover_request AND u.number = p_cover
371
+ AND u.credential = p_credential AND u.secret = p_secret AND u.purpose = p_purpose AND u.place IS NOT DISTINCT FROM p_place), false) AS is_covered
372
+ FROM i
373
+ ),
374
+ w AS (
375
+ INSERT INTO public.log_uses (number, at, request_id, credential, secret, purpose, mac, place)
376
+ SELECT nextval('public.log_number'), p_at, p_request, p_credential, p_secret, p_purpose, p_mac, p_place
377
+ -- The isolate held its cover to the cached seals' numbers when it read them.
378
+ FROM v WHERE v.state IS NULL AND NOT v.is_covered
379
+ RETURNING log_uses.number
380
+ )
381
+ SELECT v.state, (SELECT w.number FROM w), CASE WHEN v.state IS NULL AND v.is_covered THEN p_cover END, v.newest, v.newest_mac, NULL::bigint, false,
382
+ v.p, v.c_kind, v.c_scope, v.c_revoked, v.c_expires, v.c_seal, NULL::bytea,
383
+ v.m_status, v.m_owner, v.m_role, v.m_scope, v.m_seal, NULL::bytea, NULL::bigint,
384
+ (SELECT a.seq FROM public.audit_log a WHERE a.author = 'vault' AND a.subject_principal = v.p AND a.decision = 'allow'
385
+ AND a.action IN ('member.add', 'member.restore', 'member.owner', 'member.role', 'member.remove', 'access.grant', 'access.revoke') ORDER BY a.seq DESC LIMIT 1),
386
+ gr.g_project, gr.g_environment, gr.g_slug, gr.g_role, gr.g_expires,
387
+ v.r_value, v.r_seal, NULL::bytea,
388
+ v.p_value, v.p_seal, NULL::bytea, p_cap_count, p_cap_window,
389
+ v.s_project, v.s_environment, v.s_version, v.s_source, v.s_seal, NULL::bytea,
390
+ v.x_project, v.x_environment, v.x_version, v.x_source, v.x_seal, NULL::bytea,
391
+ v.envelope_version, v.ciphertext, v.iv, v.auth_tag, v.wrapped_dek, v.kek_provider, v.kek_id, v.kek_version, v.sensitive, v.v_plain,
392
+ v.e1_id, v.e1_project, v.e1_slug, v.e1_owner, v.e1_parent, v.e1_protected, v.e1_archived, v.e1_seal, NULL::bytea,
393
+ v.e2_id, v.e2_project, v.e2_slug, v.e2_owner, v.e2_parent, v.e2_protected, v.e2_archived, v.e2_seal, NULL::bytea,
394
+ v.e3_id, v.e3_project, v.e3_slug, v.e3_owner, v.e3_parent, v.e3_protected, v.e3_archived, v.e3_seal, NULL::bytea,
395
+ v.e4_id, v.e4_project, v.e4_slug, v.e4_owner, v.e4_parent, v.e4_protected, v.e4_archived, v.e4_seal, NULL::bytea,
396
+ v.is_in, v.n_others, v.v_plaintext, v.e1_project_archived, v.e2_project_archived, v.e3_project_archived, v.e4_project_archived, v.s_reference, v.x_reference
397
+ INTO r
398
+ FROM v
399
+ -- The principal's grants as five lists, one entry per grant, lapsed ones included; an environment's grant with its project, which its row finds through `environments`.
400
+ LEFT JOIN LATERAL (
401
+ SELECT array_agg(coalesce(g.project_id, ge.project_id)) AS g_project, array_agg(g.environment_id) AS g_environment,
402
+ array_agg(g.environment_slug) AS g_slug, array_agg(g.role) AS g_role, array_agg(g.expires_at) AS g_expires
403
+ FROM public.vault_grants g LEFT JOIN public.environments ge ON ge.id = g.environment_id
404
+ WHERE g.principal = v.p
405
+ ) gr ON v.m_status IS NOT NULL;
406
+ ELSE
407
+ WITH i AS MATERIALIZED (
408
+ SELECT n.number AS newest, n.mac AS newest_mac, coalesce(n.number = p_known AND n.mac = p_known_mac, false) AS cached,
409
+ f.at_floor,
410
+ c.principal AS p, c.kind AS c_kind, c.scope AS c_scope, c.revoked_at IS NOT NULL AS c_revoked,
411
+ floor(extract(epoch FROM least(c.expires_at, c.not_after)) * 1000)::bigint AS c_expires, c.seal AS c_seal,
412
+ m.status AS m_status, m.owner AS m_owner, m.role AS m_role, m.scope AS m_scope, m.seal AS m_seal,
413
+ cfg.r_value, cfg.r_seal, cfg.p_value, cfg.p_seal,
414
+ s.project_id AS s_project, s.environment_id AS s_environment, s.current_version_id AS s_version, ref.source_secret_id AS s_source, ref.reference_id AS s_reference, s.seal AS s_seal,
415
+ src.project_id AS x_project, src.environment_id AS x_environment, src.current_version_id AS x_version, xref.source_secret_id AS x_source, xref.reference_id AS x_reference, src.seal AS x_seal,
416
+ sv.envelope_version, sv.ciphertext, sv.iv, sv.auth_tag, sv.wrapped_dek, sv.kek_provider, sv.kek_id, sv.kek_version, sv.sensitive, sv.plaintext IS NOT NULL AS v_plain, sv.plaintext AS v_plaintext,
417
+ ids.e1 AS e1_id, e1.project_id AS e1_project, e1.slug AS e1_slug, e1.owner AS e1_owner, e1.parent_id AS e1_parent, e1.protected AS e1_protected, e1.archived_at IS NOT NULL AS e1_archived, (SELECT q.archived_at IS NOT NULL FROM public.projects q WHERE q.id = e1.project_id) AS e1_project_archived, e1.seal AS e1_seal,
418
+ s.environment_id AS e2_id, e2.project_id AS e2_project, e2.slug AS e2_slug, e2.owner AS e2_owner, e2.parent_id AS e2_parent, e2.protected AS e2_protected, e2.archived_at IS NOT NULL AS e2_archived, (SELECT q.archived_at IS NOT NULL FROM public.projects q WHERE q.id = e2.project_id) AS e2_project_archived, e2.seal AS e2_seal,
419
+ ids.e3 AS e3_id, e3.project_id AS e3_project, e3.slug AS e3_slug, e3.owner AS e3_owner, e3.parent_id AS e3_parent, e3.protected AS e3_protected, e3.archived_at IS NOT NULL AS e3_archived, (SELECT q.archived_at IS NOT NULL FROM public.projects q WHERE q.id = e3.project_id) AS e3_project_archived, e3.seal AS e3_seal,
420
+ CASE WHEN ve.owner IS NOT NULL THEN ve.parent END AS e4_id, e4.project_id AS e4_project, e4.slug AS e4_slug, e4.owner AS e4_owner, e4.parent_id AS e4_parent, e4.protected AS e4_protected, e4.archived_at IS NOT NULL AS e4_archived, (SELECT q.archived_at IS NOT NULL FROM public.projects q WHERE q.id = e4.project_id) AS e4_project_archived, e4.seal AS e4_seal,
421
+ b.last AS bulk_last
422
+ FROM (SELECT 1) one
423
+ LEFT JOIN LATERAL (SELECT x.number, x.mac FROM public.log_changes x ORDER BY x.number DESC LIMIT 1) n ON true
424
+ LEFT JOIN public.log_fence f ON f.only_row
425
+ -- The roles and the policy, both rows in one read of their one-page table.
426
+ LEFT JOIN LATERAL (
427
+ SELECT max(g.value) FILTER (WHERE g.name = 'roles') AS r_value, (array_agg(g.seal) FILTER (WHERE g.name = 'roles'))[1] AS r_seal,
428
+ max(g.value) FILTER (WHERE g.name = 'policy') AS p_value, (array_agg(g.seal) FILTER (WHERE g.name = 'policy'))[1] AS p_seal
429
+ FROM public.log_config g
430
+ ) cfg ON true
431
+ LEFT JOIN public.credentials c ON c.id = p_credential
432
+ LEFT JOIN public.vault_members m ON m.principal = c.principal
433
+ -- An archived key is gone (`secret.delete`): no object.
434
+ LEFT JOIN public.secrets s ON s.id = p_secret AND s.archived_at IS NULL
435
+ -- A key with no version of its own is a reference while its newest reference row names a source and has not ended (`ended_at`): an ended one's holder reads as no value, no source (`ended`).
436
+ LEFT JOIN LATERAL (
437
+ SELECT CASE WHEN y.ended_at IS NULL THEN y.source_secret_id END AS source_secret_id, CASE WHEN y.ended_at IS NULL THEN y.id END AS reference_id FROM public.secret_references y WHERE y.secret_id = s.id ORDER BY y.created_seq DESC LIMIT 1
438
+ ) ref ON s.current_version_id IS NULL
439
+ LEFT JOIN public.secrets src ON src.id = ref.source_secret_id AND src.archived_at IS NULL
440
+ LEFT JOIN LATERAL (
441
+ SELECT CASE WHEN y.ended_at IS NULL THEN y.source_secret_id END AS source_secret_id, CASE WHEN y.ended_at IS NULL THEN y.id END AS reference_id FROM public.secret_references y WHERE y.secret_id = src.id ORDER BY y.created_seq DESC LIMIT 1
442
+ ) xref ON src.current_version_id IS NULL
443
+ -- The value's version: the key's own, or its source's.
444
+ LEFT JOIN public.secret_versions sv ON sv.id = CASE WHEN ref.source_secret_id IS NULL THEN s.current_version_id ELSE src.current_version_id END
445
+ -- The environments `useEnvironments` names, each by the id the rows name, whether or not its row is there: e1 the
446
+ -- place read through, e2 the key's own, e3 its value's (a reference's source's) where that is another, and e4
447
+ -- the parent of its value's when that is a personal config.
448
+ LEFT JOIN LATERAL (
449
+ SELECT CASE WHEN p_place IS DISTINCT FROM s.environment_id THEN p_place END AS e1,
450
+ CASE WHEN src.environment_id IS DISTINCT FROM s.environment_id THEN src.environment_id END AS e3
451
+ ) ids ON true
452
+ LEFT JOIN public.environments e1 ON e1.id = ids.e1
453
+ LEFT JOIN public.environments e2 ON e2.id = s.environment_id
454
+ LEFT JOIN public.environments e3 ON e3.id = ids.e3
455
+ -- The value's environment's row: the key's own, unless it is a reference, whose source is elsewhere or gone.
456
+ LEFT JOIN LATERAL (
457
+ SELECT CASE WHEN ref.source_secret_id IS NULL OR (src.id IS NOT NULL AND ids.e3 IS NULL) THEN e2.owner ELSE e3.owner END AS owner,
458
+ CASE WHEN ref.source_secret_id IS NULL OR (src.id IS NOT NULL AND ids.e3 IS NULL) THEN e2.parent_id ELSE e3.parent_id END AS parent
459
+ ) ve ON true
460
+ LEFT JOIN public.environments e4 ON e4.id = ve.parent AND ve.owner IS NOT NULL
461
+ LEFT JOIN public.log_bulk b ON b.principal = c.principal AND b.secret = p_secret
462
+ WHERE NOT dry
463
+ ),
464
+ v AS MATERIALIZED (
465
+ SELECT i.*, c_refresh AS refresh, c_period AS period,
466
+ CASE WHEN NOT i.cached THEN 'reprobe'
467
+ WHEN i.at_floor IS NULL OR p_at < i.at_floor OR (extract(epoch FROM clock_timestamp()) * 1000)::bigint > p_deadline THEN 'late'
468
+ WHEN p_at > (extract(epoch FROM clock_timestamp()) * 1000)::bigint + 60000 THEN 'ahead'
469
+ WHEN i.p IS NOT NULL AND NOT locked AND NOT coalesce(i.bulk_last > p_at - p_cap_window - c_refresh, false) THEN 'lock'
470
+ WHEN i.p IS NOT NULL AND NOT locked AND NOT moving AND i.bulk_last < c_period THEN 'share' END AS state,
471
+ coalesce(i.bulk_last > p_at - p_cap_window - c_refresh, false) AS is_in,
472
+ -- The other keys in the set, up to the cap: read only under the principal's lock, for a key not in it.
473
+ CASE WHEN locked AND i.p IS NOT NULL AND NOT coalesce(i.bulk_last > p_at - p_cap_window - c_refresh, false) THEN (
474
+ SELECT count(*)::integer FROM (
475
+ SELECT 1 FROM public.log_bulk o
476
+ WHERE o.principal = i.p AND o.secret <> p_secret AND o.last > p_at - p_cap_window - c_refresh - public.deadline_max() - 60000
477
+ LIMIT p_cap_count
478
+ ) z
479
+ ) ELSE 0 END AS n_others,
480
+ -- A repeat rides its cover only while every part of this holds: never on a key whose bulk row is missing (CoverFresh).
481
+ coalesce(p_cover IS NOT NULL AND i.bulk_last >= c_period
482
+ AND EXISTS (SELECT 1 FROM public.log_uses u WHERE u.at = p_cover_at AND u.request_id = p_cover_request AND u.number = p_cover
483
+ AND u.credential = p_credential AND u.secret = p_secret AND u.purpose = p_purpose AND u.place IS NOT DISTINCT FROM p_place), false) AS is_covered
484
+ FROM i
485
+ ),
486
+ w AS (
487
+ INSERT INTO public.log_uses (number, at, request_id, credential, secret, purpose, mac, place)
488
+ SELECT nextval('public.log_number'), p_at, p_request, p_credential, p_secret, p_purpose, p_mac, p_place
489
+ -- The isolate held its cover to the cached seals' numbers when it read them.
490
+ FROM v WHERE v.state IS NULL AND NOT v.is_covered
491
+ RETURNING log_uses.number
492
+ )
493
+ SELECT v.state, (SELECT w.number FROM w), CASE WHEN v.state IS NULL AND v.is_covered THEN p_cover END, v.newest, v.newest_mac, NULL::bigint, false,
494
+ v.p, v.c_kind, v.c_scope, v.c_revoked, v.c_expires, v.c_seal, NULL::bytea,
495
+ v.m_status, v.m_owner, v.m_role, v.m_scope, v.m_seal, NULL::bytea, NULL::bigint,
496
+ (SELECT a.seq FROM public.audit_log a WHERE a.author = 'vault' AND a.subject_principal = v.p AND a.decision = 'allow'
497
+ AND a.action IN ('member.add', 'member.restore', 'member.owner', 'member.role', 'member.remove', 'access.grant', 'access.revoke') ORDER BY a.seq DESC LIMIT 1),
498
+ gr.g_project, gr.g_environment, gr.g_slug, gr.g_role, gr.g_expires,
499
+ v.r_value, v.r_seal, NULL::bytea,
500
+ v.p_value, v.p_seal, NULL::bytea, p_cap_count, p_cap_window,
501
+ v.s_project, v.s_environment, v.s_version, v.s_source, v.s_seal, NULL::bytea,
502
+ v.x_project, v.x_environment, v.x_version, v.x_source, v.x_seal, NULL::bytea,
503
+ v.envelope_version, v.ciphertext, v.iv, v.auth_tag, v.wrapped_dek, v.kek_provider, v.kek_id, v.kek_version, v.sensitive, v.v_plain,
504
+ v.e1_id, v.e1_project, v.e1_slug, v.e1_owner, v.e1_parent, v.e1_protected, v.e1_archived, v.e1_seal, NULL::bytea,
505
+ v.e2_id, v.e2_project, v.e2_slug, v.e2_owner, v.e2_parent, v.e2_protected, v.e2_archived, v.e2_seal, NULL::bytea,
506
+ v.e3_id, v.e3_project, v.e3_slug, v.e3_owner, v.e3_parent, v.e3_protected, v.e3_archived, v.e3_seal, NULL::bytea,
507
+ v.e4_id, v.e4_project, v.e4_slug, v.e4_owner, v.e4_parent, v.e4_protected, v.e4_archived, v.e4_seal, NULL::bytea,
508
+ v.is_in, v.n_others, v.v_plaintext, v.e1_project_archived, v.e2_project_archived, v.e3_project_archived, v.e4_project_archived, v.s_reference, v.x_reference
509
+ INTO r
510
+ FROM v
511
+ -- The principal's grants as five lists, one entry per grant, lapsed ones included; an environment's grant with its project, which its row finds through `environments`.
512
+ LEFT JOIN LATERAL (
513
+ SELECT array_agg(coalesce(g.project_id, ge.project_id)) AS g_project, array_agg(g.environment_id) AS g_environment,
514
+ array_agg(g.environment_slug) AS g_slug, array_agg(g.role) AS g_role, array_agg(g.expires_at) AS g_expires
515
+ FROM public.vault_grants g LEFT JOIN public.environments ge ON ge.id = g.environment_id
516
+ WHERE g.principal = v.p
517
+ ) gr ON v.m_status IS NOT NULL;
518
+ END IF;
519
+ IF dry THEN
520
+ -- The forms in turn: probing plain, probing whole, cached plain, cached whole; then the call itself, under the fence.
521
+ form := form + 1;
522
+ IF form < 4 THEN
523
+ probe := form < 2;
524
+ whole := form % 2 = 1;
525
+ CONTINUE;
526
+ END IF;
527
+ PERFORM set_config('coffre.use_planned', '0029', false);
528
+ dry := false;
529
+ probe := first_probe;
530
+ whole := first_whole;
531
+ PERFORM pg_advisory_xact_lock_shared(1668244070, 0);
532
+ CONTINUE;
533
+ END IF;
534
+ -- A use the statement took writes its entry or rides its cover, never neither.
535
+ IF r.state IS NULL AND r.number IS NULL AND r.covered IS NULL THEN
536
+ RAISE EXCEPTION 'a use wrote no entry and rode no cover' USING ERRCODE = 'XX000';
537
+ END IF;
538
+ IF r.state = 'reprobe' THEN probe := true; CONTINUE; END IF;
539
+ IF r.state = 'whole' THEN whole := true; CONTINUE; END IF;
540
+ EXIT WHEN r.state IS DISTINCT FROM 'lock' AND r.state IS DISTINCT FROM 'share';
541
+ -- Its key's bulk row is missing or out of the set: this use may enter it, so it takes its principal's lock exclusive
542
+ -- and reads again. Or the row is in the set, from an earlier period: this use may move it on, so it takes the lock
543
+ -- shared, beside the principal's other uses that move a key on, and reads again.
544
+ -- Holding it shared, a key never leaves the set (SharedStaysInSet) unless a row was put back under it: then the
545
+ -- call ends, refused as late, rather than ask for the lock exclusive while it holds it shared (two such would
546
+ -- deadlock).
547
+ IF r.state = 'lock' AND moving THEN
548
+ RAISE EXCEPTION 'its key left the cap''s set under the principal''s lock: a row put back' USING ERRCODE = '57014';
549
+ END IF;
550
+ IF r.state = 'lock' THEN
551
+ PERFORM pg_advisory_xact_lock(1668244066, hashtext(r.c_principal));
552
+ locked := true;
553
+ ELSE
554
+ PERFORM pg_advisory_xact_lock_shared(1668244066, hashtext(r.c_principal));
555
+ moving := true;
556
+ END IF;
557
+ END LOOP;
558
+ -- The cap's row, written only by a call that took the principal's lock, in a statement of its own after the one that
559
+ -- read, in the same transaction, under the same locks: the uses that read their key fresh, nearly all, run no write.
560
+ -- A key in the set moves on to this use's period, only ever up. A key entering the set, under the lock exclusive, is
561
+ -- made or moved on from a window past. Every request counts, whatever the Worker then decides but the cap (HotPathOneStaleBulk).
562
+ IF r.state IS NULL AND (locked OR moving) THEN
563
+ b_refresh := greatest(1, least(60000, r.cap_window / 10));
564
+ b_period := p_at - ((p_at % b_refresh) + b_refresh) % b_refresh;
565
+ IF r.in_set THEN
566
+ -- One use moves the key to this period: the one that takes a lock of the key and period's own without waiting.
567
+ -- The others take it shared, which waits only for that one's end, never for each other, and read the row again,
568
+ -- locking none: moved, they leave it. An UPDATE that waits on a row another is moving, then finds it moved,
569
+ -- keeps the row locked to its own commit, and queues the next. If the one moving it rolled back, the others
570
+ -- elect another the same way, on the lock's next round.
571
+ LOOP
572
+ b_lock := hashtextextended(r.c_principal || '/' || p_secret::text || '/' || b_period::text || '/' || b_round::text, 1668244066);
573
+ IF pg_try_advisory_xact_lock(b_lock) THEN
574
+ UPDATE public.log_bulk SET last = b_period WHERE principal = r.c_principal AND secret = p_secret AND last < b_period;
575
+ EXIT;
576
+ END IF;
577
+ PERFORM pg_advisory_xact_lock_shared(b_lock);
578
+ EXIT WHEN NOT EXISTS (SELECT 1 FROM public.log_bulk WHERE principal = r.c_principal AND secret = p_secret AND last < b_period);
579
+ b_round := b_round + 1;
580
+ END LOOP;
581
+ ELSIF r.others < r.cap_count THEN
582
+ INSERT INTO public.log_bulk (principal, secret, last) VALUES (r.c_principal, p_secret, b_period)
583
+ ON CONFLICT ON CONSTRAINT log_bulk_pkey DO UPDATE SET last = greatest(log_bulk.last, EXCLUDED.last);
584
+ END IF;
585
+ END IF;
586
+ -- The deadline holds in Postgres to the end: a call that ran past it rolls back and writes nothing. The hot login's lock_timeout and statement_timeout bound the waits.
587
+ IF r.state IS NULL AND (extract(epoch FROM clock_timestamp()) * 1000)::bigint > p_deadline THEN
588
+ RAISE EXCEPTION 'past its deadline' USING ERRCODE = '57014';
589
+ END IF;
590
+ RETURN to_json(r);
591
+ END
592
+ $$;
593
+ --> statement-breakpoint
594
+ -- `change_input` as 0025 made it, but for a key's change, the end of its reference, and an environment's project's archival, held to the project's row; and no JIT (see use_credential's).
595
+ CREATE OR REPLACE FUNCTION change_input(
596
+ p_kind text, p_id text, p_request text, p_at bigint, p_actor text, p_payload text, p_value text, p_seal_from bytea, p_seal bytea, p_mac bytea
597
+ ) RETURNS bigint
598
+ LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog, pg_temp SET jit = off AS $$
599
+ DECLARE
600
+ op jsonb := p_payload::jsonb;
601
+ v jsonb := p_value::jsonb;
602
+ change text := op ->> 'op';
603
+ target uuid := public.log_object(p_kind, p_id);
604
+ n bigint;
605
+ done integer;
606
+ member text;
607
+ held bytea;
608
+ newest bytea;
609
+ config jsonb;
610
+ BEGIN
611
+ -- Its locks in the order 0.14's writers take theirs, so that none waits on the other in a circle until 0.16 (row,
612
+ -- head, fence: Migration015.tla's RowFirst, ChangeHeadFirst): the member's queue (MEMBER_QUEUE, 0x636f666d) and row
613
+ -- first, a principal's own or a credential's (0.14's vault locks them before the head, lockMembers; a change made
614
+ -- inside a 0.14 decision holds them already), then 0.14's head (its app takes it before its rows), then the fence
615
+ -- (0.14's appends take it after the head, audit_log_fence). The head held, no 0.14 write lands between this change
616
+ -- and its entry.
617
+ member := CASE p_kind
618
+ WHEN 'principal' THEN p_id
619
+ WHEN 'credential' THEN coalesce(
620
+ (SELECT c.principal FROM public.credentials c WHERE c.id = p_id::uuid),
621
+ (SELECT b.principal FROM public.service_bindings b WHERE b.id = p_id::uuid))
622
+ END;
623
+ IF member IS NOT NULL THEN
624
+ PERFORM pg_advisory_xact_lock(1668245101, hashtext(m.principal)) FROM public.vault_members m WHERE m.principal = member FOR NO KEY UPDATE;
625
+ END IF;
626
+ PERFORM 1 FROM public.audit_chain_head WHERE only_row FOR UPDATE;
627
+ -- ChangeLocks: exclusive, so that it waits for every use in flight, and every use after it waits for its commit.
628
+ PERFORM pg_advisory_xact_lock(1668244070, 0);
629
+ SELECT c.number INTO n FROM public.log_changes c WHERE c.request_id = p_request;
630
+ IF FOUND THEN
631
+ IF NOT EXISTS (
632
+ SELECT 1 FROM public.log_changes c
633
+ WHERE c.request_id = p_request AND c.object = target AND c.payload = p_payload AND c.seal IS NOT DISTINCT FROM p_seal
634
+ ) THEN
635
+ RAISE EXCEPTION 'request % names another change', p_request USING ERRCODE = 'unique_violation';
636
+ END IF;
637
+ RETURN n;
638
+ END IF;
639
+ IF NOT (change = 'genesis'
640
+ OR (p_kind = 'principal' AND change IN ('member.add', 'member.role', 'member.remove', 'member.restore', 'access.grant', 'access.revoke'))
641
+ OR (p_kind = 'environment' AND change IN ('environment.protect', 'environment.rename', 'environment.archive'))
642
+ OR (p_kind = 'secret' AND change = 'secret.write')
643
+ OR (p_kind = 'roles' AND change = 'role.define')
644
+ OR (p_kind = 'policy' AND change = 'bulk.limit')) THEN
645
+ RAISE EXCEPTION 'this schema knows no change % to a %', coalesce(change, 'without an op'), p_kind USING ERRCODE = 'invalid_parameter_value';
646
+ END IF;
647
+ -- The payload names its object (`ref`), so that a replay knows what each change is to: the column holds only its
648
+ -- key. The cutover's genesis may leave it out, its value naming it.
649
+ IF (change <> 'genesis' OR op ? 'ref') AND (op -> 'ref' ->> 'kind' IS DISTINCT FROM p_kind OR op -> 'ref' ->> 'id' IS DISTINCT FROM p_id) THEN
650
+ RAISE EXCEPTION 'a change names the object it is to: %, %', p_kind, p_id USING ERRCODE = 'invalid_parameter_value';
651
+ END IF;
652
+ -- A value is the object as this function writes it: an environment's, which a genesis may leave out to seal the
653
+ -- row as it is; a key's, the end of its reference (`secret.write`, the one change of a key the vault makes), and
654
+ -- none at its genesis; the roles' and the policy's, but at their genesis, whose payload holds it. The others' rows
655
+ -- are written by whoever keeps them, and carry none.
656
+ IF (CASE
657
+ WHEN p_kind = 'environment' THEN change <> 'genesis' AND v IS NULL
658
+ WHEN p_kind = 'secret' THEN (change = 'secret.write') = (v IS NULL)
659
+ WHEN p_kind IN ('roles', 'policy') THEN (change = 'genesis') = (v IS NOT NULL)
660
+ ELSE v IS NOT NULL
661
+ END) THEN
662
+ RAISE EXCEPTION 'a % change % carries the object it leaves exactly when this function writes it', p_kind, change USING ERRCODE = 'invalid_parameter_value';
663
+ END IF;
664
+ -- ChangeChecksSeal: the row's seal is its object's newest change's, or the object has neither yet (one probe, as
665
+ -- a use makes). A row deleted outright, or a seal no change wrote, is stale too.
666
+ held := CASE p_kind
667
+ WHEN 'principal' THEN (SELECT m.seal FROM public.vault_members m WHERE m.principal = p_id)
668
+ WHEN 'credential' THEN coalesce(
669
+ (SELECT c.seal FROM public.credentials c WHERE c.id = p_id::uuid),
670
+ (SELECT b.seal FROM public.service_bindings b WHERE b.id = p_id::uuid))
671
+ WHEN 'environment' THEN (SELECT e.seal FROM public.environments e WHERE e.id = p_id::uuid)
672
+ WHEN 'secret' THEN (SELECT x.seal FROM public.secrets x WHERE x.id = p_id::uuid)
673
+ WHEN 'policy' THEN (SELECT g.seal FROM public.log_config g WHERE g.name = 'policy')
674
+ WHEN 'roles' THEN (SELECT g.seal FROM public.log_config g WHERE g.name = 'roles')
675
+ END;
676
+ newest := (SELECT c.seal FROM public.log_changes c WHERE c.object = target ORDER BY c.number DESC LIMIT 1);
677
+ IF held IS DISTINCT FROM newest THEN
678
+ RAISE EXCEPTION 'the % object''s row is not its newest change''s: stale', p_kind USING ERRCODE = 'data_corrupted';
679
+ END IF;
680
+ -- ChangeCAS: each write moves the row only from the seal the vault read.
681
+ CASE p_kind
682
+ WHEN 'principal' THEN
683
+ UPDATE public.vault_members SET seal = p_seal WHERE principal = p_id AND seal IS NOT DISTINCT FROM p_seal_from;
684
+ WHEN 'credential' THEN
685
+ -- A token's row, or a trust binding's: one kind of object, ids that never meet.
686
+ UPDATE public.credentials SET seal = p_seal WHERE id = p_id::uuid AND seal IS NOT DISTINCT FROM p_seal_from;
687
+ GET DIAGNOSTICS done = ROW_COUNT;
688
+ IF done = 0 THEN
689
+ UPDATE public.service_bindings SET seal = p_seal WHERE id = p_id::uuid AND seal IS NOT DISTINCT FROM p_seal_from;
690
+ END IF;
691
+ WHEN 'environment' THEN
692
+ IF v IS NULL THEN
693
+ UPDATE public.environments SET seal = p_seal WHERE id = p_id::uuid AND seal IS NOT DISTINCT FROM p_seal_from;
694
+ ELSE
695
+ -- A deleted environment's row keeps its tombstone, `dev~deleted-…` (@coffre/core/schemas's isTombstone),
696
+ -- and its object the slug before it (objects.ts, environmentObject): an environment deleted stays so,
697
+ -- its slug is never written again, and none is deleted here; an object's slug holds no tombstone.
698
+ UPDATE public.environments e
699
+ SET slug = CASE WHEN strpos(e.slug, '~') > 0 THEN e.slug ELSE v ->> 'slug' END,
700
+ protected = (v ->> 'protected')::boolean,
701
+ archived_at = CASE WHEN (v ->> 'archived')::boolean THEN coalesce(e.archived_at, to_timestamp(p_at / 1000.0)) END,
702
+ seal = p_seal
703
+ WHERE e.id = p_id::uuid AND e.seal IS NOT DISTINCT FROM p_seal_from
704
+ AND v ->> 'kind' = 'environment' AND v ->> 'id' = p_id
705
+ AND e.project_id = (v ->> 'projectId')::uuid
706
+ AND e.parent_id IS NOT DISTINCT FROM (v ->> 'parentId')::uuid
707
+ AND e.owner IS NOT DISTINCT FROM v ->> 'owner'
708
+ AND (strpos(e.slug, '~') > 0) = (v ->> 'deleted')::boolean
709
+ -- Its project's archival is the project's row's, which the app writes, and none once deleted (objects.ts): held,
710
+ -- never written here.
711
+ AND (v ->> 'projectArchived')::boolean = (strpos(e.slug, '~') = 0 AND EXISTS (SELECT 1 FROM public.projects p WHERE p.id = e.project_id AND p.archived_at IS NOT NULL))
712
+ AND strpos(v ->> 'slug', '~') = 0
713
+ AND (NOT (v ->> 'deleted')::boolean OR split_part(e.slug, '~', 1) = v ->> 'slug');
714
+ END IF;
715
+ WHEN 'secret' THEN
716
+ IF v IS NULL THEN
717
+ UPDATE public.secrets SET seal = p_seal WHERE id = p_id::uuid AND seal IS NOT DISTINCT FROM p_seal_from;
718
+ ELSE
719
+ -- A reference's end, in the vault's decision that appends 0.14's `reference.end` (EndInRow, EndWithEntry:
720
+ -- formal/SealedObjects.tla): the holder, a live key with no version of its own, left with no source, and
721
+ -- its newest reference row ended. That row must stand: its seal, which the check above held to the
722
+ -- newest change, says the holder reads its source; a row ended or gone under it is not the log's.
723
+ UPDATE public.secrets x SET seal = p_seal
724
+ WHERE x.id = p_id::uuid AND x.seal IS NOT DISTINCT FROM p_seal_from AND x.archived_at IS NULL AND x.current_version_id IS NULL
725
+ AND v ->> 'kind' = 'secret' AND v ->> 'id' = p_id AND x.project_id = (v ->> 'projectId')::uuid AND x.environment_id = (v ->> 'environmentId')::uuid
726
+ AND v ->> 'version' = '' AND v ->> 'nature' = 'secret' AND v -> 'key' = 'null'::jsonb AND v -> 'digest' = 'null'::jsonb AND v -> 'source' = 'null'::jsonb
727
+ AND v -> 'reference' = 'null'::jsonb;
728
+ GET DIAGNOSTICS done = ROW_COUNT;
729
+ IF done > 0 THEN
730
+ UPDATE public.secret_references y SET ended_at = to_timestamp(p_at / 1000.0)
731
+ WHERE y.id = (SELECT z.id FROM public.secret_references z WHERE z.secret_id = p_id::uuid ORDER BY z.created_seq DESC LIMIT 1) AND y.ended_at IS NULL;
732
+ GET DIAGNOSTICS done = ROW_COUNT;
733
+ IF done = 0 THEN
734
+ RAISE EXCEPTION 'the secret object''s reference has no standing row: stale' USING ERRCODE = 'data_corrupted';
735
+ END IF;
736
+ END IF;
737
+ END IF;
738
+ WHEN 'policy', 'roles' THEN
739
+ config := CASE change WHEN 'genesis' THEN op -> 'value' ELSE v END;
740
+ IF p_id <> 'instance' OR config IS NULL OR jsonb_typeof(config) <> 'object' OR (change = 'genesis' AND p_seal_from IS NOT NULL) THEN
741
+ RAISE EXCEPTION 'the % object is one, made once from its value and changed from its seal', p_kind USING ERRCODE = 'invalid_parameter_value';
742
+ END IF;
743
+ -- Made by its first change (genesis, or the first role defined, the first cap set); moved after from its seal.
744
+ IF p_seal_from IS NULL THEN
745
+ INSERT INTO public.log_config (name, value, seal) VALUES (p_kind, config::text, p_seal) ON CONFLICT (name) DO NOTHING;
746
+ ELSE
747
+ UPDATE public.log_config SET value = config::text, seal = p_seal WHERE log_config.name = p_kind AND seal = p_seal_from;
748
+ END IF;
749
+ ELSE
750
+ RAISE EXCEPTION 'no object kind %', p_kind USING ERRCODE = 'invalid_parameter_value';
751
+ END CASE;
752
+ -- A row gone, a row whose seal is no longer the one read, an environment that is not what the value says it
753
+ -- stays, or an object made already: nothing was done.
754
+ GET DIAGNOSTICS done = ROW_COUNT;
755
+ IF done = 0 THEN RETURN NULL; END IF;
756
+ INSERT INTO public.log_changes (number, at, request_id, object, actor, payload, seal, mac)
757
+ VALUES (nextval('public.log_number'), p_at, p_request, target, p_actor, p_payload, p_seal, p_mac)
758
+ RETURNING number INTO n;
759
+ RETURN n;
760
+ END
761
+ $$;
762
+ --> statement-breakpoint
763
+ -- `app_change` as 0026 made it, but for `project.archive`: a project archived or restored, a change of each of its environments; and no JIT.
764
+ CREATE OR REPLACE FUNCTION app_change(
765
+ p_kind text, p_id text, p_request text, p_at bigint, p_actor text, p_payload text, p_seal_from bytea, p_seal bytea, p_mac bytea
766
+ ) RETURNS bigint
767
+ LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog, pg_temp SET jit = off AS $$
768
+ DECLARE
769
+ op jsonb := p_payload::jsonb;
770
+ change text := op ->> 'op';
771
+ target uuid := public.log_object(p_kind, p_id);
772
+ n bigint;
773
+ done integer;
774
+ held bytea;
775
+ -- An issue of a credential this very transaction wrote (its row's xmin is ours: not a savepoint's, nor a row another committed).
776
+ -- Never null, whatever is missing (a 0.14 row's call carries no payload, so no op): else the fence.
777
+ fresh boolean := coalesce(change = 'credential.issue' AND p_kind = 'credential'
778
+ AND EXISTS (SELECT 1 FROM public.credentials c WHERE c.id = p_id::uuid AND c.xmin = pg_current_xact_id()::xid), false);
779
+ BEGIN
780
+ PERFORM 1 FROM public.audit_chain_head WHERE only_row FOR UPDATE;
781
+ -- The issue lock shared (IssueOwnLock); and, but for a fresh issue, the fence exclusive after it (IssueBeforeFence).
782
+ PERFORM pg_advisory_xact_lock_shared(1668244070, 1);
783
+ IF NOT fresh THEN PERFORM pg_advisory_xact_lock(1668244070, 0); END IF;
784
+ held := CASE p_kind
785
+ WHEN 'credential' THEN (SELECT c.seal FROM public.credentials c WHERE c.id = p_id::uuid)
786
+ WHEN 'secret' THEN (SELECT x.seal FROM public.secrets x WHERE x.id = p_id::uuid)
787
+ WHEN 'environment' THEN (SELECT e.seal FROM public.environments e WHERE e.id = p_id::uuid)
788
+ END;
789
+ IF p_request IS NULL THEN
790
+ IF held IS NOT NULL OR EXISTS (SELECT 1 FROM public.log_changes c WHERE c.object = target) THEN
791
+ RAISE EXCEPTION 'the % object''s row is not its newest change''s: stale', p_kind USING ERRCODE = 'data_corrupted';
792
+ END IF;
793
+ RETURN 0;
794
+ END IF;
795
+ SELECT c.number INTO n FROM public.log_changes c WHERE c.request_id = p_request;
796
+ IF FOUND THEN
797
+ IF NOT EXISTS (
798
+ SELECT 1 FROM public.log_changes c
799
+ WHERE c.request_id = p_request AND c.object = target AND c.payload = p_payload AND c.seal IS NOT DISTINCT FROM p_seal
800
+ ) THEN
801
+ RAISE EXCEPTION 'request % names another change', p_request USING ERRCODE = 'unique_violation';
802
+ END IF;
803
+ RETURN n;
804
+ END IF;
805
+ IF NOT ((p_kind = 'credential' AND change IN ('credential.issue', 'credential.revoke', 'credential.renew', 'credential.scope'))
806
+ OR (p_kind = 'secret' AND change IN ('secret.write', 'secret.delete'))
807
+ OR (p_kind = 'environment' AND change IN ('environment.create', 'environment.rename', 'environment.archive', 'project.archive', 'environment.delete'))) THEN
808
+ RAISE EXCEPTION 'the app makes no change % to a %', coalesce(change, 'without an op'), p_kind USING ERRCODE = 'invalid_parameter_value';
809
+ END IF;
810
+ IF op -> 'ref' ->> 'kind' IS DISTINCT FROM p_kind OR op -> 'ref' ->> 'id' IS DISTINCT FROM p_id THEN
811
+ RAISE EXCEPTION 'a change names the object it is to: %, %', p_kind, p_id USING ERRCODE = 'invalid_parameter_value';
812
+ END IF;
813
+ -- ChangeChecksSeal.
814
+ IF held IS DISTINCT FROM (SELECT c.seal FROM public.log_changes c WHERE c.object = target ORDER BY c.number DESC LIMIT 1) THEN
815
+ RAISE EXCEPTION 'the % object''s row is not its newest change''s: stale', p_kind USING ERRCODE = 'data_corrupted';
816
+ END IF;
817
+ -- ChangeCAS.
818
+ CASE p_kind
819
+ WHEN 'credential' THEN
820
+ UPDATE public.credentials SET seal = p_seal WHERE id = p_id::uuid AND seal IS NOT DISTINCT FROM p_seal_from;
821
+ WHEN 'secret' THEN
822
+ UPDATE public.secrets SET seal = p_seal WHERE id = p_id::uuid AND seal IS NOT DISTINCT FROM p_seal_from;
823
+ WHEN 'environment' THEN
824
+ UPDATE public.environments SET seal = p_seal WHERE id = p_id::uuid AND seal IS NOT DISTINCT FROM p_seal_from;
825
+ END CASE;
826
+ GET DIAGNOSTICS done = ROW_COUNT;
827
+ IF done = 0 THEN RETURN NULL; END IF;
828
+ INSERT INTO public.log_changes (number, at, request_id, object, actor, payload, seal, mac)
829
+ VALUES (nextval('public.log_number'), p_at, p_request, target, p_actor, p_payload, p_seal, p_mac)
830
+ RETURNING number INTO n;
831
+ RETURN n;
832
+ END
833
+ $$;
834
+ --> statement-breakpoint
835
+ -- A reference's end that changes no object (EndInRow): its row's end alone, written in the vault's decision beside 0.14's `reference.end`, for a reference no live holder stands on (its holder archived, holding a value, or standing on a newer reference), and for a standing one whose holder 0.14's log alone holds (no seal, no change: before the cutover), whose change logs nothing. Without it, a holder archived when its reference was broken would read that reference live again once unarchived (an unarchive seals the holder's object from its rows), and the cutover's genesis would seal an unlogged holder's broken reference live. Never a standing reference of a logged holder, whose end is a change of the holder (`change_input`): refused, loudly, so that its decision fails rather than commit 0.14's entry beside a reference 0.15 still serves. The holder's row is locked first, so that no write unarchives or writes it between the check and the end; the vault's decision holds 0.14's head besides. It takes no fence: no use reads a row it writes as an object's.
836
+ CREATE FUNCTION end_reference(p_reference uuid, p_at bigint) RETURNS boolean
837
+ LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog, pg_temp SET jit = off AS $$
838
+ DECLARE
839
+ done integer;
840
+ BEGIN
841
+ IF p_at IS NULL OR p_at < 0 THEN
842
+ RAISE EXCEPTION 'a reference ends at a time, ms since the epoch' USING ERRCODE = 'invalid_parameter_value';
843
+ END IF;
844
+ PERFORM 1 FROM public.secrets x JOIN public.secret_references y ON y.secret_id = x.id WHERE y.id = p_reference FOR SHARE OF x;
845
+ IF EXISTS (
846
+ SELECT 1 FROM public.secret_references y JOIN public.secrets x ON x.id = y.secret_id
847
+ WHERE y.id = p_reference AND y.ended_at IS NULL AND x.archived_at IS NULL AND x.current_version_id IS NULL
848
+ AND y.id = (SELECT z.id FROM public.secret_references z WHERE z.secret_id = x.id ORDER BY z.created_seq DESC LIMIT 1)
849
+ AND (x.seal IS NOT NULL OR EXISTS (SELECT 1 FROM public.log_changes c WHERE c.object = public.log_object('secret', x.id::text)))
850
+ ) THEN
851
+ RAISE EXCEPTION 'reference % is its holder''s standing one: its end is a change of the holder', p_reference USING ERRCODE = 'invalid_parameter_value';
852
+ END IF;
853
+ -- No row (a write that stored none, abandoned) or one ended already: nothing to do.
854
+ UPDATE public.secret_references SET ended_at = to_timestamp(p_at / 1000.0) WHERE id = p_reference AND ended_at IS NULL;
855
+ GET DIAGNOSTICS done = ROW_COUNT;
856
+ RETURN done > 0;
857
+ END
858
+ $$;
859
+ --> statement-breakpoint
860
+ REVOKE ALL ON FUNCTION end_reference(uuid, bigint) FROM PUBLIC;
861
+ --> statement-breakpoint
862
+ GRANT EXECUTE ON FUNCTION end_reference(uuid, bigint) TO coffre_vault;
863
+ --> statement-breakpoint
864
+ -- No JIT for any function that runs as its owner, those of earlier migrations here (this migration's own say it in their definitions, as every new one must): each plans its statements once a session and keeps them, and an estimate that grows with its tables past jit_above_cost would compile them at every call. use.test.ts holds every such function to it.
865
+ ALTER FUNCTION prune_consumed_tokens(integer) SET jit = off;
866
+ --> statement-breakpoint
867
+ ALTER FUNCTION vault_releases_keep() SET jit = off;
868
+ --> statement-breakpoint
869
+ ALTER FUNCTION prune_vault_releases(integer) SET jit = off;
870
+ --> statement-breakpoint
871
+ ALTER FUNCTION make_day(date) SET jit = off;
872
+ --> statement-breakpoint
873
+ ALTER FUNCTION make_tiles(bigint, bigint) SET jit = off;
874
+ --> statement-breakpoint
875
+ ALTER FUNCTION close_one() SET jit = off;
876
+ --> statement-breakpoint
877
+ ALTER FUNCTION write_checkpoint(bigint, bigint, bigint, bigint, bytea, text, bigint, bytea, text, text[], smallint[], bigint[], bytea[]) SET jit = off;
878
+ --> statement-breakpoint
879
+ ALTER FUNCTION prune_bulk(integer) SET jit = off;
880
+ --> statement-breakpoint
881
+ ALTER FUNCTION log_retention(text, bigint, text, bytea) SET jit = off;
882
+ --> statement-breakpoint
883
+ ALTER FUNCTION log_cut() SET jit = off;
884
+ --> statement-breakpoint
885
+ ALTER FUNCTION drop_day(date) SET jit = off;
886
+ --> statement-breakpoint
887
+ ALTER FUNCTION drop_tiles(bigint) SET jit = off;