@cleocode/core 2026.9.15 → 2026.9.17

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 (220) hide show
  1. package/dist/changesets/parser.d.ts.map +1 -1
  2. package/dist/changesets/parser.js +67 -0
  3. package/dist/changesets/parser.js.map +1 -1
  4. package/dist/crypto/credentials.d.ts +128 -26
  5. package/dist/crypto/credentials.d.ts.map +1 -1
  6. package/dist/crypto/credentials.js +183 -142
  7. package/dist/crypto/credentials.js.map +1 -1
  8. package/dist/docs/export-document.js +14705 -12949
  9. package/dist/docs/export-document.js.map +3 -3
  10. package/dist/doctor/index.d.ts +1 -1
  11. package/dist/doctor/index.d.ts.map +1 -1
  12. package/dist/doctor/index.js +1 -1
  13. package/dist/doctor/index.js.map +1 -1
  14. package/dist/doctor/nexus-schema-residency.d.ts +1 -1
  15. package/dist/doctor/nexus-schema-residency.d.ts.map +1 -1
  16. package/dist/doctor/nexus-schema-residency.js +1 -0
  17. package/dist/doctor/nexus-schema-residency.js.map +1 -1
  18. package/dist/doctor/project-identity.d.ts +124 -0
  19. package/dist/doctor/project-identity.d.ts.map +1 -0
  20. package/dist/doctor/project-identity.js +307 -0
  21. package/dist/doctor/project-identity.js.map +1 -0
  22. package/dist/doctor/superseded-store.d.ts +8 -0
  23. package/dist/doctor/superseded-store.d.ts.map +1 -1
  24. package/dist/doctor/superseded-store.js +48 -3
  25. package/dist/doctor/superseded-store.js.map +1 -1
  26. package/dist/init.d.ts +7 -0
  27. package/dist/init.d.ts.map +1 -1
  28. package/dist/init.js +17 -2
  29. package/dist/init.js.map +1 -1
  30. package/dist/internal.d.ts +5 -2
  31. package/dist/internal.d.ts.map +1 -1
  32. package/dist/internal.js +4 -2
  33. package/dist/internal.js.map +1 -1
  34. package/dist/llm/credentials-store.d.ts +10 -0
  35. package/dist/llm/credentials-store.d.ts.map +1 -1
  36. package/dist/llm/credentials-store.js +7 -1
  37. package/dist/llm/credentials-store.js.map +1 -1
  38. package/dist/llm/plugin-facade.js +13368 -1550
  39. package/dist/llm/plugin-facade.js.map +3 -3
  40. package/dist/nexus/analyze-orchestrator.d.ts +20 -3
  41. package/dist/nexus/analyze-orchestrator.d.ts.map +1 -1
  42. package/dist/nexus/analyze-orchestrator.js +159 -10
  43. package/dist/nexus/analyze-orchestrator.js.map +1 -1
  44. package/dist/nexus/assessment-store.d.ts +57 -0
  45. package/dist/nexus/assessment-store.d.ts.map +1 -0
  46. package/dist/nexus/assessment-store.js +73 -0
  47. package/dist/nexus/assessment-store.js.map +1 -0
  48. package/dist/nexus/freshness.d.ts +131 -0
  49. package/dist/nexus/freshness.d.ts.map +1 -0
  50. package/dist/nexus/freshness.js +391 -0
  51. package/dist/nexus/freshness.js.map +1 -0
  52. package/dist/nexus/graph-manifest.d.ts +88 -0
  53. package/dist/nexus/graph-manifest.d.ts.map +1 -0
  54. package/dist/nexus/graph-manifest.js +114 -0
  55. package/dist/nexus/graph-manifest.js.map +1 -0
  56. package/dist/nexus/knowledge.d.ts +21 -1
  57. package/dist/nexus/knowledge.d.ts.map +1 -1
  58. package/dist/nexus/knowledge.js +76 -37
  59. package/dist/nexus/knowledge.js.map +1 -1
  60. package/dist/nexus/path-map.d.ts +55 -0
  61. package/dist/nexus/path-map.d.ts.map +1 -0
  62. package/dist/nexus/path-map.js +87 -0
  63. package/dist/nexus/path-map.js.map +1 -0
  64. package/dist/nexus/projects-clean.d.ts +21 -28
  65. package/dist/nexus/projects-clean.d.ts.map +1 -1
  66. package/dist/nexus/projects-clean.js +168 -85
  67. package/dist/nexus/projects-clean.js.map +1 -1
  68. package/dist/nexus/query.d.ts.map +1 -1
  69. package/dist/nexus/query.js +3 -2
  70. package/dist/nexus/query.js.map +1 -1
  71. package/dist/nexus/registry-hygiene.d.ts +70 -0
  72. package/dist/nexus/registry-hygiene.d.ts.map +1 -0
  73. package/dist/nexus/registry-hygiene.js +113 -0
  74. package/dist/nexus/registry-hygiene.js.map +1 -0
  75. package/dist/nexus/registry.d.ts +2 -2
  76. package/dist/nexus/registry.d.ts.map +1 -1
  77. package/dist/nexus/registry.js +48 -12
  78. package/dist/nexus/registry.js.map +1 -1
  79. package/dist/paths.d.ts +26 -0
  80. package/dist/paths.d.ts.map +1 -1
  81. package/dist/paths.js +99 -6
  82. package/dist/paths.js.map +1 -1
  83. package/dist/render/nexus/graph/index.d.ts.map +1 -1
  84. package/dist/render/nexus/graph/index.js +8 -0
  85. package/dist/render/nexus/graph/index.js.map +1 -1
  86. package/dist/resources/spawn-wrapper.d.ts +0 -11
  87. package/dist/resources/spawn-wrapper.d.ts.map +1 -1
  88. package/dist/resources/spawn-wrapper.js +62 -6
  89. package/dist/resources/spawn-wrapper.js.map +1 -1
  90. package/dist/scaffold/ensure-config.d.ts +12 -3
  91. package/dist/scaffold/ensure-config.d.ts.map +1 -1
  92. package/dist/scaffold/ensure-config.js +61 -38
  93. package/dist/scaffold/ensure-config.js.map +1 -1
  94. package/dist/scaffold/project-identity.d.ts +136 -0
  95. package/dist/scaffold/project-identity.d.ts.map +1 -0
  96. package/dist/scaffold/project-identity.js +225 -0
  97. package/dist/scaffold/project-identity.js.map +1 -0
  98. package/dist/selfimprove/fix-gen.js +13319 -1556
  99. package/dist/selfimprove/fix-gen.js.map +3 -3
  100. package/dist/store/agent-api-key.d.ts +114 -0
  101. package/dist/store/agent-api-key.d.ts.map +1 -0
  102. package/dist/store/agent-api-key.js +136 -0
  103. package/dist/store/agent-api-key.js.map +1 -0
  104. package/dist/store/agent-registry-accessor.d.ts +4 -2
  105. package/dist/store/agent-registry-accessor.d.ts.map +1 -1
  106. package/dist/store/agent-registry-accessor.js +39 -89
  107. package/dist/store/agent-registry-accessor.js.map +1 -1
  108. package/dist/store/api-key-kdf.d.ts +7 -0
  109. package/dist/store/api-key-kdf.d.ts.map +1 -1
  110. package/dist/store/api-key-kdf.js +7 -0
  111. package/dist/store/api-key-kdf.js.map +1 -1
  112. package/dist/store/backup-crypto.d.ts +49 -0
  113. package/dist/store/backup-crypto.d.ts.map +1 -1
  114. package/dist/store/backup-crypto.js +154 -0
  115. package/dist/store/backup-crypto.js.map +1 -1
  116. package/dist/store/credential-transfer.d.ts +303 -0
  117. package/dist/store/credential-transfer.d.ts.map +1 -0
  118. package/dist/store/credential-transfer.js +774 -0
  119. package/dist/store/credential-transfer.js.map +1 -0
  120. package/dist/store/exodus/column-transforms.d.ts +79 -4
  121. package/dist/store/exodus/column-transforms.d.ts.map +1 -1
  122. package/dist/store/exodus/column-transforms.js +121 -4
  123. package/dist/store/exodus/column-transforms.js.map +1 -1
  124. package/dist/store/exodus/index.d.ts +1 -0
  125. package/dist/store/exodus/index.d.ts.map +1 -1
  126. package/dist/store/exodus/index.js +1 -0
  127. package/dist/store/exodus/index.js.map +1 -1
  128. package/dist/store/exodus/migrate.d.ts +17 -1
  129. package/dist/store/exodus/migrate.d.ts.map +1 -1
  130. package/dist/store/exodus/migrate.js +155 -33
  131. package/dist/store/exodus/migrate.js.map +1 -1
  132. package/dist/store/exodus/on-open.d.ts +14 -0
  133. package/dist/store/exodus/on-open.d.ts.map +1 -1
  134. package/dist/store/exodus/on-open.js +115 -6
  135. package/dist/store/exodus/on-open.js.map +1 -1
  136. package/dist/store/exodus/reconcile.d.ts +103 -0
  137. package/dist/store/exodus/reconcile.d.ts.map +1 -0
  138. package/dist/store/exodus/reconcile.js +728 -0
  139. package/dist/store/exodus/reconcile.js.map +1 -0
  140. package/dist/store/exodus/recovery.d.ts.map +1 -1
  141. package/dist/store/exodus/recovery.js +14 -2
  142. package/dist/store/exodus/recovery.js.map +1 -1
  143. package/dist/store/exodus/runtime-targets.d.ts +39 -0
  144. package/dist/store/exodus/runtime-targets.d.ts.map +1 -0
  145. package/dist/store/exodus/runtime-targets.js +63 -0
  146. package/dist/store/exodus/runtime-targets.js.map +1 -0
  147. package/dist/store/exodus/table-name-map.d.ts.map +1 -1
  148. package/dist/store/exodus/table-name-map.js +5 -0
  149. package/dist/store/exodus/table-name-map.js.map +1 -1
  150. package/dist/store/exodus/table-order.d.ts +35 -0
  151. package/dist/store/exodus/table-order.d.ts.map +1 -0
  152. package/dist/store/exodus/table-order.js +62 -0
  153. package/dist/store/exodus/table-order.js.map +1 -0
  154. package/dist/store/exodus/verify-migration.d.ts +4 -1
  155. package/dist/store/exodus/verify-migration.d.ts.map +1 -1
  156. package/dist/store/exodus/verify-migration.js +142 -10
  157. package/dist/store/exodus/verify-migration.js.map +1 -1
  158. package/dist/store/global-salt.d.ts +16 -0
  159. package/dist/store/global-salt.d.ts.map +1 -1
  160. package/dist/store/global-salt.js +22 -7
  161. package/dist/store/global-salt.js.map +1 -1
  162. package/dist/store/legacy-tasks-lineage.d.ts +86 -0
  163. package/dist/store/legacy-tasks-lineage.d.ts.map +1 -0
  164. package/dist/store/legacy-tasks-lineage.js +285 -0
  165. package/dist/store/legacy-tasks-lineage.js.map +1 -0
  166. package/dist/store/migration-manager.d.ts +27 -0
  167. package/dist/store/migration-manager.d.ts.map +1 -1
  168. package/dist/store/migration-manager.js +63 -0
  169. package/dist/store/migration-manager.js.map +1 -1
  170. package/dist/store/portable-bundle-import.d.ts +117 -0
  171. package/dist/store/portable-bundle-import.d.ts.map +1 -0
  172. package/dist/store/portable-bundle-import.js +589 -0
  173. package/dist/store/portable-bundle-import.js.map +1 -0
  174. package/dist/store/portable-bundle-relocate.d.ts +66 -0
  175. package/dist/store/portable-bundle-relocate.d.ts.map +1 -0
  176. package/dist/store/portable-bundle-relocate.js +304 -0
  177. package/dist/store/portable-bundle-relocate.js.map +1 -0
  178. package/dist/store/portable-bundle-scan.d.ts +167 -0
  179. package/dist/store/portable-bundle-scan.d.ts.map +1 -0
  180. package/dist/store/portable-bundle-scan.js +569 -0
  181. package/dist/store/portable-bundle-scan.js.map +1 -0
  182. package/dist/store/portable-bundle.d.ts +146 -0
  183. package/dist/store/portable-bundle.d.ts.map +1 -0
  184. package/dist/store/portable-bundle.js +674 -0
  185. package/dist/store/portable-bundle.js.map +1 -0
  186. package/dist/store/regenerators.d.ts +3 -1
  187. package/dist/store/regenerators.d.ts.map +1 -1
  188. package/dist/store/regenerators.js +6 -2
  189. package/dist/store/regenerators.js.map +1 -1
  190. package/dist/store/schema/cleo-global/nexus.d.ts +98 -0
  191. package/dist/store/schema/cleo-global/nexus.d.ts.map +1 -1
  192. package/dist/store/schema/cleo-global/nexus.js +19 -0
  193. package/dist/store/schema/cleo-global/nexus.js.map +1 -1
  194. package/dist/store/schema/nexus-schema.d.ts +102 -0
  195. package/dist/store/schema/nexus-schema.d.ts.map +1 -1
  196. package/dist/store/schema/nexus-schema.js +24 -0
  197. package/dist/store/schema/nexus-schema.js.map +1 -1
  198. package/dist/store/split-brain-import.d.ts +176 -0
  199. package/dist/store/split-brain-import.d.ts.map +1 -0
  200. package/dist/store/split-brain-import.js +680 -0
  201. package/dist/store/split-brain-import.js.map +1 -0
  202. package/dist/store/sqlite.d.ts +29 -0
  203. package/dist/store/sqlite.d.ts.map +1 -1
  204. package/dist/store/sqlite.js +57 -5
  205. package/dist/store/sqlite.js.map +1 -1
  206. package/dist/system/health.d.ts.map +1 -1
  207. package/dist/system/health.js +30 -5
  208. package/dist/system/health.js.map +1 -1
  209. package/dist/upgrade.d.ts.map +1 -1
  210. package/dist/upgrade.js +60 -0
  211. package/dist/upgrade.js.map +1 -1
  212. package/dist/validation/doctor/checks.d.ts +14 -0
  213. package/dist/validation/doctor/checks.d.ts.map +1 -1
  214. package/dist/validation/doctor/checks.js +33 -0
  215. package/dist/validation/doctor/checks.js.map +1 -1
  216. package/migrations/drizzle-cleo-global/20260924000000_t12354-project-paths/migration.sql +40 -0
  217. package/migrations/drizzle-nexus/20260924000000_t12315-nexus-parse-cache/migration.sql +21 -0
  218. package/package.json +13 -13
  219. package/templates/CLEO-INJECTION.md +3 -3
  220. package/templates/cleo-gitignore +7 -0
