@hs-x/cli 0.2.7 → 0.3.6

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 (285) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +25 -8
  3. package/dist/audit-interactive.d.ts +29 -0
  4. package/dist/audit-interactive.d.ts.map +1 -0
  5. package/dist/audit-interactive.js +156 -0
  6. package/dist/audit-interactive.js.map +1 -0
  7. package/dist/bin/hs-x.js.map +1 -1
  8. package/dist/checkpoint-interactive.d.ts +24 -0
  9. package/dist/checkpoint-interactive.d.ts.map +1 -0
  10. package/dist/checkpoint-interactive.js +65 -0
  11. package/dist/checkpoint-interactive.js.map +1 -0
  12. package/dist/cli/index.d.ts.map +1 -1
  13. package/dist/cli/index.js +266 -39
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/cli/kit.d.ts +1 -1
  16. package/dist/cli/kit.d.ts.map +1 -1
  17. package/dist/cli/kit.js +19 -3
  18. package/dist/cli/kit.js.map +1 -1
  19. package/dist/cloudflare-auth.d.ts +8 -0
  20. package/dist/cloudflare-auth.d.ts.map +1 -1
  21. package/dist/cloudflare-auth.js +12 -2
  22. package/dist/cloudflare-auth.js.map +1 -1
  23. package/dist/cloudflare-oauth-store.d.ts +9 -1
  24. package/dist/cloudflare-oauth-store.d.ts.map +1 -1
  25. package/dist/cloudflare-oauth-store.js +23 -4
  26. package/dist/cloudflare-oauth-store.js.map +1 -1
  27. package/dist/cloudflare-oauth.d.ts +1 -0
  28. package/dist/cloudflare-oauth.d.ts.map +1 -1
  29. package/dist/cloudflare-oauth.js +6 -2
  30. package/dist/cloudflare-oauth.js.map +1 -1
  31. package/dist/commands/account.d.ts +28 -13
  32. package/dist/commands/account.d.ts.map +1 -1
  33. package/dist/commands/account.js +219 -69
  34. package/dist/commands/account.js.map +1 -1
  35. package/dist/commands/api.d.ts.map +1 -1
  36. package/dist/commands/api.js +7 -11
  37. package/dist/commands/api.js.map +1 -1
  38. package/dist/commands/completion.d.ts.map +1 -1
  39. package/dist/commands/completion.js +13 -7
  40. package/dist/commands/completion.js.map +1 -1
  41. package/dist/commands/connect.d.ts.map +1 -1
  42. package/dist/commands/connect.js +239 -125
  43. package/dist/commands/connect.js.map +1 -1
  44. package/dist/commands/control-plane-read.d.ts +71 -10
  45. package/dist/commands/control-plane-read.d.ts.map +1 -1
  46. package/dist/commands/control-plane-read.js +1035 -58
  47. package/dist/commands/control-plane-read.js.map +1 -1
  48. package/dist/commands/deploy-promote.d.ts +25 -3
  49. package/dist/commands/deploy-promote.d.ts.map +1 -1
  50. package/dist/commands/deploy-promote.js +314 -38
  51. package/dist/commands/deploy-promote.js.map +1 -1
  52. package/dist/commands/deploy.d.ts +69 -4
  53. package/dist/commands/deploy.d.ts.map +1 -1
  54. package/dist/commands/deploy.js +1512 -276
  55. package/dist/commands/deploy.js.map +1 -1
  56. package/dist/commands/dev.d.ts +5 -1
  57. package/dist/commands/dev.d.ts.map +1 -1
  58. package/dist/commands/dev.js +502 -107
  59. package/dist/commands/dev.js.map +1 -1
  60. package/dist/commands/doctor.d.ts +14 -0
  61. package/dist/commands/doctor.d.ts.map +1 -1
  62. package/dist/commands/doctor.js +131 -20
  63. package/dist/commands/doctor.js.map +1 -1
  64. package/dist/commands/flags.d.ts +108 -6
  65. package/dist/commands/flags.d.ts.map +1 -1
  66. package/dist/commands/flags.js +996 -75
  67. package/dist/commands/flags.js.map +1 -1
  68. package/dist/commands/help-command.d.ts +14 -0
  69. package/dist/commands/help-command.d.ts.map +1 -1
  70. package/dist/commands/help-command.js +144 -44
  71. package/dist/commands/help-command.js.map +1 -1
  72. package/dist/commands/history.js +14 -10
  73. package/dist/commands/history.js.map +1 -1
  74. package/dist/commands/init.d.ts +4 -2
  75. package/dist/commands/init.d.ts.map +1 -1
  76. package/dist/commands/init.js +98 -17
  77. package/dist/commands/init.js.map +1 -1
  78. package/dist/commands/link.d.ts +2 -3
  79. package/dist/commands/link.d.ts.map +1 -1
  80. package/dist/commands/link.js +201 -101
  81. package/dist/commands/link.js.map +1 -1
  82. package/dist/commands/login.d.ts.map +1 -1
  83. package/dist/commands/login.js +22 -5
  84. package/dist/commands/login.js.map +1 -1
  85. package/dist/commands/migrate.d.ts +37 -17
  86. package/dist/commands/migrate.d.ts.map +1 -1
  87. package/dist/commands/migrate.js +500 -73
  88. package/dist/commands/migrate.js.map +1 -1
  89. package/dist/commands/rollback.d.ts +42 -2
  90. package/dist/commands/rollback.d.ts.map +1 -1
  91. package/dist/commands/rollback.js +422 -58
  92. package/dist/commands/rollback.js.map +1 -1
  93. package/dist/commands/secrets.d.ts +76 -3
  94. package/dist/commands/secrets.d.ts.map +1 -1
  95. package/dist/commands/secrets.js +328 -41
  96. package/dist/commands/secrets.js.map +1 -1
  97. package/dist/commands/status.d.ts +13 -2
  98. package/dist/commands/status.d.ts.map +1 -1
  99. package/dist/commands/status.js +154 -17
  100. package/dist/commands/status.js.map +1 -1
  101. package/dist/commands/unlink.d.ts +2 -2
  102. package/dist/commands/unlink.d.ts.map +1 -1
  103. package/dist/commands/unlink.js +2 -2
  104. package/dist/commands/unlink.js.map +1 -1
  105. package/dist/commands/update.d.ts.map +1 -1
  106. package/dist/commands/update.js +4 -3
  107. package/dist/commands/update.js.map +1 -1
  108. package/dist/commands/validate.d.ts +12 -3
  109. package/dist/commands/validate.d.ts.map +1 -1
  110. package/dist/commands/validate.js +70 -8
  111. package/dist/commands/validate.js.map +1 -1
  112. package/dist/constants.d.ts +1 -1
  113. package/dist/constants.js +1 -1
  114. package/dist/control-plane-fetch.d.ts.map +1 -1
  115. package/dist/control-plane-fetch.js +5 -4
  116. package/dist/control-plane-fetch.js.map +1 -1
  117. package/dist/deploy-revision-capture.d.ts +48 -0
  118. package/dist/deploy-revision-capture.d.ts.map +1 -0
  119. package/dist/deploy-revision-capture.js +125 -0
  120. package/dist/deploy-revision-capture.js.map +1 -0
  121. package/dist/dev/event-bus.d.ts +5 -2
  122. package/dist/dev/event-bus.d.ts.map +1 -1
  123. package/dist/dev/event-bus.js +4 -1
  124. package/dist/dev/event-bus.js.map +1 -1
  125. package/dist/dev/hubspot-logs-poller.d.ts +35 -0
  126. package/dist/dev/hubspot-logs-poller.d.ts.map +1 -0
  127. package/dist/dev/hubspot-logs-poller.js +94 -0
  128. package/dist/dev/hubspot-logs-poller.js.map +1 -0
  129. package/dist/dev/invoke.d.ts +9 -1
  130. package/dist/dev/invoke.d.ts.map +1 -1
  131. package/dist/dev/invoke.js +69 -20
  132. package/dist/dev/invoke.js.map +1 -1
  133. package/dist/dev/keyboard.d.ts +31 -0
  134. package/dist/dev/keyboard.d.ts.map +1 -0
  135. package/dist/dev/keyboard.js +143 -0
  136. package/dist/dev/keyboard.js.map +1 -0
  137. package/dist/dev/log-server.d.ts +7 -0
  138. package/dist/dev/log-server.d.ts.map +1 -1
  139. package/dist/dev/log-server.js +68 -31
  140. package/dist/dev/log-server.js.map +1 -1
  141. package/dist/dev/migrated-card.d.ts +17 -0
  142. package/dist/dev/migrated-card.d.ts.map +1 -0
  143. package/dist/dev/migrated-card.js +114 -0
  144. package/dist/dev/migrated-card.js.map +1 -0
  145. package/dist/dev/stream-filter.d.ts +40 -0
  146. package/dist/dev/stream-filter.d.ts.map +1 -0
  147. package/dist/dev/stream-filter.js +93 -0
  148. package/dist/dev/stream-filter.js.map +1 -0
  149. package/dist/dev/stream-renderer.d.ts +34 -0
  150. package/dist/dev/stream-renderer.d.ts.map +1 -1
  151. package/dist/dev/stream-renderer.js +222 -8
  152. package/dist/dev/stream-renderer.js.map +1 -1
  153. package/dist/errors-registry.d.ts.map +1 -1
  154. package/dist/errors-registry.js +280 -36
  155. package/dist/errors-registry.js.map +1 -1
  156. package/dist/errors.d.ts.map +1 -1
  157. package/dist/errors.js.map +1 -1
  158. package/dist/help.d.ts.map +1 -1
  159. package/dist/help.js +8 -0
  160. package/dist/help.js.map +1 -1
  161. package/dist/hubspot-app-logs.d.ts +76 -0
  162. package/dist/hubspot-app-logs.d.ts.map +1 -0
  163. package/dist/hubspot-app-logs.js +323 -0
  164. package/dist/hubspot-app-logs.js.map +1 -0
  165. package/dist/hubspot-auth.d.ts +8 -2
  166. package/dist/hubspot-auth.d.ts.map +1 -1
  167. package/dist/hubspot-auth.js +9 -9
  168. package/dist/hubspot-auth.js.map +1 -1
  169. package/dist/hubspot-developer-client.js +119 -11
  170. package/dist/hubspot-developer-client.js.map +1 -1
  171. package/dist/index.d.ts +1 -0
  172. package/dist/index.d.ts.map +1 -1
  173. package/dist/index.js +1 -0
  174. package/dist/index.js.map +1 -1
  175. package/dist/init/templates.d.ts +11 -1
  176. package/dist/init/templates.d.ts.map +1 -1
  177. package/dist/init/templates.js +228 -15
  178. package/dist/init/templates.js.map +1 -1
  179. package/dist/link-migration-journal.d.ts +131 -0
  180. package/dist/link-migration-journal.d.ts.map +1 -0
  181. package/dist/link-migration-journal.js +395 -0
  182. package/dist/link-migration-journal.js.map +1 -0
  183. package/dist/link-preflight.d.ts +78 -0
  184. package/dist/link-preflight.d.ts.map +1 -0
  185. package/dist/link-preflight.js +231 -0
  186. package/dist/link-preflight.js.map +1 -0
  187. package/dist/local-project-picker.d.ts +34 -0
  188. package/dist/local-project-picker.d.ts.map +1 -0
  189. package/dist/local-project-picker.js +121 -0
  190. package/dist/local-project-picker.js.map +1 -0
  191. package/dist/logs-interactive.d.ts +37 -0
  192. package/dist/logs-interactive.d.ts.map +1 -0
  193. package/dist/logs-interactive.js +110 -0
  194. package/dist/logs-interactive.js.map +1 -0
  195. package/dist/output-context.d.ts +13 -0
  196. package/dist/output-context.d.ts.map +1 -0
  197. package/dist/output-context.js +46 -0
  198. package/dist/output-context.js.map +1 -0
  199. package/dist/project-context.d.ts +55 -0
  200. package/dist/project-context.d.ts.map +1 -0
  201. package/dist/project-context.js +115 -0
  202. package/dist/project-context.js.map +1 -0
  203. package/dist/project-picker.d.ts +65 -0
  204. package/dist/project-picker.d.ts.map +1 -0
  205. package/dist/project-picker.js +227 -0
  206. package/dist/project-picker.js.map +1 -0
  207. package/dist/prompt.d.ts +8 -0
  208. package/dist/prompt.d.ts.map +1 -1
  209. package/dist/prompt.js +81 -13
  210. package/dist/prompt.js.map +1 -1
  211. package/dist/reporter/human.d.ts +10 -2
  212. package/dist/reporter/human.d.ts.map +1 -1
  213. package/dist/reporter/human.js +82 -14
  214. package/dist/reporter/human.js.map +1 -1
  215. package/dist/reporter/index.d.ts.map +1 -1
  216. package/dist/reporter/index.js +7 -5
  217. package/dist/reporter/index.js.map +1 -1
  218. package/dist/reporter/json.d.ts +2 -1
  219. package/dist/reporter/json.d.ts.map +1 -1
  220. package/dist/reporter/json.js +10 -7
  221. package/dist/reporter/json.js.map +1 -1
  222. package/dist/reporter/style.d.ts +19 -0
  223. package/dist/reporter/style.d.ts.map +1 -1
  224. package/dist/reporter/style.js +126 -6
  225. package/dist/reporter/style.js.map +1 -1
  226. package/dist/reporter/types.d.ts +34 -4
  227. package/dist/reporter/types.d.ts.map +1 -1
  228. package/dist/reporter/types.js.map +1 -1
  229. package/dist/services/account-store.d.ts +3 -1
  230. package/dist/services/account-store.d.ts.map +1 -1
  231. package/dist/services/account-store.js +2 -1
  232. package/dist/services/account-store.js.map +1 -1
  233. package/dist/services/app-paths.d.ts.map +1 -1
  234. package/dist/services/app-paths.js +3 -1
  235. package/dist/services/app-paths.js.map +1 -1
  236. package/dist/services/cloudflare-auth.d.ts +1 -1
  237. package/dist/services/cloudflare-auth.d.ts.map +1 -1
  238. package/dist/services/cloudflare-auth.js +2 -2
  239. package/dist/services/cloudflare-auth.js.map +1 -1
  240. package/dist/services/cloudflare-kv.d.ts.map +1 -1
  241. package/dist/services/cloudflare-kv.js.map +1 -1
  242. package/dist/services/command-history.d.ts.map +1 -1
  243. package/dist/services/command-history.js.map +1 -1
  244. package/dist/services/config.d.ts.map +1 -1
  245. package/dist/services/config.js +1 -1
  246. package/dist/services/config.js.map +1 -1
  247. package/dist/services/control-plane.d.ts.map +1 -1
  248. package/dist/services/control-plane.js +3 -3
  249. package/dist/services/control-plane.js.map +1 -1
  250. package/dist/services/env-loader.d.ts.map +1 -1
  251. package/dist/services/env-loader.js +1 -3
  252. package/dist/services/env-loader.js.map +1 -1
  253. package/dist/services/http.d.ts.map +1 -1
  254. package/dist/services/http.js.map +1 -1
  255. package/dist/services/hubspot-dev-client.d.ts.map +1 -1
  256. package/dist/services/hubspot-dev-client.js +30 -0
  257. package/dist/services/hubspot-dev-client.js.map +1 -1
  258. package/dist/services/live.d.ts +2 -2
  259. package/dist/services/live.d.ts.map +1 -1
  260. package/dist/services/live.js +1 -1
  261. package/dist/services/live.js.map +1 -1
  262. package/dist/services/machine-id.d.ts.map +1 -1
  263. package/dist/services/machine-id.js.map +1 -1
  264. package/dist/services/reporter.d.ts +8 -5
  265. package/dist/services/reporter.d.ts.map +1 -1
  266. package/dist/services/reporter.js +6 -4
  267. package/dist/services/reporter.js.map +1 -1
  268. package/dist/services/tenant-state.d.ts.map +1 -1
  269. package/dist/services/tenant-state.js.map +1 -1
  270. package/dist/state-store.d.ts +4 -0
  271. package/dist/state-store.d.ts.map +1 -1
  272. package/dist/state-store.js +15 -0
  273. package/dist/state-store.js.map +1 -1
  274. package/dist/status-drilldown.d.ts +24 -0
  275. package/dist/status-drilldown.d.ts.map +1 -0
  276. package/dist/status-drilldown.js +50 -0
  277. package/dist/status-drilldown.js.map +1 -0
  278. package/dist/tenant-state.d.ts.map +1 -1
  279. package/dist/tenant-state.js +3 -2
  280. package/dist/tenant-state.js.map +1 -1
  281. package/dist/token-key-custody.d.ts +91 -0
  282. package/dist/token-key-custody.d.ts.map +1 -0
  283. package/dist/token-key-custody.js +375 -0
  284. package/dist/token-key-custody.js.map +1 -0
  285. package/package.json +14 -15
