@almadar/shell-runtime 0.0.0-stage → 0.1.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 (180) hide show
  1. package/dist/chunk-2JU7Y4KU.js +21 -0
  2. package/dist/chunk-46RWK2B5.js +28 -0
  3. package/dist/chunk-4J2QEIUV.js +78 -0
  4. package/dist/chunk-4SEK5ER2.js +8 -0
  5. package/dist/chunk-4ZICQ5PM.js +192 -0
  6. package/dist/chunk-53IPZXDP.js +76 -0
  7. package/dist/chunk-5D63EMO5.js +16 -0
  8. package/dist/chunk-64IH2SMY.js +22 -0
  9. package/dist/chunk-7SDV3QWR.js +67 -0
  10. package/dist/chunk-CPSQXEJ6.js +55 -0
  11. package/dist/chunk-CQP5XAX6.js +173 -0
  12. package/dist/chunk-CROS2GEX.js +49 -0
  13. package/dist/chunk-D4XJTBLB.js +217 -0
  14. package/dist/chunk-D5USD2EX.js +232 -0
  15. package/dist/chunk-DYOJGLIX.js +25 -0
  16. package/dist/chunk-ELBLT2HZ.js +1 -0
  17. package/dist/chunk-EV56K4GB.js +63 -0
  18. package/dist/chunk-EZZYLTWY.js +1 -0
  19. package/dist/chunk-FQ6IZMIU.js +10 -0
  20. package/dist/chunk-FWA5K7Q2.js +48 -0
  21. package/dist/chunk-HC6ERSAH.js +55 -0
  22. package/dist/chunk-HLOUDLPI.js +48 -0
  23. package/dist/chunk-HMK6YMQS.js +135 -0
  24. package/dist/chunk-HNOAZWFH.js +19 -0
  25. package/dist/chunk-IHQLE5M7.js +62 -0
  26. package/dist/chunk-IOPUS6Q5.js +15 -0
  27. package/dist/chunk-JFWFDUTR.js +35 -0
  28. package/dist/chunk-LZIOTBFX.js +1 -0
  29. package/dist/chunk-MHYSHSRG.js +54 -0
  30. package/dist/chunk-MLKGABMK.js +7 -0
  31. package/dist/chunk-MZKIL5IN.js +74 -0
  32. package/dist/chunk-NAECVIAR.js +322 -0
  33. package/dist/chunk-NATGCIVM.js +583 -0
  34. package/dist/chunk-O57GKTYU.js +30 -0
  35. package/dist/chunk-ODJCZ27I.js +50 -0
  36. package/dist/chunk-P53CKOME.js +1 -0
  37. package/dist/chunk-Q2H75UYT.js +92 -0
  38. package/dist/chunk-Q7JK4KY7.js +9 -0
  39. package/dist/chunk-QELZ6TEJ.js +208 -0
  40. package/dist/chunk-QQRDUMB2.js +40 -0
  41. package/dist/chunk-RBUMDYFS.js +681 -0
  42. package/dist/chunk-REPX5QPI.js +57 -0
  43. package/dist/chunk-SAS5KKCR.js +43 -0
  44. package/dist/chunk-SKLMWHQD.js +109 -0
  45. package/dist/chunk-SOQDEPME.js +131 -0
  46. package/dist/chunk-SWPDU752.js +28 -0
  47. package/dist/chunk-T775TUCD.js +18 -0
  48. package/dist/chunk-UBN5JJKZ.js +26 -0
  49. package/dist/chunk-UJHOUZAL.js +36 -0
  50. package/dist/chunk-UP6GGGAN.js +34 -0
  51. package/dist/chunk-VMOBW7RK.js +21 -0
  52. package/dist/chunk-VNMG7NKY.js +69 -0
  53. package/dist/chunk-WNPNNYXY.js +22 -0
  54. package/dist/chunk-XB5GYODW.js +28 -0
  55. package/dist/chunk-XXWNMJI6.js +15 -0
  56. package/dist/chunk-YL2SO5TP.js +334 -0
  57. package/dist/chunk-YO4LWS3I.js +606 -0
  58. package/dist/chunk-ZAQSS47X.js +157 -0
  59. package/dist/chunk-ZCQUUJIF.js +129 -0
  60. package/dist/client/EventTransport.d.ts +131 -0
  61. package/dist/client/EventTransport.js +2 -0
  62. package/dist/client/ServerBridge.d.ts +126 -0
  63. package/dist/client/ServerBridge.js +3 -0
  64. package/dist/client/awaiting.d.ts +28 -0
  65. package/dist/client/awaiting.js +2 -0
  66. package/dist/client/channel-transport.d.ts +69 -0
  67. package/dist/client/channel-transport.js +2 -0
  68. package/dist/client/circuit-store.d.ts +76 -0
  69. package/dist/client/circuit-store.js +4 -0
  70. package/dist/client/client-kernel.d.ts +113 -0
  71. package/dist/client/client-kernel.js +1 -0
  72. package/dist/client/declared-input-dispatch.d.ts +5 -0
  73. package/dist/client/declared-input-dispatch.js +2 -0
  74. package/dist/client/inbound-dispatch.d.ts +5 -0
  75. package/dist/client/inbound-dispatch.js +2 -0
  76. package/dist/client/local-fold.d.ts +26 -0
  77. package/dist/client/local-fold.js +2 -0
  78. package/dist/client/render-sigils.d.ts +24 -0
  79. package/dist/client/render-sigils.js +2 -0
  80. package/dist/client/state-book.d.ts +37 -0
  81. package/dist/client/state-book.js +2 -0
  82. package/dist/client/trait-mount-error.d.ts +9 -0
  83. package/dist/client/trait-mount-error.js +2 -0
  84. package/dist/client/trait-types.d.ts +67 -0
  85. package/dist/client/trait-types.js +1 -0
  86. package/dist/credentials/env-vars.d.ts +14 -0
  87. package/dist/credentials/env-vars.js +3 -0
  88. package/dist/credentials/scope.d.ts +50 -0
  89. package/dist/credentials/scope.js +2 -0
  90. package/dist/credentials/seed.d.ts +21 -0
  91. package/dist/credentials/seed.js +2 -0
  92. package/dist/credentials/store.d.ts +110 -0
  93. package/dist/credentials/store.js +2 -0
  94. package/dist/effects/in-flight-calls.d.ts +36 -0
  95. package/dist/effects/in-flight-calls.js +2 -0
  96. package/dist/effects/on-device-model.d.ts +22 -0
  97. package/dist/effects/on-device-model.js +2 -0
  98. package/dist/events/EventBus.d.ts +90 -0
  99. package/dist/events/EventBus.js +2 -0
  100. package/dist/events/emit-stamp.d.ts +47 -0
  101. package/dist/events/emit-stamp.js +2 -0
  102. package/dist/index-hT1xCdTq.d.ts +46 -0
  103. package/dist/index.d.ts +60 -0
  104. package/dist/index.js +59 -0
  105. package/dist/kernel/beacon.d.ts +32 -0
  106. package/dist/kernel/beacon.js +2 -0
  107. package/dist/kernel/decode.d.ts +15 -0
  108. package/dist/kernel/decode.js +2 -0
  109. package/dist/kernel/engine.d.ts +94 -0
  110. package/dist/kernel/engine.js +11 -0
  111. package/dist/kernel/frame-applier.d.ts +43 -0
  112. package/dist/kernel/frame-applier.js +2 -0
  113. package/dist/kernel/host.d.ts +95 -0
  114. package/dist/kernel/host.js +9 -0
  115. package/dist/kernel/http.d.ts +302 -0
  116. package/dist/kernel/http.js +6 -0
  117. package/dist/kernel/lane.d.ts +28 -0
  118. package/dist/kernel/lane.js +2 -0
  119. package/dist/kernel/oauth-effects.d.ts +25 -0
  120. package/dist/kernel/oauth-effects.js +7 -0
  121. package/dist/kernel/oauth-port.d.ts +31 -0
  122. package/dist/kernel/oauth-port.js +2 -0
  123. package/dist/kernel/os-port.d.ts +133 -0
  124. package/dist/kernel/os-port.js +2 -0
  125. package/dist/kernel/program-effects.d.ts +65 -0
  126. package/dist/kernel/program-effects.js +2 -0
  127. package/dist/kernel/wire.d.ts +66 -0
  128. package/dist/kernel/wire.js +1 -0
  129. package/dist/loader/http-loader.d.ts +111 -0
  130. package/dist/loader/http-loader.js +2 -0
  131. package/dist/loader/lazy-page.d.ts +12 -0
  132. package/dist/loader/lazy-page.js +2 -0
  133. package/dist/loader/schema-loader.d.ts +160 -0
  134. package/dist/loader/schema-loader.js +1 -0
  135. package/dist/persistence/native-memory.d.ts +28 -0
  136. package/dist/persistence/native-memory.js +2 -0
  137. package/dist/runtime/effects/composition/compose-behaviors.d.ts +58 -0
  138. package/dist/runtime/effects/composition/compose-behaviors.js +4 -0
  139. package/dist/runtime/effects/composition/event-wiring.d.ts +38 -0
  140. package/dist/runtime/effects/composition/event-wiring.js +2 -0
  141. package/dist/runtime/effects/composition/index.d.ts +5 -0
  142. package/dist/runtime/effects/composition/index.js +6 -0
  143. package/dist/runtime/effects/composition/layout-strategy.d.ts +35 -0
  144. package/dist/runtime/effects/composition/layout-strategy.js +2 -0
  145. package/dist/runtime/effects/composition/pipe.d.ts +25 -0
  146. package/dist/runtime/effects/composition/pipe.js +2 -0
  147. package/dist/runtime/entities/mockStoreSeed.d.ts +56 -0
  148. package/dist/runtime/entities/mockStoreSeed.js +3 -0
  149. package/dist/runtime/entities/seedBrowserStore.d.ts +28 -0
  150. package/dist/runtime/entities/seedBrowserStore.js +2 -0
  151. package/dist/runtime/entities/std-mock-seed.d.ts +87 -0
  152. package/dist/runtime/entities/std-mock-seed.js +2 -0
  153. package/dist/runtime/evaluation/local-store-transport.d.ts +68 -0
  154. package/dist/runtime/evaluation/local-store-transport.js +18 -0
  155. package/dist/runtime/evaluation/residence-transport.d.ts +15 -0
  156. package/dist/runtime/evaluation/residence-transport.js +2 -0
  157. package/dist/runtime/evaluation/routed-persistence.d.ts +9 -0
  158. package/dist/runtime/evaluation/routed-persistence.js +2 -0
  159. package/dist/runtime/events/identity/routing.d.ts +84 -0
  160. package/dist/runtime/events/identity/routing.js +2 -0
  161. package/dist/runtime/time/TickScheduler.d.ts +77 -0
  162. package/dist/runtime/time/TickScheduler.js +3 -0
  163. package/dist/runtime/time/cron.d.ts +25 -0
  164. package/dist/runtime/time/cron.js +2 -0
  165. package/dist/runtime/time/duration.d.ts +20 -0
  166. package/dist/runtime/time/duration.js +2 -0
  167. package/dist/runtime/traits/OrbitalTraitParsing.d.ts +34 -0
  168. package/dist/runtime/traits/OrbitalTraitParsing.js +2 -0
  169. package/dist/runtime/traits/listen-targets.d.ts +16 -0
  170. package/dist/runtime/traits/listen-targets.js +3 -0
  171. package/dist/runtime/traits/trait-index.d.ts +35 -0
  172. package/dist/runtime/traits/trait-index.js +3 -0
  173. package/dist/runtime/types.d.ts +574 -0
  174. package/dist/runtime/types.js +2 -0
  175. package/dist/service/default-call-service.d.ts +23 -0
  176. package/dist/service/default-call-service.js +3 -0
  177. package/dist/service/service-provider.d.ts +37 -0
  178. package/dist/service/service-provider.js +2 -0
  179. package/package.json +46 -3
  180. package/README.md +0 -3
