@coffre/db 0.14.5 → 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 (214) 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 -622
  17. package/dist/log.js +178 -13
  18. package/dist/migrate.d.ts +43 -3
  19. package/dist/migrate.js +149 -29
  20. package/dist/migrations/postgres/0022_log_015.sql +672 -0
  21. package/dist/migrations/postgres/0023_change_path.sql +153 -0
  22. package/dist/migrations/postgres/0024_use_015.sql +556 -0
  23. package/dist/migrations/postgres/0025_app_change.sql +221 -0
  24. package/dist/migrations/postgres/0026_issue_lock.sql +106 -0
  25. package/dist/migrations/postgres/0027_retention.sql +118 -0
  26. package/dist/migrations/postgres/0028_use_stalls.sql +594 -0
  27. package/dist/migrations/postgres/0029_use_objects.sql +887 -0
  28. package/dist/migrations/postgres/0030_unarchive_restores.sql +259 -0
  29. package/dist/migrations/postgres/0031_app_read.sql +65 -0
  30. package/dist/migrations/postgres/0032_secrets_live_index.sql +4 -0
  31. package/dist/migrations/postgres/0033_use_generations.sql +582 -0
  32. package/dist/migrations/postgres/0034_vault_credentials.sql +153 -0
  33. package/dist/migrations/postgres/0035_first_days.sql +3 -0
  34. package/dist/migrations/postgres/0036_vault_issues.sql +91 -0
  35. package/dist/migrations/postgres/0037_vault_bindings.sql +271 -0
  36. package/dist/migrations/postgres/0040_binding_origin.sql +159 -0
  37. package/dist/migrations/postgres/0041_cutover.sql +132 -0
  38. package/dist/migrations/postgres/0042_sign_in_index.sql +7 -0
  39. package/dist/migrations/postgres/0043_issue_revoke_index.sql +4 -0
  40. package/dist/migrations/postgres/0044_use_by_name.sql +705 -0
  41. package/dist/migrations/postgres/0045_decide_first.sql +878 -0
  42. package/dist/migrations/postgres/0046_way_in_uses.sql +1149 -0
  43. package/dist/migrations/postgres/0047_cutover_finish.sql +268 -0
  44. package/dist/migrations/postgres/0048_use_batch.sql +59 -0
  45. package/dist/migrations/postgres/0049_reads.sql +171 -0
  46. package/dist/migrations/postgres/meta/0022_snapshot.json +3831 -0
  47. package/dist/migrations/postgres/meta/0023_snapshot.json +3831 -0
  48. package/dist/migrations/postgres/meta/0024_snapshot.json +3831 -0
  49. package/dist/migrations/postgres/meta/0025_snapshot.json +3831 -0
  50. package/dist/migrations/postgres/meta/0026_snapshot.json +3831 -0
  51. package/dist/migrations/postgres/meta/0027_snapshot.json +3853 -0
  52. package/dist/migrations/postgres/meta/0028_snapshot.json +3853 -0
  53. package/dist/migrations/postgres/meta/0029_snapshot.json +3859 -0
  54. package/dist/migrations/postgres/meta/0030_snapshot.json +3859 -0
  55. package/dist/migrations/postgres/meta/0031_snapshot.json +3859 -0
  56. package/dist/migrations/postgres/meta/0032_snapshot.json +3875 -0
  57. package/dist/migrations/postgres/meta/0033_snapshot.json +3875 -0
  58. package/dist/migrations/postgres/meta/0034_snapshot.json +3875 -0
  59. package/dist/migrations/postgres/meta/0035_snapshot.json +3875 -0
  60. package/dist/migrations/postgres/meta/0036_snapshot.json +3875 -0
  61. package/dist/migrations/postgres/meta/0037_snapshot.json +3875 -0
  62. package/dist/migrations/postgres/meta/0038_snapshot.json +3881 -0
  63. package/dist/migrations/postgres/meta/0039_snapshot.json +3881 -0
  64. package/dist/migrations/postgres/meta/0040_snapshot.json +3931 -0
  65. package/dist/migrations/postgres/meta/0041_snapshot.json +3981 -0
  66. package/dist/migrations/postgres/meta/0042_snapshot.json +3981 -0
  67. package/dist/migrations/postgres/meta/0043_snapshot.json +3981 -0
  68. package/dist/migrations/postgres/meta/0044_snapshot.json +3991 -0
  69. package/dist/migrations/postgres/meta/0045_snapshot.json +3997 -0
  70. package/dist/migrations/postgres/meta/0046_snapshot.json +3997 -0
  71. package/dist/migrations/postgres/meta/0047_snapshot.json +4234 -0
  72. package/dist/migrations/postgres/meta/_journal.json +182 -0
  73. package/dist/migrations/sqlite/0018_log_015.sql +84 -0
  74. package/dist/migrations/sqlite/0019_retention.sql +2 -0
  75. package/dist/migrations/sqlite/0020_ended_references.sql +1 -0
  76. package/dist/migrations/sqlite/0021_secrets_live_index.sql +1 -0
  77. package/dist/migrations/sqlite/0022_binding_origin.sql +1 -0
  78. package/dist/migrations/sqlite/0023_sign_in_index.sql +2 -0
  79. package/dist/migrations/sqlite/0024_issue_revoke_index.sql +2 -0
  80. package/dist/migrations/sqlite/0025_way_in_uses.sql +42 -0
  81. package/dist/migrations/sqlite/0026_cutover_finish.sql +11 -0
  82. package/dist/migrations/sqlite/0027_reads.sql +38 -0
  83. package/dist/migrations/sqlite/meta/0018_snapshot.json +3529 -0
  84. package/dist/migrations/sqlite/meta/0019_snapshot.json +3538 -0
  85. package/dist/migrations/sqlite/meta/0020_snapshot.json +3545 -0
  86. package/dist/migrations/sqlite/meta/0021_snapshot.json +3553 -0
  87. package/dist/migrations/sqlite/meta/0022_snapshot.json +3560 -0
  88. package/dist/migrations/sqlite/meta/0023_snapshot.json +3579 -0
  89. package/dist/migrations/sqlite/meta/0024_snapshot.json +3598 -0
  90. package/dist/migrations/sqlite/meta/0025_snapshot.json +3609 -0
  91. package/dist/migrations/sqlite/meta/0026_snapshot.json +3616 -0
  92. package/dist/migrations/sqlite/meta/0027_snapshot.json +3834 -0
  93. package/dist/migrations/sqlite/meta/_journal.json +70 -0
  94. package/dist/platform-Y3gICK6n.d.ts +58 -0
  95. package/dist/portable.d.ts +2 -2
  96. package/dist/postgres-BI3xTnyw.js +1718 -0
  97. package/dist/postgres-platform-node.d.ts +14 -0
  98. package/dist/postgres-platform-node.js +21 -0
  99. package/dist/postgres-platform-workerd.d.ts +61 -0
  100. package/dist/postgres-platform-workerd.js +163 -0
  101. package/dist/{schema-BfQEBqFv.d.ts → schema-DgoPqqsI.d.ts} +1326 -7
  102. package/dist/schema-sqlite.d.ts +1306 -6
  103. package/dist/schema-sqlite.js +154 -18
  104. package/dist/schema-version.d.ts +1 -1
  105. package/dist/schema-version.js +254 -2
  106. package/dist/schema.d.ts +2 -2
  107. package/dist/schema.js +226 -20
  108. package/package.json +13 -5
  109. package/src/active-subject.ts +9 -0
  110. package/src/change-records.ts +38 -0
  111. package/src/connect.ts +67 -13
  112. package/src/database.ts +67 -51
  113. package/src/dialect.ts +17 -8
  114. package/src/grants.ts +3 -3
  115. package/src/hyperdrive.ts +181 -104
  116. package/src/log.ts +196 -15
  117. package/src/migrate.ts +157 -30
  118. package/src/migrations/postgres/0022_log_015.sql +672 -0
  119. package/src/migrations/postgres/0023_change_path.sql +153 -0
  120. package/src/migrations/postgres/0024_use_015.sql +556 -0
  121. package/src/migrations/postgres/0025_app_change.sql +221 -0
  122. package/src/migrations/postgres/0026_issue_lock.sql +106 -0
  123. package/src/migrations/postgres/0027_retention.sql +118 -0
  124. package/src/migrations/postgres/0028_use_stalls.sql +594 -0
  125. package/src/migrations/postgres/0029_use_objects.sql +887 -0
  126. package/src/migrations/postgres/0030_unarchive_restores.sql +259 -0
  127. package/src/migrations/postgres/0031_app_read.sql +65 -0
  128. package/src/migrations/postgres/0032_secrets_live_index.sql +4 -0
  129. package/src/migrations/postgres/0033_use_generations.sql +582 -0
  130. package/src/migrations/postgres/0034_vault_credentials.sql +153 -0
  131. package/src/migrations/postgres/0035_first_days.sql +3 -0
  132. package/src/migrations/postgres/0036_vault_issues.sql +91 -0
  133. package/src/migrations/postgres/0037_vault_bindings.sql +271 -0
  134. package/src/migrations/postgres/0040_binding_origin.sql +159 -0
  135. package/src/migrations/postgres/0041_cutover.sql +132 -0
  136. package/src/migrations/postgres/0042_sign_in_index.sql +7 -0
  137. package/src/migrations/postgres/0043_issue_revoke_index.sql +4 -0
  138. package/src/migrations/postgres/0044_use_by_name.sql +705 -0
  139. package/src/migrations/postgres/0045_decide_first.sql +878 -0
  140. package/src/migrations/postgres/0046_way_in_uses.sql +1149 -0
  141. package/src/migrations/postgres/0047_cutover_finish.sql +268 -0
  142. package/src/migrations/postgres/0048_use_batch.sql +59 -0
  143. package/src/migrations/postgres/0049_reads.sql +171 -0
  144. package/src/migrations/postgres/meta/0022_snapshot.json +3831 -0
  145. package/src/migrations/postgres/meta/0023_snapshot.json +3831 -0
  146. package/src/migrations/postgres/meta/0024_snapshot.json +3831 -0
  147. package/src/migrations/postgres/meta/0025_snapshot.json +3831 -0
  148. package/src/migrations/postgres/meta/0026_snapshot.json +3831 -0
  149. package/src/migrations/postgres/meta/0027_snapshot.json +3853 -0
  150. package/src/migrations/postgres/meta/0028_snapshot.json +3853 -0
  151. package/src/migrations/postgres/meta/0029_snapshot.json +3859 -0
  152. package/src/migrations/postgres/meta/0030_snapshot.json +3859 -0
  153. package/src/migrations/postgres/meta/0031_snapshot.json +3859 -0
  154. package/src/migrations/postgres/meta/0032_snapshot.json +3875 -0
  155. package/src/migrations/postgres/meta/0033_snapshot.json +3875 -0
  156. package/src/migrations/postgres/meta/0034_snapshot.json +3875 -0
  157. package/src/migrations/postgres/meta/0035_snapshot.json +3875 -0
  158. package/src/migrations/postgres/meta/0036_snapshot.json +3875 -0
  159. package/src/migrations/postgres/meta/0037_snapshot.json +3875 -0
  160. package/src/migrations/postgres/meta/0038_snapshot.json +3881 -0
  161. package/src/migrations/postgres/meta/0039_snapshot.json +3881 -0
  162. package/src/migrations/postgres/meta/0040_snapshot.json +3931 -0
  163. package/src/migrations/postgres/meta/0041_snapshot.json +3981 -0
  164. package/src/migrations/postgres/meta/0042_snapshot.json +3981 -0
  165. package/src/migrations/postgres/meta/0043_snapshot.json +3981 -0
  166. package/src/migrations/postgres/meta/0044_snapshot.json +3991 -0
  167. package/src/migrations/postgres/meta/0045_snapshot.json +3997 -0
  168. package/src/migrations/postgres/meta/0046_snapshot.json +3997 -0
  169. package/src/migrations/postgres/meta/0047_snapshot.json +4234 -0
  170. package/src/migrations/postgres/meta/_journal.json +182 -0
  171. package/src/migrations/sqlite/0018_log_015.sql +84 -0
  172. package/src/migrations/sqlite/0019_retention.sql +2 -0
  173. package/src/migrations/sqlite/0020_ended_references.sql +1 -0
  174. package/src/migrations/sqlite/0021_secrets_live_index.sql +1 -0
  175. package/src/migrations/sqlite/0022_binding_origin.sql +1 -0
  176. package/src/migrations/sqlite/0023_sign_in_index.sql +2 -0
  177. package/src/migrations/sqlite/0024_issue_revoke_index.sql +2 -0
  178. package/src/migrations/sqlite/0025_way_in_uses.sql +42 -0
  179. package/src/migrations/sqlite/0026_cutover_finish.sql +11 -0
  180. package/src/migrations/sqlite/0027_reads.sql +38 -0
  181. package/src/migrations/sqlite/meta/0018_snapshot.json +3529 -0
  182. package/src/migrations/sqlite/meta/0019_snapshot.json +3538 -0
  183. package/src/migrations/sqlite/meta/0020_snapshot.json +3545 -0
  184. package/src/migrations/sqlite/meta/0021_snapshot.json +3553 -0
  185. package/src/migrations/sqlite/meta/0022_snapshot.json +3560 -0
  186. package/src/migrations/sqlite/meta/0023_snapshot.json +3579 -0
  187. package/src/migrations/sqlite/meta/0024_snapshot.json +3598 -0
  188. package/src/migrations/sqlite/meta/0025_snapshot.json +3609 -0
  189. package/src/migrations/sqlite/meta/0026_snapshot.json +3616 -0
  190. package/src/migrations/sqlite/meta/0027_snapshot.json +3834 -0
  191. package/src/migrations/sqlite/meta/_journal.json +70 -0
  192. package/src/postgres-js/README.md +183 -0
  193. package/src/postgres-js/UNLICENSE +24 -0
  194. package/src/postgres-js/bytes.ts +89 -0
  195. package/src/postgres-js/connection.ts +882 -0
  196. package/src/postgres-js/errors.ts +84 -0
  197. package/src/postgres-js/index.ts +516 -0
  198. package/src/postgres-js/platform.node.ts +29 -0
  199. package/src/postgres-js/platform.ts +60 -0
  200. package/src/postgres-js/platform.workerd.ts +225 -0
  201. package/src/postgres-js/query.ts +123 -0
  202. package/src/postgres-js/queue.ts +42 -0
  203. package/src/postgres-js/result.ts +46 -0
  204. package/src/postgres-js/types.ts +52 -0
  205. package/src/postgres.ts +375 -26
  206. package/src/schema-version.ts +2 -2
  207. package/src/schema.sqlite.ts +198 -7
  208. package/src/schema.ts +291 -6
  209. package/src/verify-runtime.ts +18 -29
  210. package/LICENSE +0 -21
  211. package/dist/database-wq1AVRAP.d.ts +0 -73
  212. package/dist/postgres-Bmar8y0q.js +0 -29
  213. package/dist/relations-BfR3JjT1.js +0 -38
  214. package/src/relations.ts +0 -40