@@ -0,0 +1,131 @@
1
+ import type { LinkPreflightPlan } from './link-preflight.js';
2
+ import { type TokenKeyCustodyAdapter } from './token-key-custody.js';
3
+ /**
4
+ * Ordered step boundaries. Each is idempotent and resumable; the journal
5
+ * records which boundaries a run has crossed so a resumed `hs-x link` picks up
6
+ * exactly where a crash left off and converges to the same final state.
7
+ */
8
+ export declare const LINK_MIGRATION_STEPS: readonly ["preflight-passed", "kv-owner-prefix-rewritten", "pointer-rewritten", "probe-passed", "cp-committed", "custody-rekeyed", "done"];
9
+ export type LinkMigrationStep = (typeof LINK_MIGRATION_STEPS)[number];
10
+ declare const LINK_MIGRATION_CODES: readonly ["HSX_E_LINK_PROBE_FAILED", "HSX_E_LINK_MIGRATION_INTERRUPTED"];
11
+ export type LinkMigrationErrorCode = (typeof LINK_MIGRATION_CODES)[number];
12
+ /** One binding's preserve-set — references only, never key bytes. */
13
+ export interface LinkMigrationBinding {
14
+ readonly projectId: string;
15
+ readonly environment: string;
16
+ readonly hubSpotAppId: number;
17
+ readonly installKvNamespaceId: string;
18
+ readonly installKvNamespaceName: string;
19
+ readonly tokenKeySecretName: string;
20
+ readonly custodyMechanism: string;
21
+ /** custodyId embedding the OLD owner (what the binding records pre-link). */
22
+ readonly oldCustodyId: string;
23
+ /** custodyId embedding the NEW owner (what the binding records post-link). */
24
+ readonly newCustodyId: string;
25
+ /** Authoritative Worker names the migration must never rename. */
26
+ readonly workerNames: readonly string[];
27
+ }
28
+ export interface LinkMigrationJournal {
29
+ readonly schemaVersion: 1;
30
+ readonly journalId: string;
31
+ readonly status: 'in-progress' | 'done' | 'aborted';
32
+ readonly cloudflareAccountId: string;
33
+ readonly stateKvNamespaceId: string;
34
+ readonly priorOwnerId: string;
35
+ readonly newOwnerId: string;
36
+ readonly bindings: readonly LinkMigrationBinding[];
37
+ /** Union of every binding's Worker names — authoritative, never renamed. */
38
+ readonly workerNames: readonly string[];
39
+ readonly completedSteps: readonly LinkMigrationStep[];
40
+ /** Whether the CP commit reported success (gates old-custody removal). */
41
+ readonly cpCommitOk?: boolean;
42
+ readonly createdAt: string;
43
+ readonly updatedAt: string;
44
+ }
45
+ export declare function defaultLinkMigrationJournalPath(env?: NodeJS.ProcessEnv): string;
46
+ /**
47
+ * Deterministic journal id from the STABLE identity of the claim: the
48
+ * Cloudflare account, the tenant-state namespace, and the TARGET owner. It does
49
+ * NOT depend on the prior owner, so a resumed run finds the journal even after
50
+ * the local pointer has already been rewritten to the new owner.
51
+ */
52
+ export declare function linkMigrationJournalId(input: {
53
+ readonly cloudflareAccountId: string;
54
+ readonly stateKvNamespaceId: string;
55
+ readonly newOwnerId: string;
56
+ }): string;
57
+ export declare function loadLinkMigrationJournal(journalId: string, path?: string): Promise<LinkMigrationJournal | undefined>;
58
+ export declare function saveLinkMigrationJournal(journal: LinkMigrationJournal, path?: string): Promise<void>;
59
+ export declare function deleteLinkMigrationJournal(journalId: string, path?: string): Promise<void>;
60
+ /**
61
+ * Build a fresh (unpersisted) journal from a passed link-preflight plan. The
62
+ * preserve-set — Worker names, INSTALL_KV namespace ids, custody references —
63
+ * comes straight from the plan's STRUCTURED fields (never the informational
64
+ * `checks` strings). Only escrowed bindings can reach here (the preflight fails
65
+ * closed on `worker-secret-only` custody), so each binding has a custody id.
66
+ */
67
+ export declare function buildLinkMigrationJournal(input: {
68
+ readonly plan: LinkPreflightPlan;
69
+ readonly newOwnerId: string;
70
+ readonly now?: () => Date;
71
+ }): LinkMigrationJournal;
72
+ /** The read/write surface the migration needs over a KV namespace. */
73
+ export interface LinkMigrationKvClient {
74
+ get(key: string): Promise<string | undefined>;
75
+ put(key: string, value: string): Promise<void>;
76
+ list(prefix: string): Promise<readonly string[]>;
77
+ delete(key: string): Promise<void>;
78
+ }
79
+ export interface LinkProbeInput {
80
+ readonly binding: LinkMigrationBinding;
81
+ /** The escrowed token key, already proven to open the binding's canary. */
82
+ readonly resolvedTokenKey: string;
83
+ /** Read-only handle bound to the binding's EXACT INSTALL_KV namespace. */
84
+ readonly installKv: Pick<LinkMigrationKvClient, 'get' | 'list'>;
85
+ }
86
+ export type LinkProbe = (input: LinkProbeInput) => Promise<void>;
87
+ export interface LinkMigrationSeams {
88
+ readonly stateKv: LinkMigrationKvClient;
89
+ readonly makeInstallKv: (namespaceId: string) => Pick<LinkMigrationKvClient, 'get' | 'list'>;
90
+ readonly custodyAdapter: TokenKeyCustodyAdapter;
91
+ /** Rewrite the local pointer's ownerId (forward = new, compensate = old). */
92
+ readonly rewritePointer: (ownerId: string) => Promise<void>;
93
+ /** Commit the owner rewrite to the control plane; undefined = ok. */
94
+ readonly commit: () => Promise<string | undefined>;
95
+ readonly probe?: LinkProbe;
96
+ readonly now?: () => Date;
97
+ }
98
+ export interface LinkMigrationResult {
99
+ readonly journal: LinkMigrationJournal;
100
+ readonly rewrittenKeyCount: number;
101
+ readonly rekeyedBindingCount: number;
102
+ readonly notifyWarning?: string;
103
+ readonly resumed: boolean;
104
+ }
105
+ /**
106
+ * Default probe: a harmless signed proof that token resolution still works AND
107
+ * the exact INSTALL_KV namespace is reachable after the owner rewrite. The
108
+ * canary was already opened (the resolved key is passed in); this additionally
109
+ * performs a read against the binding's INSTALL_KV namespace (a real HubSpot
110
+ * call is T04/live and out of scope). Throws a typed error on failure.
111
+ */
112
+ export declare const defaultLinkProbe: LinkProbe;
113
+ /**
114
+ * Run (or resume) the journaled link migration to convergence. Each step is
115
+ * skipped when the journal already records it, and every action is idempotent,
116
+ * so a resumed run reaches a final state identical to the unfailed path.
117
+ *
118
+ * `failAfter` injects a fatal interruption immediately AFTER a boundary is
119
+ * persisted — the seam the acceptance tests use to prove convergence. A probe
120
+ * failure (as opposed to an injected interruption after the probe boundary)
121
+ * compensates: the KV owner prefix and pointer are rolled back and the journal
122
+ * is cleared so the operation is re-runnable from a clean pre-link state.
123
+ */
124
+ export declare function runLinkMigration(input: {
125
+ readonly journal: LinkMigrationJournal;
126
+ readonly seams: LinkMigrationSeams;
127
+ readonly journalPath?: string;
128
+ readonly failAfter?: string;
129
+ }): Promise<LinkMigrationResult>;
130
+ export {};
131
+ //# sourceMappingURL=link-migration-journal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-migration-journal.d.ts","sourceRoot":"","sources":["../src/link-migration-journal.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,OAAO,EACL,KAAK,sBAAsB,EAI5B,MAAM,wBAAwB,CAAC;AAIhC;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,4IAQvB,CAAC;AACX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,QAAA,MAAM,oBAAoB,0EAOhB,CAAC;AACX,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3E,qEAAqE;AACrE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,6EAA6E;IAC7E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,kEAAkE;IAClE,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAAC;IACpD,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACnD,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,cAAc,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACtD,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAaD,wBAAgB,+BAA+B,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAI5F;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B,GAAG,MAAM,CAKT;AAkCD,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,MAA0C,GAC/C,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAG3C;AAED,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,oBAAoB,EAC7B,IAAI,GAAE,MAA0C,GAC/C,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAsB,0BAA0B,CAC9C,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,MAA0C,GAC/C,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE;IAC/C,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B,GAAG,oBAAoB,CAsCvB;AAMD,sEAAsE;AACtE,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC9C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;IACjD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC;IACvC,2EAA2E;IAC3E,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,0EAA0E;IAC1E,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC,qBAAqB,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC;CACjE;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEjE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;IACxC,QAAQ,CAAC,aAAa,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC,qBAAqB,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC;IAC7F,QAAQ,CAAC,cAAc,EAAE,sBAAsB,CAAC;IAChD,6EAA6E;IAC7E,QAAQ,CAAC,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACnD,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC;IACvC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAMD;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAmB9B,CAAC;AAoFF;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE;IAC5C,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA2I/B"}
@@ -0,0 +1,395 @@
1
+ // ADR-022 T03 — journaled, resumable transactional link migration.
2
+ //
3
+ // Linking an installed project must PRESERVE Worker identity, the exact
4
+ // INSTALL_KV namespace, and the escrowed token key. The mutation sequence
5
+ // (KV owner-prefix rewrite → local pointer rewrite → token-resolution probe →
6
+ // control-plane owner commit → token-key custody re-key) is journaled with
7
+ // explicit step boundaries so an injected failure at any boundary re-runs to
8
+ // convergence WITHOUT replacing a key or a namespace. The probe (a harmless
9
+ // signed proof, BEFORE the CP commit) is the point of no return that is still
10
+ // reversible: if it fails, the KV owner prefix and the local pointer are rolled
11
+ // back to the pre-link owner and the operation aborts with a typed error.
12
+ //
13
+ // The custody re-key follows the T01 handoff: the token key is re-escrowed
14
+ // under the NEW owner's custody id while the OLD entry is kept, and the old
15
+ // entry is removed only AFTER the CP commit succeeds — so there is never a
16
+ // window with zero custody entries and the key bytes are never regenerated.
17
+ //
18
+ // The journal is a VERSIONED local record (CLI state dir, 0600) that carries
19
+ // ONLY references — owner ids, namespace ids, worker names, secret NAMES, and
20
+ // custody ids. It never contains key bytes or a sealed canary.
21
+ import { createHash } from 'node:crypto';
22
+ import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
23
+ import { dirname } from 'node:path';
24
+ import { Schema, schemas } from '@hs-x/types';
25
+ import { CliError } from './cli-error.js';
26
+ import { statePath } from './paths.js';
27
+ import { escrowTokenKey, resolveEscrowedTokenKey, tokenKeyCustodyIdFor, } from './token-key-custody.js';
28
+ /**
29
+ * Ordered step boundaries. Each is idempotent and resumable; the journal
30
+ * records which boundaries a run has crossed so a resumed `hs-x link` picks up
31
+ * exactly where a crash left off and converges to the same final state.
32
+ */
33
+ export const LINK_MIGRATION_STEPS = [
34
+ 'preflight-passed',
35
+ 'kv-owner-prefix-rewritten',
36
+ 'pointer-rewritten',
37
+ 'probe-passed',
38
+ 'cp-committed',
39
+ 'custody-rekeyed',
40
+ 'done',
41
+ ];
42
+ const LINK_MIGRATION_CODES = [
43
+ // The token-resolution probe failed AFTER the KV/pointer rewrite: the owner
44
+ // prefix and pointer were rolled back to the pre-link owner. Re-runnable.
45
+ 'HSX_E_LINK_PROBE_FAILED',
46
+ // A journaled boundary was crossed and then the run was interrupted (the
47
+ // injected-failure seam, or a real crash). Re-running `hs-x link` resumes.
48
+ 'HSX_E_LINK_MIGRATION_INTERRUPTED',
49
+ ];
50
+ const EMPTY_STORE = { schemaVersion: 1, journals: {} };
51
+ export function defaultLinkMigrationJournalPath(env = process.env) {
52
+ const override = env.HSX_LINK_JOURNAL_PATH;
53
+ if (override && override.length > 0)
54
+ return override;
55
+ return statePath('link-migrations.json');
56
+ }
57
+ /**
58
+ * Deterministic journal id from the STABLE identity of the claim: the
59
+ * Cloudflare account, the tenant-state namespace, and the TARGET owner. It does
60
+ * NOT depend on the prior owner, so a resumed run finds the journal even after
61
+ * the local pointer has already been rewritten to the new owner.
62
+ */
63
+ export function linkMigrationJournalId(input) {
64
+ return createHash('sha256')
65
+ .update(`${input.cloudflareAccountId}\n${input.stateKvNamespaceId}\n${input.newOwnerId}`)
66
+ .digest('hex')
67
+ .slice(0, 24);
68
+ }
69
+ async function loadStore(path) {
70
+ let raw;
71
+ try {
72
+ raw = await readFile(path, 'utf8');
73
+ }
74
+ catch {
75
+ return EMPTY_STORE;
76
+ }
77
+ try {
78
+ const parsed = JSON.parse(raw);
79
+ if (parsed.schemaVersion !== 1 ||
80
+ typeof parsed.journals !== 'object' ||
81
+ parsed.journals === null) {
82
+ return EMPTY_STORE;
83
+ }
84
+ return { schemaVersion: 1, journals: parsed.journals };
85
+ }
86
+ catch {
87
+ return EMPTY_STORE;
88
+ }
89
+ }
90
+ async function writeStore(path, store) {
91
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
92
+ await writeFile(path, `${JSON.stringify(store, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
93
+ try {
94
+ await chmod(path, 0o600);
95
+ }
96
+ catch {
97
+ // best effort on platforms without POSIX perms
98
+ }
99
+ }
100
+ export async function loadLinkMigrationJournal(journalId, path = defaultLinkMigrationJournalPath()) {
101
+ const store = await loadStore(path);
102
+ return store.journals[journalId];
103
+ }
104
+ export async function saveLinkMigrationJournal(journal, path = defaultLinkMigrationJournalPath()) {
105
+ const store = await loadStore(path);
106
+ await writeStore(path, {
107
+ schemaVersion: 1,
108
+ journals: { ...store.journals, [journal.journalId]: journal },
109
+ });
110
+ }
111
+ export async function deleteLinkMigrationJournal(journalId, path = defaultLinkMigrationJournalPath()) {
112
+ const store = await loadStore(path);
113
+ if (!(journalId in store.journals))
114
+ return;
115
+ const { [journalId]: _removed, ...rest } = store.journals;
116
+ const remaining = rest;
117
+ if (Object.keys(remaining).length === 0) {
118
+ await rm(path, { force: true });
119
+ return;
120
+ }
121
+ await writeStore(path, { schemaVersion: 1, journals: remaining });
122
+ }
123
+ /**
124
+ * Build a fresh (unpersisted) journal from a passed link-preflight plan. The
125
+ * preserve-set — Worker names, INSTALL_KV namespace ids, custody references —
126
+ * comes straight from the plan's STRUCTURED fields (never the informational
127
+ * `checks` strings). Only escrowed bindings can reach here (the preflight fails
128
+ * closed on `worker-secret-only` custody), so each binding has a custody id.
129
+ */
130
+ export function buildLinkMigrationJournal(input) {
131
+ const nowIso = (input.now ? input.now() : new Date()).toISOString();
132
+ const bindings = input.plan.bindings.map((binding) => ({
133
+ projectId: binding.projectId,
134
+ environment: binding.environment,
135
+ hubSpotAppId: binding.hubSpotAppId,
136
+ installKvNamespaceId: binding.installKvNamespaceId,
137
+ installKvNamespaceName: binding.installKvNamespaceName,
138
+ tokenKeySecretName: binding.tokenKeySecretName,
139
+ custodyMechanism: binding.tokenKeyCustody.mechanism,
140
+ oldCustodyId: binding.tokenKeyCustody.custodyId,
141
+ newCustodyId: tokenKeyCustodyIdFor({
142
+ ownerId: input.newOwnerId,
143
+ projectId: binding.projectId,
144
+ environment: binding.environment,
145
+ hubSpotAppId: binding.hubSpotAppId,
146
+ }),
147
+ workerNames: binding.workerNames,
148
+ }));
149
+ const workerNames = [...new Set(bindings.flatMap((binding) => binding.workerNames))].sort();
150
+ return {
151
+ schemaVersion: 1,
152
+ journalId: linkMigrationJournalId({
153
+ cloudflareAccountId: input.plan.cloudflareAccountId,
154
+ stateKvNamespaceId: input.plan.stateKvNamespaceId,
155
+ newOwnerId: input.newOwnerId,
156
+ }),
157
+ status: 'in-progress',
158
+ cloudflareAccountId: input.plan.cloudflareAccountId,
159
+ stateKvNamespaceId: input.plan.stateKvNamespaceId,
160
+ priorOwnerId: input.plan.priorOwnerId,
161
+ newOwnerId: input.newOwnerId,
162
+ bindings,
163
+ workerNames,
164
+ completedSteps: [],
165
+ createdAt: nowIso,
166
+ updatedAt: nowIso,
167
+ };
168
+ }
169
+ function migrationError(code, detail) {
170
+ return new CliError({ code, message: `${code}: ${detail}` });
171
+ }
172
+ /**
173
+ * Default probe: a harmless signed proof that token resolution still works AND
174
+ * the exact INSTALL_KV namespace is reachable after the owner rewrite. The
175
+ * canary was already opened (the resolved key is passed in); this additionally
176
+ * performs a read against the binding's INSTALL_KV namespace (a real HubSpot
177
+ * call is T04/live and out of scope). Throws a typed error on failure.
178
+ */
179
+ export const defaultLinkProbe = async (input) => {
180
+ if (input.resolvedTokenKey.length === 0) {
181
+ throw migrationError('HSX_E_LINK_PROBE_FAILED', `token resolution for ${input.binding.installKvNamespaceId} produced an empty key.`);
182
+ }
183
+ try {
184
+ // Harmless token-state read: prove the preserved INSTALL_KV namespace still
185
+ // answers under the rewritten owner. We do not require any entries.
186
+ await input.installKv.list('token-state/');
187
+ }
188
+ catch (cause) {
189
+ throw migrationError('HSX_E_LINK_PROBE_FAILED', `INSTALL_KV namespace ${input.binding.installKvNamespaceId} did not respond to a token-state read: ${cause instanceof Error ? cause.message : String(cause)}`);
190
+ }
191
+ };
192
+ // ---------------------------------------------------------------------------
193
+ // Owner-prefix rewrite (idempotent copy-then-delete; used forward + reverse)
194
+ // ---------------------------------------------------------------------------
195
+ async function rewriteOwnerPrefix(kv, from, to) {
196
+ const fromPrefix = `owners/${from}/`;
197
+ const toPrefix = `owners/${to}/`;
198
+ const fromKeys = await kv.list(fromPrefix);
199
+ if (fromKeys.length === 0)
200
+ return [];
201
+ const rewritten = [];
202
+ for (const fromKey of fromKeys) {
203
+ const value = await kv.get(fromKey);
204
+ if (value === undefined)
205
+ continue;
206
+ const toKey = `${toPrefix}${fromKey.slice(fromPrefix.length)}`;
207
+ await kv.put(toKey, rewriteOwnerIdValue(value, from, to));
208
+ rewritten.push({ fromKey, toKey });
209
+ }
210
+ // Delete sources only after every write succeeded, so a mid-loop crash leaves
211
+ // both prefixes populated rather than losing data.
212
+ for (const { fromKey } of rewritten) {
213
+ await kv.delete(fromKey);
214
+ }
215
+ return rewritten;
216
+ }
217
+ function rewriteOwnerIdValue(value, from, to) {
218
+ try {
219
+ const parsed = JSON.parse(value);
220
+ if (typeof parsed === 'object' &&
221
+ parsed !== null &&
222
+ !Array.isArray(parsed) &&
223
+ parsed.ownerId === from) {
224
+ return JSON.stringify({ ...parsed, ownerId: to });
225
+ }
226
+ }
227
+ catch {
228
+ // Non-JSON values keep their bytes.
229
+ }
230
+ return value;
231
+ }
232
+ function bindingKvKey(ownerId, binding) {
233
+ return `owners/${ownerId}/projects/${binding.projectId}/bindings/${binding.environment}/${binding.hubSpotAppId}`;
234
+ }
235
+ async function readBindingRecord(kv, ownerId, binding) {
236
+ const key = bindingKvKey(ownerId, binding);
237
+ const raw = await kv.get(key);
238
+ if (raw === undefined) {
239
+ throw migrationError('HSX_E_LINK_MIGRATION_INTERRUPTED', `binding record ${key} is missing after the owner rewrite — cannot re-key custody.`);
240
+ }
241
+ return Schema.decodeUnknownSync(schemas.LocalAppRuntimeBinding)(JSON.parse(raw));
242
+ }
243
+ // ---------------------------------------------------------------------------
244
+ // The journaled runner
245
+ // ---------------------------------------------------------------------------
246
+ function withStep(journal, step, patch, nowIso) {
247
+ const completedSteps = journal.completedSteps.includes(step)
248
+ ? journal.completedSteps
249
+ : [...journal.completedSteps, step];
250
+ return { ...journal, ...patch, completedSteps, updatedAt: nowIso };
251
+ }
252
+ /**
253
+ * Run (or resume) the journaled link migration to convergence. Each step is
254
+ * skipped when the journal already records it, and every action is idempotent,
255
+ * so a resumed run reaches a final state identical to the unfailed path.
256
+ *
257
+ * `failAfter` injects a fatal interruption immediately AFTER a boundary is
258
+ * persisted — the seam the acceptance tests use to prove convergence. A probe
259
+ * failure (as opposed to an injected interruption after the probe boundary)
260
+ * compensates: the KV owner prefix and pointer are rolled back and the journal
261
+ * is cleared so the operation is re-runnable from a clean pre-link state.
262
+ */
263
+ export async function runLinkMigration(input) {
264
+ const path = input.journalPath ?? defaultLinkMigrationJournalPath();
265
+ const now = input.seams.now ?? (() => new Date());
266
+ const probe = input.seams.probe ?? defaultLinkProbe;
267
+ const resumed = input.journal.completedSteps.length > 0;
268
+ let journal = input.journal;
269
+ let rewrittenKeyCount = 0;
270
+ let rekeyedBindingCount = 0;
271
+ let notifyWarning;
272
+ const persist = async (step, patch = {}) => {
273
+ journal = withStep(journal, step, patch, now().toISOString());
274
+ await saveLinkMigrationJournal(journal, path);
275
+ if (input.failAfter && input.failAfter === step) {
276
+ throw migrationError('HSX_E_LINK_MIGRATION_INTERRUPTED', `injected failure after boundary "${step}".`);
277
+ }
278
+ };
279
+ const done = (step) => journal.completedSteps.includes(step);
280
+ // ── preflight-passed: recorded at journal creation. ──────────────────────
281
+ if (!done('preflight-passed')) {
282
+ await persist('preflight-passed');
283
+ }
284
+ // ── KV owner-prefix rewrite (idempotent copy-then-delete). ───────────────
285
+ if (!done('kv-owner-prefix-rewritten')) {
286
+ const rewritten = await rewriteOwnerPrefix(input.seams.stateKv, journal.priorOwnerId, journal.newOwnerId);
287
+ rewrittenKeyCount = rewritten.length;
288
+ await persist('kv-owner-prefix-rewritten');
289
+ }
290
+ // ── Local pointer rewrite (idempotent). ──────────────────────────────────
291
+ if (!done('pointer-rewritten')) {
292
+ await input.seams.rewritePointer(journal.newOwnerId);
293
+ await persist('pointer-rewritten');
294
+ }
295
+ // ── Probe: token resolution + INSTALL_KV read. Reversible on failure. ────
296
+ if (!done('probe-passed')) {
297
+ try {
298
+ for (const binding of journal.bindings) {
299
+ const record = await readBindingRecord(input.seams.stateKv, journal.newOwnerId, binding);
300
+ if (record.tokenKeyCustody.kind !== 'escrowed') {
301
+ throw migrationError('HSX_E_LINK_PROBE_FAILED', `binding ${bindingKvKey(journal.newOwnerId, binding)} no longer records escrowed custody.`);
302
+ }
303
+ const resolvedTokenKey = await resolveEscrowedTokenKey({
304
+ custody: record.tokenKeyCustody,
305
+ adapter: input.seams.custodyAdapter,
306
+ });
307
+ await probe({
308
+ binding,
309
+ resolvedTokenKey,
310
+ installKv: input.seams.makeInstallKv(binding.installKvNamespaceId),
311
+ });
312
+ }
313
+ }
314
+ catch (cause) {
315
+ await compensate(journal, input.seams);
316
+ await deleteLinkMigrationJournal(journal.journalId, path);
317
+ if (cause instanceof CliError)
318
+ throw cause;
319
+ throw migrationError('HSX_E_LINK_PROBE_FAILED', cause instanceof Error ? cause.message : String(cause));
320
+ }
321
+ await persist('probe-passed');
322
+ }
323
+ // ── Control-plane owner commit (best-effort; gates old-custody removal). ─
324
+ if (!done('cp-committed')) {
325
+ notifyWarning = await input.seams.commit();
326
+ await persist('cp-committed', { cpCommitOk: notifyWarning === undefined });
327
+ }
328
+ // ── Custody re-key: re-escrow under the new owner's id, then (only once ───
329
+ // the CP commit succeeded) remove the old entry. Never a zero-entry gap.
330
+ if (!done('custody-rekeyed')) {
331
+ const cpCommitOk = journal.cpCommitOk === true;
332
+ for (const binding of journal.bindings) {
333
+ const record = await readBindingRecord(input.seams.stateKv, journal.newOwnerId, binding);
334
+ if (record.tokenKeyCustody.kind !== 'escrowed')
335
+ continue;
336
+ if (record.tokenKeyCustody.custodyId === binding.newCustodyId) {
337
+ // Already re-keyed on a prior (interrupted) run — just ensure the old
338
+ // entry is gone (idempotent) when the CP commit has succeeded.
339
+ if (cpCommitOk)
340
+ await input.seams.custodyAdapter.remove(binding.oldCustodyId);
341
+ continue;
342
+ }
343
+ // Resolve the SAME key from the old custody, re-escrow it under the new
344
+ // custody id (a fresh canary is sealed under the identical key — no new
345
+ // key is minted), then repoint the binding record at the new custody.
346
+ const tokenKey = await resolveEscrowedTokenKey({
347
+ custody: record.tokenKeyCustody,
348
+ adapter: input.seams.custodyAdapter,
349
+ });
350
+ const newCustody = await escrowTokenKey({
351
+ adapter: input.seams.custodyAdapter,
352
+ custodyId: binding.newCustodyId,
353
+ tokenKeySecretValue: tokenKey,
354
+ ...(input.seams.now ? { now: input.seams.now } : {}),
355
+ });
356
+ const updated = {
357
+ ...record,
358
+ tokenKeyCustody: newCustody,
359
+ updatedAt: now().toISOString(),
360
+ };
361
+ await input.seams.stateKv.put(bindingKvKey(journal.newOwnerId, binding), JSON.stringify(Schema.encodeSync(schemas.LocalAppRuntimeBinding)(updated)));
362
+ // Old entry removed only after the CP commit succeeded (both entries
363
+ // coexist until here, so there is never a window with zero custody).
364
+ if (cpCommitOk)
365
+ await input.seams.custodyAdapter.remove(binding.oldCustodyId);
366
+ rekeyedBindingCount += 1;
367
+ }
368
+ await persist('custody-rekeyed');
369
+ }
370
+ // ── Done. ────────────────────────────────────────────────────────────────
371
+ if (!done('done')) {
372
+ await persist('done', { status: 'done' });
373
+ }
374
+ return {
375
+ journal,
376
+ rewrittenKeyCount,
377
+ rekeyedBindingCount,
378
+ resumed,
379
+ ...(notifyWarning !== undefined ? { notifyWarning } : {}),
380
+ };
381
+ }
382
+ /**
383
+ * Roll the reversible steps back to the pre-link owner: reverse the KV owner
384
+ * prefix (new → old) and the local pointer. Custody is untouched at probe time
385
+ * (re-key runs later), so there is nothing to undo there.
386
+ */
387
+ async function compensate(journal, seams) {
388
+ if (journal.completedSteps.includes('kv-owner-prefix-rewritten')) {
389
+ await rewriteOwnerPrefix(seams.stateKv, journal.newOwnerId, journal.priorOwnerId);
390
+ }
391
+ if (journal.completedSteps.includes('pointer-rewritten')) {
392
+ await seams.rewritePointer(journal.priorOwnerId);
393
+ }
394
+ }
395
+ //# sourceMappingURL=link-migration-journal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-migration-journal.js","sourceRoot":"","sources":["../src/link-migration-journal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import { Schema, schemas } from '@hs-x/types';
2
+ import { type TokenKeyCustodyAdapter } from './token-key-custody.js';
3
+ type CloudflarePointer = Schema.Schema.Type<typeof schemas.CloudflarePointer>;
4
+ declare const LINK_PREFLIGHT_CODES: readonly ["HSX_E_LINK_PREFLIGHT_NAMESPACE", "HSX_E_LINK_PREFLIGHT_STATE_CORRUPT", "HSX_E_LINK_PREFLIGHT_AMBIGUOUS", "HSX_E_LINK_PREFLIGHT_UNPROVEN"];
5
+ export type LinkPreflightErrorCode = (typeof LINK_PREFLIGHT_CODES)[number];
6
+ /** Read-only slice of the tenant-state KV surface the preflight is allowed. */
7
+ export interface LinkPreflightReadOnlyKv {
8
+ get(key: string): Promise<string | undefined>;
9
+ list(prefix: string): Promise<readonly string[]>;
10
+ }
11
+ export interface LinkPreflightNamespaceInfo {
12
+ readonly id: string;
13
+ readonly title: string;
14
+ }
15
+ /** One binding's redacted continuity plan — names and ids, never key bytes. */
16
+ export interface LinkPreflightBindingPlan {
17
+ readonly projectId: string;
18
+ readonly environment: string;
19
+ readonly hubSpotAppId: number;
20
+ /** Worker names recorded by the active deploy for this project/environment. */
21
+ readonly workerNames: readonly string[];
22
+ readonly installKvNamespaceId: string;
23
+ readonly installKvNamespaceName: string;
24
+ readonly tokenKeySecretName: string;
25
+ readonly tokenKeyCustody: {
26
+ readonly kind: 'escrowed';
27
+ readonly mechanism: string;
28
+ readonly custodyId: string;
29
+ /** The escrowed key was retrieved and opened the sealed canary. */
30
+ readonly canaryProven: true;
31
+ };
32
+ }
33
+ export interface LinkPreflightPlan {
34
+ readonly plan: 'link-preflight';
35
+ readonly schemaVersion: 1;
36
+ readonly priorOwnerId: string;
37
+ readonly newOwnerId?: string;
38
+ readonly cloudflareAccountId: string;
39
+ readonly stateKvNamespaceId: string;
40
+ readonly ownerMetaPresent: boolean;
41
+ readonly bindings: readonly LinkPreflightBindingPlan[];
42
+ /** Deterministic, ordered record of what was proven. */
43
+ readonly checks: readonly string[];
44
+ }
45
+ /**
46
+ * Read-only Cloudflare KV namespace metadata lookup:
47
+ * `GET /client/v4/accounts/{account}/storage/kv/namespaces/{id}`.
48
+ * `undefined` = the namespace does not exist. Transport / auth failures throw
49
+ * `HSX_E_API_NETWORK` (they are not evidence of a missing namespace).
50
+ */
51
+ export declare function fetchCloudflareKvNamespace(input: {
52
+ readonly accountId: string;
53
+ readonly apiToken: string;
54
+ readonly namespaceId: string;
55
+ readonly baseUrl?: string;
56
+ readonly fetchImpl?: typeof fetch;
57
+ }): Promise<LinkPreflightNamespaceInfo | undefined>;
58
+ /**
59
+ * Run the ADR-022 continuity preflight for one owner prefix. Read-only by
60
+ * construction: the KV surface exposes only get/list, the namespace lookup is
61
+ * a metadata GET, and custody resolution is the T01 read-only primitive.
62
+ * Throws typed `CliError`s (the link-preflight codes above, or the T01
63
+ * token-key-custody codes); returns the redacted plan on success.
64
+ */
65
+ export declare function runLinkPreflight(input: {
66
+ readonly pointer: CloudflarePointer;
67
+ /** The `local_*` (or current) owner prefix link intends to rewrite. */
68
+ readonly priorOwnerId: string;
69
+ /** Known claim target; omitted in the dashboard flow (target chosen later). */
70
+ readonly newOwnerId?: string;
71
+ readonly apiToken?: string;
72
+ readonly baseUrl?: string;
73
+ readonly kv?: LinkPreflightReadOnlyKv;
74
+ readonly getNamespace?: (namespaceId: string) => Promise<LinkPreflightNamespaceInfo | undefined>;
75
+ readonly custodyAdapter?: TokenKeyCustodyAdapter;
76
+ }): Promise<LinkPreflightPlan>;
77
+ export {};
78
+ //# sourceMappingURL=link-preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-preflight.d.ts","sourceRoot":"","sources":["../src/link-preflight.ts"],"names":[],"mappings":"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"}