@@ -0,0 +1,322 @@
1
+ import { credentialStorageKey } from '@almadar/core';
2
+
3
+ // src/credentials/store.ts
4
+ var CREDENTIAL_ENTITY_TYPE = "AlmadarIntegrationCredential";
5
+ var CREDENTIAL_MASTER_KEY_ENV = "ALMADAR_CREDENTIAL_MASTER_KEY";
6
+ var CREDENTIAL_PREVIOUS_MASTER_KEY_ENV = "ALMADAR_CREDENTIAL_MASTER_KEY_PREVIOUS";
7
+ var TAG_BYTES = 16;
8
+ var IV_BYTES = 12;
9
+ function fromHex(hex) {
10
+ const bytes = new Uint8Array(hex.length / 2);
11
+ for (let i = 0; i < bytes.length; i += 1) bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
12
+ return bytes;
13
+ }
14
+ function toHex(bytes) {
15
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
16
+ }
17
+ function utf8(text) {
18
+ return new TextEncoder().encode(text);
19
+ }
20
+ async function sha256Hex(bytes) {
21
+ return toHex(new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", bytes)));
22
+ }
23
+ function environment(name) {
24
+ return typeof process === "undefined" ? void 0 : process.env[name];
25
+ }
26
+ var DERIVED_ENTITY_TYPE = "AlmadarKernelDerivedSecret";
27
+ function keepsDerived(adapter) {
28
+ return "getById" in adapter && typeof adapter.getById === "function";
29
+ }
30
+ function derivedId(identity) {
31
+ return sha256Hex(utf8(credentialStorageKey(identity)));
32
+ }
33
+ function isNonEmptyString(v) {
34
+ return typeof v === "string" && v.length > 0;
35
+ }
36
+ async function keyIdOf(masterKeyHex) {
37
+ return (await sha256Hex(fromHex(masterKeyHex))).slice(0, 16);
38
+ }
39
+ var MASTER_KEY_SHAPE = /^[0-9a-fA-F]{64}$/;
40
+ var CredentialStore = class {
41
+ adapter;
42
+ masterKeyHex;
43
+ previousKeyHex;
44
+ byIdentity = /* @__PURE__ */ new Map();
45
+ listeners = /* @__PURE__ */ new Set();
46
+ warmed = false;
47
+ constructor(adapter, masterKey, previousMasterKey) {
48
+ this.adapter = adapter;
49
+ const key = masterKey ?? environment(CREDENTIAL_MASTER_KEY_ENV);
50
+ const valid = typeof key === "string" && MASTER_KEY_SHAPE.test(key);
51
+ if (!valid && environment("NODE_ENV") === "production") {
52
+ throw new Error(
53
+ `${CREDENTIAL_MASTER_KEY_ENV} must be a 64-character hex string (32 bytes) in production \u2014 generate one with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`
54
+ );
55
+ }
56
+ this.masterKeyHex = valid ? key : void 0;
57
+ const prev = previousMasterKey ?? environment(CREDENTIAL_PREVIOUS_MASTER_KEY_ENV);
58
+ this.previousKeyHex = typeof prev === "string" && MASTER_KEY_SHAPE.test(prev) ? prev : void 0;
59
+ }
60
+ /** True when a valid master key is present (writes and decryption enabled). */
61
+ get enabled() {
62
+ return this.masterKeyHex !== void 0;
63
+ }
64
+ /** Subscribe to credential changes; returns an unsubscribe function. */
65
+ subscribe(listener) {
66
+ this.listeners.add(listener);
67
+ return () => this.listeners.delete(listener);
68
+ }
69
+ notify(principal) {
70
+ for (const listener of this.listeners) listener(principal);
71
+ }
72
+ async encrypt(plaintext, identity) {
73
+ if (!this.masterKeyHex) {
74
+ throw new Error(`${CREDENTIAL_MASTER_KEY_ENV} is not configured \u2014 cannot store credentials`);
75
+ }
76
+ const iv = globalThis.crypto.getRandomValues(new Uint8Array(IV_BYTES));
77
+ const key = await globalThis.crypto.subtle.importKey("raw", fromHex(this.masterKeyHex), "AES-GCM", false, ["encrypt"]);
78
+ const sealed = new Uint8Array(
79
+ await globalThis.crypto.subtle.encrypt(
80
+ { name: "AES-GCM", iv, tagLength: TAG_BYTES * 8, ...identity?.principal !== void 0 ? { additionalData: utf8(credentialStorageKey(identity)) } : {} },
81
+ key,
82
+ utf8(plaintext)
83
+ )
84
+ );
85
+ return {
86
+ ciphertext: toHex(sealed.subarray(0, sealed.length - TAG_BYTES)),
87
+ iv: toHex(iv),
88
+ authTag: toHex(sealed.subarray(sealed.length - TAG_BYTES)),
89
+ keyId: await keyIdOf(this.masterKeyHex)
90
+ };
91
+ }
92
+ async decryptWithKey(keyHex, ciphertext, ivHex, authTagHex, identity) {
93
+ const key = await globalThis.crypto.subtle.importKey("raw", fromHex(keyHex), "AES-GCM", false, ["decrypt"]);
94
+ const body = fromHex(ciphertext);
95
+ const tag = fromHex(authTagHex);
96
+ const joined = new Uint8Array(body.length + tag.length);
97
+ joined.set(body, 0);
98
+ joined.set(tag, body.length);
99
+ const plain = await globalThis.crypto.subtle.decrypt(
100
+ { name: "AES-GCM", iv: fromHex(ivHex), tagLength: TAG_BYTES * 8, ...identity?.principal !== void 0 ? { additionalData: utf8(credentialStorageKey(identity)) } : {} },
101
+ key,
102
+ joined
103
+ );
104
+ return new TextDecoder().decode(plain);
105
+ }
106
+ /**
107
+ * Decrypt one row, selecting the key by the row's stamped `keyId` (I-28):
108
+ * current key first, then the rotation-window previous key. Legacy rows
109
+ * (no `keyId`) try current then previous. Returns null when no held key
110
+ * decrypts the row — the row is unreadable, never silently wrong (GCM
111
+ * auth tags make a wrong-key success impossible).
112
+ */
113
+ async decryptRow(ciphertext, ivHex, authTagHex, rowKeyId, identity) {
114
+ if (!this.masterKeyHex) {
115
+ throw new Error(`${CREDENTIAL_MASTER_KEY_ENV} is not configured \u2014 cannot decrypt credentials`);
116
+ }
117
+ const candidates = [];
118
+ if (rowKeyId === void 0 || rowKeyId === await keyIdOf(this.masterKeyHex)) {
119
+ candidates.push(this.masterKeyHex);
120
+ }
121
+ if (this.previousKeyHex !== void 0 && (rowKeyId === void 0 || rowKeyId === await keyIdOf(this.previousKeyHex))) {
122
+ candidates.push(this.previousKeyHex);
123
+ }
124
+ for (const key of candidates) {
125
+ try {
126
+ return await this.decryptWithKey(key, ciphertext, ivHex, authTagHex, identity);
127
+ } catch {
128
+ }
129
+ }
130
+ return null;
131
+ }
132
+ /**
133
+ * Load and decrypt every stored row into memory. Returns the number of
134
+ * resolvable credentials. Without a master key nothing decrypts (rows are
135
+ * left in place, resolution falls through to env).
136
+ */
137
+ async warm() {
138
+ this.byIdentity.clear();
139
+ this.warmed = true;
140
+ if (!this.enabled) return 0;
141
+ const rows = await this.adapter.list(CREDENTIAL_ENTITY_TYPE);
142
+ for (const row of rows) {
143
+ const { id, service, envVar, ciphertext, iv, authTag, principal } = row;
144
+ if (!isNonEmptyString(id) || !isNonEmptyString(service) || !isNonEmptyString(envVar) || typeof ciphertext !== "string" || !isNonEmptyString(iv) || !isNonEmptyString(authTag) || principal !== void 0 && !isNonEmptyString(principal)) {
145
+ continue;
146
+ }
147
+ const value = await this.decryptRow(
148
+ ciphertext,
149
+ iv,
150
+ authTag,
151
+ isNonEmptyString(row.keyId) ? row.keyId : void 0,
152
+ { name: envVar, ...principal !== void 0 ? { principal } : {} }
153
+ );
154
+ if (value === null) continue;
155
+ this.byIdentity.set(credentialStorageKey({ name: envVar, ...principal !== void 0 ? { principal } : {} }), {
156
+ id,
157
+ service,
158
+ envVar,
159
+ value,
160
+ last4: value.slice(-4),
161
+ updatedAt: typeof row.updatedAt === "number" ? row.updatedAt : 0,
162
+ ...isNonEmptyString(row.label) ? { label: row.label } : {},
163
+ ...principal !== void 0 ? { principal } : {}
164
+ });
165
+ }
166
+ return this.byIdentity.size;
167
+ }
168
+ /** Resolve one credential by env-var name (warmed values only). */
169
+ resolve(envVar, principal) {
170
+ return this.byIdentity.get(credentialStorageKey({ name: envVar, ...principal !== void 0 ? { principal } : {} }))?.value;
171
+ }
172
+ /** The non-secret account label stored in the clear beside a credential's ciphertext; undefined when its row carries none. */
173
+ label(envVar, principal) {
174
+ return this.byIdentity.get(credentialStorageKey({ name: envVar, ...principal !== void 0 ? { principal } : {} }))?.label;
175
+ }
176
+ /** All warmed values as an env-shaped map, for merging over the process env. */
177
+ snapshotEnv() {
178
+ const env = {};
179
+ for (const entry of this.byIdentity.values()) {
180
+ if (entry.principal === void 0) env[entry.envVar] = entry.value;
181
+ }
182
+ return env;
183
+ }
184
+ /** Masked entries for display — never includes plaintext. */
185
+ entries(principal) {
186
+ return Array.from(this.byIdentity.values()).filter((entry) => entry.principal === principal).map(({ service, envVar, last4, updatedAt, principal: owner }) => ({ service, envVar, last4, updatedAt, ...owner !== void 0 ? { principal: owner } : {} })).sort((a, b) => a.service.localeCompare(b.service) || a.envVar.localeCompare(b.envVar));
187
+ }
188
+ /**
189
+ * Upsert one credential (encrypts, persists, re-warms the entry, notifies). `label` is a non-secret account name kept
190
+ * in the clear in the row beside the ciphertext; left out, the row's label stays as it is.
191
+ */
192
+ async set(service, envVar, value, principal, label) {
193
+ if (principal !== void 0 && !isNonEmptyString(principal)) throw new Error("credential principal must be a nonempty string");
194
+ if (label !== void 0 && !isNonEmptyString(label)) throw new Error("credential label must be a nonempty string");
195
+ if (!this.warmed) await this.warm();
196
+ const identity = { name: envVar, ...principal !== void 0 ? { principal } : {} };
197
+ const storageKey = credentialStorageKey(identity);
198
+ const { ciphertext, iv, authTag, keyId } = await this.encrypt(value, identity);
199
+ const updatedAt = Date.now();
200
+ const existing = this.byIdentity.get(storageKey);
201
+ const data = { service, envVar, ciphertext, iv, authTag, keyId, updatedAt, ...principal !== void 0 ? { principal } : {}, ...label !== void 0 ? { label } : {} };
202
+ let id;
203
+ if (existing) {
204
+ id = existing.id;
205
+ await this.adapter.update(CREDENTIAL_ENTITY_TYPE, id, data);
206
+ } else {
207
+ ({ id } = await this.adapter.create(CREDENTIAL_ENTITY_TYPE, data));
208
+ }
209
+ const kept = label ?? existing?.label;
210
+ const entry = { id, service, envVar, value, last4: value.slice(-4), updatedAt, ...kept !== void 0 ? { label: kept } : {}, ...principal !== void 0 ? { principal } : {} };
211
+ this.byIdentity.set(storageKey, entry);
212
+ this.notify(principal);
213
+ const { service: s, envVar: e, last4, updatedAt: u } = entry;
214
+ return { service: s, envVar: e, last4, updatedAt: u, ...principal !== void 0 ? { principal } : {} };
215
+ }
216
+ /**
217
+ * Re-encrypt every readable row under the CURRENT master key (I-28).
218
+ * Rotation window: deploy the new key as `ALMADAR_CREDENTIAL_MASTER_KEY`
219
+ * with the old one as `..._PREVIOUS`, call `rotate()`, then retire the
220
+ * previous key. Rows no held key can read are counted, never dropped.
221
+ */
222
+ async rotate() {
223
+ if (!this.masterKeyHex) {
224
+ throw new Error(`${CREDENTIAL_MASTER_KEY_ENV} is not configured \u2014 cannot rotate`);
225
+ }
226
+ const currentKeyId = await keyIdOf(this.masterKeyHex);
227
+ let rotated = 0;
228
+ let alreadyCurrent = 0;
229
+ let unreadable = 0;
230
+ const rows = await this.adapter.list(CREDENTIAL_ENTITY_TYPE);
231
+ for (const row of rows) {
232
+ const { id, service, envVar, ciphertext, iv, authTag, principal } = row;
233
+ if (!isNonEmptyString(id) || !isNonEmptyString(service) || !isNonEmptyString(envVar) || typeof ciphertext !== "string" || !isNonEmptyString(iv) || !isNonEmptyString(authTag) || principal !== void 0 && !isNonEmptyString(principal)) {
234
+ unreadable += 1;
235
+ continue;
236
+ }
237
+ const rowKeyId = isNonEmptyString(row.keyId) ? row.keyId : void 0;
238
+ const identity = { name: envVar, ...principal !== void 0 ? { principal } : {} };
239
+ const value = await this.decryptRow(ciphertext, iv, authTag, rowKeyId, identity);
240
+ if (value === null) {
241
+ unreadable += 1;
242
+ continue;
243
+ }
244
+ if (rowKeyId === currentKeyId) {
245
+ alreadyCurrent += 1;
246
+ continue;
247
+ }
248
+ if (rowKeyId === void 0 && await this.decryptRow(ciphertext, iv, authTag, currentKeyId, identity) !== null) {
249
+ alreadyCurrent += 1;
250
+ await this.adapter.update(CREDENTIAL_ENTITY_TYPE, id, { ...row, keyId: currentKeyId });
251
+ continue;
252
+ }
253
+ const fresh = await this.encrypt(value, identity);
254
+ await this.adapter.update(CREDENTIAL_ENTITY_TYPE, id, {
255
+ service,
256
+ envVar,
257
+ ciphertext: fresh.ciphertext,
258
+ iv: fresh.iv,
259
+ authTag: fresh.authTag,
260
+ keyId: fresh.keyId,
261
+ updatedAt: Date.now(),
262
+ ...principal !== void 0 ? { principal } : {}
263
+ });
264
+ rotated += 1;
265
+ }
266
+ await this.warm();
267
+ this.notify();
268
+ return { rotated, alreadyCurrent, unreadable };
269
+ }
270
+ /** Whether one-use values are shared through the adapter: a master key and an adapter that reads a row by id. */
271
+ get sharesDerived() {
272
+ return this.enabled && keepsDerived(this.adapter);
273
+ }
274
+ /** Keep `value` encrypted in the adapter until `expiresAt` (epoch ms) so any instance on the same store can take it once. */
275
+ async holdDerived(name, value, expiresAt, principal) {
276
+ const adapter = this.adapter;
277
+ if (!this.enabled || !keepsDerived(adapter)) throw new Error("this host cannot share one-use values through its credential store");
278
+ const identity = { name, ...principal !== void 0 ? { principal } : {} };
279
+ const id = await derivedId(identity);
280
+ const { ciphertext, iv, authTag, keyId } = await this.encrypt(value, identity);
281
+ const fields = { envVar: name, ciphertext, iv, authTag, keyId, expiresAt, ...principal !== void 0 ? { principal } : {} };
282
+ if (await adapter.getById(DERIVED_ENTITY_TYPE, id) !== null) await adapter.update(DERIVED_ENTITY_TYPE, id, fields);
283
+ else await adapter.create(DERIVED_ENTITY_TYPE, { id, ...fields });
284
+ }
285
+ /**
286
+ * Remove and return a held value. Where the adapter has a conditional delete only the instance whose delete
287
+ * lands gets the value; elsewhere the delete is unconditional, so two instances racing for one state can both
288
+ * read it. An expired value is consumed and not returned.
289
+ */
290
+ async takeDerived(name, principal, now) {
291
+ const adapter = this.adapter;
292
+ if (!this.enabled || !keepsDerived(adapter)) return void 0;
293
+ const identity = { name, ...principal !== void 0 ? { principal } : {} };
294
+ const id = await derivedId(identity);
295
+ const row = await adapter.getById(DERIVED_ENTITY_TYPE, id);
296
+ if (row === null) return void 0;
297
+ const conditional = adapter.writeCapabilities?.(DERIVED_ENTITY_TYPE)?.conditional === true;
298
+ const outcome = conditional ? await adapter.writeWhere?.({ kind: "delete", entityType: DERIVED_ENTITY_TYPE, id }) : void 0;
299
+ if (outcome !== void 0) {
300
+ if (outcome.kind !== "deleted") return void 0;
301
+ } else {
302
+ await adapter.delete(DERIVED_ENTITY_TYPE, id);
303
+ }
304
+ const { ciphertext, iv, authTag, expiresAt } = row;
305
+ if (typeof expiresAt !== "number" || expiresAt <= now) return void 0;
306
+ if (typeof ciphertext !== "string" || !isNonEmptyString(iv) || !isNonEmptyString(authTag)) return void 0;
307
+ return await this.decryptRow(ciphertext, iv, authTag, isNonEmptyString(row.keyId) ? row.keyId : void 0, identity) ?? void 0;
308
+ }
309
+ /** Delete one credential row; resolution falls back to env afterwards. */
310
+ async remove(envVar, principal) {
311
+ if (!this.warmed) await this.warm();
312
+ const storageKey = credentialStorageKey({ name: envVar, ...principal !== void 0 ? { principal } : {} });
313
+ const existing = this.byIdentity.get(storageKey);
314
+ if (!existing) return false;
315
+ await this.adapter.delete(CREDENTIAL_ENTITY_TYPE, existing.id);
316
+ this.byIdentity.delete(storageKey);
317
+ this.notify(principal);
318
+ return true;
319
+ }
320
+ };
321
+
322
+ export { CREDENTIAL_ENTITY_TYPE, CREDENTIAL_MASTER_KEY_ENV, CREDENTIAL_PREVIOUS_MASTER_KEY_ENV, CredentialStore, DERIVED_ENTITY_TYPE };