@@ -0,0 +1,114 @@
1
+ /**
2
+ * agent-api-key.ts — at-rest storage of agent API keys in
3
+ * `agent_registry_agents.api_key_encrypted` (global `cleo.db`).
4
+ *
5
+ * ## What went wrong (T12352)
6
+ *
7
+ * ADR-037 §5 specified the global KDF as
8
+ * `apiKey = HMAC-SHA256(machine-key ‖ global-salt, agentId)`. That output is an
9
+ * ENCRYPTION KEY: the same section says the migration "MUST decrypt each
10
+ * existing key … and re-encrypt using the new KDF", and the `AgentCredential`
11
+ * contract says the key is "stored encrypted at rest". But the variable was
12
+ * named `apiKey`, and T355 implemented it literally. `createProjectAgent`,
13
+ * `update` and `rotateKey` in `agent-registry-accessor.ts` stored the hex of
14
+ * the HMAC in `api_key_encrypted` and DISCARDED the real key, including the
15
+ * fresh key the cloud returned from `rotate-key`. The reader then returned hex
16
+ * of that hex as `apiKey`. Every consumer sends it as `Authorization: Bearer`
17
+ * to SignalDock (`cleo agent …`, `conduit` dispatch, the HTTP/SSE transports),
18
+ * and every one of them needs the real key.
19
+ *
20
+ * ## What is stored now
21
+ *
22
+ * `api_key_encrypted` holds `gk1:` followed by {@link encryptGlobal}
23
+ * ciphertext of the real key, under the global KDF ADR-037 intended, with
24
+ * credential id `agent:<agentId>`. It is recoverable after a restart. It moves
25
+ * to another device via the passphrase-sealed credential transfer
26
+ * (`store/credential-transfer.ts`), never by copying the machine-key.
27
+ *
28
+ * ## Rows written before the fix
29
+ *
30
+ * They hold 64 hex characters (the HMAC) or an unrecognised legacy value.
31
+ * Neither contains the real key, so neither can be migrated. To avoid changing
32
+ * behaviour for anything that depends on today's value, they are read back
33
+ * exactly as before, but flagged `requiresReauth` with the command that
34
+ * re-registers the agent's real key.
35
+ *
36
+ * @task T12352
37
+ * @module store/agent-api-key
38
+ */
39
+ import { type CredentialKeyOptions } from '../crypto/credentials.js';
40
+ /** Prefix marking an `api_key_encrypted` value as a T12352 global-KDF ciphertext. */
41
+ export declare const AGENT_KEY_CIPHERTEXT_PREFIX = "gk1:";
42
+ /**
43
+ * The global-KDF credential id for an agent's API key. The `agent:` prefix
44
+ * separates agent keys from other global credentials (`service:<p>:<l>`).
45
+ *
46
+ * @param agentId - Agent business identifier.
47
+ * @returns The id passed to {@link encryptGlobal} / {@link decryptGlobal}.
48
+ * @task T12352
49
+ */
50
+ export declare function agentKeyCredentialId(agentId: string): string;
51
+ /**
52
+ * How an `api_key_encrypted` value was written.
53
+ *
54
+ * - `none` — empty / NULL.
55
+ * - `ciphertext` — a T12352 `gk1:` global-KDF ciphertext (recoverable).
56
+ * - `legacy-derived` — the pre-T12352 hex HMAC (the real key was discarded).
57
+ * - `legacy-unknown` — anything else (e.g. a pre-T310 path-KDF ciphertext).
58
+ *
59
+ * @task T12352
60
+ */
61
+ export type StoredAgentKeyKind = 'none' | 'ciphertext' | 'legacy-derived' | 'legacy-unknown';
62
+ /**
63
+ * Classify a stored `api_key_encrypted` value without decrypting it.
64
+ *
65
+ * @param stored - Raw column value.
66
+ * @returns Its kind.
67
+ * @task T12352
68
+ */
69
+ export declare function classifyStoredAgentKey(stored: string | null | undefined): StoredAgentKeyKind;
70
+ /**
71
+ * Encrypt an agent API key for `api_key_encrypted`.
72
+ *
73
+ * @param apiKey - The real key (e.g. `sk_live_…`). Empty yields `null`.
74
+ * @param agentId - Agent business identifier (binds the key derivation).
75
+ * @param options - Which CLEO home's machine-key + salt (defaults to ambient).
76
+ * @returns `gk1:<ciphertext>`, or `null` when there is no key to store.
77
+ * @task T12352
78
+ */
79
+ export declare function sealAgentApiKey(apiKey: string, agentId: string, options?: CredentialKeyOptions): Promise<string | null>;
80
+ /**
81
+ * Result of reading a stored agent API key.
82
+ *
83
+ * @task T12352
84
+ */
85
+ export interface AgentApiKeyRead {
86
+ /** The value to expose as `AgentCredential.apiKey`. */
87
+ readonly apiKey: string;
88
+ /** Kind of the stored value. */
89
+ readonly kind: StoredAgentKeyKind;
90
+ /**
91
+ * True when the real key is not recoverable from storage and must be
92
+ * re-registered: legacy values, or a ciphertext this device cannot decrypt.
93
+ */
94
+ readonly requiresReauth: boolean;
95
+ /** Why re-registration is needed (absent when not). */
96
+ readonly reason?: string;
97
+ }
98
+ /**
99
+ * Read an agent API key from `api_key_encrypted`.
100
+ *
101
+ * A `gk1:` ciphertext is decrypted to the real key. Legacy values are returned
102
+ * exactly as the pre-T12352 reader returned them, so nothing that works today
103
+ * changes, but they are flagged `requiresReauth`. A ciphertext that does not
104
+ * decrypt here (another device's key) is flagged too; it never throws, because
105
+ * a listing must not fail on one bad row.
106
+ *
107
+ * @param stored - Raw column value.
108
+ * @param agentId - Agent business identifier.
109
+ * @param options - Which CLEO home's machine-key + salt (defaults to ambient).
110
+ * @returns The key to expose, its kind, and whether re-registration is needed.
111
+ * @task T12352
112
+ */
113
+ export declare function openAgentApiKey(stored: string | null | undefined, agentId: string, options?: CredentialKeyOptions): Promise<AgentApiKeyRead>;
114
+ //# sourceMappingURL=agent-api-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-api-key.d.ts","sourceRoot":"","sources":["../../src/store/agent-api-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,EAAE,KAAK,oBAAoB,EAAgC,MAAM,0BAA0B,CAAC;AAEnG,qFAAqF;AACrF,eAAO,MAAM,2BAA2B,SAAS,CAAC;AAKlD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,YAAY,GAAG,gBAAgB,GAAG,gBAAgB,CAAC;AAE7F;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,kBAAkB,CAK5F;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMxB;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gCAAgC;IAChC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,uDAAuD;IACvD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAUD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,eAAe,CAAC,CA+B1B"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * agent-api-key.ts — at-rest storage of agent API keys in
3
+ * `agent_registry_agents.api_key_encrypted` (global `cleo.db`).
4
+ *
5
+ * ## What went wrong (T12352)
6
+ *
7
+ * ADR-037 §5 specified the global KDF as
8
+ * `apiKey = HMAC-SHA256(machine-key ‖ global-salt, agentId)`. That output is an
9
+ * ENCRYPTION KEY: the same section says the migration "MUST decrypt each
10
+ * existing key … and re-encrypt using the new KDF", and the `AgentCredential`
11
+ * contract says the key is "stored encrypted at rest". But the variable was
12
+ * named `apiKey`, and T355 implemented it literally. `createProjectAgent`,
13
+ * `update` and `rotateKey` in `agent-registry-accessor.ts` stored the hex of
14
+ * the HMAC in `api_key_encrypted` and DISCARDED the real key, including the
15
+ * fresh key the cloud returned from `rotate-key`. The reader then returned hex
16
+ * of that hex as `apiKey`. Every consumer sends it as `Authorization: Bearer`
17
+ * to SignalDock (`cleo agent …`, `conduit` dispatch, the HTTP/SSE transports),
18
+ * and every one of them needs the real key.
19
+ *
20
+ * ## What is stored now
21
+ *
22
+ * `api_key_encrypted` holds `gk1:` followed by {@link encryptGlobal}
23
+ * ciphertext of the real key, under the global KDF ADR-037 intended, with
24
+ * credential id `agent:<agentId>`. It is recoverable after a restart. It moves
25
+ * to another device via the passphrase-sealed credential transfer
26
+ * (`store/credential-transfer.ts`), never by copying the machine-key.
27
+ *
28
+ * ## Rows written before the fix
29
+ *
30
+ * They hold 64 hex characters (the HMAC) or an unrecognised legacy value.
31
+ * Neither contains the real key, so neither can be migrated. To avoid changing
32
+ * behaviour for anything that depends on today's value, they are read back
33
+ * exactly as before, but flagged `requiresReauth` with the command that
34
+ * re-registers the agent's real key.
35
+ *
36
+ * @task T12352
37
+ * @module store/agent-api-key
38
+ */
39
+ import { decryptGlobal, encryptGlobal } from '../crypto/credentials.js';
40
+ /** Prefix marking an `api_key_encrypted` value as a T12352 global-KDF ciphertext. */
41
+ export const AGENT_KEY_CIPHERTEXT_PREFIX = 'gk1:';
42
+ /** Shape of a pre-T12352 value: the hex HMAC the old code stored instead of the key. */
43
+ const LEGACY_DERIVED_PATTERN = /^[0-9a-f]{64}$/;
44
+ /**
45
+ * The global-KDF credential id for an agent's API key. The `agent:` prefix
46
+ * separates agent keys from other global credentials (`service:<p>:<l>`).
47
+ *
48
+ * @param agentId - Agent business identifier.
49
+ * @returns The id passed to {@link encryptGlobal} / {@link decryptGlobal}.
50
+ * @task T12352
51
+ */
52
+ export function agentKeyCredentialId(agentId) {
53
+ return `agent:${agentId}`;
54
+ }
55
+ /**
56
+ * Classify a stored `api_key_encrypted` value without decrypting it.
57
+ *
58
+ * @param stored - Raw column value.
59
+ * @returns Its kind.
60
+ * @task T12352
61
+ */
62
+ export function classifyStoredAgentKey(stored) {
63
+ if (stored === null || stored === undefined || stored === '')
64
+ return 'none';
65
+ if (stored.startsWith(AGENT_KEY_CIPHERTEXT_PREFIX))
66
+ return 'ciphertext';
67
+ if (LEGACY_DERIVED_PATTERN.test(stored))
68
+ return 'legacy-derived';
69
+ return 'legacy-unknown';
70
+ }
71
+ /**
72
+ * Encrypt an agent API key for `api_key_encrypted`.
73
+ *
74
+ * @param apiKey - The real key (e.g. `sk_live_…`). Empty yields `null`.
75
+ * @param agentId - Agent business identifier (binds the key derivation).
76
+ * @param options - Which CLEO home's machine-key + salt (defaults to ambient).
77
+ * @returns `gk1:<ciphertext>`, or `null` when there is no key to store.
78
+ * @task T12352
79
+ */
80
+ export async function sealAgentApiKey(apiKey, agentId, options = {}) {
81
+ if (apiKey.length === 0)
82
+ return null;
83
+ return (AGENT_KEY_CIPHERTEXT_PREFIX +
84
+ (await encryptGlobal(apiKey, agentKeyCredentialId(agentId), options)));
85
+ }
86
+ /**
87
+ * The value pre-T12352 readers returned for a stored string: its UTF-8 bytes
88
+ * as hex. Kept for legacy rows so their observable `apiKey` does not change.
89
+ */
90
+ function legacyReadValue(stored) {
91
+ return Buffer.from(stored).toString('hex');
92
+ }
93
+ /**
94
+ * Read an agent API key from `api_key_encrypted`.
95
+ *
96
+ * A `gk1:` ciphertext is decrypted to the real key. Legacy values are returned
97
+ * exactly as the pre-T12352 reader returned them, so nothing that works today
98
+ * changes, but they are flagged `requiresReauth`. A ciphertext that does not
99
+ * decrypt here (another device's key) is flagged too; it never throws, because
100
+ * a listing must not fail on one bad row.
101
+ *
102
+ * @param stored - Raw column value.
103
+ * @param agentId - Agent business identifier.
104
+ * @param options - Which CLEO home's machine-key + salt (defaults to ambient).
105
+ * @returns The key to expose, its kind, and whether re-registration is needed.
106
+ * @task T12352
107
+ */
108
+ export async function openAgentApiKey(stored, agentId, options = {}) {
109
+ const kind = classifyStoredAgentKey(stored);
110
+ if (kind === 'none' || stored === null || stored === undefined) {
111
+ return { apiKey: '', kind, requiresReauth: false };
112
+ }
113
+ if (kind === 'ciphertext') {
114
+ try {
115
+ const apiKey = await decryptGlobal(stored.slice(AGENT_KEY_CIPHERTEXT_PREFIX.length), agentKeyCredentialId(agentId), options);
116
+ return { apiKey, kind, requiresReauth: false };
117
+ }
118
+ catch (err) {
119
+ return {
120
+ apiKey: '',
121
+ kind,
122
+ requiresReauth: true,
123
+ reason: `stored key does not decrypt on this device: ${err instanceof Error ? err.message : String(err)}`,
124
+ };
125
+ }
126
+ }
127
+ return {
128
+ apiKey: legacyReadValue(stored),
129
+ kind,
130
+ requiresReauth: true,
131
+ reason: kind === 'legacy-derived'
132
+ ? 'stored before T12352: the column holds a derived HMAC, not the API key, which was discarded'
133
+ : 'stored in an unrecognised legacy format that does not contain a recoverable API key',
134
+ };
135
+ }
136
+ //# sourceMappingURL=agent-api-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-api-key.js","sourceRoot":"","sources":["../../src/store/agent-api-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,EAA6B,aAAa,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEnG,qFAAqF;AACrF,MAAM,CAAC,MAAM,2BAA2B,GAAG,MAAM,CAAC;AAElD,wFAAwF;AACxF,MAAM,sBAAsB,GAAG,gBAAgB,CAAC;AAEhD;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAe;IAClD,OAAO,SAAS,OAAO,EAAE,CAAC;AAC5B,CAAC;AAcD;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAiC;IACtE,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,EAAE;QAAE,OAAO,MAAM,CAAC;IAC5E,IAAI,MAAM,CAAC,UAAU,CAAC,2BAA2B,CAAC;QAAE,OAAO,YAAY,CAAC;IACxE,IAAI,sBAAsB,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,gBAAgB,CAAC;IACjE,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAc,EACd,OAAe,EACf,UAAgC,EAAE;IAElC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,OAAO,CACL,2BAA2B;QAC3B,CAAC,MAAM,aAAa,CAAC,MAAM,EAAE,oBAAoB,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,CACtE,CAAC;AACJ,CAAC;AAqBD;;;GAGG;AACH,SAAS,eAAe,CAAC,MAAc;IACrC,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAiC,EACjC,OAAe,EACf,UAAgC,EAAE;IAElC,MAAM,IAAI,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,IAAI,KAAK,MAAM,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QAC/D,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;IACrD,CAAC;IACD,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;QAC1B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,aAAa,CAChC,MAAM,CAAC,KAAK,CAAC,2BAA2B,CAAC,MAAM,CAAC,EAChD,oBAAoB,CAAC,OAAO,CAAC,EAC7B,OAAO,CACR,CAAC;YACF,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;QACjD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,MAAM,EAAE,EAAE;gBACV,IAAI;gBACJ,cAAc,EAAE,IAAI;gBACpB,MAAM,EAAE,+CAA+C,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;aAC1G,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO;QACL,MAAM,EAAE,eAAe,CAAC,MAAM,CAAC;QAC/B,IAAI;QACJ,cAAc,EAAE,IAAI;QACpB,MAAM,EACJ,IAAI,KAAK,gBAAgB;YACvB,CAAC,CAAC,6FAA6F;YAC/F,CAAC,CAAC,qFAAqF;KAC5F,CAAC;AACJ,CAAC"}
@@ -174,7 +174,9 @@ export declare function listAgentsForProject(projectRoot: string, opts?: {
174
174
  * Write order: global first, then project ref. If the project ref write fails,
175
175
  * the global row remains (recoverable via `cleo agent attach <id>`).
176
176
  *
177
- * API key derivation: HMAC-SHA256(machineKey || globalSalt, agentId) per ADR-037 §5.
177
+ * API key storage (T12352): the real `spec.apiKey` is stored encrypted under the
178
+ * global KDF (ADR-037 §5) — see `agent-api-key.ts`. An empty key on re-register
179
+ * keeps the stored one.
178
180
  *
179
181
  * @param projectRoot - Absolute path to the project root directory.
180
182
  * @param spec - Agent creation spec (without createdAt/updatedAt).
@@ -338,7 +340,7 @@ export declare class AgentRegistryAccessor implements AgentRegistryAPI {
338
340
  force?: boolean;
339
341
  }): Promise<void>;
340
342
  /**
341
- * Rotate API key via cloud endpoint and re-encrypt with the new T310 KDF
343
+ * Rotate API key via cloud endpoint and store the NEW key encrypted (T12352)
342
344
  * in global cleo.db (Agent Registry).
343
345
  *
344
346
  * @param agentId - Agent business identifier.
@@ -1 +1 @@
1
- {"version":3,"file":"agent-registry-accessor.d.ts","sourceRoot":"","sources":["../../src/store/agent-registry-accessor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent-registry-accessor.d.ts","sourceRoot":"","sources":["../../src/store/agent-registry-accessor.ts"],"names":[],"mappings":"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"}
@@ -42,15 +42,10 @@
42
42
  * @epic T310
43
43
  * @epic T11249
44
44
  */
45
- import { randomBytes } from 'node:crypto';
46
- import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
47
45
  import { createRequire } from 'node:module';
48
- import { join } from 'node:path';
49
- import { getCleoHome } from '../paths.js';
46
+ import { openAgentApiKey, sealAgentApiKey } from './agent-api-key.js';
50
47
  import { ensureGlobalAgentRegistryDb, getGlobalAgentRegistryNativeDb, } from './agent-registry-store.js';
51
- import { deriveApiKey } from './api-key-kdf.js';
52
48
  import { ensureConduitDb, getConduitDbPath } from './conduit-sqlite.js';
53
- import { getGlobalSalt } from './global-salt.js';
54
49
  import { applyPerfPragmas } from './sqlite-pragmas.js';
55
50
  // ---------------------------------------------------------------------------
56
51
  // node:sqlite interop (createRequire for ESM / Vitest compat)
@@ -58,45 +53,6 @@ import { applyPerfPragmas } from './sqlite-pragmas.js';
58
53
  const _require = createRequire(import.meta.url);
59
54
  const { DatabaseSync } = _require('node:sqlite');
60
55
  // ---------------------------------------------------------------------------
61
- // Machine-key helper (internal — mirrors credentials.ts private getMachineKey)
62
- // ---------------------------------------------------------------------------
63
- /** Machine-key constants. */
64
- const MACHINE_KEY_LENGTH = 32;
65
- /**
66
- * Read or auto-generate the machine key (32 bytes).
67
- * Machine key lives at `getCleoHome()/machine-key` (same XDG root as the global salt).
68
- *
69
- * @returns A 32-byte Buffer.
70
- * @task T355
71
- * @epic T310
72
- */
73
- function readMachineKey() {
74
- const keyPath = join(getCleoHome(), 'machine-key');
75
- if (!existsSync(keyPath)) {
76
- const cleoHome = getCleoHome();
77
- if (!existsSync(cleoHome)) {
78
- mkdirSync(cleoHome, { recursive: true });
79
- }
80
- const key = randomBytes(MACHINE_KEY_LENGTH);
81
- writeFileSync(keyPath, key, { mode: 0o600 });
82
- return key;
83
- }
84
- // Validate permissions on POSIX
85
- if (process.platform !== 'win32') {
86
- const stat = statSync(keyPath);
87
- const mode = stat.mode & 0o777;
88
- if (mode !== 0o600) {
89
- throw new Error(`Machine key at ${keyPath} has wrong permissions: expected 0o600, got 0o${mode.toString(8)}. ` +
90
- `Fix with: chmod 600 ${keyPath}`);
91
- }
92
- }
93
- const key = readFileSync(keyPath);
94
- if (key.length !== MACHINE_KEY_LENGTH) {
95
- throw new Error(`Machine key at ${keyPath} has wrong length: expected ${MACHINE_KEY_LENGTH} bytes, got ${key.length}.`);
96
- }
97
- return key;
98
- }
99
- // ---------------------------------------------------------------------------
100
56
  // Row-to-type converters
101
57
  // ---------------------------------------------------------------------------
102
58
  /**
@@ -118,24 +74,26 @@ function rowToProjectRef(row) {
118
74
  };
119
75
  }
120
76
  /**
121
- * Convert a global cleo.db:agent_registry_agents row to an `AgentCredential`.
122
- * API key is stored as binary (derived via KDF) — returned as hex string.
123
- * Legacy encrypted values (pre-T310) are left as-is; the reauth flag handles
124
- * forced re-authentication at the CLI layer.
77
+ * Convert a global cleo.db:agent_registry_agents row to an `AgentCredential`,
78
+ * decrypting the stored API key (T12352).
79
+ *
80
+ * `api_key_encrypted` holds a `gk1:` global-KDF ciphertext of the real key.
81
+ * Rows written before T12352 hold a derived HMAC instead (the real key was
82
+ * discarded); they read back exactly as before and carry `requiresReauth`.
125
83
  *
126
84
  * @param row - Raw SQLite row from global cleo.db:agent_registry_agents.
127
- * @returns Typed `AgentCredential` (apiKey is hex-encoded derived bytes or empty).
85
+ * @returns Typed `AgentCredential`.
128
86
  * @task T355
87
+ * @task T12352
129
88
  * @epic T310
130
89
  */
131
- function rowToCredential(row) {
90
+ async function rowToCredential(row) {
91
+ const key = await openAgentApiKey(row.api_key_encrypted, row.agent_id);
132
92
  return {
133
93
  agentId: row.agent_id,
134
94
  displayName: row.name,
135
- // api_key_encrypted stores the KDF-derived key as binary or a legacy ciphertext.
136
- // Return as hex-encoded bytes for callers that need the raw key.
137
- // The reauth flow in `cleo agent auth` handles re-keying (T358).
138
- apiKey: row.api_key_encrypted ? Buffer.from(row.api_key_encrypted).toString('hex') : '',
95
+ apiKey: key.apiKey,
96
+ ...(key.requiresReauth ? { requiresReauth: true } : {}),
139
97
  apiBaseUrl: row.api_base_url,
140
98
  classification: row.classification ?? undefined,
141
99
  privacyTier: row.privacy_tier,
@@ -239,9 +197,9 @@ export function rowToResolvedAgent(row) {
239
197
  * @task T355
240
198
  * @epic T310
241
199
  */
242
- function mergeToAgentWithOverride(agentRow, refRow) {
200
+ async function mergeToAgentWithOverride(agentRow, refRow) {
243
201
  return {
244
- ...rowToCredential(agentRow),
202
+ ...(await rowToCredential(agentRow)),
245
203
  projectRef: refRow ? rowToProjectRef(refRow) : null,
246
204
  };
247
205
  }
@@ -397,11 +355,11 @@ export async function lookupAgent(projectRoot, agentId, opts) {
397
355
  // INNER JOIN semantics: must have a project ref with enabled=1
398
356
  if (!refRow || refRow.enabled === 0)
399
357
  return null;
400
- return mergeToAgentWithOverride(agentRow, refRow);
358
+ return await mergeToAgentWithOverride(agentRow, refRow);
401
359
  }
402
360
  // includeGlobal=true: return global agent; populate projectRef only when enabled=1
403
361
  const effectiveRef = refRow && refRow.enabled === 1 ? refRow : null;
404
- return mergeToAgentWithOverride(agentRow, effectiveRef);
362
+ return await mergeToAgentWithOverride(agentRow, effectiveRef);
405
363
  }
406
364
  finally {
407
365
  // NOTE: globalDb is the SHARED dual-scope handle — never close it (T11562).
@@ -464,7 +422,7 @@ export async function listAgentsForProject(projectRoot, opts) {
464
422
  if (includeGlobal) {
465
423
  // Return all global agents; populate projectRef only for attached ones
466
424
  const effectiveRef = ref && ref.enabled === 1 ? ref : null;
467
- result.push(mergeToAgentWithOverride(agentRow, effectiveRef));
425
+ result.push(await mergeToAgentWithOverride(agentRow, effectiveRef));
468
426
  }
469
427
  else {
470
428
  // INNER JOIN: only agents with a project ref row
@@ -472,7 +430,7 @@ export async function listAgentsForProject(projectRoot, opts) {
472
430
  continue;
473
431
  if (!includeDisabled && ref.enabled === 0)
474
432
  continue;
475
- result.push(mergeToAgentWithOverride(agentRow, ref));
433
+ result.push(await mergeToAgentWithOverride(agentRow, ref));
476
434
  }
477
435
  }
478
436
  return result;
@@ -489,7 +447,9 @@ export async function listAgentsForProject(projectRoot, opts) {
489
447
  * Write order: global first, then project ref. If the project ref write fails,
490
448
  * the global row remains (recoverable via `cleo agent attach <id>`).
491
449
  *
492
- * API key derivation: HMAC-SHA256(machineKey || globalSalt, agentId) per ADR-037 §5.
450
+ * API key storage (T12352): the real `spec.apiKey` is stored encrypted under the
451
+ * global KDF (ADR-037 §5) — see `agent-api-key.ts`. An empty key on re-register
452
+ * keeps the stored one.
493
453
  *
494
454
  * @param projectRoot - Absolute path to the project root directory.
495
455
  * @param spec - Agent creation spec (without createdAt/updatedAt).
@@ -506,16 +466,9 @@ export async function createProjectAgent(projectRoot, spec) {
506
466
  await ensureConduitDb(projectRoot);
507
467
  const nowTs = Math.floor(Date.now() / 1000);
508
468
  const nowIso = new Date(nowTs * 1000).toISOString();
509
- // Derive API key using the T310 KDF
510
- const machineKey = readMachineKey();
511
- const globalSalt = getGlobalSalt();
512
- const derivedKey = deriveApiKey({
513
- machineKey,
514
- globalSalt,
515
- agentId: spec.agentId,
516
- });
517
- // Store as hex string in the encrypted column
518
- const apiKeyEncrypted = derivedKey.toString('hex');
469
+ // T12352: store the REAL key, encrypted under the global KDF (ADR-037 §5).
470
+ // null = no key supplied: a re-register keeps the stored key.
471
+ const apiKeyEncrypted = await sealAgentApiKey(spec.apiKey, spec.agentId);
519
472
  // SHARED global cleo.db handle — do NOT close (lifecycle owned by openDualScopeDb).
520
473
  const globalDb = await openGlobalDb();
521
474
  {
@@ -540,9 +493,11 @@ export async function createProjectAgent(projectRoot, spec) {
540
493
  // Update identity in global DB (idempotent re-register)
541
494
  globalDb
542
495
  .prepare(`UPDATE agent_registry_agents SET name = ?, class = ?, privacy_tier = ?, capabilities = ?, skills = ?,
543
- transport_type = ?, api_key_encrypted = ?, api_base_url = ?, classification = ?,
496
+ transport_type = ?, api_key_encrypted = COALESCE(?, api_key_encrypted),
497
+ requires_reauth = CASE WHEN ? IS NULL THEN requires_reauth ELSE 0 END,
498
+ api_base_url = ?, classification = ?,
544
499
  transport_config = ?, is_active = ?, updated_at = ? WHERE agent_id = ?`)
545
- .run(spec.displayName, spec.classification ?? 'custom', spec.privacyTier, JSON.stringify(spec.capabilities), JSON.stringify(spec.skills), spec.transportType ?? 'http', apiKeyEncrypted, spec.apiBaseUrl, spec.classification ?? null, JSON.stringify(spec.transportConfig), spec.isActive ? 1 : 0, nowIso, spec.agentId);
500
+ .run(spec.displayName, spec.classification ?? 'custom', spec.privacyTier, JSON.stringify(spec.capabilities), JSON.stringify(spec.skills), spec.transportType ?? 'http', apiKeyEncrypted, apiKeyEncrypted, spec.apiBaseUrl, spec.classification ?? null, JSON.stringify(spec.transportConfig), spec.isActive ? 1 : 0, nowIso, spec.agentId);
546
501
  syncJunctionTables(globalDb, agentUuid, spec.capabilities, spec.skills);
547
502
  }
548
503
  }
@@ -779,7 +734,7 @@ export class AgentRegistryAccessor {
779
734
  : globalDb
780
735
  .prepare('SELECT * FROM agent_registry_agents ORDER BY name ASC')
781
736
  .all();
782
- return rows.map(rowToCredential);
737
+ return Promise.all(rows.map(rowToCredential));
783
738
  }
784
739
  /**
785
740
  * Update agent identity fields in global cleo.db (Agent Registry).
@@ -841,12 +796,9 @@ export class AgentRegistryAccessor {
841
796
  params.push(updates.isActive ? 1 : 0);
842
797
  }
843
798
  if (updates.apiKey !== undefined) {
844
- // Re-derive using new T310 KDF
845
- const machineKey = readMachineKey();
846
- const globalSalt = getGlobalSalt();
847
- const derivedKey = deriveApiKey({ machineKey, globalSalt, agentId });
848
- sets.push('api_key_encrypted = ?');
849
- params.push(derivedKey.toString('hex'));
799
+ // T12352: store the supplied key itself, encrypted (was: a derived HMAC).
800
+ sets.push('api_key_encrypted = ?', 'requires_reauth = 0');
801
+ params.push(await sealAgentApiKey(updates.apiKey, agentId));
850
802
  }
851
803
  params.push(agentId);
852
804
  globalDb
@@ -933,7 +885,7 @@ export class AgentRegistryAccessor {
933
885
  // NOTE: globalDb is the SHARED dual-scope handle — never close it (T11562).
934
886
  }
935
887
  /**
936
- * Rotate API key via cloud endpoint and re-encrypt with the new T310 KDF
888
+ * Rotate API key via cloud endpoint and store the NEW key encrypted (T12352)
937
889
  * in global cleo.db (Agent Registry).
938
890
  *
939
891
  * @param agentId - Agent business identifier.
@@ -960,17 +912,15 @@ export class AgentRegistryAccessor {
960
912
  const newApiKey = data.data?.apiKey;
961
913
  if (!newApiKey)
962
914
  throw new Error('Cloud API did not return a new API key');
963
- // Re-derive and store using T310 KDF
964
- const machineKey = readMachineKey();
965
- const globalSalt = getGlobalSalt();
966
- const derivedKey = deriveApiKey({ machineKey, globalSalt, agentId });
915
+ // T12352: keep the key the cloud just issued (it was discarded before).
916
+ const sealedKey = await sealAgentApiKey(newApiKey, agentId);
967
917
  // T11622 cutover: `updated_at` is TEXT ISO-8601 (GLOB CHECK).
968
918
  const nowIso = new Date().toISOString();
969
919
  // SHARED global cleo.db handle — do NOT close (lifecycle owned by openDualScopeDb).
970
920
  const globalDb = await openGlobalDb();
971
921
  globalDb
972
922
  .prepare('UPDATE agent_registry_agents SET api_key_encrypted = ?, updated_at = ?, requires_reauth = 0 WHERE agent_id = ?')
973
- .run(derivedKey.toString('hex'), nowIso, agentId);
923
+ .run(sealedKey, nowIso, agentId);
974
924
  // NOTE: globalDb is the SHARED dual-scope handle — never close it (T11562).
975
925
  return { agentId, newApiKey: `${newApiKey.substring(0, 8)}...rotated` };
976
926
  }
@@ -996,7 +946,7 @@ export class AgentRegistryAccessor {
996
946
  .prepare('SELECT * FROM agent_registry_agents WHERE agent_id = ? AND is_active = 1')
997
947
  .get(ref.agent_id);
998
948
  if (agentRow)
999
- return rowToCredential(agentRow);
949
+ return await rowToCredential(agentRow);
1000
950
  }
1001
951
  // Fall back to global last_used_at if no project-local activity recorded
1002
952
  const row = globalDb
@@ -1004,7 +954,7 @@ export class AgentRegistryAccessor {
1004
954
  .get();
1005
955
  if (!row)
1006
956
  return null;
1007
- return rowToCredential(row);
957
+ return await rowToCredential(row);
1008
958
  }
1009
959
  finally {
1010
960
  // NOTE: globalDb is the SHARED dual-scope handle — never close it (T11562).