@@ -0,0 +1,1149 @@
1
+ -- 0.15's uses through a way in with no credentials row (T147, T136; docs/design/0.15-plan.md, "As built: T147"; the spec: formal/WaysIn.tla): an MCP connection, a Cloudflare service binding, and a browser behind Cloudflare Access. Until now each such request read as 0.14's do: the vault's unwrap under 0.14's head, then the app's `credentialStill`. Now each is one `use_credential` call, as through a credential, deciding on the way in it came through (WayReadInUse): a connection's row, now a sealed object of the log whose every end is a change under the fence (`app_change`; RevokeTakesFence); a binding's, sealed already, which the vault ends under it (`change_bindings`; UnbindTakesFence); and behind Access, which has no row of coffre's, the account alone (AccountReadInUse). It only adds: a column, and functions only 0.15's code calls, behind the switch.
2
+ ALTER TABLE "mcp_connections" ADD COLUMN "seal" "bytea";--> statement-breakpoint
3
+ ALTER TABLE "mcp_connections" ADD CONSTRAINT "mcp_connections_seal_check" CHECK ("mcp_connections"."seal" IS NULL OR octet_length("mcp_connections"."seal") = 16);
4
+ --> statement-breakpoint
5
+ -- `change_input` as 0030 made it, but for an MCP connection's object (T147): the cutover's genesis seals 0.14's, its row held first as a credential's is.
6
+ CREATE OR REPLACE FUNCTION change_input(
7
+ 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
8
+ ) RETURNS bigint
9
+ LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog, pg_temp SET jit = off AS $$
10
+ DECLARE
11
+ op jsonb := p_payload::jsonb;
12
+ v jsonb := p_value::jsonb;
13
+ change text := op ->> 'op';
14
+ target uuid := public.log_object(p_kind, p_id);
15
+ n bigint;
16
+ done integer;
17
+ member text;
18
+ held bytea;
19
+ newest bytea;
20
+ config jsonb;
21
+ BEGIN
22
+ -- 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,
23
+ -- head, fence: Migration015.tla's RowFirst, ChangeHeadFirst): the member's queue (MEMBER_QUEUE, 0x636f666d) and row
24
+ -- first, a principal's own or a credential's (0.14's vault locks them before the head, lockMembers; a change made
25
+ -- inside a 0.14 decision holds them already), then 0.14's head (its app takes it before its rows), then the fence
26
+ -- (0.14's appends take it after the head, audit_log_fence). The head held, no 0.14 write lands between this change
27
+ -- and its entry.
28
+ member := CASE p_kind
29
+ WHEN 'principal' THEN p_id
30
+ WHEN 'credential' THEN coalesce(
31
+ (SELECT c.principal FROM public.credentials c WHERE c.id = p_id::uuid),
32
+ (SELECT b.principal FROM public.service_bindings b WHERE b.id = p_id::uuid))
33
+ WHEN 'connection' THEN (SELECT k.principal FROM public.mcp_connections k WHERE k.id = p_id::uuid)
34
+ END;
35
+ IF member IS NOT NULL THEN
36
+ PERFORM pg_advisory_xact_lock(1668245101, hashtext(m.principal)) FROM public.vault_members m WHERE m.principal = member FOR NO KEY UPDATE;
37
+ END IF;
38
+ PERFORM 1 FROM public.audit_chain_head WHERE only_row FOR UPDATE;
39
+ -- ChangeLocks: exclusive, so that it waits for every use in flight, and every use after it waits for its commit.
40
+ PERFORM pg_advisory_xact_lock(1668244070, 0);
41
+ SELECT c.number INTO n FROM public.log_changes c WHERE c.request_id = p_request;
42
+ IF FOUND THEN
43
+ IF NOT EXISTS (
44
+ SELECT 1 FROM public.log_changes c
45
+ WHERE c.request_id = p_request AND c.object = target AND c.payload = p_payload AND c.seal IS NOT DISTINCT FROM p_seal
46
+ ) THEN
47
+ RAISE EXCEPTION 'request % names another change', p_request USING ERRCODE = 'unique_violation';
48
+ END IF;
49
+ RETURN n;
50
+ END IF;
51
+ IF NOT (change = 'genesis'
52
+ OR (p_kind = 'principal' AND change IN ('member.add', 'member.role', 'member.remove', 'member.restore', 'access.grant', 'access.revoke'))
53
+ OR (p_kind = 'environment' AND change IN ('environment.protect', 'environment.rename', 'environment.archive'))
54
+ OR (p_kind = 'secret' AND change IN ('secret.write', 'secret.delete'))
55
+ OR (p_kind = 'roles' AND change = 'role.define')
56
+ OR (p_kind = 'policy' AND change = 'bulk.limit')) THEN
57
+ RAISE EXCEPTION 'this schema knows no change % to a %', coalesce(change, 'without an op'), p_kind USING ERRCODE = 'invalid_parameter_value';
58
+ END IF;
59
+ -- The payload names its object (`ref`), so that a replay knows what each change is to: the column holds only its
60
+ -- key. The cutover's genesis may leave it out, its value naming it.
61
+ 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
62
+ RAISE EXCEPTION 'a change names the object it is to: %, %', p_kind, p_id USING ERRCODE = 'invalid_parameter_value';
63
+ END IF;
64
+ -- A value is the object as this function writes it: an environment's, which a genesis may leave out to seal the
65
+ -- row as it is; a key's, the end of its reference (`secret.write`, the one change of a key the vault makes), and
66
+ -- none at its genesis; the roles' and the policy's, but at their genesis, whose payload holds it. The others' rows
67
+ -- are written by whoever keeps them, and carry none.
68
+ IF (CASE
69
+ WHEN p_kind = 'environment' THEN change <> 'genesis' AND v IS NULL
70
+ WHEN p_kind = 'secret' THEN (change = 'secret.write') = (v IS NULL)
71
+ WHEN p_kind IN ('roles', 'policy') THEN (change = 'genesis') = (v IS NOT NULL)
72
+ ELSE v IS NOT NULL
73
+ END) THEN
74
+ RAISE EXCEPTION 'a % change % carries the object it leaves exactly when this function writes it', p_kind, change USING ERRCODE = 'invalid_parameter_value';
75
+ END IF;
76
+ -- ChangeChecksSeal: the row's seal is its object's newest change's, or the object has neither yet (one probe, as
77
+ -- a use makes). A row deleted outright, or a seal no change wrote, is stale too.
78
+ held := CASE p_kind
79
+ WHEN 'principal' THEN (SELECT m.seal FROM public.vault_members m WHERE m.principal = p_id)
80
+ WHEN 'credential' THEN coalesce(
81
+ (SELECT c.seal FROM public.credentials c WHERE c.id = p_id::uuid),
82
+ (SELECT b.seal FROM public.service_bindings b WHERE b.id = p_id::uuid))
83
+ WHEN 'connection' THEN (SELECT k.seal FROM public.mcp_connections k WHERE k.id = p_id::uuid)
84
+ WHEN 'environment' THEN (SELECT e.seal FROM public.environments e WHERE e.id = p_id::uuid)
85
+ WHEN 'secret' THEN (SELECT x.seal FROM public.secrets x WHERE x.id = p_id::uuid)
86
+ WHEN 'policy' THEN (SELECT g.seal FROM public.log_config g WHERE g.name = 'policy')
87
+ WHEN 'roles' THEN (SELECT g.seal FROM public.log_config g WHERE g.name = 'roles')
88
+ END;
89
+ newest := (SELECT c.seal FROM public.log_changes c WHERE c.object = target ORDER BY c.number DESC LIMIT 1);
90
+ IF held IS DISTINCT FROM newest THEN
91
+ RAISE EXCEPTION 'the % object''s row is not its newest change''s: stale', p_kind USING ERRCODE = 'data_corrupted';
92
+ END IF;
93
+ -- ChangeCAS: each write moves the row only from the seal the vault read.
94
+ CASE p_kind
95
+ WHEN 'principal' THEN
96
+ UPDATE public.vault_members SET seal = p_seal WHERE principal = p_id AND seal IS NOT DISTINCT FROM p_seal_from;
97
+ WHEN 'credential' THEN
98
+ -- A token's row, or a trust binding's: one kind of object, ids that never meet.
99
+ UPDATE public.credentials SET seal = p_seal WHERE id = p_id::uuid AND seal IS NOT DISTINCT FROM p_seal_from;
100
+ GET DIAGNOSTICS done = ROW_COUNT;
101
+ IF done = 0 THEN
102
+ UPDATE public.service_bindings SET seal = p_seal WHERE id = p_id::uuid AND seal IS NOT DISTINCT FROM p_seal_from;
103
+ END IF;
104
+ WHEN 'connection' THEN
105
+ -- An MCP connection's genesis, the cutover's (T147): its row sealed as it is. The app's changes make and end the others.
106
+ UPDATE public.mcp_connections SET seal = p_seal WHERE id = p_id::uuid AND seal IS NOT DISTINCT FROM p_seal_from;
107
+ WHEN 'environment' THEN
108
+ IF v IS NULL THEN
109
+ UPDATE public.environments SET seal = p_seal WHERE id = p_id::uuid AND seal IS NOT DISTINCT FROM p_seal_from;
110
+ ELSE
111
+ -- A deleted environment's row keeps its tombstone, `dev~deleted-…` (@coffre/core/schemas's isTombstone),
112
+ -- and its object the slug before it (objects.ts, environmentObject): an environment deleted stays so,
113
+ -- its slug is never written again, and none is deleted here; an object's slug holds no tombstone.
114
+ UPDATE public.environments e
115
+ SET slug = CASE WHEN strpos(e.slug, '~') > 0 THEN e.slug ELSE v ->> 'slug' END,
116
+ protected = (v ->> 'protected')::boolean,
117
+ archived_at = CASE WHEN (v ->> 'archived')::boolean THEN coalesce(e.archived_at, to_timestamp(p_at / 1000.0)) END,
118
+ seal = p_seal
119
+ WHERE e.id = p_id::uuid AND e.seal IS NOT DISTINCT FROM p_seal_from
120
+ AND v ->> 'kind' = 'environment' AND v ->> 'id' = p_id
121
+ AND e.project_id = (v ->> 'projectId')::uuid
122
+ AND e.parent_id IS NOT DISTINCT FROM (v ->> 'parentId')::uuid
123
+ AND e.owner IS NOT DISTINCT FROM v ->> 'owner'
124
+ AND (strpos(e.slug, '~') > 0) = (v ->> 'deleted')::boolean
125
+ -- Its project's archival is the project's row's, which the app writes, and none once deleted (objects.ts): held,
126
+ -- never written here.
127
+ 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))
128
+ AND strpos(v ->> 'slug', '~') = 0
129
+ AND (NOT (v ->> 'deleted')::boolean OR split_part(e.slug, '~', 1) = v ->> 'slug');
130
+ END IF;
131
+ WHEN 'secret' THEN
132
+ IF v IS NULL THEN
133
+ UPDATE public.secrets SET seal = p_seal WHERE id = p_id::uuid AND seal IS NOT DISTINCT FROM p_seal_from;
134
+ ELSE
135
+ -- A reference's end, in the vault's decision that appends 0.14's `reference.end` (EndInRow, EndWithEntry:
136
+ -- formal/SealedObjects.tla): the holder, a key with no version of its own, left with no source. Live, from
137
+ -- its seal, its newest reference row ended. That row must stand: its seal, which the check above held to
138
+ -- the newest change, says the holder reads its source; a row ended or gone under it is not the log's.
139
+ -- Archived, from none (EndWhileArchived): the object its unarchive would restore, ended, then deleted
140
+ -- again by the next call (`secret.delete`); its row was ended by its id (`end_reference`).
141
+ UPDATE public.secrets x SET seal = p_seal
142
+ WHERE x.id = p_id::uuid AND x.seal IS NOT DISTINCT FROM p_seal_from AND (x.archived_at IS NULL) = (p_seal_from IS NOT NULL)
143
+ AND x.current_version_id IS NULL
144
+ AND v ->> 'kind' = 'secret' AND v ->> 'id' = p_id AND x.project_id = (v ->> 'projectId')::uuid AND x.environment_id = (v ->> 'environmentId')::uuid
145
+ AND v ->> 'version' = '' AND v ->> 'nature' = 'secret' AND v -> 'key' = 'null'::jsonb AND v -> 'digest' = 'null'::jsonb AND v -> 'source' = 'null'::jsonb
146
+ AND v -> 'reference' = 'null'::jsonb;
147
+ GET DIAGNOSTICS done = ROW_COUNT;
148
+ IF done > 0 AND p_seal_from IS NOT NULL THEN
149
+ UPDATE public.secret_references y SET ended_at = to_timestamp(p_at / 1000.0)
150
+ 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;
151
+ GET DIAGNOSTICS done = ROW_COUNT;
152
+ IF done = 0 THEN
153
+ RAISE EXCEPTION 'the secret object''s reference has no standing row: stale' USING ERRCODE = 'data_corrupted';
154
+ END IF;
155
+ END IF;
156
+ END IF;
157
+ WHEN 'policy', 'roles' THEN
158
+ config := CASE change WHEN 'genesis' THEN op -> 'value' ELSE v END;
159
+ IF p_id <> 'instance' OR config IS NULL OR jsonb_typeof(config) <> 'object' OR (change = 'genesis' AND p_seal_from IS NOT NULL) THEN
160
+ RAISE EXCEPTION 'the % object is one, made once from its value and changed from its seal', p_kind USING ERRCODE = 'invalid_parameter_value';
161
+ END IF;
162
+ -- Made by its first change (genesis, or the first role defined, the first cap set); moved after from its seal.
163
+ IF p_seal_from IS NULL THEN
164
+ INSERT INTO public.log_config (name, value, seal) VALUES (p_kind, config::text, p_seal) ON CONFLICT (name) DO NOTHING;
165
+ ELSE
166
+ UPDATE public.log_config SET value = config::text, seal = p_seal WHERE log_config.name = p_kind AND seal = p_seal_from;
167
+ END IF;
168
+ ELSE
169
+ RAISE EXCEPTION 'no object kind %', p_kind USING ERRCODE = 'invalid_parameter_value';
170
+ END CASE;
171
+ -- A row gone, a row whose seal is no longer the one read, an environment that is not what the value says it
172
+ -- stays, or an object made already: nothing was done.
173
+ GET DIAGNOSTICS done = ROW_COUNT;
174
+ IF done = 0 THEN RETURN NULL; END IF;
175
+ INSERT INTO public.log_changes (number, at, request_id, object, actor, payload, seal, mac)
176
+ VALUES (nextval('public.log_number'), p_at, p_request, target, p_actor, p_payload, p_seal, p_mac)
177
+ RETURNING number INTO n;
178
+ RETURN n;
179
+ END
180
+ $$;
181
+ --> statement-breakpoint
182
+ -- `app_change` as 0030 made it, but for an MCP connection's object (T147): made with its row, fresh (no use can name it yet: the issue lock only, as a credential's issue), and ended by a revoke under the fence, its seal moved from the one read (ChangeCAS), as a credential's.
183
+ CREATE OR REPLACE FUNCTION app_change(
184
+ 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, p_restores bytea
185
+ ) RETURNS bigint
186
+ LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog, pg_temp SET jit = off AS $$
187
+ DECLARE
188
+ op jsonb := p_payload::jsonb;
189
+ change text := op ->> 'op';
190
+ target uuid := public.log_object(p_kind, p_id);
191
+ n bigint;
192
+ done integer;
193
+ held bytea;
194
+ -- An issue of a credential, or a connection made, that this very transaction wrote (its row's xmin is ours: not a
195
+ -- savepoint's, nor a row another committed): no use can name it yet. Never null, whatever is missing (a 0.14 row's
196
+ -- call carries no payload, so no op): else the fence.
197
+ fresh boolean := coalesce((change = 'credential.issue' AND p_kind = 'credential'
198
+ AND EXISTS (SELECT 1 FROM public.credentials c WHERE c.id = p_id::uuid AND c.xmin = pg_current_xact_id()::xid))
199
+ OR (change = 'connection.make' AND p_kind = 'connection'
200
+ AND EXISTS (SELECT 1 FROM public.mcp_connections k WHERE k.id = p_id::uuid AND k.xmin = pg_current_xact_id()::xid)), false);
201
+ BEGIN
202
+ PERFORM 1 FROM public.audit_chain_head WHERE only_row FOR UPDATE;
203
+ -- The issue lock shared (IssueOwnLock); and, but for a fresh issue, the fence exclusive after it (IssueBeforeFence).
204
+ PERFORM pg_advisory_xact_lock_shared(1668244070, 1);
205
+ IF NOT fresh THEN PERFORM pg_advisory_xact_lock(1668244070, 0); END IF;
206
+ held := CASE p_kind
207
+ WHEN 'credential' THEN (SELECT c.seal FROM public.credentials c WHERE c.id = p_id::uuid)
208
+ WHEN 'connection' THEN (SELECT k.seal FROM public.mcp_connections k WHERE k.id = p_id::uuid)
209
+ WHEN 'secret' THEN (SELECT x.seal FROM public.secrets x WHERE x.id = p_id::uuid)
210
+ WHEN 'environment' THEN (SELECT e.seal FROM public.environments e WHERE e.id = p_id::uuid)
211
+ END;
212
+ IF p_request IS NULL THEN
213
+ IF held IS NOT NULL OR EXISTS (SELECT 1 FROM public.log_changes c WHERE c.object = target) THEN
214
+ RAISE EXCEPTION 'the % object''s row is not its newest change''s: stale', p_kind USING ERRCODE = 'data_corrupted';
215
+ END IF;
216
+ RETURN 0;
217
+ END IF;
218
+ SELECT c.number INTO n FROM public.log_changes c WHERE c.request_id = p_request;
219
+ IF FOUND THEN
220
+ IF NOT EXISTS (
221
+ SELECT 1 FROM public.log_changes c
222
+ WHERE c.request_id = p_request AND c.object = target AND c.payload = p_payload AND c.seal IS NOT DISTINCT FROM p_seal
223
+ ) THEN
224
+ RAISE EXCEPTION 'request % names another change', p_request USING ERRCODE = 'unique_violation';
225
+ END IF;
226
+ RETURN n;
227
+ END IF;
228
+ IF NOT ((p_kind = 'credential' AND change IN ('credential.issue', 'credential.revoke', 'credential.renew', 'credential.scope'))
229
+ OR (p_kind = 'connection' AND change IN ('connection.make', 'connection.revoke'))
230
+ OR (p_kind = 'secret' AND change IN ('secret.write', 'secret.delete'))
231
+ OR (p_kind = 'environment' AND change IN ('environment.create', 'environment.rename', 'environment.archive', 'project.archive', 'environment.delete'))) THEN
232
+ RAISE EXCEPTION 'the app makes no change % to a %', coalesce(change, 'without an op'), p_kind USING ERRCODE = 'invalid_parameter_value';
233
+ END IF;
234
+ IF op -> 'ref' ->> 'kind' IS DISTINCT FROM p_kind OR op -> 'ref' ->> 'id' IS DISTINCT FROM p_id THEN
235
+ RAISE EXCEPTION 'a change names the object it is to: %, %', p_kind, p_id USING ERRCODE = 'invalid_parameter_value';
236
+ END IF;
237
+ -- ChangeChecksSeal.
238
+ 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
239
+ RAISE EXCEPTION 'the % object''s row is not its newest change''s: stale', p_kind USING ERRCODE = 'data_corrupted';
240
+ END IF;
241
+ -- RestorePinned: an unarchive, a key's object made again from none, restores the one its log last sealed, still so.
242
+ IF p_restores IS NOT NULL THEN
243
+ IF p_kind <> 'secret' OR change <> 'secret.write' OR p_seal_from IS NOT NULL THEN
244
+ RAISE EXCEPTION 'only a key''s object made again from none restores one' USING ERRCODE = 'invalid_parameter_value';
245
+ END IF;
246
+ IF p_restores IS DISTINCT FROM (SELECT c.seal FROM public.log_changes c WHERE c.object = target AND c.seal IS NOT NULL ORDER BY c.number DESC LIMIT 1) THEN
247
+ RAISE EXCEPTION 'the secret object an unarchive restores is not its newest sealed one: stale' USING ERRCODE = 'data_corrupted';
248
+ END IF;
249
+ END IF;
250
+ -- ChangeCAS.
251
+ CASE p_kind
252
+ WHEN 'credential' THEN
253
+ UPDATE public.credentials SET seal = p_seal WHERE id = p_id::uuid AND seal IS NOT DISTINCT FROM p_seal_from;
254
+ WHEN 'connection' THEN
255
+ UPDATE public.mcp_connections SET seal = p_seal WHERE id = p_id::uuid AND seal IS NOT DISTINCT FROM p_seal_from;
256
+ WHEN 'secret' THEN
257
+ UPDATE public.secrets SET seal = p_seal WHERE id = p_id::uuid AND seal IS NOT DISTINCT FROM p_seal_from;
258
+ WHEN 'environment' THEN
259
+ UPDATE public.environments SET seal = p_seal WHERE id = p_id::uuid AND seal IS NOT DISTINCT FROM p_seal_from;
260
+ END CASE;
261
+ GET DIAGNOSTICS done = ROW_COUNT;
262
+ IF done = 0 THEN RETURN NULL; END IF;
263
+ INSERT INTO public.log_changes (number, at, request_id, object, actor, payload, seal, mac)
264
+ VALUES (nextval('public.log_number'), p_at, p_request, target, p_actor, p_payload, p_seal, p_mac)
265
+ RETURNING number INTO n;
266
+ RETURN n;
267
+ END
268
+ $$;
269
+ --> statement-breakpoint
270
+ -- The MCP connections the app vouched for, as the cutover reads them under its locks to hold them to that (T147): their objects' columns, their seals, and whether the log has any change of them. The vault's login reads no `mcp_connections`.
271
+ CREATE FUNCTION cutover_connections(p_ids uuid[]) RETURNS TABLE (
272
+ id uuid, principal text, generation integer, expires_at bigint, revoked boolean, seal bytea, logged boolean
273
+ )
274
+ LANGUAGE sql STABLE SECURITY DEFINER SET search_path = pg_catalog, pg_temp SET jit = off AS $$
275
+ SELECT k.id, k.principal, k.generation, floor(extract(epoch FROM k.expires_at) * 1000)::bigint, k.revoked_at IS NOT NULL, k.seal,
276
+ EXISTS (SELECT 1 FROM public.log_changes l WHERE l.object = public.log_object('connection', k.id::text))
277
+ FROM public.mcp_connections k WHERE k.id = ANY (p_ids)
278
+ $$;
279
+ --> statement-breakpoint
280
+ REVOKE ALL ON FUNCTION cutover_connections(uuid[]) FROM PUBLIC;
281
+ --> statement-breakpoint
282
+ GRANT EXECUTE ON FUNCTION cutover_connections(uuid[]) TO coffre_vault;
283
+ --> statement-breakpoint
284
+ -- A binding's row, as a use through it read it (`w_row`): its object is the whole of it, which the vault checks its seal over.
285
+ ALTER TYPE use_row ADD ATTRIBUTE w_row json;
286
+ --> statement-breakpoint
287
+ DROP FUNCTION use_credential(uuid, uuid, bigint, bigint, smallint, uuid, bytea, bigint, bigint, bigint, bigint, bigint, bytea, integer, bigint, bytea, text, text, text, boolean, boolean, text[], boolean, text[], jsonb);
288
+ --> statement-breakpoint
289
+ -- `use_credential` as 0045 made it (its comments, and those it names, say how), but for its way in (`p_way`, `p_principal`; T147; WaysIn.tla): a credential's row by its id, as before; an MCP connection's or a Cloudflare binding's, by theirs, each read as the credential it stands for (WayReadInUse); or, behind Cloudflare Access, the person alone, whose account is all the use weighs (AccountReadInUse), the entry naming their object's key. Its kind rides the purpose (512 each, under the entry's MAC), as whether a value is kept sensitive does: the row keeps its eight fixed-width columns. The credential's own path reads the same rows as 0045's: each other way's join key is null, which no index scan descends for. A named call is a credential's alone. Its warm-up's mark moves on (`coffre.use_planned`).
290
+ CREATE FUNCTION use_credential(
291
+ p_credential uuid, p_secret uuid, p_request bigint, p_at bigint, p_purpose smallint, p_place uuid, p_mac bytea, p_deadline bigint,
292
+ 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,
293
+ p_cap_count integer DEFAULT NULL, p_cap_window bigint DEFAULT NULL,
294
+ p_token_hash bytea DEFAULT NULL, p_project text DEFAULT NULL, p_environment text DEFAULT NULL, p_key text DEFAULT NULL,
295
+ p_decided boolean DEFAULT false,
296
+ p_handle boolean DEFAULT false,
297
+ p_unless text[] DEFAULT NULL,
298
+ p_signin boolean DEFAULT NULL,
299
+ p_issuers text[] DEFAULT NULL,
300
+ p_roles jsonb DEFAULT NULL,
301
+ p_way smallint DEFAULT 0,
302
+ p_principal text DEFAULT NULL
303
+ ) RETURNS json
304
+ LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog, pg_temp SET bytea_output = 'hex' SET jit = off AS $$
305
+ DECLARE
306
+ r public.use_row;
307
+ -- The principal's lock held: exclusive (`locked`), to enter its key in the cap's set; shared (`moving`), to move it on.
308
+ locked boolean := false;
309
+ moving boolean := false;
310
+ probe boolean := p_known IS NULL OR p_cap_count IS NULL OR p_cap_window IS NULL;
311
+ -- A use through a place, or of a key with no value of its own (a reference), reads every input; the others, most
312
+ -- uses, the plain shape: no place, no source, none of their joins.
313
+ whole boolean := p_place IS NOT NULL;
314
+ -- The cached forms' refresh and period, from the cap the isolate passes; the probing forms work theirs out from the cap they read.
315
+ c_refresh bigint := greatest(1, least(60000, p_cap_window / 10));
316
+ c_period bigint := p_at - ((p_at % c_refresh) + c_refresh) % c_refresh;
317
+ -- Whether this session has yet to plan the four statements (T131), and the form the warm-up is at.
318
+ dry boolean := current_setting('coffre.use_planned', true) IS DISTINCT FROM '0046';
319
+ form integer := 0;
320
+ first_probe boolean;
321
+ first_whole boolean;
322
+ -- The period this call writes, if it writes the cap's row, and the lock that elects the one use that moves it.
323
+ b_refresh bigint;
324
+ b_period bigint;
325
+ b_lock bigint;
326
+ b_round integer := 0;
327
+ -- What the call reads by: the ids passed, or, for a named call, those its names name now (D153), read once under
328
+ -- the fence; whether they are the ids passed (`v_right`), and what the names name if not a live place (`v_named`).
329
+ v_credential uuid := p_credential;
330
+ v_secret uuid := p_secret;
331
+ v_place uuid := p_place;
332
+ v_right boolean := true;
333
+ v_named text;
334
+ v_accepted boolean;
335
+ v_np uuid;
336
+ v_np_project uuid;
337
+ v_places text;
338
+ v_bound uuid;
339
+ v_live boolean;
340
+ v_project text;
341
+ v_environment text;
342
+ resolved boolean := p_token_hash IS NULL;
343
+ -- Whether a named call's caller stands at the place its names name, for its purpose (`use_stands`, W268): only then
344
+ -- does it read the key.
345
+ v_stands boolean;
346
+ BEGIN
347
+ -- The way in (T147): a credential, 0; an MCP connection, 1; a Cloudflare binding, 2; each by its id; or, behind
348
+ -- Cloudflare Access, the account, 3, a person's or a service token's, whose object's key the entry names. Its kind is in the entry, the purpose's
349
+ -- bits above 512, under its MAC; and only a credential's token is named.
350
+ IF p_way IS NULL OR p_way NOT IN (0, 1, 2, 3) OR p_purpose / 512 <> p_way OR (p_way = 3) <> (p_principal IS NOT NULL)
351
+ OR (p_way <> 0 AND p_token_hash IS NOT NULL)
352
+ OR (p_way = 3 AND (NOT (p_principal LIKE 'user:%' OR p_principal LIKE 'token:%') OR p_credential IS DISTINCT FROM public.log_object('principal', p_principal))) THEN
353
+ RAISE EXCEPTION 'a use names its way in: a credential, a connection or a binding by id, or an account behind Access by its key' USING ERRCODE = 'invalid_parameter_value';
354
+ END IF;
355
+ IF p_deadline IS NULL OR p_deadline > p_at + public.deadline_max() THEN r.state := 'late'; RETURN to_json(r); END IF;
356
+ -- Its Worker's clock ahead by over a minute (0022 says why): checked here at no cost, and again in the statement.
357
+ IF p_at > (extract(epoch FROM clock_timestamp()) * 1000)::bigint + 60000 THEN r.state := 'ahead'; RETURN to_json(r); END IF;
358
+ IF dry THEN
359
+ -- Each form first runs dry, outside the fence, so that its plan is made before this call holds the fence.
360
+ first_probe := probe;
361
+ first_whole := whole;
362
+ probe := true;
363
+ whole := false;
364
+ ELSE
365
+ PERFORM pg_advisory_xact_lock_shared(1668244070, 0);
366
+ END IF;
367
+ LOOP
368
+ IF NOT dry AND NOT resolved THEN
369
+ -- What the request names, by its names, under the fence, which no change moves (as `use_resolve` reads it):
370
+ -- the credential of its token's hash; the place of its slugs, or a handle's one place; its key there by name.
371
+ -- Each by an index, its plan generic (the hot login's `force_generic_plan`): a handle's place by its id, a
372
+ -- named one by its slugs, never one plan for both, which no index serves (review r1 of D153). The dry run
373
+ -- skips this block: these statements, the gate's among them, are planned on a session's first named call,
374
+ -- under the fence, which hangs on nothing a request names.
375
+ -- The credential first, held to what the app's config accepts (`p_signin`, `p_issuers`; review r6 and r7 of
376
+ -- D153; Probing.tla, AcceptedFirst): the deployment signs in through coffre's own sign-in, and a browser
377
+ -- session's identity names a provider it configures, as the app's own check has it (`#liveCredential`). One it
378
+ -- would refuse is answered as a token no credential has, `unknown`, here: no place, no key, no lock read or
379
+ -- taken, whatever the request names.
380
+ SELECT c.id, c.places,
381
+ coalesce(p_signin, false) AND (c.kind <> 'browser' OR (x.provider || ':' || x.issuer_hash) = ANY (coalesce(p_issuers, '{}')))
382
+ INTO v_credential, v_places, v_accepted
383
+ FROM public.credentials c LEFT JOIN public.identities x ON x.id = c.identity_id
384
+ WHERE c.token_hash = p_token_hash;
385
+ IF v_credential IS NULL OR NOT coalesce(v_accepted, false) THEN r.state := 'unknown'; RETURN to_json(r); END IF;
386
+ IF p_handle THEN
387
+ -- Unchecked yet, so parsed without raising, each step in turn (a CASE's order, which AND does not promise).
388
+ v_bound := CASE WHEN NOT coalesce(pg_input_is_valid(v_places, 'json'), false) THEN NULL
389
+ WHEN json_typeof(v_places::json) IS DISTINCT FROM 'array' OR json_array_length(v_places::json) <> 1 THEN NULL
390
+ WHEN json_typeof(v_places::json -> 0) IS DISTINCT FROM 'object' THEN NULL
391
+ WHEN coalesce(v_places::json -> 0 ->> 'personal', 'false') <> 'false' THEN NULL
392
+ WHEN NOT coalesce(pg_input_is_valid(v_places::json -> 0 ->> 'environmentId', 'uuid'), false) THEN NULL
393
+ ELSE (v_places::json -> 0 ->> 'environmentId')::uuid END;
394
+ SELECT e.id, e.project_id, q.slug, e.slug, e.archived_at IS NULL AND q.archived_at IS NULL AND e.parent_id IS NULL AND e.owner IS NULL
395
+ INTO v_np, v_np_project, v_project, v_environment, v_live
396
+ FROM public.environments e JOIN public.projects q ON q.id = e.project_id
397
+ WHERE e.id = v_bound AND e.slug NOT LIKE '%~%' AND q.slug NOT LIKE '%~%';
398
+ ELSE
399
+ SELECT e.id, e.project_id, q.slug, e.slug, e.archived_at IS NULL AND q.archived_at IS NULL AND e.parent_id IS NULL AND e.owner IS NULL
400
+ INTO v_np, v_np_project, v_project, v_environment, v_live
401
+ FROM public.projects q JOIN public.environments e ON e.project_id = q.id AND e.slug = p_environment
402
+ WHERE q.slug = p_project AND q.slug NOT LIKE '%~%' AND e.slug NOT LIKE '%~%';
403
+ END IF;
404
+ -- A handle's place the stable URL does not mean, by the slugs read here: the app's, as a place that is none.
405
+ v_named := CASE WHEN v_np IS NULL OR NOT v_live OR (p_handle AND (v_project || '/' || v_environment) = ANY (coalesce(p_unless, '{}'))) THEN 'nowhere' END;
406
+ -- The caller's standing at the place, for the request's purpose, from rows no key decides (W268; Probing.tla,
407
+ -- StandingFirst): its key's name is read only for one who stands there. Without the roles coffre ships, which
408
+ -- every named call of the vault's passes, it stands: never refused here where the vault's rule allows.
409
+ v_stands := CASE WHEN v_named IS NOT NULL THEN false WHEN p_roles IS NULL THEN true
410
+ ELSE public.use_stands(v_credential, v_np, p_purpose, p_at, p_roles) END;
411
+ IF p_key IS NOT NULL AND v_stands THEN
412
+ SELECT x.id INTO v_secret FROM public.secrets x
413
+ WHERE x.project_id = v_np_project AND x.environment_id = v_np AND x.key = p_key AND x.archived_at IS NULL LIMIT 1;
414
+ ELSE
415
+ v_secret := NULL;
416
+ END IF;
417
+ -- A key named is read by its id; one the place does not hold, through the place (the whole form, e1).
418
+ v_place := CASE WHEN v_secret IS NULL THEN v_np END;
419
+ v_right := coalesce(v_credential = p_credential, false) AND v_secret IS NOT DISTINCT FROM p_secret AND v_place IS NOT DISTINCT FROM p_place;
420
+ IF NOT v_stands THEN
421
+ -- No standing, or no live place: answered on the place alone, as for a key it does not hold, probing,
422
+ -- whatever the isolate passed: no cover, no seals (NoKeyBeforeStanding). Nothing is written or locked,
423
+ -- as for any key the place does not hold.
424
+ p_cover := NULL;
425
+ probe := true;
426
+ END IF;
427
+ whole := v_place IS NOT NULL;
428
+ resolved := true;
429
+ END IF;
430
+ IF probe AND NOT whole THEN
431
+ WITH i AS MATERIALIZED (
432
+ SELECT n.number AS newest, n.mac AS newest_mac, coalesce(n.number = p_known AND n.mac = p_known_mac, false) AS cached,
433
+ f.at_floor, (lpol.payload::jsonb #>> '{value,bulk,count}')::integer AS cap_count, (lpol.payload::jsonb #>> '{value,bulk,windowMs}')::bigint AS cap_window,
434
+ lpol.number AS lpol_number, lpol.seal AS p_last,
435
+ w.principal AS p, c.kind AS c_kind, w.scope AS c_scope, w.revoked_at IS NOT NULL AS c_revoked,
436
+ floor(extract(epoch FROM w.expires_at) * 1000)::bigint AS c_expires, w.seal AS c_seal,
437
+ 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, w.generation AS c_generation, m.generation AS m_generation,
438
+ w.row AS w_row,
439
+ -- What the request named, for the names it checks, and the rows the app checks, when it names them (0044).
440
+ c.token_hash AS c_token_hash,
441
+ 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,
442
+ CASE WHEN p_token_hash IS NOT NULL AND s.current_version_id IS NOT NULL THEN to_json(sv) END AS sv_row,
443
+ cfg.r_value, cfg.r_seal, cfg.p_value, cfg.p_seal,
444
+ 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,
445
+ 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,
446
+ 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,
447
+ 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,
448
+ 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,
449
+ 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,
450
+ 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,
451
+ b.last AS bulk_last,
452
+ -- 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).
453
+ public.log_object(w.kind, v_credential::text) AS k_credential,
454
+ public.log_object('principal', w.principal) AS k_member,
455
+ public.log_object('secret', v_secret::text) AS k_secret,
456
+ public.log_object('environment', s.environment_id::text) AS k_e2,
457
+ public.log_object('environment', (CASE WHEN e2.owner IS NOT NULL THEN e2.parent_id END)::text) AS k_e4
458
+ FROM (SELECT 1) one
459
+ LEFT JOIN LATERAL (SELECT x.number, x.mac FROM public.log_changes x ORDER BY x.number DESC LIMIT 1) n ON true
460
+ LEFT JOIN public.log_fence f ON f.only_row
461
+ -- The roles and the policy, both rows in one read of their one-page table.
462
+ LEFT JOIN LATERAL (
463
+ 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,
464
+ max(g.value) FILTER (WHERE g.name = 'policy') AS p_value, (array_agg(g.seal) FILTER (WHERE g.name = 'policy'))[1] AS p_seal
465
+ FROM public.log_config g
466
+ ) cfg ON true
467
+ -- The cap as the policy's newest change says it (CapSealed): every policy change logs the whole policy as its `value`.
468
+ LEFT JOIN LATERAL (
469
+ 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
470
+ ) lpol ON true
471
+ -- The way in (T147), one of these by its id; or, behind Access, none: the account alone, whose row is all the
472
+ -- use weighs of it. The way rides the join key: the others' is null, which no index scan descends for. A
473
+ -- `p_way = 1` beside the key is no one-time filter of an outer join's inner side, and its probe ran (review r2).
474
+ LEFT JOIN public.credentials c ON c.id = CASE WHEN p_way = 0 THEN v_credential END
475
+ LEFT JOIN public.mcp_connections k ON k.id = CASE WHEN p_way = 1 THEN v_credential END
476
+ LEFT JOIN public.service_bindings wb ON wb.id = CASE WHEN p_way = 2 THEN v_credential END
477
+ CROSS JOIN LATERAL (
478
+ SELECT CASE p_way WHEN 0 THEN 'credential' WHEN 1 THEN 'connection' WHEN 2 THEN 'binding' END AS kind,
479
+ CASE p_way WHEN 0 THEN c.principal WHEN 1 THEN k.principal WHEN 2 THEN wb.principal WHEN 3 THEN p_principal END AS principal,
480
+ CASE p_way WHEN 0 THEN c.scope WHEN 2 THEN wb.scope END AS scope,
481
+ CASE p_way WHEN 0 THEN c.revoked_at WHEN 1 THEN k.revoked_at WHEN 2 THEN wb.revoked_at END AS revoked_at,
482
+ CASE p_way WHEN 0 THEN least(c.expires_at, c.not_after) WHEN 1 THEN k.expires_at END AS expires_at,
483
+ CASE p_way WHEN 0 THEN c.seal WHEN 1 THEN k.seal WHEN 2 THEN wb.seal END AS seal,
484
+ CASE p_way WHEN 0 THEN c.generation WHEN 1 THEN k.generation WHEN 2 THEN wb.generation END AS generation,
485
+ -- A binding's object is its whole row (objects.ts, bindingObject), its seal checked over it.
486
+ CASE WHEN p_way = 2 AND wb.id IS NOT NULL THEN to_json(wb) END AS row
487
+ ) w
488
+ LEFT JOIN public.vault_members m ON m.principal = w.principal
489
+ -- An archived key is gone (`secret.delete`): no object.
490
+ LEFT JOIN public.secrets s ON s.id = v_secret AND s.archived_at IS NULL
491
+ -- The key's own version, its own environment, and that environment's parent when it is a personal config.
492
+ LEFT JOIN public.secret_versions sv ON sv.id = s.current_version_id
493
+ LEFT JOIN public.environments e2 ON e2.id = s.environment_id
494
+ LEFT JOIN public.environments e4 ON e4.id = e2.parent_id AND e2.owner IS NOT NULL
495
+ LEFT JOIN public.log_bulk b ON b.principal = w.principal AND b.secret = v_secret
496
+ WHERE NOT dry
497
+ ),
498
+ v AS MATERIALIZED (
499
+ SELECT i.*, cap.refresh, cap.period,
500
+ CASE WHEN i.s_project IS NOT NULL AND i.s_version IS NULL THEN 'whole'
501
+ -- What no key decides comes first (T158; Probing.tla, AdmissionBeforeKey; review d3 of #416): the
502
+ -- floor, the deadline, the clock, the cap's config; then what the names decide, which the vault
503
+ -- answers on the caller's standing first.
504
+ WHEN i.at_floor IS NULL OR p_at < i.at_floor OR (extract(epoch FROM clock_timestamp()) * 1000)::bigint > p_deadline THEN 'late'
505
+ WHEN p_at > (extract(epoch FROM clock_timestamp()) * 1000)::bigint + 60000 THEN 'ahead'
506
+ WHEN i.cap_count IS NULL OR i.cap_window IS NULL THEN 'unconfigured'
507
+ -- What the names name, read by them: no credential of that token, no live place of those names.
508
+ WHEN v_named IS NOT NULL THEN v_named
509
+ -- The ids the vault passed are not the ones the names name now (D153): the names' rows are read,
510
+ -- and nothing is written or locked.
511
+ WHEN NOT v_right THEN 'moved'
512
+ 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)
513
+ OR coalesce(i.e1_project_archived, false) OR coalesce(i.e2_project_archived, false) OR coalesce(i.e3_project_archived, false)) THEN 'archived'
514
+ 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'
515
+ 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,
516
+ greatest(lc.number, lm.number, lr.number, i.lpol_number, ls.number, l2.number, l4.number) AS inputs,
517
+ 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,
518
+ NULL::bytea AS e1_last, l2.seal AS e2_last, NULL::bytea AS e3_last, l4.seal AS e4_last,
519
+ coalesce(i.bulk_last > p_at - i.cap_window - cap.refresh, false) AS is_in,
520
+ -- The other keys in the set, up to the cap: read only under the principal's lock, for a key not in it.
521
+ CASE WHEN locked AND i.p IS NOT NULL AND NOT coalesce(i.bulk_last > p_at - i.cap_window - cap.refresh, false) THEN (
522
+ SELECT count(*)::integer FROM (
523
+ SELECT 1 FROM public.log_bulk o
524
+ WHERE o.principal = i.p AND o.secret <> v_secret AND o.last > p_at - i.cap_window - cap.refresh - public.deadline_max() - 60000
525
+ LIMIT i.cap_count
526
+ ) z
527
+ ) ELSE 0 END AS n_others,
528
+ -- A repeat rides its cover only while every part of this holds: never on a key whose bulk row is missing (CoverFresh).
529
+ 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)
530
+ 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
531
+ 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
532
+ FROM i
533
+ CROSS JOIN LATERAL (SELECT greatest(1, least(60000, i.cap_window / 10)) AS refresh) r0
534
+ CROSS JOIN LATERAL (SELECT r0.refresh, p_at - ((p_at % r0.refresh) + r0.refresh) % r0.refresh AS period) cap
535
+ -- Each input's newest change: one index-only probe each (log_changes_object_idx holds the seal), the principal's
536
+ -- with its payload, which names the 0.14 entry it accounts for.
537
+ 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
538
+ 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
539
+ 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
540
+ 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
541
+ 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
542
+ 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
543
+ ),
544
+ w AS (
545
+ INSERT INTO public.log_uses (number, at, request_id, credential, secret, purpose, mac, place)
546
+ SELECT nextval('public.log_number'), p_at, p_request, p_credential, p_secret, p_purpose, p_mac, p_place
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, v.inputs, true,
551
+ v.p, v.c_kind, v.c_scope, v.c_revoked, v.c_expires, v.c_seal, v.c_last,
552
+ v.m_status, v.m_owner, v.m_role, v.m_scope, v.m_seal, v.m_last, v.m_old,
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, v.r_last,
557
+ v.p_value, v.p_seal, v.p_last, v.cap_count, v.cap_window,
558
+ v.s_project, v.s_environment, v.s_version, v.s_source, v.s_seal, v.s_last,
559
+ v.x_project, v.x_environment, v.x_version, v.x_source, v.x_seal, v.x_last,
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, v.e1_last,
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, v.e2_last,
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, v.e3_last,
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, v.e4_last,
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
+ -- A named call's (`n_*`), set below; a binding's row.
573
+ NULL::uuid, NULL::uuid, NULL::uuid, NULL::text, NULL::text, NULL::boolean, v.w_row
574
+ INTO r
575
+ FROM v
576
+ -- 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`.
577
+ LEFT JOIN LATERAL (
578
+ SELECT array_agg(coalesce(g.project_id, ge.project_id)) AS g_project, array_agg(g.environment_id) AS g_environment,
579
+ array_agg(g.environment_slug) AS g_slug, array_agg(g.role) AS g_role, array_agg(g.expires_at) AS g_expires
580
+ FROM public.vault_grants g LEFT JOIN public.environments ge ON ge.id = g.environment_id
581
+ WHERE g.principal = v.p
582
+ ) gr ON v.m_status IS NOT NULL;
583
+ ELSIF probe THEN
584
+ WITH i AS MATERIALIZED (
585
+ SELECT n.number AS newest, n.mac AS newest_mac, coalesce(n.number = p_known AND n.mac = p_known_mac, false) AS cached,
586
+ f.at_floor, (lpol.payload::jsonb #>> '{value,bulk,count}')::integer AS cap_count, (lpol.payload::jsonb #>> '{value,bulk,windowMs}')::bigint AS cap_window,
587
+ lpol.number AS lpol_number, lpol.seal AS p_last,
588
+ w.principal AS p, c.kind AS c_kind, w.scope AS c_scope, w.revoked_at IS NOT NULL AS c_revoked,
589
+ floor(extract(epoch FROM w.expires_at) * 1000)::bigint AS c_expires, w.seal AS c_seal,
590
+ 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, w.generation AS c_generation, m.generation AS m_generation,
591
+ w.row AS w_row,
592
+ -- What the request named, for the names it checks, and the rows the app checks, when it names them (0044).
593
+ c.token_hash AS c_token_hash,
594
+ 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,
595
+ CASE WHEN p_token_hash IS NOT NULL AND s.current_version_id IS NOT NULL THEN to_json(sv) END AS sv_row,
596
+ cfg.r_value, cfg.r_seal, cfg.p_value, cfg.p_seal,
597
+ 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,
598
+ 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,
599
+ 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,
600
+ 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,
601
+ 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,
602
+ 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,
603
+ 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,
604
+ b.last AS bulk_last,
605
+ -- 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).
606
+ public.log_object(w.kind, v_credential::text) AS k_credential,
607
+ public.log_object('principal', w.principal) AS k_member,
608
+ public.log_object('secret', v_secret::text) AS k_secret,
609
+ public.log_object('secret', ref.source_secret_id::text) AS k_source,
610
+ public.log_object('environment', ids.e1::text) AS k_e1,
611
+ public.log_object('environment', s.environment_id::text) AS k_e2,
612
+ public.log_object('environment', ids.e3::text) AS k_e3,
613
+ public.log_object('environment', (CASE WHEN ve.owner IS NOT NULL THEN ve.parent END)::text) AS k_e4
614
+ FROM (SELECT 1) one
615
+ LEFT JOIN LATERAL (SELECT x.number, x.mac FROM public.log_changes x ORDER BY x.number DESC LIMIT 1) n ON true
616
+ LEFT JOIN public.log_fence f ON f.only_row
617
+ -- The roles and the policy, both rows in one read of their one-page table.
618
+ LEFT JOIN LATERAL (
619
+ 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,
620
+ max(g.value) FILTER (WHERE g.name = 'policy') AS p_value, (array_agg(g.seal) FILTER (WHERE g.name = 'policy'))[1] AS p_seal
621
+ FROM public.log_config g
622
+ ) cfg ON true
623
+ -- The cap as the policy's newest change says it (CapSealed): every policy change logs the whole policy as its `value`.
624
+ LEFT JOIN LATERAL (
625
+ 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
626
+ ) lpol ON true
627
+ -- The way in (T147), one of these by its id; or, behind Access, none: the account alone, whose row is all the
628
+ -- use weighs of it. The way rides the join key: the others' is null, which no index scan descends for. A
629
+ -- `p_way = 1` beside the key is no one-time filter of an outer join's inner side, and its probe ran (review r2).
630
+ LEFT JOIN public.credentials c ON c.id = CASE WHEN p_way = 0 THEN v_credential END
631
+ LEFT JOIN public.mcp_connections k ON k.id = CASE WHEN p_way = 1 THEN v_credential END
632
+ LEFT JOIN public.service_bindings wb ON wb.id = CASE WHEN p_way = 2 THEN v_credential END
633
+ CROSS JOIN LATERAL (
634
+ SELECT CASE p_way WHEN 0 THEN 'credential' WHEN 1 THEN 'connection' WHEN 2 THEN 'binding' END AS kind,
635
+ CASE p_way WHEN 0 THEN c.principal WHEN 1 THEN k.principal WHEN 2 THEN wb.principal WHEN 3 THEN p_principal END AS principal,
636
+ CASE p_way WHEN 0 THEN c.scope WHEN 2 THEN wb.scope END AS scope,
637
+ CASE p_way WHEN 0 THEN c.revoked_at WHEN 1 THEN k.revoked_at WHEN 2 THEN wb.revoked_at END AS revoked_at,
638
+ CASE p_way WHEN 0 THEN least(c.expires_at, c.not_after) WHEN 1 THEN k.expires_at END AS expires_at,
639
+ CASE p_way WHEN 0 THEN c.seal WHEN 1 THEN k.seal WHEN 2 THEN wb.seal END AS seal,
640
+ CASE p_way WHEN 0 THEN c.generation WHEN 1 THEN k.generation WHEN 2 THEN wb.generation END AS generation,
641
+ -- A binding's object is its whole row (objects.ts, bindingObject), its seal checked over it.
642
+ CASE WHEN p_way = 2 AND wb.id IS NOT NULL THEN to_json(wb) END AS row
643
+ ) w
644
+ LEFT JOIN public.vault_members m ON m.principal = w.principal
645
+ -- An archived key is gone (`secret.delete`): no object.
646
+ LEFT JOIN public.secrets s ON s.id = v_secret AND s.archived_at IS NULL
647
+ -- 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`).
648
+ LEFT JOIN LATERAL (
649
+ 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
650
+ ) ref ON s.current_version_id IS NULL
651
+ LEFT JOIN public.secrets src ON src.id = ref.source_secret_id AND src.archived_at IS NULL
652
+ LEFT JOIN LATERAL (
653
+ 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
654
+ ) xref ON src.current_version_id IS NULL
655
+ -- The value's version: the key's own, or its source's.
656
+ 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
657
+ -- The environments `useEnvironments` names, each by the id the rows name, whether or not its row is there: e1 the
658
+ -- place read through, e2 the key's own, e3 its value's (a reference's source's) where that is another, and e4
659
+ -- the parent of its value's when that is a personal config.
660
+ LEFT JOIN LATERAL (
661
+ SELECT CASE WHEN v_place IS DISTINCT FROM s.environment_id THEN v_place END AS e1,
662
+ CASE WHEN src.environment_id IS DISTINCT FROM s.environment_id THEN src.environment_id END AS e3
663
+ ) ids ON true
664
+ LEFT JOIN public.environments e1 ON e1.id = ids.e1
665
+ LEFT JOIN public.environments e2 ON e2.id = s.environment_id
666
+ LEFT JOIN public.environments e3 ON e3.id = ids.e3
667
+ -- The value's environment's row: the key's own, unless it is a reference, whose source is elsewhere or gone.
668
+ LEFT JOIN LATERAL (
669
+ 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,
670
+ 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
671
+ ) ve ON true
672
+ LEFT JOIN public.environments e4 ON e4.id = ve.parent AND ve.owner IS NOT NULL
673
+ LEFT JOIN public.log_bulk b ON b.principal = w.principal AND b.secret = v_secret
674
+ WHERE NOT dry
675
+ ),
676
+ v AS MATERIALIZED (
677
+ SELECT i.*, cap.refresh, cap.period,
678
+ -- What no key decides comes first (T158; Probing.tla, AdmissionBeforeKey; review d3 of #416): the
679
+ -- floor, the deadline, the clock, the cap's config; then what the names decide, which the vault
680
+ -- answers on the caller's standing first.
681
+ 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'
682
+ WHEN p_at > (extract(epoch FROM clock_timestamp()) * 1000)::bigint + 60000 THEN 'ahead'
683
+ WHEN i.cap_count IS NULL OR i.cap_window IS NULL THEN 'unconfigured'
684
+ -- What the names name, read by them: no credential of that token, no live place of those names.
685
+ WHEN v_named IS NOT NULL THEN v_named
686
+ -- The ids the vault passed are not the ones the names name now (D153): the names' rows are read,
687
+ -- and nothing is written or locked.
688
+ WHEN NOT v_right THEN 'moved'
689
+ 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)
690
+ OR coalesce(i.e1_project_archived, false) OR coalesce(i.e2_project_archived, false) OR coalesce(i.e3_project_archived, false)) THEN 'archived'
691
+ 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'
692
+ 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,
693
+ greatest(lc.number, lm.number, lr.number, i.lpol_number, ls.number, lx.number, l1.number, l2.number, l3.number, l4.number) AS inputs,
694
+ 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,
695
+ l1.seal AS e1_last, l2.seal AS e2_last, l3.seal AS e3_last, l4.seal AS e4_last,
696
+ coalesce(i.bulk_last > p_at - i.cap_window - cap.refresh, false) AS is_in,
697
+ -- The other keys in the set, up to the cap: read only under the principal's lock, for a key not in it.
698
+ CASE WHEN locked AND i.p IS NOT NULL AND NOT coalesce(i.bulk_last > p_at - i.cap_window - cap.refresh, false) THEN (
699
+ SELECT count(*)::integer FROM (
700
+ SELECT 1 FROM public.log_bulk o
701
+ WHERE o.principal = i.p AND o.secret <> v_secret AND o.last > p_at - i.cap_window - cap.refresh - public.deadline_max() - 60000
702
+ LIMIT i.cap_count
703
+ ) z
704
+ ) ELSE 0 END AS n_others,
705
+ -- A repeat rides its cover only while every part of this holds: never on a key whose bulk row is missing (CoverFresh).
706
+ 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)
707
+ 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
708
+ 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
709
+ FROM i
710
+ CROSS JOIN LATERAL (SELECT greatest(1, least(60000, i.cap_window / 10)) AS refresh) r0
711
+ CROSS JOIN LATERAL (SELECT r0.refresh, p_at - ((p_at % r0.refresh) + r0.refresh) % r0.refresh AS period) cap
712
+ -- Each input's newest change: one index-only probe each (log_changes_object_idx holds the seal), the principal's
713
+ -- with its payload, which names the 0.14 entry it accounts for.
714
+ 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
715
+ 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
716
+ 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
717
+ 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
718
+ 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
719
+ 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
720
+ 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
721
+ 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
722
+ 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
723
+ ),
724
+ w AS (
725
+ INSERT INTO public.log_uses (number, at, request_id, credential, secret, purpose, mac, place)
726
+ SELECT nextval('public.log_number'), p_at, p_request, p_credential, p_secret, p_purpose, p_mac, p_place
727
+ 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)))
728
+ RETURNING log_uses.number
729
+ )
730
+ 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,
731
+ v.p, v.c_kind, v.c_scope, v.c_revoked, v.c_expires, v.c_seal, v.c_last,
732
+ v.m_status, v.m_owner, v.m_role, v.m_scope, v.m_seal, v.m_last, v.m_old,
733
+ (SELECT a.seq FROM public.audit_log a WHERE a.author = 'vault' AND a.subject_principal = v.p AND a.decision = 'allow'
734
+ 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),
735
+ gr.g_project, gr.g_environment, gr.g_slug, gr.g_role, gr.g_expires,
736
+ v.r_value, v.r_seal, v.r_last,
737
+ v.p_value, v.p_seal, v.p_last, v.cap_count, v.cap_window,
738
+ v.s_project, v.s_environment, v.s_version, v.s_source, v.s_seal, v.s_last,
739
+ v.x_project, v.x_environment, v.x_version, v.x_source, v.x_seal, v.x_last,
740
+ 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,
741
+ 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,
742
+ 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,
743
+ 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,
744
+ 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,
745
+ 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,
746
+ CASE WHEN p_token_hash IS NOT NULL THEN json_build_object(
747
+ '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),
748
+ '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),
749
+ 'project', (SELECT to_json(q) FROM public.projects q WHERE q.id = coalesce(v.e1_project, v.e2_project)),
750
+ 'environment', (SELECT to_json(e) FROM public.environments e WHERE e.id = coalesce(v.e1_id, v.e2_id)), 'parent', NULL,
751
+ '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,
752
+ -- A named call's (`n_*`), set below; a binding's row.
753
+ NULL::uuid, NULL::uuid, NULL::uuid, NULL::text, NULL::text, NULL::boolean, v.w_row
754
+ INTO r
755
+ FROM v
756
+ -- 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`.
757
+ LEFT JOIN LATERAL (
758
+ SELECT array_agg(coalesce(g.project_id, ge.project_id)) AS g_project, array_agg(g.environment_id) AS g_environment,
759
+ array_agg(g.environment_slug) AS g_slug, array_agg(g.role) AS g_role, array_agg(g.expires_at) AS g_expires
760
+ FROM public.vault_grants g LEFT JOIN public.environments ge ON ge.id = g.environment_id
761
+ WHERE g.principal = v.p
762
+ ) gr ON v.m_status IS NOT NULL;
763
+ ELSIF NOT whole THEN
764
+ -- The isolate's seals, and its cap, read at the change it names: no probe, only whether a change came since
765
+ -- (then nothing is written: 'reprobe').
766
+ WITH i AS MATERIALIZED (
767
+ SELECT n.number AS newest, n.mac AS newest_mac, coalesce(n.number = p_known AND n.mac = p_known_mac, false) AS cached,
768
+ f.at_floor,
769
+ w.principal AS p, c.kind AS c_kind, w.scope AS c_scope, w.revoked_at IS NOT NULL AS c_revoked,
770
+ floor(extract(epoch FROM w.expires_at) * 1000)::bigint AS c_expires, w.seal AS c_seal,
771
+ 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, w.generation AS c_generation, m.generation AS m_generation,
772
+ w.row AS w_row,
773
+ -- What the request named, for the names it checks, and the rows the app checks, when it names them (0044).
774
+ c.token_hash AS c_token_hash,
775
+ 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,
776
+ CASE WHEN p_token_hash IS NOT NULL AND s.current_version_id IS NOT NULL THEN to_json(sv) END AS sv_row,
777
+ cfg.r_value, cfg.r_seal, cfg.p_value, cfg.p_seal,
778
+ 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,
779
+ 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,
780
+ 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,
781
+ 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,
782
+ 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,
783
+ 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,
784
+ 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,
785
+ b.last AS bulk_last
786
+ FROM (SELECT 1) one
787
+ LEFT JOIN LATERAL (SELECT x.number, x.mac FROM public.log_changes x ORDER BY x.number DESC LIMIT 1) n ON true
788
+ LEFT JOIN public.log_fence f ON f.only_row
789
+ -- The roles and the policy, both rows in one read of their one-page table.
790
+ LEFT JOIN LATERAL (
791
+ 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,
792
+ max(g.value) FILTER (WHERE g.name = 'policy') AS p_value, (array_agg(g.seal) FILTER (WHERE g.name = 'policy'))[1] AS p_seal
793
+ FROM public.log_config g
794
+ ) cfg ON true
795
+ -- The way in (T147), one of these by its id; or, behind Access, none: the account alone, whose row is all the
796
+ -- use weighs of it. The way rides the join key: the others' is null, which no index scan descends for. A
797
+ -- `p_way = 1` beside the key is no one-time filter of an outer join's inner side, and its probe ran (review r2).
798
+ LEFT JOIN public.credentials c ON c.id = CASE WHEN p_way = 0 THEN v_credential END
799
+ LEFT JOIN public.mcp_connections k ON k.id = CASE WHEN p_way = 1 THEN v_credential END
800
+ LEFT JOIN public.service_bindings wb ON wb.id = CASE WHEN p_way = 2 THEN v_credential END
801
+ CROSS JOIN LATERAL (
802
+ SELECT CASE p_way WHEN 0 THEN 'credential' WHEN 1 THEN 'connection' WHEN 2 THEN 'binding' END AS kind,
803
+ CASE p_way WHEN 0 THEN c.principal WHEN 1 THEN k.principal WHEN 2 THEN wb.principal WHEN 3 THEN p_principal END AS principal,
804
+ CASE p_way WHEN 0 THEN c.scope WHEN 2 THEN wb.scope END AS scope,
805
+ CASE p_way WHEN 0 THEN c.revoked_at WHEN 1 THEN k.revoked_at WHEN 2 THEN wb.revoked_at END AS revoked_at,
806
+ CASE p_way WHEN 0 THEN least(c.expires_at, c.not_after) WHEN 1 THEN k.expires_at END AS expires_at,
807
+ CASE p_way WHEN 0 THEN c.seal WHEN 1 THEN k.seal WHEN 2 THEN wb.seal END AS seal,
808
+ CASE p_way WHEN 0 THEN c.generation WHEN 1 THEN k.generation WHEN 2 THEN wb.generation END AS generation,
809
+ -- A binding's object is its whole row (objects.ts, bindingObject), its seal checked over it.
810
+ CASE WHEN p_way = 2 AND wb.id IS NOT NULL THEN to_json(wb) END AS row
811
+ ) w
812
+ LEFT JOIN public.vault_members m ON m.principal = w.principal
813
+ -- An archived key is gone (`secret.delete`): no object.
814
+ LEFT JOIN public.secrets s ON s.id = v_secret AND s.archived_at IS NULL
815
+ -- The key's own version, its own environment, and that environment's parent when it is a personal config.
816
+ LEFT JOIN public.secret_versions sv ON sv.id = s.current_version_id
817
+ LEFT JOIN public.environments e2 ON e2.id = s.environment_id
818
+ LEFT JOIN public.environments e4 ON e4.id = e2.parent_id AND e2.owner IS NOT NULL
819
+ LEFT JOIN public.log_bulk b ON b.principal = w.principal AND b.secret = v_secret
820
+ WHERE NOT dry
821
+ ),
822
+ v AS MATERIALIZED (
823
+ SELECT i.*, c_refresh AS refresh, c_period AS period,
824
+ CASE WHEN NOT i.cached THEN 'reprobe'
825
+ WHEN i.s_project IS NOT NULL AND i.s_version IS NULL THEN 'whole'
826
+ -- What no key decides comes first (T158; Probing.tla, AdmissionBeforeKey; review d3 of #416): the
827
+ -- floor, the deadline, the clock, the cap's config; then what the names decide, which the vault
828
+ -- answers on the caller's standing first.
829
+ WHEN i.at_floor IS NULL OR p_at < i.at_floor OR (extract(epoch FROM clock_timestamp()) * 1000)::bigint > p_deadline THEN 'late'
830
+ WHEN p_at > (extract(epoch FROM clock_timestamp()) * 1000)::bigint + 60000 THEN 'ahead'
831
+ -- What the names name, read by them: no credential of that token, no live place of those names.
832
+ WHEN v_named IS NOT NULL THEN v_named
833
+ -- The ids the vault passed are not the ones the names name now (D153): the names' rows are read,
834
+ -- and nothing is written or locked.
835
+ WHEN NOT v_right THEN 'moved'
836
+ 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)
837
+ OR coalesce(i.e1_project_archived, false) OR coalesce(i.e2_project_archived, false) OR coalesce(i.e3_project_archived, false)) THEN 'archived'
838
+ 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'
839
+ 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,
840
+ coalesce(i.bulk_last > p_at - p_cap_window - c_refresh, false) AS is_in,
841
+ -- The other keys in the set, up to the cap: read only under the principal's lock, for a key not in it.
842
+ CASE WHEN locked AND i.p IS NOT NULL AND NOT coalesce(i.bulk_last > p_at - p_cap_window - c_refresh, false) THEN (
843
+ SELECT count(*)::integer FROM (
844
+ SELECT 1 FROM public.log_bulk o
845
+ WHERE o.principal = i.p AND o.secret <> v_secret AND o.last > p_at - p_cap_window - c_refresh - public.deadline_max() - 60000
846
+ LIMIT p_cap_count
847
+ ) z
848
+ ) ELSE 0 END AS n_others,
849
+ -- A repeat rides its cover only while every part of this holds: never on a key whose bulk row is missing (CoverFresh).
850
+ coalesce(p_cover IS NOT NULL AND i.bulk_last >= c_period
851
+ 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
852
+ 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
853
+ FROM i
854
+ ),
855
+ w AS (
856
+ INSERT INTO public.log_uses (number, at, request_id, credential, secret, purpose, mac, place)
857
+ SELECT nextval('public.log_number'), p_at, p_request, p_credential, p_secret, p_purpose, p_mac, p_place
858
+ -- The isolate held its cover to the cached seals' numbers when it read them.
859
+ 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)))
860
+ RETURNING log_uses.number
861
+ )
862
+ 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,
863
+ v.p, v.c_kind, v.c_scope, v.c_revoked, v.c_expires, v.c_seal, NULL::bytea,
864
+ v.m_status, v.m_owner, v.m_role, v.m_scope, v.m_seal, NULL::bytea, NULL::bigint,
865
+ (SELECT a.seq FROM public.audit_log a WHERE a.author = 'vault' AND a.subject_principal = v.p AND a.decision = 'allow'
866
+ 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),
867
+ gr.g_project, gr.g_environment, gr.g_slug, gr.g_role, gr.g_expires,
868
+ v.r_value, v.r_seal, NULL::bytea,
869
+ v.p_value, v.p_seal, NULL::bytea, p_cap_count, p_cap_window,
870
+ v.s_project, v.s_environment, v.s_version, v.s_source, v.s_seal, NULL::bytea,
871
+ v.x_project, v.x_environment, v.x_version, v.x_source, v.x_seal, NULL::bytea,
872
+ 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,
873
+ 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,
874
+ 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,
875
+ 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,
876
+ 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,
877
+ 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,
878
+ CASE WHEN p_token_hash IS NOT NULL THEN json_build_object(
879
+ '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),
880
+ '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),
881
+ 'project', (SELECT to_json(q) FROM public.projects q WHERE q.id = coalesce(v.e1_project, v.e2_project)),
882
+ 'environment', (SELECT to_json(e) FROM public.environments e WHERE e.id = coalesce(v.e1_id, v.e2_id)), 'parent', NULL,
883
+ '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,
884
+ -- A named call's (`n_*`), set below; a binding's row.
885
+ NULL::uuid, NULL::uuid, NULL::uuid, NULL::text, NULL::text, NULL::boolean, v.w_row
886
+ INTO r
887
+ FROM v
888
+ -- 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`.
889
+ LEFT JOIN LATERAL (
890
+ SELECT array_agg(coalesce(g.project_id, ge.project_id)) AS g_project, array_agg(g.environment_id) AS g_environment,
891
+ array_agg(g.environment_slug) AS g_slug, array_agg(g.role) AS g_role, array_agg(g.expires_at) AS g_expires
892
+ FROM public.vault_grants g LEFT JOIN public.environments ge ON ge.id = g.environment_id
893
+ WHERE g.principal = v.p
894
+ ) gr ON v.m_status IS NOT NULL;
895
+ ELSE
896
+ WITH i AS MATERIALIZED (
897
+ SELECT n.number AS newest, n.mac AS newest_mac, coalesce(n.number = p_known AND n.mac = p_known_mac, false) AS cached,
898
+ f.at_floor,
899
+ w.principal AS p, c.kind AS c_kind, w.scope AS c_scope, w.revoked_at IS NOT NULL AS c_revoked,
900
+ floor(extract(epoch FROM w.expires_at) * 1000)::bigint AS c_expires, w.seal AS c_seal,
901
+ 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, w.generation AS c_generation, m.generation AS m_generation,
902
+ w.row AS w_row,
903
+ -- What the request named, for the names it checks, and the rows the app checks, when it names them (0044).
904
+ c.token_hash AS c_token_hash,
905
+ 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,
906
+ CASE WHEN p_token_hash IS NOT NULL AND s.current_version_id IS NOT NULL THEN to_json(sv) END AS sv_row,
907
+ cfg.r_value, cfg.r_seal, cfg.p_value, cfg.p_seal,
908
+ 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,
909
+ 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,
910
+ 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,
911
+ 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,
912
+ 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,
913
+ 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,
914
+ 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,
915
+ b.last AS bulk_last
916
+ FROM (SELECT 1) one
917
+ LEFT JOIN LATERAL (SELECT x.number, x.mac FROM public.log_changes x ORDER BY x.number DESC LIMIT 1) n ON true
918
+ LEFT JOIN public.log_fence f ON f.only_row
919
+ -- The roles and the policy, both rows in one read of their one-page table.
920
+ LEFT JOIN LATERAL (
921
+ 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,
922
+ max(g.value) FILTER (WHERE g.name = 'policy') AS p_value, (array_agg(g.seal) FILTER (WHERE g.name = 'policy'))[1] AS p_seal
923
+ FROM public.log_config g
924
+ ) cfg ON true
925
+ -- The way in (T147), one of these by its id; or, behind Access, none: the account alone, whose row is all the
926
+ -- use weighs of it. The way rides the join key: the others' is null, which no index scan descends for. A
927
+ -- `p_way = 1` beside the key is no one-time filter of an outer join's inner side, and its probe ran (review r2).
928
+ LEFT JOIN public.credentials c ON c.id = CASE WHEN p_way = 0 THEN v_credential END
929
+ LEFT JOIN public.mcp_connections k ON k.id = CASE WHEN p_way = 1 THEN v_credential END
930
+ LEFT JOIN public.service_bindings wb ON wb.id = CASE WHEN p_way = 2 THEN v_credential END
931
+ CROSS JOIN LATERAL (
932
+ SELECT CASE p_way WHEN 0 THEN 'credential' WHEN 1 THEN 'connection' WHEN 2 THEN 'binding' END AS kind,
933
+ CASE p_way WHEN 0 THEN c.principal WHEN 1 THEN k.principal WHEN 2 THEN wb.principal WHEN 3 THEN p_principal END AS principal,
934
+ CASE p_way WHEN 0 THEN c.scope WHEN 2 THEN wb.scope END AS scope,
935
+ CASE p_way WHEN 0 THEN c.revoked_at WHEN 1 THEN k.revoked_at WHEN 2 THEN wb.revoked_at END AS revoked_at,
936
+ CASE p_way WHEN 0 THEN least(c.expires_at, c.not_after) WHEN 1 THEN k.expires_at END AS expires_at,
937
+ CASE p_way WHEN 0 THEN c.seal WHEN 1 THEN k.seal WHEN 2 THEN wb.seal END AS seal,
938
+ CASE p_way WHEN 0 THEN c.generation WHEN 1 THEN k.generation WHEN 2 THEN wb.generation END AS generation,
939
+ -- A binding's object is its whole row (objects.ts, bindingObject), its seal checked over it.
940
+ CASE WHEN p_way = 2 AND wb.id IS NOT NULL THEN to_json(wb) END AS row
941
+ ) w
942
+ LEFT JOIN public.vault_members m ON m.principal = w.principal
943
+ -- An archived key is gone (`secret.delete`): no object.
944
+ LEFT JOIN public.secrets s ON s.id = v_secret AND s.archived_at IS NULL
945
+ -- 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`).
946
+ LEFT JOIN LATERAL (
947
+ 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
948
+ ) ref ON s.current_version_id IS NULL
949
+ LEFT JOIN public.secrets src ON src.id = ref.source_secret_id AND src.archived_at IS NULL
950
+ LEFT JOIN LATERAL (
951
+ 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
952
+ ) xref ON src.current_version_id IS NULL
953
+ -- The value's version: the key's own, or its source's.
954
+ 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
955
+ -- The environments `useEnvironments` names, each by the id the rows name, whether or not its row is there: e1 the
956
+ -- place read through, e2 the key's own, e3 its value's (a reference's source's) where that is another, and e4
957
+ -- the parent of its value's when that is a personal config.
958
+ LEFT JOIN LATERAL (
959
+ SELECT CASE WHEN v_place IS DISTINCT FROM s.environment_id THEN v_place END AS e1,
960
+ CASE WHEN src.environment_id IS DISTINCT FROM s.environment_id THEN src.environment_id END AS e3
961
+ ) ids ON true
962
+ LEFT JOIN public.environments e1 ON e1.id = ids.e1
963
+ LEFT JOIN public.environments e2 ON e2.id = s.environment_id
964
+ LEFT JOIN public.environments e3 ON e3.id = ids.e3
965
+ -- The value's environment's row: the key's own, unless it is a reference, whose source is elsewhere or gone.
966
+ LEFT JOIN LATERAL (
967
+ 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,
968
+ 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
969
+ ) ve ON true
970
+ LEFT JOIN public.environments e4 ON e4.id = ve.parent AND ve.owner IS NOT NULL
971
+ LEFT JOIN public.log_bulk b ON b.principal = w.principal AND b.secret = v_secret
972
+ WHERE NOT dry
973
+ ),
974
+ v AS MATERIALIZED (
975
+ SELECT i.*, c_refresh AS refresh, c_period AS period,
976
+ CASE WHEN NOT i.cached THEN 'reprobe'
977
+ -- What no key decides comes first (T158; Probing.tla, AdmissionBeforeKey; review d3 of #416): the
978
+ -- floor, the deadline, the clock, the cap's config; then what the names decide, which the vault
979
+ -- answers on the caller's standing first.
980
+ WHEN i.at_floor IS NULL OR p_at < i.at_floor OR (extract(epoch FROM clock_timestamp()) * 1000)::bigint > p_deadline THEN 'late'
981
+ WHEN p_at > (extract(epoch FROM clock_timestamp()) * 1000)::bigint + 60000 THEN 'ahead'
982
+ -- What the names name, read by them: no credential of that token, no live place of those names.
983
+ WHEN v_named IS NOT NULL THEN v_named
984
+ -- The ids the vault passed are not the ones the names name now (D153): the names' rows are read,
985
+ -- and nothing is written or locked.
986
+ WHEN NOT v_right THEN 'moved'
987
+ 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)
988
+ OR coalesce(i.e1_project_archived, false) OR coalesce(i.e2_project_archived, false) OR coalesce(i.e3_project_archived, false)) THEN 'archived'
989
+ 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'
990
+ 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,
991
+ coalesce(i.bulk_last > p_at - p_cap_window - c_refresh, false) AS is_in,
992
+ -- The other keys in the set, up to the cap: read only under the principal's lock, for a key not in it.
993
+ CASE WHEN locked AND i.p IS NOT NULL AND NOT coalesce(i.bulk_last > p_at - p_cap_window - c_refresh, false) THEN (
994
+ SELECT count(*)::integer FROM (
995
+ SELECT 1 FROM public.log_bulk o
996
+ WHERE o.principal = i.p AND o.secret <> v_secret AND o.last > p_at - p_cap_window - c_refresh - public.deadline_max() - 60000
997
+ LIMIT p_cap_count
998
+ ) z
999
+ ) ELSE 0 END AS n_others,
1000
+ -- A repeat rides its cover only while every part of this holds: never on a key whose bulk row is missing (CoverFresh).
1001
+ coalesce(p_cover IS NOT NULL AND i.bulk_last >= c_period
1002
+ 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
1003
+ 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
1004
+ FROM i
1005
+ ),
1006
+ w AS (
1007
+ INSERT INTO public.log_uses (number, at, request_id, credential, secret, purpose, mac, place)
1008
+ SELECT nextval('public.log_number'), p_at, p_request, p_credential, p_secret, p_purpose, p_mac, p_place
1009
+ -- The isolate held its cover to the cached seals' numbers when it read them.
1010
+ 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)))
1011
+ RETURNING log_uses.number
1012
+ )
1013
+ 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,
1014
+ v.p, v.c_kind, v.c_scope, v.c_revoked, v.c_expires, v.c_seal, NULL::bytea,
1015
+ v.m_status, v.m_owner, v.m_role, v.m_scope, v.m_seal, NULL::bytea, NULL::bigint,
1016
+ (SELECT a.seq FROM public.audit_log a WHERE a.author = 'vault' AND a.subject_principal = v.p AND a.decision = 'allow'
1017
+ 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),
1018
+ gr.g_project, gr.g_environment, gr.g_slug, gr.g_role, gr.g_expires,
1019
+ v.r_value, v.r_seal, NULL::bytea,
1020
+ v.p_value, v.p_seal, NULL::bytea, p_cap_count, p_cap_window,
1021
+ v.s_project, v.s_environment, v.s_version, v.s_source, v.s_seal, NULL::bytea,
1022
+ v.x_project, v.x_environment, v.x_version, v.x_source, v.x_seal, NULL::bytea,
1023
+ 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,
1024
+ 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,
1025
+ 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,
1026
+ 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,
1027
+ 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,
1028
+ 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,
1029
+ CASE WHEN p_token_hash IS NOT NULL THEN json_build_object(
1030
+ '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),
1031
+ '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),
1032
+ 'project', (SELECT to_json(q) FROM public.projects q WHERE q.id = coalesce(v.e1_project, v.e2_project)),
1033
+ 'environment', (SELECT to_json(e) FROM public.environments e WHERE e.id = coalesce(v.e1_id, v.e2_id)), 'parent', NULL,
1034
+ '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,
1035
+ -- A named call's (`n_*`), set below; a binding's row.
1036
+ NULL::uuid, NULL::uuid, NULL::uuid, NULL::text, NULL::text, NULL::boolean, v.w_row
1037
+ INTO r
1038
+ FROM v
1039
+ -- 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`.
1040
+ LEFT JOIN LATERAL (
1041
+ SELECT array_agg(coalesce(g.project_id, ge.project_id)) AS g_project, array_agg(g.environment_id) AS g_environment,
1042
+ array_agg(g.environment_slug) AS g_slug, array_agg(g.role) AS g_role, array_agg(g.expires_at) AS g_expires
1043
+ FROM public.vault_grants g LEFT JOIN public.environments ge ON ge.id = g.environment_id
1044
+ WHERE g.principal = v.p
1045
+ ) gr ON v.m_status IS NOT NULL;
1046
+ END IF;
1047
+ IF dry THEN
1048
+ -- The forms in turn: probing plain, probing whole, cached plain, cached whole; then the call itself, under the fence.
1049
+ form := form + 1;
1050
+ IF form < 4 THEN
1051
+ probe := form < 2;
1052
+ whole := form % 2 = 1;
1053
+ CONTINUE;
1054
+ END IF;
1055
+ PERFORM set_config('coffre.use_planned', '0046', false);
1056
+ dry := false;
1057
+ probe := first_probe;
1058
+ whole := first_whole;
1059
+ PERFORM pg_advisory_xact_lock_shared(1668244070, 0);
1060
+ CONTINUE;
1061
+ END IF;
1062
+ -- A named call answered `moved` or `archived` is decided by the vault before anything else is done (T158, D153):
1063
+ -- it is read again probing, so that every row's newest seal is there to check, in this call.
1064
+ IF r.state IN ('moved', 'archived') AND p_token_hash IS NOT NULL AND NOT probe THEN
1065
+ probe := true;
1066
+ CONTINUE;
1067
+ END IF;
1068
+ -- A use the statement took writes its entry or rides its cover, never neither: but a named one of a key the place
1069
+ -- does not hold, or of a plain value, which writes none.
1070
+ 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
1071
+ RAISE EXCEPTION 'a use wrote no entry and rode no cover' USING ERRCODE = 'XX000';
1072
+ END IF;
1073
+ IF r.state = 'reprobe' THEN probe := true; CONTINUE; END IF;
1074
+ IF r.state = 'whole' THEN whole := true; CONTINUE; END IF;
1075
+ -- A use whose key would take its principal's lock (to enter the cap's set, or move on in it), named or by ids,
1076
+ -- takes it only in the vault's call after it decided the use on the rows of this one (`p_decided`; T158, D153;
1077
+ -- formal/Probing.tla, DecideBeforeLock, DecideEachLock; HotPathOne, NoLockWithoutStanding). Otherwise the call ends
1078
+ -- here, nothing locked or written, and answers the rows it read (`decide`): the vault refuses a use it may not make
1079
+ -- on them, the same as for a key never made, and makes one it may, in a second call.
1080
+ IF r.state IN ('lock', 'share') AND NOT p_decided AND NOT locked AND NOT moving THEN
1081
+ r.state := 'decide';
1082
+ EXIT;
1083
+ END IF;
1084
+ EXIT WHEN r.state IS DISTINCT FROM 'lock' AND r.state IS DISTINCT FROM 'share';
1085
+ -- 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
1086
+ -- 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
1087
+ -- shared, beside the principal's other uses that move a key on, and reads again.
1088
+ -- Holding it shared, a key never leaves the set (SharedStaysInSet) unless a row was put back under it: then the
1089
+ -- call ends, refused as late, rather than ask for the lock exclusive while it holds it shared (two such would
1090
+ -- deadlock).
1091
+ IF r.state = 'lock' AND moving THEN
1092
+ RAISE EXCEPTION 'its key left the cap''s set under the principal''s lock: a row put back' USING ERRCODE = '57014';
1093
+ END IF;
1094
+ IF r.state = 'lock' THEN
1095
+ PERFORM pg_advisory_xact_lock(1668244066, hashtext(r.c_principal));
1096
+ locked := true;
1097
+ ELSE
1098
+ PERFORM pg_advisory_xact_lock_shared(1668244066, hashtext(r.c_principal));
1099
+ moving := true;
1100
+ END IF;
1101
+ END LOOP;
1102
+ -- 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
1103
+ -- read, in the same transaction, under the same locks: the uses that read their key fresh, nearly all, run no write.
1104
+ -- 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
1105
+ -- made or moved on from a window past. Every request counts, whatever the Worker then decides but the cap (HotPathOneStaleBulk).
1106
+ IF r.state IS NULL AND (locked OR moving) THEN
1107
+ b_refresh := greatest(1, least(60000, r.cap_window / 10));
1108
+ b_period := p_at - ((p_at % b_refresh) + b_refresh) % b_refresh;
1109
+ IF r.in_set THEN
1110
+ -- One use moves the key to this period: the one that takes a lock of the key and period's own without waiting.
1111
+ -- The others take it shared, which waits only for that one's end, never for each other, and read the row again,
1112
+ -- locking none: moved, they leave it. An UPDATE that waits on a row another is moving, then finds it moved,
1113
+ -- keeps the row locked to its own commit, and queues the next. If the one moving it rolled back, the others
1114
+ -- elect another the same way, on the lock's next round.
1115
+ LOOP
1116
+ b_lock := hashtextextended(r.c_principal || '/' || p_secret::text || '/' || b_period::text || '/' || b_round::text, 1668244066);
1117
+ IF pg_try_advisory_xact_lock(b_lock) THEN
1118
+ UPDATE public.log_bulk SET last = b_period WHERE principal = r.c_principal AND secret = p_secret AND last < b_period;
1119
+ EXIT;
1120
+ END IF;
1121
+ PERFORM pg_advisory_xact_lock_shared(b_lock);
1122
+ EXIT WHEN NOT EXISTS (SELECT 1 FROM public.log_bulk WHERE principal = r.c_principal AND secret = p_secret AND last < b_period);
1123
+ b_round := b_round + 1;
1124
+ END LOOP;
1125
+ ELSIF r.others < r.cap_count THEN
1126
+ INSERT INTO public.log_bulk (principal, secret, last) VALUES (r.c_principal, p_secret, b_period)
1127
+ ON CONFLICT ON CONSTRAINT log_bulk_pkey DO UPDATE SET last = greatest(log_bulk.last, EXCLUDED.last);
1128
+ END IF;
1129
+ END IF;
1130
+ -- 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.
1131
+ IF r.state IS NULL AND (extract(epoch FROM clock_timestamp()) * 1000)::bigint > p_deadline THEN
1132
+ RAISE EXCEPTION 'past its deadline' USING ERRCODE = '57014';
1133
+ END IF;
1134
+ -- What a named call's names name: the ids the vault makes the use by once it has decided, and the place's slugs.
1135
+ IF p_token_hash IS NOT NULL THEN
1136
+ r.n_credential := v_credential;
1137
+ r.n_secret := v_secret;
1138
+ r.n_place := v_np;
1139
+ r.n_project := v_project;
1140
+ r.n_environment := v_environment;
1141
+ r.n_standing := v_stands;
1142
+ END IF;
1143
+ RETURN to_json(r);
1144
+ END
1145
+ $$;
1146
+ --> statement-breakpoint
1147
+ 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, boolean, boolean, text[], boolean, text[], jsonb, smallint, text) FROM PUBLIC;
1148
+ --> statement-breakpoint
1149
+ 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, boolean, boolean, text[], boolean, text[], jsonb, smallint, text) TO coffre_vault;