@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,705 @@
1
+ -- 0.15's use as one call of the vault's, from what the request names (T145, PR B; docs/design/0.15-plan.md, "As built: T145, PR B"; formal/Probing.tla, formal/Credentials.tla Revoking015Caller). The vault maps a request's token hash, place slugs and key name to ids, from its own cache or with `use_resolve` on a miss, and makes the use by ids: a use's entry is MACed over its ids before the call, with a key the database never holds, so no statement can both resolve and log. It only adds: two attributes at the end of the row, and functions only 0.15's hot path calls, behind the switch.
2
+ --> statement-breakpoint
3
+ ALTER TYPE use_row ADD ATTRIBUTE c_token_hash bytea, ADD ATTRIBUTE app json;
4
+ --> statement-breakpoint
5
+ -- `use_credential` as 0033 made it, but for what the request named (`p_token_hash`, `p_project`, `p_environment`, `p_key`; all null for a caller that names nothing, as before):
6
+ -- - `moved`: the credential's token hash, the key's name or the slugs of the place it is read at are not the ones named, the vault's ids for them out of date. Nothing is written or locked, and the vault resolves them again, once.
7
+ -- - `unnamed`: a place it reads, its own, the key's or a reference's source's, or its project, is archived. No name names an archived place (`use_resolve`), and the app answers it as before 0044, where it refused it before any use: nothing is written, and the vault serves it not by name.
8
+ -- - Named, a key the place does not hold, its place passed as `p_place`, and a plain value: decided by the vault on the caller's standing (Probing.tla, MissingByStanding, PlainByStanding), with no entry, no cap's row and no lock of the principal's, as neither is used nor counted. A use by ids (no name) logs its request whatever it finds, as 0033's: a key archived since the app read it included.
9
+ -- - `app`: with a token hash named, the rows the app checks (the credential's, with its MAC, its identity, the newest policy change, the place's, the key's and its version's), as `app_read` reads them, so that the app reads nothing itself.
10
+ -- - `c_token_hash`, which the credential's object seals.
11
+ -- Its signature grows, so it is made anew: nothing calls the old one but 0.15's hot path, behind the switch, as when 0024 replaced 0022's.
12
+ DROP FUNCTION use_credential(uuid, uuid, bigint, bigint, smallint, uuid, bytea, bigint, bigint, bigint, bigint, bigint, bytea, integer, bigint);
13
+ --> statement-breakpoint
14
+ CREATE FUNCTION use_credential(
15
+ p_credential uuid, p_secret uuid, p_request bigint, p_at bigint, p_purpose smallint, p_place uuid, p_mac bytea, p_deadline bigint,
16
+ 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,
17
+ p_cap_count integer DEFAULT NULL, p_cap_window bigint DEFAULT NULL,
18
+ p_token_hash bytea DEFAULT NULL, p_project text DEFAULT NULL, p_environment text DEFAULT NULL, p_key text DEFAULT NULL
19
+ ) RETURNS json
20
+ LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog, pg_temp SET bytea_output = 'hex' SET jit = off AS $$
21
+ DECLARE
22
+ r public.use_row;
23
+ -- The principal's lock held: exclusive (`locked`), to enter its key in the cap's set; shared (`moving`), to move it on.
24
+ locked boolean := false;
25
+ moving boolean := false;
26
+ probe boolean := p_known IS NULL OR p_cap_count IS NULL OR p_cap_window IS NULL;
27
+ -- A use through a place, or of a key with no value of its own (a reference), reads every input; the others, most
28
+ -- uses, the plain shape: no place, no source, none of their joins.
29
+ whole boolean := p_place IS NOT NULL;
30
+ -- The cached forms' refresh and period, from the cap the isolate passes; the probing forms work theirs out from the cap they read.
31
+ c_refresh bigint := greatest(1, least(60000, p_cap_window / 10));
32
+ c_period bigint := p_at - ((p_at % c_refresh) + c_refresh) % c_refresh;
33
+ -- Whether this session has yet to plan the four statements (T131), and the form the warm-up is at.
34
+ dry boolean := current_setting('coffre.use_planned', true) IS DISTINCT FROM '0044';
35
+ form integer := 0;
36
+ first_probe boolean;
37
+ first_whole boolean;
38
+ -- The period this call writes, if it writes the cap's row, and the lock that elects the one use that moves it.
39
+ b_refresh bigint;
40
+ b_period bigint;
41
+ b_lock bigint;
42
+ b_round integer := 0;
43
+ BEGIN
44
+ IF p_deadline IS NULL OR p_deadline > p_at + public.deadline_max() THEN r.state := 'late'; RETURN to_json(r); END IF;
45
+ -- Its Worker's clock ahead by over a minute (0022 says why): checked here at no cost, and again in the statement.
46
+ IF p_at > (extract(epoch FROM clock_timestamp()) * 1000)::bigint + 60000 THEN r.state := 'ahead'; RETURN to_json(r); END IF;
47
+ IF dry THEN
48
+ -- Each form first runs dry, outside the fence, so that its plan is made before this call holds the fence.
49
+ first_probe := probe;
50
+ first_whole := whole;
51
+ probe := true;
52
+ whole := false;
53
+ ELSE
54
+ PERFORM pg_advisory_xact_lock_shared(1668244070, 0);
55
+ END IF;
56
+ LOOP
57
+ IF probe AND NOT whole THEN
58
+ WITH i AS MATERIALIZED (
59
+ SELECT n.number AS newest, n.mac AS newest_mac, coalesce(n.number = p_known AND n.mac = p_known_mac, false) AS cached,
60
+ f.at_floor, (lpol.payload::jsonb #>> '{value,bulk,count}')::integer AS cap_count, (lpol.payload::jsonb #>> '{value,bulk,windowMs}')::bigint AS cap_window,
61
+ lpol.number AS lpol_number, lpol.seal AS p_last,
62
+ c.principal AS p, c.kind AS c_kind, c.scope AS c_scope, c.revoked_at IS NOT NULL AS c_revoked,
63
+ floor(extract(epoch FROM least(c.expires_at, c.not_after)) * 1000)::bigint AS c_expires, c.seal AS c_seal,
64
+ 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, c.generation AS c_generation, m.generation AS m_generation,
65
+ -- What the request named, for the names it checks, and the rows the app checks, when it names them (0044).
66
+ c.token_hash AS c_token_hash, s.key AS s_key, CASE WHEN p_project IS NOT NULL THEN (SELECT q.slug FROM public.projects q WHERE q.id = e2.project_id) END AS named_project,
67
+ CASE WHEN p_token_hash IS NOT NULL THEN to_json(c) END AS c_row, CASE WHEN p_token_hash IS NOT NULL THEN to_json(s) END AS s_row,
68
+ CASE WHEN p_token_hash IS NOT NULL AND s.current_version_id IS NOT NULL THEN to_json(sv) END AS sv_row,
69
+ cfg.r_value, cfg.r_seal, cfg.p_value, cfg.p_seal,
70
+ 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,
71
+ 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,
72
+ 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,
73
+ 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,
74
+ 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,
75
+ 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,
76
+ 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,
77
+ b.last AS bulk_last,
78
+ -- 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).
79
+ public.log_object('credential', p_credential::text) AS k_credential,
80
+ public.log_object('principal', c.principal) AS k_member,
81
+ public.log_object('secret', p_secret::text) AS k_secret,
82
+ public.log_object('environment', s.environment_id::text) AS k_e2,
83
+ public.log_object('environment', (CASE WHEN e2.owner IS NOT NULL THEN e2.parent_id END)::text) AS k_e4
84
+ FROM (SELECT 1) one
85
+ LEFT JOIN LATERAL (SELECT x.number, x.mac FROM public.log_changes x ORDER BY x.number DESC LIMIT 1) n ON true
86
+ LEFT JOIN public.log_fence f ON f.only_row
87
+ -- The roles and the policy, both rows in one read of their one-page table.
88
+ LEFT JOIN LATERAL (
89
+ 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,
90
+ max(g.value) FILTER (WHERE g.name = 'policy') AS p_value, (array_agg(g.seal) FILTER (WHERE g.name = 'policy'))[1] AS p_seal
91
+ FROM public.log_config g
92
+ ) cfg ON true
93
+ -- The cap as the policy's newest change says it (CapSealed): every policy change logs the whole policy as its `value`.
94
+ LEFT JOIN LATERAL (
95
+ 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
96
+ ) lpol ON true
97
+ LEFT JOIN public.credentials c ON c.id = p_credential
98
+ LEFT JOIN public.vault_members m ON m.principal = c.principal
99
+ -- An archived key is gone (`secret.delete`): no object.
100
+ LEFT JOIN public.secrets s ON s.id = p_secret AND s.archived_at IS NULL
101
+ -- The key's own version, its own environment, and that environment's parent when it is a personal config.
102
+ LEFT JOIN public.secret_versions sv ON sv.id = s.current_version_id
103
+ LEFT JOIN public.environments e2 ON e2.id = s.environment_id
104
+ LEFT JOIN public.environments e4 ON e4.id = e2.parent_id AND e2.owner IS NOT NULL
105
+ LEFT JOIN public.log_bulk b ON b.principal = c.principal AND b.secret = p_secret
106
+ WHERE NOT dry
107
+ ),
108
+ v AS MATERIALIZED (
109
+ SELECT i.*, cap.refresh, cap.period,
110
+ CASE WHEN i.s_project IS NOT NULL AND i.s_version IS NULL THEN 'whole'
111
+ WHEN (p_token_hash IS NOT NULL AND i.c_token_hash IS DISTINCT FROM p_token_hash)
112
+ OR (p_key IS NOT NULL AND i.s_key IS NOT NULL AND i.s_key IS DISTINCT FROM p_key)
113
+ OR (p_environment IS NOT NULL AND coalesce(i.e1_slug, i.e2_slug) IS DISTINCT FROM p_environment)
114
+ OR (p_project IS NOT NULL AND i.named_project IS DISTINCT FROM p_project) THEN 'moved'
115
+ WHEN p_token_hash IS NOT NULL AND (coalesce(i.e1_archived, false) OR coalesce(i.e2_archived, false) OR coalesce(i.e3_archived, false)
116
+ OR coalesce(i.e1_project_archived, false) OR coalesce(i.e2_project_archived, false) OR coalesce(i.e3_project_archived, false)) THEN 'unnamed'
117
+ WHEN i.at_floor IS NULL OR p_at < i.at_floor OR (extract(epoch FROM clock_timestamp()) * 1000)::bigint > p_deadline THEN 'late'
118
+ WHEN p_at > (extract(epoch FROM clock_timestamp()) * 1000)::bigint + 60000 THEN 'ahead'
119
+ WHEN i.cap_count IS NULL OR i.cap_window IS NULL THEN 'unconfigured'
120
+ WHEN i.p IS NOT NULL AND NOT locked AND NOT (p_token_hash IS NOT NULL AND (i.s_project IS NULL OR coalesce(i.v_plain, false))) AND NOT coalesce(i.bulk_last > p_at - i.cap_window - cap.refresh, false) THEN 'lock'
121
+ WHEN i.p IS NOT NULL AND NOT locked AND NOT moving AND NOT (p_token_hash IS NOT NULL AND (i.s_project IS NULL OR coalesce(i.v_plain, false))) AND i.bulk_last < cap.period THEN 'share' END AS state,
122
+ greatest(lc.number, lm.number, lr.number, i.lpol_number, ls.number, l2.number, l4.number) AS inputs,
123
+ 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,
124
+ NULL::bytea AS e1_last, l2.seal AS e2_last, NULL::bytea AS e3_last, l4.seal AS e4_last,
125
+ coalesce(i.bulk_last > p_at - i.cap_window - cap.refresh, false) AS is_in,
126
+ -- The other keys in the set, up to the cap: read only under the principal's lock, for a key not in it.
127
+ CASE WHEN locked AND i.p IS NOT NULL AND NOT coalesce(i.bulk_last > p_at - i.cap_window - cap.refresh, false) THEN (
128
+ SELECT count(*)::integer FROM (
129
+ SELECT 1 FROM public.log_bulk o
130
+ WHERE o.principal = i.p AND o.secret <> p_secret AND o.last > p_at - i.cap_window - cap.refresh - public.deadline_max() - 60000
131
+ LIMIT i.cap_count
132
+ ) z
133
+ ) ELSE 0 END AS n_others,
134
+ -- A repeat rides its cover only while every part of this holds: never on a key whose bulk row is missing (CoverFresh).
135
+ 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)
136
+ 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
137
+ 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
138
+ FROM i
139
+ CROSS JOIN LATERAL (SELECT greatest(1, least(60000, i.cap_window / 10)) AS refresh) r0
140
+ CROSS JOIN LATERAL (SELECT r0.refresh, p_at - ((p_at % r0.refresh) + r0.refresh) % r0.refresh AS period) cap
141
+ -- Each input's newest change: one index-only probe each (log_changes_object_idx holds the seal), the principal's
142
+ -- with its payload, which names the 0.14 entry it accounts for.
143
+ 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
144
+ 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
145
+ 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
146
+ 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
147
+ 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
148
+ 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
149
+ ),
150
+ w AS (
151
+ INSERT INTO public.log_uses (number, at, request_id, credential, secret, purpose, mac, place)
152
+ SELECT nextval('public.log_number'), p_at, p_request, p_credential, p_secret, p_purpose, p_mac, p_place
153
+ FROM v WHERE v.state IS NULL AND NOT v.is_covered AND NOT (p_token_hash IS NOT NULL AND (v.s_project IS NULL OR coalesce(v.v_plain, false)))
154
+ RETURNING log_uses.number
155
+ )
156
+ 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,
157
+ v.p, v.c_kind, v.c_scope, v.c_revoked, v.c_expires, v.c_seal, v.c_last,
158
+ v.m_status, v.m_owner, v.m_role, v.m_scope, v.m_seal, v.m_last, v.m_old,
159
+ (SELECT a.seq FROM public.audit_log a WHERE a.author = 'vault' AND a.subject_principal = v.p AND a.decision = 'allow'
160
+ 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),
161
+ gr.g_project, gr.g_environment, gr.g_slug, gr.g_role, gr.g_expires,
162
+ v.r_value, v.r_seal, v.r_last,
163
+ v.p_value, v.p_seal, v.p_last, v.cap_count, v.cap_window,
164
+ v.s_project, v.s_environment, v.s_version, v.s_source, v.s_seal, v.s_last,
165
+ v.x_project, v.x_environment, v.x_version, v.x_source, v.x_seal, v.x_last,
166
+ 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,
167
+ 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,
168
+ 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,
169
+ 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,
170
+ 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,
171
+ 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, v.c_generation, v.m_generation, v.c_token_hash,
172
+ CASE WHEN p_token_hash IS NOT NULL THEN json_build_object(
173
+ 'now', CURRENT_TIMESTAMP, 'credential', v.c_row, 'identity', (SELECT to_json(x) FROM public.identities x WHERE x.id = (v.c_row ->> 'identity_id')::uuid),
174
+ 'policy', (SELECT a.seq::text FROM public.audit_log a WHERE a.author = 'vault' AND a.action = 'policy.change' AND a.decision = 'allow' ORDER BY a.seq DESC LIMIT 1),
175
+ 'project', (SELECT to_json(q) FROM public.projects q WHERE q.id = coalesce(v.e1_project, v.e2_project)),
176
+ 'environment', (SELECT to_json(e) FROM public.environments e WHERE e.id = coalesce(v.e1_id, v.e2_id)), 'parent', NULL,
177
+ 'secrets', CASE WHEN v.s_row IS NULL THEN '[]'::json ELSE json_build_array(json_build_object('secret', v.s_row, 'version', v.sv_row)) END) END
178
+ INTO r
179
+ FROM v
180
+ -- 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`.
181
+ LEFT JOIN LATERAL (
182
+ SELECT array_agg(coalesce(g.project_id, ge.project_id)) AS g_project, array_agg(g.environment_id) AS g_environment,
183
+ array_agg(g.environment_slug) AS g_slug, array_agg(g.role) AS g_role, array_agg(g.expires_at) AS g_expires
184
+ FROM public.vault_grants g LEFT JOIN public.environments ge ON ge.id = g.environment_id
185
+ WHERE g.principal = v.p
186
+ ) gr ON v.m_status IS NOT NULL;
187
+ ELSIF probe THEN
188
+ WITH i AS MATERIALIZED (
189
+ SELECT n.number AS newest, n.mac AS newest_mac, coalesce(n.number = p_known AND n.mac = p_known_mac, false) AS cached,
190
+ f.at_floor, (lpol.payload::jsonb #>> '{value,bulk,count}')::integer AS cap_count, (lpol.payload::jsonb #>> '{value,bulk,windowMs}')::bigint AS cap_window,
191
+ lpol.number AS lpol_number, lpol.seal AS p_last,
192
+ c.principal AS p, c.kind AS c_kind, c.scope AS c_scope, c.revoked_at IS NOT NULL AS c_revoked,
193
+ floor(extract(epoch FROM least(c.expires_at, c.not_after)) * 1000)::bigint AS c_expires, c.seal AS c_seal,
194
+ 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, c.generation AS c_generation, m.generation AS m_generation,
195
+ -- What the request named, for the names it checks, and the rows the app checks, when it names them (0044).
196
+ c.token_hash AS c_token_hash, s.key AS s_key, CASE WHEN p_project IS NOT NULL THEN (SELECT q.slug FROM public.projects q WHERE q.id = coalesce(e1.project_id, e2.project_id)) END AS named_project,
197
+ CASE WHEN p_token_hash IS NOT NULL THEN to_json(c) END AS c_row, CASE WHEN p_token_hash IS NOT NULL THEN to_json(s) END AS s_row,
198
+ CASE WHEN p_token_hash IS NOT NULL AND s.current_version_id IS NOT NULL THEN to_json(sv) END AS sv_row,
199
+ cfg.r_value, cfg.r_seal, cfg.p_value, cfg.p_seal,
200
+ 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,
201
+ 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,
202
+ 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,
203
+ 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,
204
+ 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,
205
+ 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,
206
+ 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,
207
+ b.last AS bulk_last,
208
+ -- 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).
209
+ public.log_object('credential', p_credential::text) AS k_credential,
210
+ public.log_object('principal', c.principal) AS k_member,
211
+ public.log_object('secret', p_secret::text) AS k_secret,
212
+ public.log_object('secret', ref.source_secret_id::text) AS k_source,
213
+ public.log_object('environment', ids.e1::text) AS k_e1,
214
+ public.log_object('environment', s.environment_id::text) AS k_e2,
215
+ public.log_object('environment', ids.e3::text) AS k_e3,
216
+ public.log_object('environment', (CASE WHEN ve.owner IS NOT NULL THEN ve.parent END)::text) AS k_e4
217
+ FROM (SELECT 1) one
218
+ LEFT JOIN LATERAL (SELECT x.number, x.mac FROM public.log_changes x ORDER BY x.number DESC LIMIT 1) n ON true
219
+ LEFT JOIN public.log_fence f ON f.only_row
220
+ -- The roles and the policy, both rows in one read of their one-page table.
221
+ LEFT JOIN LATERAL (
222
+ 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,
223
+ max(g.value) FILTER (WHERE g.name = 'policy') AS p_value, (array_agg(g.seal) FILTER (WHERE g.name = 'policy'))[1] AS p_seal
224
+ FROM public.log_config g
225
+ ) cfg ON true
226
+ -- The cap as the policy's newest change says it (CapSealed): every policy change logs the whole policy as its `value`.
227
+ LEFT JOIN LATERAL (
228
+ 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
229
+ ) lpol ON true
230
+ LEFT JOIN public.credentials c ON c.id = p_credential
231
+ LEFT JOIN public.vault_members m ON m.principal = c.principal
232
+ -- An archived key is gone (`secret.delete`): no object.
233
+ LEFT JOIN public.secrets s ON s.id = p_secret AND s.archived_at IS NULL
234
+ -- 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`).
235
+ LEFT JOIN LATERAL (
236
+ 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
237
+ ) ref ON s.current_version_id IS NULL
238
+ LEFT JOIN public.secrets src ON src.id = ref.source_secret_id AND src.archived_at IS NULL
239
+ LEFT JOIN LATERAL (
240
+ 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
241
+ ) xref ON src.current_version_id IS NULL
242
+ -- The value's version: the key's own, or its source's.
243
+ 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
244
+ -- The environments `useEnvironments` names, each by the id the rows name, whether or not its row is there: e1 the
245
+ -- place read through, e2 the key's own, e3 its value's (a reference's source's) where that is another, and e4
246
+ -- the parent of its value's when that is a personal config.
247
+ LEFT JOIN LATERAL (
248
+ SELECT CASE WHEN p_place IS DISTINCT FROM s.environment_id THEN p_place END AS e1,
249
+ CASE WHEN src.environment_id IS DISTINCT FROM s.environment_id THEN src.environment_id END AS e3
250
+ ) ids ON true
251
+ LEFT JOIN public.environments e1 ON e1.id = ids.e1
252
+ LEFT JOIN public.environments e2 ON e2.id = s.environment_id
253
+ LEFT JOIN public.environments e3 ON e3.id = ids.e3
254
+ -- The value's environment's row: the key's own, unless it is a reference, whose source is elsewhere or gone.
255
+ LEFT JOIN LATERAL (
256
+ 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,
257
+ 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
258
+ ) ve ON true
259
+ LEFT JOIN public.environments e4 ON e4.id = ve.parent AND ve.owner IS NOT NULL
260
+ LEFT JOIN public.log_bulk b ON b.principal = c.principal AND b.secret = p_secret
261
+ WHERE NOT dry
262
+ ),
263
+ v AS MATERIALIZED (
264
+ SELECT i.*, cap.refresh, cap.period,
265
+ CASE WHEN (p_token_hash IS NOT NULL AND i.c_token_hash IS DISTINCT FROM p_token_hash)
266
+ OR (p_key IS NOT NULL AND i.s_key IS NOT NULL AND i.s_key IS DISTINCT FROM p_key)
267
+ OR (p_environment IS NOT NULL AND coalesce(i.e1_slug, i.e2_slug) IS DISTINCT FROM p_environment)
268
+ OR (p_project IS NOT NULL AND i.named_project IS DISTINCT FROM p_project) THEN 'moved'
269
+ WHEN p_token_hash IS NOT NULL AND (coalesce(i.e1_archived, false) OR coalesce(i.e2_archived, false) OR coalesce(i.e3_archived, false)
270
+ OR coalesce(i.e1_project_archived, false) OR coalesce(i.e2_project_archived, false) OR coalesce(i.e3_project_archived, false)) THEN 'unnamed'
271
+ WHEN i.at_floor IS NULL OR p_at < i.at_floor OR (extract(epoch FROM clock_timestamp()) * 1000)::bigint > p_deadline THEN 'late'
272
+ WHEN p_at > (extract(epoch FROM clock_timestamp()) * 1000)::bigint + 60000 THEN 'ahead'
273
+ WHEN i.cap_count IS NULL OR i.cap_window IS NULL THEN 'unconfigured'
274
+ WHEN i.p IS NOT NULL AND NOT locked AND NOT (p_token_hash IS NOT NULL AND (i.s_project IS NULL OR coalesce(i.v_plain, false))) AND NOT coalesce(i.bulk_last > p_at - i.cap_window - cap.refresh, false) THEN 'lock'
275
+ WHEN i.p IS NOT NULL AND NOT locked AND NOT moving AND NOT (p_token_hash IS NOT NULL AND (i.s_project IS NULL OR coalesce(i.v_plain, false))) AND i.bulk_last < cap.period THEN 'share' END AS state,
276
+ greatest(lc.number, lm.number, lr.number, i.lpol_number, ls.number, lx.number, l1.number, l2.number, l3.number, l4.number) AS inputs,
277
+ 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,
278
+ l1.seal AS e1_last, l2.seal AS e2_last, l3.seal AS e3_last, l4.seal AS e4_last,
279
+ coalesce(i.bulk_last > p_at - i.cap_window - cap.refresh, false) AS is_in,
280
+ -- The other keys in the set, up to the cap: read only under the principal's lock, for a key not in it.
281
+ CASE WHEN locked AND i.p IS NOT NULL AND NOT coalesce(i.bulk_last > p_at - i.cap_window - cap.refresh, false) THEN (
282
+ SELECT count(*)::integer FROM (
283
+ SELECT 1 FROM public.log_bulk o
284
+ WHERE o.principal = i.p AND o.secret <> p_secret AND o.last > p_at - i.cap_window - cap.refresh - public.deadline_max() - 60000
285
+ LIMIT i.cap_count
286
+ ) z
287
+ ) ELSE 0 END AS n_others,
288
+ -- A repeat rides its cover only while every part of this holds: never on a key whose bulk row is missing (CoverFresh).
289
+ 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)
290
+ 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
291
+ 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
292
+ FROM i
293
+ CROSS JOIN LATERAL (SELECT greatest(1, least(60000, i.cap_window / 10)) AS refresh) r0
294
+ CROSS JOIN LATERAL (SELECT r0.refresh, p_at - ((p_at % r0.refresh) + r0.refresh) % r0.refresh AS period) cap
295
+ -- Each input's newest change: one index-only probe each (log_changes_object_idx holds the seal), the principal's
296
+ -- with its payload, which names the 0.14 entry it accounts for.
297
+ 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
298
+ 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
299
+ 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
300
+ 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
301
+ 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
302
+ 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
303
+ 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
304
+ 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
305
+ 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
306
+ ),
307
+ w AS (
308
+ INSERT INTO public.log_uses (number, at, request_id, credential, secret, purpose, mac, place)
309
+ SELECT nextval('public.log_number'), p_at, p_request, p_credential, p_secret, p_purpose, p_mac, p_place
310
+ FROM v WHERE v.state IS NULL AND NOT v.is_covered AND NOT (p_token_hash IS NOT NULL AND (v.s_project IS NULL OR coalesce(v.v_plain, false)))
311
+ RETURNING log_uses.number
312
+ )
313
+ 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,
314
+ v.p, v.c_kind, v.c_scope, v.c_revoked, v.c_expires, v.c_seal, v.c_last,
315
+ v.m_status, v.m_owner, v.m_role, v.m_scope, v.m_seal, v.m_last, v.m_old,
316
+ (SELECT a.seq FROM public.audit_log a WHERE a.author = 'vault' AND a.subject_principal = v.p AND a.decision = 'allow'
317
+ 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),
318
+ gr.g_project, gr.g_environment, gr.g_slug, gr.g_role, gr.g_expires,
319
+ v.r_value, v.r_seal, v.r_last,
320
+ v.p_value, v.p_seal, v.p_last, v.cap_count, v.cap_window,
321
+ v.s_project, v.s_environment, v.s_version, v.s_source, v.s_seal, v.s_last,
322
+ v.x_project, v.x_environment, v.x_version, v.x_source, v.x_seal, v.x_last,
323
+ 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,
324
+ 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,
325
+ 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,
326
+ 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,
327
+ 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,
328
+ 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, v.c_generation, v.m_generation, v.c_token_hash,
329
+ CASE WHEN p_token_hash IS NOT NULL THEN json_build_object(
330
+ 'now', CURRENT_TIMESTAMP, 'credential', v.c_row, 'identity', (SELECT to_json(x) FROM public.identities x WHERE x.id = (v.c_row ->> 'identity_id')::uuid),
331
+ 'policy', (SELECT a.seq::text FROM public.audit_log a WHERE a.author = 'vault' AND a.action = 'policy.change' AND a.decision = 'allow' ORDER BY a.seq DESC LIMIT 1),
332
+ 'project', (SELECT to_json(q) FROM public.projects q WHERE q.id = coalesce(v.e1_project, v.e2_project)),
333
+ 'environment', (SELECT to_json(e) FROM public.environments e WHERE e.id = coalesce(v.e1_id, v.e2_id)), 'parent', NULL,
334
+ 'secrets', CASE WHEN v.s_row IS NULL THEN '[]'::json ELSE json_build_array(json_build_object('secret', v.s_row, 'version', v.sv_row)) END) END
335
+ INTO r
336
+ FROM v
337
+ -- 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`.
338
+ LEFT JOIN LATERAL (
339
+ SELECT array_agg(coalesce(g.project_id, ge.project_id)) AS g_project, array_agg(g.environment_id) AS g_environment,
340
+ array_agg(g.environment_slug) AS g_slug, array_agg(g.role) AS g_role, array_agg(g.expires_at) AS g_expires
341
+ FROM public.vault_grants g LEFT JOIN public.environments ge ON ge.id = g.environment_id
342
+ WHERE g.principal = v.p
343
+ ) gr ON v.m_status IS NOT NULL;
344
+ ELSIF NOT whole THEN
345
+ -- The isolate's seals, and its cap, read at the change it names: no probe, only whether a change came since
346
+ -- (then nothing is written: 'reprobe').
347
+ WITH i AS MATERIALIZED (
348
+ SELECT n.number AS newest, n.mac AS newest_mac, coalesce(n.number = p_known AND n.mac = p_known_mac, false) AS cached,
349
+ f.at_floor,
350
+ c.principal AS p, c.kind AS c_kind, c.scope AS c_scope, c.revoked_at IS NOT NULL AS c_revoked,
351
+ floor(extract(epoch FROM least(c.expires_at, c.not_after)) * 1000)::bigint AS c_expires, c.seal AS c_seal,
352
+ 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, c.generation AS c_generation, m.generation AS m_generation,
353
+ -- What the request named, for the names it checks, and the rows the app checks, when it names them (0044).
354
+ c.token_hash AS c_token_hash, s.key AS s_key, CASE WHEN p_project IS NOT NULL THEN (SELECT q.slug FROM public.projects q WHERE q.id = e2.project_id) END AS named_project,
355
+ CASE WHEN p_token_hash IS NOT NULL THEN to_json(c) END AS c_row, CASE WHEN p_token_hash IS NOT NULL THEN to_json(s) END AS s_row,
356
+ CASE WHEN p_token_hash IS NOT NULL AND s.current_version_id IS NOT NULL THEN to_json(sv) END AS sv_row,
357
+ cfg.r_value, cfg.r_seal, cfg.p_value, cfg.p_seal,
358
+ 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,
359
+ 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,
360
+ 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,
361
+ 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,
362
+ 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,
363
+ 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,
364
+ 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,
365
+ b.last AS bulk_last
366
+ FROM (SELECT 1) one
367
+ LEFT JOIN LATERAL (SELECT x.number, x.mac FROM public.log_changes x ORDER BY x.number DESC LIMIT 1) n ON true
368
+ LEFT JOIN public.log_fence f ON f.only_row
369
+ -- The roles and the policy, both rows in one read of their one-page table.
370
+ LEFT JOIN LATERAL (
371
+ 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,
372
+ max(g.value) FILTER (WHERE g.name = 'policy') AS p_value, (array_agg(g.seal) FILTER (WHERE g.name = 'policy'))[1] AS p_seal
373
+ FROM public.log_config g
374
+ ) cfg ON true
375
+ LEFT JOIN public.credentials c ON c.id = p_credential
376
+ LEFT JOIN public.vault_members m ON m.principal = c.principal
377
+ -- An archived key is gone (`secret.delete`): no object.
378
+ LEFT JOIN public.secrets s ON s.id = p_secret AND s.archived_at IS NULL
379
+ -- The key's own version, its own environment, and that environment's parent when it is a personal config.
380
+ LEFT JOIN public.secret_versions sv ON sv.id = s.current_version_id
381
+ LEFT JOIN public.environments e2 ON e2.id = s.environment_id
382
+ LEFT JOIN public.environments e4 ON e4.id = e2.parent_id AND e2.owner IS NOT NULL
383
+ LEFT JOIN public.log_bulk b ON b.principal = c.principal AND b.secret = p_secret
384
+ WHERE NOT dry
385
+ ),
386
+ v AS MATERIALIZED (
387
+ SELECT i.*, c_refresh AS refresh, c_period AS period,
388
+ CASE WHEN NOT i.cached THEN 'reprobe'
389
+ WHEN i.s_project IS NOT NULL AND i.s_version IS NULL THEN 'whole'
390
+ WHEN (p_token_hash IS NOT NULL AND i.c_token_hash IS DISTINCT FROM p_token_hash)
391
+ OR (p_key IS NOT NULL AND i.s_key IS NOT NULL AND i.s_key IS DISTINCT FROM p_key)
392
+ OR (p_environment IS NOT NULL AND coalesce(i.e1_slug, i.e2_slug) IS DISTINCT FROM p_environment)
393
+ OR (p_project IS NOT NULL AND i.named_project IS DISTINCT FROM p_project) THEN 'moved'
394
+ WHEN p_token_hash IS NOT NULL AND (coalesce(i.e1_archived, false) OR coalesce(i.e2_archived, false) OR coalesce(i.e3_archived, false)
395
+ OR coalesce(i.e1_project_archived, false) OR coalesce(i.e2_project_archived, false) OR coalesce(i.e3_project_archived, false)) THEN 'unnamed'
396
+ WHEN i.at_floor IS NULL OR p_at < i.at_floor OR (extract(epoch FROM clock_timestamp()) * 1000)::bigint > p_deadline THEN 'late'
397
+ WHEN p_at > (extract(epoch FROM clock_timestamp()) * 1000)::bigint + 60000 THEN 'ahead'
398
+ WHEN i.p IS NOT NULL AND NOT locked AND NOT (p_token_hash IS NOT NULL AND (i.s_project IS NULL OR coalesce(i.v_plain, false))) AND NOT coalesce(i.bulk_last > p_at - p_cap_window - c_refresh, false) THEN 'lock'
399
+ WHEN i.p IS NOT NULL AND NOT locked AND NOT moving AND NOT (p_token_hash IS NOT NULL AND (i.s_project IS NULL OR coalesce(i.v_plain, false))) AND i.bulk_last < c_period THEN 'share' END AS state,
400
+ coalesce(i.bulk_last > p_at - p_cap_window - c_refresh, false) AS is_in,
401
+ -- The other keys in the set, up to the cap: read only under the principal's lock, for a key not in it.
402
+ CASE WHEN locked AND i.p IS NOT NULL AND NOT coalesce(i.bulk_last > p_at - p_cap_window - c_refresh, false) THEN (
403
+ SELECT count(*)::integer FROM (
404
+ SELECT 1 FROM public.log_bulk o
405
+ WHERE o.principal = i.p AND o.secret <> p_secret AND o.last > p_at - p_cap_window - c_refresh - public.deadline_max() - 60000
406
+ LIMIT p_cap_count
407
+ ) z
408
+ ) ELSE 0 END AS n_others,
409
+ -- A repeat rides its cover only while every part of this holds: never on a key whose bulk row is missing (CoverFresh).
410
+ coalesce(p_cover IS NOT NULL AND i.bulk_last >= c_period
411
+ 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
412
+ 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
413
+ FROM i
414
+ ),
415
+ w AS (
416
+ INSERT INTO public.log_uses (number, at, request_id, credential, secret, purpose, mac, place)
417
+ SELECT nextval('public.log_number'), p_at, p_request, p_credential, p_secret, p_purpose, p_mac, p_place
418
+ -- The isolate held its cover to the cached seals' numbers when it read them.
419
+ FROM v WHERE v.state IS NULL AND NOT v.is_covered AND NOT (p_token_hash IS NOT NULL AND (v.s_project IS NULL OR coalesce(v.v_plain, false)))
420
+ RETURNING log_uses.number
421
+ )
422
+ 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,
423
+ v.p, v.c_kind, v.c_scope, v.c_revoked, v.c_expires, v.c_seal, NULL::bytea,
424
+ v.m_status, v.m_owner, v.m_role, v.m_scope, v.m_seal, NULL::bytea, NULL::bigint,
425
+ (SELECT a.seq FROM public.audit_log a WHERE a.author = 'vault' AND a.subject_principal = v.p AND a.decision = 'allow'
426
+ 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),
427
+ gr.g_project, gr.g_environment, gr.g_slug, gr.g_role, gr.g_expires,
428
+ v.r_value, v.r_seal, NULL::bytea,
429
+ v.p_value, v.p_seal, NULL::bytea, p_cap_count, p_cap_window,
430
+ v.s_project, v.s_environment, v.s_version, v.s_source, v.s_seal, NULL::bytea,
431
+ v.x_project, v.x_environment, v.x_version, v.x_source, v.x_seal, NULL::bytea,
432
+ 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,
433
+ 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,
434
+ 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,
435
+ 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,
436
+ 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,
437
+ 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, v.c_generation, v.m_generation, v.c_token_hash,
438
+ CASE WHEN p_token_hash IS NOT NULL THEN json_build_object(
439
+ 'now', CURRENT_TIMESTAMP, 'credential', v.c_row, 'identity', (SELECT to_json(x) FROM public.identities x WHERE x.id = (v.c_row ->> 'identity_id')::uuid),
440
+ 'policy', (SELECT a.seq::text FROM public.audit_log a WHERE a.author = 'vault' AND a.action = 'policy.change' AND a.decision = 'allow' ORDER BY a.seq DESC LIMIT 1),
441
+ 'project', (SELECT to_json(q) FROM public.projects q WHERE q.id = coalesce(v.e1_project, v.e2_project)),
442
+ 'environment', (SELECT to_json(e) FROM public.environments e WHERE e.id = coalesce(v.e1_id, v.e2_id)), 'parent', NULL,
443
+ 'secrets', CASE WHEN v.s_row IS NULL THEN '[]'::json ELSE json_build_array(json_build_object('secret', v.s_row, 'version', v.sv_row)) END) END
444
+ INTO r
445
+ FROM v
446
+ -- 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`.
447
+ LEFT JOIN LATERAL (
448
+ SELECT array_agg(coalesce(g.project_id, ge.project_id)) AS g_project, array_agg(g.environment_id) AS g_environment,
449
+ array_agg(g.environment_slug) AS g_slug, array_agg(g.role) AS g_role, array_agg(g.expires_at) AS g_expires
450
+ FROM public.vault_grants g LEFT JOIN public.environments ge ON ge.id = g.environment_id
451
+ WHERE g.principal = v.p
452
+ ) gr ON v.m_status IS NOT NULL;
453
+ ELSE
454
+ WITH i AS MATERIALIZED (
455
+ SELECT n.number AS newest, n.mac AS newest_mac, coalesce(n.number = p_known AND n.mac = p_known_mac, false) AS cached,
456
+ f.at_floor,
457
+ c.principal AS p, c.kind AS c_kind, c.scope AS c_scope, c.revoked_at IS NOT NULL AS c_revoked,
458
+ floor(extract(epoch FROM least(c.expires_at, c.not_after)) * 1000)::bigint AS c_expires, c.seal AS c_seal,
459
+ 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, c.generation AS c_generation, m.generation AS m_generation,
460
+ -- What the request named, for the names it checks, and the rows the app checks, when it names them (0044).
461
+ c.token_hash AS c_token_hash, s.key AS s_key, CASE WHEN p_project IS NOT NULL THEN (SELECT q.slug FROM public.projects q WHERE q.id = coalesce(e1.project_id, e2.project_id)) END AS named_project,
462
+ CASE WHEN p_token_hash IS NOT NULL THEN to_json(c) END AS c_row, CASE WHEN p_token_hash IS NOT NULL THEN to_json(s) END AS s_row,
463
+ CASE WHEN p_token_hash IS NOT NULL AND s.current_version_id IS NOT NULL THEN to_json(sv) END AS sv_row,
464
+ cfg.r_value, cfg.r_seal, cfg.p_value, cfg.p_seal,
465
+ 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,
466
+ 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,
467
+ 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,
468
+ 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,
469
+ 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,
470
+ 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,
471
+ 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,
472
+ b.last AS bulk_last
473
+ FROM (SELECT 1) one
474
+ LEFT JOIN LATERAL (SELECT x.number, x.mac FROM public.log_changes x ORDER BY x.number DESC LIMIT 1) n ON true
475
+ LEFT JOIN public.log_fence f ON f.only_row
476
+ -- The roles and the policy, both rows in one read of their one-page table.
477
+ LEFT JOIN LATERAL (
478
+ 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,
479
+ max(g.value) FILTER (WHERE g.name = 'policy') AS p_value, (array_agg(g.seal) FILTER (WHERE g.name = 'policy'))[1] AS p_seal
480
+ FROM public.log_config g
481
+ ) cfg ON true
482
+ LEFT JOIN public.credentials c ON c.id = p_credential
483
+ LEFT JOIN public.vault_members m ON m.principal = c.principal
484
+ -- An archived key is gone (`secret.delete`): no object.
485
+ LEFT JOIN public.secrets s ON s.id = p_secret AND s.archived_at IS NULL
486
+ -- 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`).
487
+ LEFT JOIN LATERAL (
488
+ 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
489
+ ) ref ON s.current_version_id IS NULL
490
+ LEFT JOIN public.secrets src ON src.id = ref.source_secret_id AND src.archived_at IS NULL
491
+ LEFT JOIN LATERAL (
492
+ 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
493
+ ) xref ON src.current_version_id IS NULL
494
+ -- The value's version: the key's own, or its source's.
495
+ 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
496
+ -- The environments `useEnvironments` names, each by the id the rows name, whether or not its row is there: e1 the
497
+ -- place read through, e2 the key's own, e3 its value's (a reference's source's) where that is another, and e4
498
+ -- the parent of its value's when that is a personal config.
499
+ LEFT JOIN LATERAL (
500
+ SELECT CASE WHEN p_place IS DISTINCT FROM s.environment_id THEN p_place END AS e1,
501
+ CASE WHEN src.environment_id IS DISTINCT FROM s.environment_id THEN src.environment_id END AS e3
502
+ ) ids ON true
503
+ LEFT JOIN public.environments e1 ON e1.id = ids.e1
504
+ LEFT JOIN public.environments e2 ON e2.id = s.environment_id
505
+ LEFT JOIN public.environments e3 ON e3.id = ids.e3
506
+ -- The value's environment's row: the key's own, unless it is a reference, whose source is elsewhere or gone.
507
+ LEFT JOIN LATERAL (
508
+ 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,
509
+ 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
510
+ ) ve ON true
511
+ LEFT JOIN public.environments e4 ON e4.id = ve.parent AND ve.owner IS NOT NULL
512
+ LEFT JOIN public.log_bulk b ON b.principal = c.principal AND b.secret = p_secret
513
+ WHERE NOT dry
514
+ ),
515
+ v AS MATERIALIZED (
516
+ SELECT i.*, c_refresh AS refresh, c_period AS period,
517
+ CASE WHEN NOT i.cached THEN 'reprobe'
518
+ WHEN (p_token_hash IS NOT NULL AND i.c_token_hash IS DISTINCT FROM p_token_hash)
519
+ OR (p_key IS NOT NULL AND i.s_key IS NOT NULL AND i.s_key IS DISTINCT FROM p_key)
520
+ OR (p_environment IS NOT NULL AND coalesce(i.e1_slug, i.e2_slug) IS DISTINCT FROM p_environment)
521
+ OR (p_project IS NOT NULL AND i.named_project IS DISTINCT FROM p_project) THEN 'moved'
522
+ WHEN p_token_hash IS NOT NULL AND (coalesce(i.e1_archived, false) OR coalesce(i.e2_archived, false) OR coalesce(i.e3_archived, false)
523
+ OR coalesce(i.e1_project_archived, false) OR coalesce(i.e2_project_archived, false) OR coalesce(i.e3_project_archived, false)) THEN 'unnamed'
524
+ WHEN i.at_floor IS NULL OR p_at < i.at_floor OR (extract(epoch FROM clock_timestamp()) * 1000)::bigint > p_deadline THEN 'late'
525
+ WHEN p_at > (extract(epoch FROM clock_timestamp()) * 1000)::bigint + 60000 THEN 'ahead'
526
+ WHEN i.p IS NOT NULL AND NOT locked AND NOT (p_token_hash IS NOT NULL AND (i.s_project IS NULL OR coalesce(i.v_plain, false))) AND NOT coalesce(i.bulk_last > p_at - p_cap_window - c_refresh, false) THEN 'lock'
527
+ WHEN i.p IS NOT NULL AND NOT locked AND NOT moving AND NOT (p_token_hash IS NOT NULL AND (i.s_project IS NULL OR coalesce(i.v_plain, false))) AND i.bulk_last < c_period THEN 'share' END AS state,
528
+ coalesce(i.bulk_last > p_at - p_cap_window - c_refresh, false) AS is_in,
529
+ -- The other keys in the set, up to the cap: read only under the principal's lock, for a key not in it.
530
+ CASE WHEN locked AND i.p IS NOT NULL AND NOT coalesce(i.bulk_last > p_at - p_cap_window - c_refresh, false) THEN (
531
+ SELECT count(*)::integer FROM (
532
+ SELECT 1 FROM public.log_bulk o
533
+ WHERE o.principal = i.p AND o.secret <> p_secret AND o.last > p_at - p_cap_window - c_refresh - public.deadline_max() - 60000
534
+ LIMIT p_cap_count
535
+ ) z
536
+ ) ELSE 0 END AS n_others,
537
+ -- A repeat rides its cover only while every part of this holds: never on a key whose bulk row is missing (CoverFresh).
538
+ coalesce(p_cover IS NOT NULL AND i.bulk_last >= c_period
539
+ 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
540
+ 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
541
+ FROM i
542
+ ),
543
+ w AS (
544
+ INSERT INTO public.log_uses (number, at, request_id, credential, secret, purpose, mac, place)
545
+ SELECT nextval('public.log_number'), p_at, p_request, p_credential, p_secret, p_purpose, p_mac, p_place
546
+ -- The isolate held its cover to the cached seals' numbers when it read them.
547
+ FROM v WHERE v.state IS NULL AND NOT v.is_covered AND NOT (p_token_hash IS NOT NULL AND (v.s_project IS NULL OR coalesce(v.v_plain, false)))
548
+ RETURNING log_uses.number
549
+ )
550
+ 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,
551
+ v.p, v.c_kind, v.c_scope, v.c_revoked, v.c_expires, v.c_seal, NULL::bytea,
552
+ v.m_status, v.m_owner, v.m_role, v.m_scope, v.m_seal, NULL::bytea, NULL::bigint,
553
+ (SELECT a.seq FROM public.audit_log a WHERE a.author = 'vault' AND a.subject_principal = v.p AND a.decision = 'allow'
554
+ 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),
555
+ gr.g_project, gr.g_environment, gr.g_slug, gr.g_role, gr.g_expires,
556
+ v.r_value, v.r_seal, NULL::bytea,
557
+ v.p_value, v.p_seal, NULL::bytea, p_cap_count, p_cap_window,
558
+ v.s_project, v.s_environment, v.s_version, v.s_source, v.s_seal, NULL::bytea,
559
+ v.x_project, v.x_environment, v.x_version, v.x_source, v.x_seal, NULL::bytea,
560
+ 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,
561
+ 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,
562
+ 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,
563
+ 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,
564
+ 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,
565
+ 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, v.c_generation, v.m_generation, v.c_token_hash,
566
+ CASE WHEN p_token_hash IS NOT NULL THEN json_build_object(
567
+ 'now', CURRENT_TIMESTAMP, 'credential', v.c_row, 'identity', (SELECT to_json(x) FROM public.identities x WHERE x.id = (v.c_row ->> 'identity_id')::uuid),
568
+ 'policy', (SELECT a.seq::text FROM public.audit_log a WHERE a.author = 'vault' AND a.action = 'policy.change' AND a.decision = 'allow' ORDER BY a.seq DESC LIMIT 1),
569
+ 'project', (SELECT to_json(q) FROM public.projects q WHERE q.id = coalesce(v.e1_project, v.e2_project)),
570
+ 'environment', (SELECT to_json(e) FROM public.environments e WHERE e.id = coalesce(v.e1_id, v.e2_id)), 'parent', NULL,
571
+ 'secrets', CASE WHEN v.s_row IS NULL THEN '[]'::json ELSE json_build_array(json_build_object('secret', v.s_row, 'version', v.sv_row)) END) END
572
+ INTO r
573
+ FROM v
574
+ -- 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`.
575
+ LEFT JOIN LATERAL (
576
+ SELECT array_agg(coalesce(g.project_id, ge.project_id)) AS g_project, array_agg(g.environment_id) AS g_environment,
577
+ array_agg(g.environment_slug) AS g_slug, array_agg(g.role) AS g_role, array_agg(g.expires_at) AS g_expires
578
+ FROM public.vault_grants g LEFT JOIN public.environments ge ON ge.id = g.environment_id
579
+ WHERE g.principal = v.p
580
+ ) gr ON v.m_status IS NOT NULL;
581
+ END IF;
582
+ IF dry THEN
583
+ -- The forms in turn: probing plain, probing whole, cached plain, cached whole; then the call itself, under the fence.
584
+ form := form + 1;
585
+ IF form < 4 THEN
586
+ probe := form < 2;
587
+ whole := form % 2 = 1;
588
+ CONTINUE;
589
+ END IF;
590
+ PERFORM set_config('coffre.use_planned', '0044', false);
591
+ dry := false;
592
+ probe := first_probe;
593
+ whole := first_whole;
594
+ PERFORM pg_advisory_xact_lock_shared(1668244070, 0);
595
+ CONTINUE;
596
+ END IF;
597
+ -- A use the statement took writes its entry or rides its cover, never neither: but a named one of a key the place
598
+ -- does not hold, or of a plain value, which writes none.
599
+ IF r.state IS NULL AND r.number IS NULL AND r.covered IS NULL AND NOT (p_token_hash IS NOT NULL AND (r.s_project IS NULL OR coalesce(r.v_plain, false))) THEN
600
+ RAISE EXCEPTION 'a use wrote no entry and rode no cover' USING ERRCODE = 'XX000';
601
+ END IF;
602
+ IF r.state = 'reprobe' THEN probe := true; CONTINUE; END IF;
603
+ IF r.state = 'whole' THEN whole := true; CONTINUE; END IF;
604
+ EXIT WHEN r.state IS DISTINCT FROM 'lock' AND r.state IS DISTINCT FROM 'share';
605
+ -- 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
606
+ -- 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
607
+ -- shared, beside the principal's other uses that move a key on, and reads again.
608
+ -- Holding it shared, a key never leaves the set (SharedStaysInSet) unless a row was put back under it: then the
609
+ -- call ends, refused as late, rather than ask for the lock exclusive while it holds it shared (two such would
610
+ -- deadlock).
611
+ IF r.state = 'lock' AND moving THEN
612
+ RAISE EXCEPTION 'its key left the cap''s set under the principal''s lock: a row put back' USING ERRCODE = '57014';
613
+ END IF;
614
+ IF r.state = 'lock' THEN
615
+ PERFORM pg_advisory_xact_lock(1668244066, hashtext(r.c_principal));
616
+ locked := true;
617
+ ELSE
618
+ PERFORM pg_advisory_xact_lock_shared(1668244066, hashtext(r.c_principal));
619
+ moving := true;
620
+ END IF;
621
+ END LOOP;
622
+ -- 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
623
+ -- read, in the same transaction, under the same locks: the uses that read their key fresh, nearly all, run no write.
624
+ -- 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
625
+ -- made or moved on from a window past. Every request counts, whatever the Worker then decides but the cap (HotPathOneStaleBulk).
626
+ IF r.state IS NULL AND (locked OR moving) THEN
627
+ b_refresh := greatest(1, least(60000, r.cap_window / 10));
628
+ b_period := p_at - ((p_at % b_refresh) + b_refresh) % b_refresh;
629
+ IF r.in_set THEN
630
+ -- One use moves the key to this period: the one that takes a lock of the key and period's own without waiting.
631
+ -- The others take it shared, which waits only for that one's end, never for each other, and read the row again,
632
+ -- locking none: moved, they leave it. An UPDATE that waits on a row another is moving, then finds it moved,
633
+ -- keeps the row locked to its own commit, and queues the next. If the one moving it rolled back, the others
634
+ -- elect another the same way, on the lock's next round.
635
+ LOOP
636
+ b_lock := hashtextextended(r.c_principal || '/' || p_secret::text || '/' || b_period::text || '/' || b_round::text, 1668244066);
637
+ IF pg_try_advisory_xact_lock(b_lock) THEN
638
+ UPDATE public.log_bulk SET last = b_period WHERE principal = r.c_principal AND secret = p_secret AND last < b_period;
639
+ EXIT;
640
+ END IF;
641
+ PERFORM pg_advisory_xact_lock_shared(b_lock);
642
+ EXIT WHEN NOT EXISTS (SELECT 1 FROM public.log_bulk WHERE principal = r.c_principal AND secret = p_secret AND last < b_period);
643
+ b_round := b_round + 1;
644
+ END LOOP;
645
+ ELSIF r.others < r.cap_count THEN
646
+ INSERT INTO public.log_bulk (principal, secret, last) VALUES (r.c_principal, p_secret, b_period)
647
+ ON CONFLICT ON CONSTRAINT log_bulk_pkey DO UPDATE SET last = greatest(log_bulk.last, EXCLUDED.last);
648
+ END IF;
649
+ END IF;
650
+ -- 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.
651
+ IF r.state IS NULL AND (extract(epoch FROM clock_timestamp()) * 1000)::bigint > p_deadline THEN
652
+ RAISE EXCEPTION 'past its deadline' USING ERRCODE = '57014';
653
+ END IF;
654
+ RETURN to_json(r);
655
+ END
656
+ $$;
657
+ --> statement-breakpoint
658
+ REVOKE ALL ON FUNCTION use_credential(uuid, uuid, bigint, bigint, smallint, uuid, bytea, bigint, bigint, bigint, bigint, bigint, bytea, integer, bigint, bytea, text, text, text) FROM PUBLIC;
659
+ --> statement-breakpoint
660
+ GRANT EXECUTE ON FUNCTION use_credential(uuid, uuid, bigint, bigint, smallint, uuid, bytea, bigint, bigint, bigint, bigint, bigint, bytea, integer, bigint, bytea, text, text, text) TO coffre_vault;
661
+ --> statement-breakpoint
662
+ -- What the vault's ids are for what a request names (T145, PR B): the credential by its token's hash, the place by its slugs, or a handle's one place (the stable URL's), and each key named there, live, by name. Read on a miss of the vault's cache, by the hot path's login, through this function's owner, as `use_credential` reads the same rows: it grants the login no table. A fork, a personal config, or a handle of several places, it says so, and names no key: the app reads those as before.
663
+ CREATE FUNCTION use_resolve(p_token_hash bytea, p_project text, p_environment text, p_keys text[]) RETURNS json
664
+ LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = pg_catalog, pg_temp SET plan_cache_mode = force_generic_plan SET enable_seqscan = off SET jit = off AS $$
665
+ DECLARE
666
+ v_credential uuid;
667
+ v_places text;
668
+ v_place uuid;
669
+ v_project uuid;
670
+ v_environment uuid;
671
+ v_parent uuid;
672
+ v_owner text;
673
+ v_slugs json;
674
+ v_keys json;
675
+ BEGIN
676
+ SELECT c.id, c.places INTO v_credential, v_places FROM public.credentials c WHERE c.token_hash = p_token_hash;
677
+ IF v_credential IS NULL THEN RETURN json_build_object('credential', NULL); END IF;
678
+ IF p_project IS NOT NULL THEN
679
+ SELECT p.id, e.id, e.parent_id, e.owner, json_build_object('project', p.slug, 'environment', e.slug) INTO v_project, v_environment, v_parent, v_owner, v_slugs
680
+ FROM public.projects p JOIN public.environments e ON e.project_id = p.id AND e.slug = p_environment AND e.slug NOT LIKE '%~%' AND e.archived_at IS NULL
681
+ WHERE p.slug = p_project AND p.slug NOT LIKE '%~%' AND p.archived_at IS NULL;
682
+ ELSIF v_places IS NOT NULL AND json_array_length(v_places::json) = 1 AND NOT coalesce((v_places::json -> 0 ->> 'personal')::boolean, false) THEN
683
+ v_place := (v_places::json -> 0 ->> 'environmentId')::uuid;
684
+ SELECT e.project_id, e.id, e.parent_id, e.owner, json_build_object('project', p.slug, 'environment', e.slug) INTO v_project, v_environment, v_parent, v_owner, v_slugs
685
+ FROM public.environments e JOIN public.projects p ON p.id = e.project_id AND p.archived_at IS NULL
686
+ WHERE e.id = v_place AND e.archived_at IS NULL AND e.slug NOT LIKE '%~%' AND p.slug NOT LIKE '%~%';
687
+ END IF;
688
+ IF v_environment IS NOT NULL AND v_parent IS NULL AND v_owner IS NULL AND p_keys IS NOT NULL THEN
689
+ SELECT json_object_agg(k.key, s.id) INTO v_keys
690
+ FROM unnest(p_keys) k (key)
691
+ -- LIMIT 1, a key being unique in its environment: a probe by name (`app_read`, review r4 of #397).
692
+ CROSS JOIN LATERAL (
693
+ SELECT x.id FROM public.secrets x WHERE x.project_id = v_project AND x.environment_id = v_environment AND x.key = k.key AND x.archived_at IS NULL LIMIT 1
694
+ ) s;
695
+ END IF;
696
+ RETURN json_build_object(
697
+ 'credential', v_credential, 'project', v_project, 'environment', v_environment, 'slugs', v_slugs,
698
+ 'shared', v_parent IS NOT NULL OR v_owner IS NOT NULL, 'keys', coalesce(v_keys, '{}'::json)
699
+ );
700
+ END;
701
+ $$;
702
+ --> statement-breakpoint
703
+ REVOKE ALL ON FUNCTION use_resolve(bytea, text, text, text[]) FROM PUBLIC;
704
+ --> statement-breakpoint
705
+ GRANT EXECUTE ON FUNCTION use_resolve(bytea, text, text, text[]) TO coffre_vault;