@skrr-ai/cli 0.1.122 → 0.1.124

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 (111) hide show
  1. package/dist/commands/agents/chat.d.ts +7 -0
  2. package/dist/commands/agents/chat.js +32 -6
  3. package/dist/commands/agents/delete.d.ts +8 -0
  4. package/dist/commands/agents/delete.js +16 -0
  5. package/dist/commands/context.js +7 -0
  6. package/dist/commands/followups/act.js +3 -0
  7. package/dist/commands/followups/checkin.js +3 -0
  8. package/dist/commands/followups/remind.js +3 -0
  9. package/dist/commands/followups/show.js +1 -1
  10. package/dist/commands/followups/watch.js +3 -0
  11. package/dist/commands/search/workspace.js +1 -1
  12. package/dist/commands/store/gallery-admissions/approve.js +1 -1
  13. package/dist/commands/store/gallery-admissions/preview.js +1 -1
  14. package/dist/commands/store/gallery-admissions/refuse.js +1 -1
  15. package/dist/commands/store/gallery-admissions/show.js +1 -1
  16. package/dist/lib/agentic-stream.d.ts +46 -0
  17. package/dist/lib/agentic-stream.js +135 -14
  18. package/dist/lib/auth-core-init.d.ts +15 -2
  19. package/dist/lib/auth-core-init.js +47 -6
  20. package/dist/lib/auth-failure.js +12 -0
  21. package/dist/lib/command-hygiene.js +30 -1
  22. package/dist/lib/daemonBrokerRefusal.js +5 -1
  23. package/dist/lib/exec-runtime-binary.d.ts +9 -0
  24. package/dist/lib/exec-runtime-binary.js +15 -1
  25. package/dist/lib/followups.d.ts +13 -0
  26. package/dist/lib/followups.js +8 -0
  27. package/dist/lib/keychain.js +41 -4
  28. package/dist/lib/profile-machine-identity-guard.js +2 -1
  29. package/dist/lib/runtime-start-verification.js +6 -1
  30. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +5 -1
  31. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +1 -1
  32. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +69 -27
  33. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +135 -4
  34. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +440 -83
  35. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.d.ts +106 -7
  36. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.js +77 -19
  37. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHandle.d.ts +33 -0
  38. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHandle.js +77 -0
  39. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHardware.d.ts +68 -0
  40. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHardware.js +264 -0
  41. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyServerSupport.d.ts +31 -0
  42. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyServerSupport.js +90 -0
  43. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dpop.js +2 -0
  44. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineReservation.js +2 -0
  45. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +7 -3
  46. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +110 -0
  47. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.d.ts +1 -0
  48. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.js +13 -0
  49. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/linux.d.ts +18 -0
  50. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/linux.js +11 -2
  51. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.d.ts +22 -0
  52. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.js +14 -2
  53. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.d.ts +69 -0
  54. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.js +67 -0
  55. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/liveState.d.ts +154 -0
  56. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/liveState.js +281 -0
  57. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +6 -3
  58. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +8 -5
  59. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.d.ts +1 -1
  60. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.js +1 -0
  61. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/profileStateDir.js +7 -27
  62. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +1 -1
  63. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +24 -16
  64. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/stateFiles.d.ts +31 -0
  65. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/stateFiles.js +89 -0
  66. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/telemetryRedaction.d.ts +3 -0
  67. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/telemetryRedaction.js +41 -3
  68. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/types.d.ts +7 -1
  69. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +5 -1
  70. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +4 -1
  71. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +122 -38
  72. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +135 -4
  73. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +603 -90
  74. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.d.ts +106 -7
  75. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.js +131 -27
  76. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHandle.d.ts +33 -0
  77. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHandle.js +81 -0
  78. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHardware.d.ts +68 -0
  79. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHardware.js +317 -0
  80. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyServerSupport.d.ts +31 -0
  81. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyServerSupport.js +86 -0
  82. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dpop.js +4 -0
  83. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineReservation.js +4 -0
  84. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +7 -3
  85. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +14 -4
  86. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.d.ts +1 -0
  87. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.js +1 -0
  88. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/linux.d.ts +18 -0
  89. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/linux.js +18 -2
  90. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.d.ts +22 -0
  91. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.js +23 -2
  92. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.d.ts +69 -0
  93. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.js +134 -0
  94. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/liveState.d.ts +154 -0
  95. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/liveState.js +386 -0
  96. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +6 -3
  97. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +11 -5
  98. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.d.ts +1 -1
  99. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.js +1 -0
  100. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/profileStateDir.js +6 -35
  101. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +1 -1
  102. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +45 -16
  103. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/stateFiles.d.ts +31 -0
  104. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/stateFiles.js +73 -0
  105. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/telemetryRedaction.d.ts +3 -0
  106. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/telemetryRedaction.js +40 -3
  107. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/types.d.ts +7 -1
  108. package/dist/node_modules/@skrr-ai/auth-core/package.json +21 -1
  109. package/dist/node_modules/@skrr-ai/data-provider/index.js +7349 -7325
  110. package/oclif.manifest.json +11 -2
  111. package/package.json +4 -1
@@ -28,30 +28,45 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
  var deviceIdentityBridge_exports = {};
30
30
  __export(deviceIdentityBridge_exports, {
31
+ DEVICE_KEY_UNREADABLE_CREDENTIAL_HINT: () => DEVICE_KEY_UNREADABLE_CREDENTIAL_HINT,
32
+ PENDING_HARDWARE_KEY_MAX_AGE_MS: () => PENDING_HARDWARE_KEY_MAX_AGE_MS,
31
33
  __enrollmentBackoffPathForTest: () => __enrollmentBackoffPathForTest,
32
34
  __enrollmentMarkerPathForTest: () => __enrollmentMarkerPathForTest,
33
35
  __getStateForTest: () => __getStateForTest,
34
36
  __privateKeyPathForTest: () => __privateKeyPathForTest,
35
37
  __publicKeyPathForTest: () => __publicKeyPathForTest,
36
38
  __setStateForTest: () => __setStateForTest,
39
+ __setUnreadableReloadIntervalForTest: () => __setUnreadableReloadIntervalForTest,
37
40
  clearEnrollmentMarker: () => clearEnrollmentMarker,
41
+ commitDeviceKeyCandidate: () => commitDeviceKeyCandidate,
42
+ commitPendingHardwareKey: () => commitPendingHardwareKey,
38
43
  describeDeviceIdentityState: () => describeDeviceIdentityState,
39
44
  describeDeviceKeySplit: () => describeDeviceKeySplit,
45
+ describeStoredDeviceKey: () => describeStoredDeviceKey,
46
+ discardPendingHardwareKey: () => discardPendingHardwareKey,
40
47
  enrollWithServer: () => enrollWithServer,
41
48
  getDevicePublicKey: () => getDevicePublicKey,
42
49
  initDeviceIdentity: () => initDeviceIdentity,
43
50
  isDeviceIdentityActive: () => isDeviceIdentityActive,
51
+ persistPendingHardwareKey: () => persistPendingHardwareKey,
52
+ prepareHardwareKeyCandidate: () => prepareHardwareKeyCandidate,
44
53
  resetDeviceIdentity: () => resetDeviceIdentity,
45
- signServerRequest: () => signServerRequest
54
+ signServerRequest: () => signServerRequest,
55
+ signWithCandidate: () => signWithCandidate
46
56
  });
47
57
  module.exports = __toCommonJS(deviceIdentityBridge_exports);
48
58
  var import_node_fs = __toESM(require("node:fs"), 1);
49
59
  var import_node_path = __toESM(require("node:path"), 1);
50
60
  var import_proper_lockfile = __toESM(require("proper-lockfile"), 1);
51
61
  var import_deviceKey = require("./deviceKey.js");
62
+ var import_deviceKeyHandle = require("./deviceKeyHandle.js");
63
+ var import_deviceKeyHardware = require("./deviceKeyHardware.js");
52
64
  var import_credentialEnvelopeBridge = require("./credentialEnvelopeBridge.js");
53
65
  var import_profileStateDir = require("./profileStateDir.js");
66
+ var import_liveState = require("./liveState.js");
54
67
  var import_runtime = require("./runtime.js");
68
+ var import_stateFiles = require("./stateFiles.js");
69
+ const DEVICE_KEY_UNREADABLE_CREDENTIAL_HINT = "stored device key is unreadable on this machine \u2014 only an interactive daemon sign-in replaces it";
55
70
  const PUBLIC_KEY_FILENAME = "device-public-key.jwk";
56
71
  const PRIVATE_KEY_FILENAME = "device-private-key";
57
72
  const ENROLLMENT_MARKER_FILENAME = "device-enrollment.json";
@@ -65,9 +80,114 @@ const DEVICE_IDENTITY_FILES = [
65
80
  const DAEMON_NOT_FOUND_BACKOFF_MS = 15 * 60 * 1e3;
66
81
  let _state = { kind: "uninit" };
67
82
  let _initializing = null;
83
+ let _unreadableSeen = null;
84
+ let _unreadableReloadIntervalMs = 5e3;
85
+ function __setUnreadableReloadIntervalForTest(ms) {
86
+ _unreadableReloadIntervalMs = ms ?? 5e3;
87
+ }
88
+ function keyFilesStamp(profile) {
89
+ const part = (file) => {
90
+ try {
91
+ const s = import_node_fs.default.statSync(file);
92
+ return `${s.mtimeMs}:${s.size}`;
93
+ } catch {
94
+ return "absent";
95
+ }
96
+ };
97
+ return `${part(publicKeyPath(profile))}|${part(privateKeyPath(profile))}`;
98
+ }
99
+ let _activeSeen = null;
100
+ function trackActiveKeyFiles(profile) {
101
+ try {
102
+ _activeSeen = { profile, stamp: keyFilesStamp(profile), checkedAt: Date.now() };
103
+ } catch {
104
+ _activeSeen = null;
105
+ }
106
+ }
107
+ function reloadIfRecovered() {
108
+ reloadIfActiveKeyChanged();
109
+ if (_state.kind !== "disabled" || _state.reason !== "unreadable_key")
110
+ return;
111
+ const seen = _unreadableSeen;
112
+ if (!seen || _initializing)
113
+ return;
114
+ const now = Date.now();
115
+ if (now - seen.checkedAt < _unreadableReloadIntervalMs)
116
+ return;
117
+ seen.checkedAt = now;
118
+ let stamp;
119
+ try {
120
+ stamp = keyFilesStamp(seen.profile);
121
+ } catch {
122
+ return;
123
+ }
124
+ if (stamp === seen.stamp)
125
+ return;
126
+ seen.stamp = stamp;
127
+ let key = null;
128
+ try {
129
+ key = loadStoredKey(seen.profile);
130
+ } catch {
131
+ key = null;
132
+ }
133
+ if (!key)
134
+ return;
135
+ _state = {
136
+ kind: "active",
137
+ key,
138
+ profile: seen.profile,
139
+ envelopeProtected: key.custody === "software" && (0, import_credentialEnvelopeBridge.isCredEnvelopeActive)()
140
+ };
141
+ _unreadableSeen = null;
142
+ trackActiveKeyFiles(seen.profile);
143
+ emit("reloaded_after_recovery", true, {
144
+ profile: seen.profile,
145
+ thumbprint: key.thumbprint,
146
+ custody: key.custody
147
+ });
148
+ }
149
+ function reloadIfActiveKeyChanged() {
150
+ const state = _state;
151
+ const seen = _activeSeen;
152
+ if (state.kind !== "active" || !seen || seen.profile !== state.profile || _initializing)
153
+ return;
154
+ const now = Date.now();
155
+ if (now - seen.checkedAt < _unreadableReloadIntervalMs)
156
+ return;
157
+ seen.checkedAt = now;
158
+ let stamp;
159
+ try {
160
+ stamp = keyFilesStamp(seen.profile);
161
+ } catch {
162
+ return;
163
+ }
164
+ if (stamp === seen.stamp)
165
+ return;
166
+ seen.stamp = stamp;
167
+ let key = null;
168
+ try {
169
+ key = loadStoredKey(seen.profile);
170
+ } catch {
171
+ key = null;
172
+ }
173
+ if (!key || key.thumbprint === state.key.thumbprint)
174
+ return;
175
+ _state = {
176
+ kind: "active",
177
+ key,
178
+ profile: seen.profile,
179
+ envelopeProtected: key.custody === "software" && (0, import_credentialEnvelopeBridge.isCredEnvelopeActive)()
180
+ };
181
+ emit("reloaded_after_key_change", true, {
182
+ profile: seen.profile,
183
+ from: state.key.thumbprint,
184
+ thumbprint: key.thumbprint,
185
+ custody: key.custody
186
+ });
187
+ }
68
188
  async function withDeviceIdentityLock(profile, body) {
69
189
  const dir = (0, import_profileStateDir.profileStateDir)(profile);
70
- import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
190
+ (0, import_stateFiles.ensureStateDir)(dir, `take the device identity lock for profile ${profile}`);
71
191
  const release = await import_proper_lockfile.default.lock(dir, {
72
192
  realpath: false,
73
193
  lockfilePath: import_node_path.default.join(dir, ".device-identity.lock"),
@@ -123,29 +243,6 @@ function enrollmentMarkerPath(profile) {
123
243
  function enrollmentBackoffPath(profile) {
124
244
  return import_node_path.default.join(profileConfigDir(profile), ENROLLMENT_BACKOFF_FILENAME);
125
245
  }
126
- function atomicWrite0600(target, value) {
127
- const dir = import_node_path.default.dirname(target);
128
- import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
129
- const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
130
- const buf = typeof value === "string" ? Buffer.from(value, "utf-8") : value;
131
- const fd = import_node_fs.default.openSync(tmp, "w", 384);
132
- try {
133
- import_node_fs.default.writeSync(fd, buf);
134
- import_node_fs.default.fsyncSync(fd);
135
- } finally {
136
- import_node_fs.default.closeSync(fd);
137
- }
138
- import_node_fs.default.renameSync(tmp, target);
139
- try {
140
- const dirFd = import_node_fs.default.openSync(dir, "r");
141
- try {
142
- import_node_fs.default.fsyncSync(dirFd);
143
- } finally {
144
- import_node_fs.default.closeSync(dirFd);
145
- }
146
- } catch {
147
- }
148
- }
149
246
  function emit(eventName, success, metadata, opts = {}) {
150
247
  (0, import_runtime.emitAuthTelemetry)({
151
248
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -163,17 +260,31 @@ function emit(eventName, success, metadata, opts = {}) {
163
260
  }
164
261
  }
165
262
  }
166
- function persistKeypair(profile, kp) {
167
- const publicPayload = JSON.stringify({
168
- publicJwk: kp.publicJwk,
169
- thumbprint: kp.thumbprint,
263
+ function writePublicKeyRecord(profile, publicJwk) {
264
+ (0, import_stateFiles.atomicWrite0600)(publicKeyPath(profile), JSON.stringify({
265
+ publicJwk,
266
+ thumbprint: (0, import_deviceKey.jwkThumbprint)(publicJwk),
267
+ alg: publicJwk.kty === "EC" ? "ES256" : "EdDSA",
268
+ custody: publicJwk.kty === "EC" ? "hardware" : "software",
170
269
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
270
+ }), `write the device public key for profile ${profile}`);
271
+ }
272
+ function persistHardwareKey(profile, record) {
273
+ (0, import_stateFiles.atomicWrite0600)(privateKeyPath(profile), (0, import_deviceKeyHardware.serializeHardwareKeyRecord)(record), `write the hardware device key record for profile ${profile}`);
274
+ writePublicKeyRecord(profile, record.publicJwk);
275
+ emit("keypair_persisted", true, {
276
+ profile,
277
+ thumbprint: (0, import_deviceKey.jwkThumbprint)(record.publicJwk),
278
+ custody: "hardware",
279
+ envelopeProtected: false
171
280
  });
172
- atomicWrite0600(publicKeyPath(profile), publicPayload);
281
+ }
282
+ function persistKeypair(profile, kp) {
283
+ writePublicKeyRecord(profile, kp.publicJwk);
173
284
  const privatePlaintext = JSON.stringify(kp.privateJwk);
174
285
  const wireForm = (0, import_credentialEnvelopeBridge.wrapIfActiveOrPassthrough)(privatePlaintext);
175
286
  const envelopeProtected = wireForm !== privatePlaintext;
176
- atomicWrite0600(privateKeyPath(profile), wireForm);
287
+ (0, import_stateFiles.atomicWrite0600)(privateKeyPath(profile), wireForm, `write the device private key for profile ${profile}`);
177
288
  emit("keypair_persisted", true, {
178
289
  profile,
179
290
  thumbprint: kp.thumbprint,
@@ -214,7 +325,7 @@ function readIndependentIdentityFile(file, sealed) {
214
325
  import_node_fs.default.closeSync(fd);
215
326
  }
216
327
  }
217
- function loadKeypair(profile) {
328
+ function loadStoredKey(profile) {
218
329
  const pubPath = publicKeyPath(profile);
219
330
  const privPath = privateKeyPath(profile);
220
331
  const independent = /^desktop-bg-[a-f0-9]{32}$/i.test(profile);
@@ -228,6 +339,8 @@ function loadKeypair(profile) {
228
339
  let storedThumbprint;
229
340
  try {
230
341
  const parsed = JSON.parse(pubRaw);
342
+ if (!(0, import_deviceKey.isDevicePublicJwk)(parsed.publicJwk))
343
+ throw new Error("public key is not a device key");
231
344
  publicJwk = parsed.publicJwk;
232
345
  storedThumbprint = parsed.thumbprint;
233
346
  } catch (err) {
@@ -237,7 +350,22 @@ function loadKeypair(profile) {
237
350
  });
238
351
  return null;
239
352
  }
240
- const decryptResult = (0, import_credentialEnvelopeBridge.maybeDecryptOnRead)(privRaw);
353
+ const hardware = independent ? null : (0, import_deviceKeyHardware.parseHardwareKeyRecord)(privRaw);
354
+ if (hardware) {
355
+ if ((0, import_deviceKey.jwkThumbprint)(hardware.publicJwk) !== (0, import_deviceKey.jwkThumbprint)(publicJwk)) {
356
+ try {
357
+ writePublicKeyRecord(profile, hardware.publicJwk);
358
+ emit("public_key_repaired_from_hardware_record", true, { profile }, { logLevel: "info" });
359
+ } catch (err) {
360
+ emit("public_key_unreadable", false, { profile, message: err?.message ?? "unknown" });
361
+ return null;
362
+ }
363
+ }
364
+ return (0, import_deviceKeyHardware.hardwareDeviceKeyHandle)(hardware);
365
+ }
366
+ const decryptResult = (0, import_credentialEnvelopeBridge.maybeDecryptOnRead)(privRaw, {
367
+ unreadableHint: DEVICE_KEY_UNREADABLE_CREDENTIAL_HINT
368
+ });
241
369
  if (decryptResult.plaintext === null) {
242
370
  emit("private_key_decrypt_failed", false, { profile });
243
371
  return null;
@@ -253,7 +381,7 @@ function loadKeypair(profile) {
253
381
  });
254
382
  return null;
255
383
  }
256
- if (publicJwk.x !== privateJwk.x) {
384
+ if (publicJwk.kty !== "OKP" || publicJwk.x !== privateJwk.x) {
257
385
  emit("keypair_inconsistent", false, { profile });
258
386
  return null;
259
387
  }
@@ -265,16 +393,20 @@ function loadKeypair(profile) {
265
393
  recomputed
266
394
  });
267
395
  }
268
- return {
269
- publicJwk,
270
- privateJwk,
271
- thumbprint: recomputed
272
- };
396
+ try {
397
+ return (0, import_deviceKeyHandle.softwareEd25519Handle)({ publicJwk, privateJwk, thumbprint: recomputed });
398
+ } catch (err) {
399
+ emit("private_key_malformed", false, {
400
+ profile,
401
+ message: err?.message ?? "unknown"
402
+ });
403
+ return null;
404
+ }
273
405
  }
274
406
  function deviceIdentityOf(dir) {
275
407
  try {
276
408
  const parsed = JSON.parse(import_node_fs.default.readFileSync(import_node_path.default.join(dir, PUBLIC_KEY_FILENAME), "utf-8"));
277
- if (!parsed.publicJwk || typeof parsed.publicJwk.x !== "string")
409
+ if (!(0, import_deviceKey.isDevicePublicJwk)(parsed.publicJwk))
278
410
  return null;
279
411
  return (0, import_deviceKey.jwkThumbprint)(parsed.publicJwk);
280
412
  } catch {
@@ -317,6 +449,7 @@ async function consolidateDeviceIdentity(profile) {
317
449
  onEvent: (name, success, meta) => emit(name, success, meta)
318
450
  });
319
451
  } catch (err) {
452
+ (0, import_liveState.rethrowIfLiveStateRefusal)(err);
320
453
  emit("path_consolidation.failed", false, {
321
454
  profile,
322
455
  message: err?.message ?? "unknown"
@@ -337,6 +470,7 @@ async function initDeviceIdentity(profile, options = {}) {
337
470
  if (_state.kind === "uninit")
338
471
  await initDeviceIdentityLocked(profile, options);
339
472
  }).catch((err) => {
473
+ (0, import_liveState.rethrowIfLiveStateRefusal)(err);
340
474
  _state = { kind: "disabled", reason: "init_failed" };
341
475
  emit("init_failed", false, {
342
476
  profile,
@@ -356,22 +490,24 @@ async function initDeviceIdentityLocked(profile, options = {}) {
356
490
  const independent = /^desktop-bg-[a-f0-9]{32}$/i.test(profile);
357
491
  if (!independent)
358
492
  await consolidateDeviceIdentity(profile);
359
- let keypair = null;
493
+ let stored = null;
360
494
  try {
361
- keypair = loadKeypair(profile);
495
+ stored = loadStoredKey(profile);
362
496
  } catch (err) {
363
497
  emit("load_failed", false, {
364
498
  profile,
365
499
  message: err?.message ?? "unknown"
366
500
  });
367
- keypair = null;
501
+ stored = null;
368
502
  }
369
- if (keypair) {
370
- const envelopeProtected = (0, import_credentialEnvelopeBridge.isCredEnvelopeActive)();
371
- _state = { kind: "active", keypair, profile, envelopeProtected };
503
+ if (stored) {
504
+ const envelopeProtected = stored.custody === "software" && (0, import_credentialEnvelopeBridge.isCredEnvelopeActive)();
505
+ _state = { kind: "active", key: stored, profile, envelopeProtected };
506
+ trackActiveKeyFiles(profile);
372
507
  emit("active", true, {
373
508
  profile,
374
- thumbprint: keypair.thumbprint,
509
+ thumbprint: stored.thumbprint,
510
+ custody: stored.custody,
375
511
  envelopeProtected,
376
512
  regenerated: false
377
513
  });
@@ -380,6 +516,7 @@ async function initDeviceIdentityLocked(profile, options = {}) {
380
516
  const hasStoredKey = storedKeyMaterialExists(profile);
381
517
  if (hasStoredKey && (!options.recoverUnreadable || independent)) {
382
518
  _state = { kind: "disabled", reason: "unreadable_key" };
519
+ _unreadableSeen = independent ? null : { profile, stamp: keyFilesStamp(profile), checkedAt: Date.now() };
383
520
  emit("recovery_required", false, { profile, reason: "stored_key_unreadable" });
384
521
  return;
385
522
  }
@@ -404,6 +541,7 @@ async function initDeviceIdentityLocked(profile, options = {}) {
404
541
  try {
405
542
  persistResult = persistKeypair(profile, fresh);
406
543
  } catch (err) {
544
+ (0, import_liveState.rethrowIfLiveStateRefusal)(err);
407
545
  _state = { kind: "disabled", reason: "persist_failed" };
408
546
  emit("persist_failed", false, {
409
547
  profile,
@@ -411,15 +549,29 @@ async function initDeviceIdentityLocked(profile, options = {}) {
411
549
  });
412
550
  return;
413
551
  }
552
+ let freshKey;
553
+ try {
554
+ freshKey = (0, import_deviceKeyHandle.softwareEd25519Handle)(fresh);
555
+ } catch (err) {
556
+ _state = { kind: "disabled", reason: "init_failed" };
557
+ emit("init_failed", false, {
558
+ profile,
559
+ reason: "keygen_threw",
560
+ message: err?.message ?? "unknown"
561
+ });
562
+ return;
563
+ }
414
564
  _state = {
415
565
  kind: "active",
416
- keypair: fresh,
566
+ key: freshKey,
417
567
  profile,
418
568
  envelopeProtected: persistResult.envelopeProtected
419
569
  };
570
+ trackActiveKeyFiles(profile);
420
571
  emit("active", true, {
421
572
  profile,
422
573
  thumbprint: fresh.thumbprint,
574
+ custody: "software",
423
575
  envelopeProtected: persistResult.envelopeProtected,
424
576
  regenerated: true
425
577
  });
@@ -428,12 +580,18 @@ async function resetDeviceIdentity(opts) {
428
580
  if (_initializing)
429
581
  await _initializing;
430
582
  if (opts?.clearOnDisk && opts.profile) {
431
- const targets = [PUBLIC_KEY_FILENAME, PRIVATE_KEY_FILENAME].flatMap((name) => (0, import_profileStateDir.profileStateFilePathsToClear)(opts.profile, name));
583
+ const targets = [
584
+ ...[PUBLIC_KEY_FILENAME, PRIVATE_KEY_FILENAME].flatMap((name) => (0, import_profileStateDir.profileStateFilePathsToClear)(opts.profile, name)),
585
+ ...retiredKeyCopies(opts.profile, PUBLIC_KEY_FILENAME),
586
+ ...retiredKeyCopies(opts.profile, PRIVATE_KEY_FILENAME),
587
+ pendingHardwareKeyPath(opts.profile)
588
+ ];
432
589
  await withDeviceIdentityLock(opts.profile, async () => {
433
590
  for (const p of targets) {
434
591
  try {
435
- import_node_fs.default.unlinkSync(p);
592
+ (0, import_stateFiles.removeStateFile)(p, `delete the device key file ${import_node_path.default.basename(p)}`);
436
593
  } catch (err) {
594
+ (0, import_liveState.rethrowIfLiveStateRefusal)(err);
437
595
  if (err.code !== "ENOENT") {
438
596
  emit("clear_failed", false, {
439
597
  profile: opts.profile,
@@ -446,34 +604,210 @@ async function resetDeviceIdentity(opts) {
446
604
  });
447
605
  }
448
606
  _state = { kind: "uninit" };
607
+ _unreadableSeen = null;
608
+ _activeSeen = null;
609
+ }
610
+ async function prepareHardwareKeyCandidate(profile) {
611
+ if (/^desktop-bg-[a-f0-9]{32}$/i.test(profile))
612
+ return { ok: false, reason: "independent_profile" };
613
+ if (_state.kind === "active" && _state.key.custody === "hardware") {
614
+ return { ok: false, reason: "already_hardware" };
615
+ }
616
+ const capability = await (0, import_deviceKeyHardware.probeHardwareDeviceKey)();
617
+ if (!capability.available)
618
+ return { ok: false, reason: "unavailable", capability };
619
+ try {
620
+ const { record, handle } = await (0, import_deviceKeyHardware.createHardwareDeviceKey)();
621
+ return { ok: true, candidate: { key: handle, record } };
622
+ } catch (err) {
623
+ emit("hardware_key_create_failed", false, {
624
+ profile,
625
+ ...err instanceof import_deviceKeyHardware.HardwareDeviceKeyError ? { hardwareFailure: err.code } : {},
626
+ message: err?.message ?? "unknown"
627
+ });
628
+ return { ok: false, reason: "create_failed", message: err?.message ?? "unknown" };
629
+ }
630
+ }
631
+ async function signWithCandidate(candidate, opts) {
632
+ try {
633
+ return await (0, import_deviceKey.signProofWith)(candidate.key, opts);
634
+ } catch (err) {
635
+ emit("sign_failed", false, {
636
+ thumbprint: candidate.key.thumbprint,
637
+ custody: "hardware",
638
+ candidate: true,
639
+ method: opts.method,
640
+ ...err instanceof import_deviceKeyHardware.HardwareDeviceKeyError ? { hardwareFailure: err.code } : {},
641
+ message: err?.message ?? "unknown"
642
+ });
643
+ return null;
644
+ }
645
+ }
646
+ function retiredKeyCopies(profile, filename) {
647
+ const dir = import_node_path.default.dirname(filename === PUBLIC_KEY_FILENAME ? publicKeyPath(profile) : privateKeyPath(profile));
648
+ const prefix = `${filename}${import_profileStateDir.RETIRED_COPY_SUFFIX}.`;
649
+ try {
650
+ return import_node_fs.default.readdirSync(dir).filter((name) => name.startsWith(prefix)).sort().map((name) => import_node_path.default.join(dir, name));
651
+ } catch {
652
+ return [];
653
+ }
654
+ }
655
+ function retireSoftwareKey(profile) {
656
+ try {
657
+ const priv = privateKeyPath(profile);
658
+ const raw = import_node_fs.default.readFileSync(priv, "utf-8");
659
+ if ((0, import_deviceKeyHardware.parseHardwareKeyRecord)(raw))
660
+ return;
661
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
662
+ (0, import_stateFiles.atomicWrite0600)(`${priv}${import_profileStateDir.RETIRED_COPY_SUFFIX}.${stamp}`, raw, `keep the replaced software device key for profile ${profile}`);
663
+ try {
664
+ (0, import_stateFiles.atomicWrite0600)(`${publicKeyPath(profile)}${import_profileStateDir.RETIRED_COPY_SUFFIX}.${stamp}`, import_node_fs.default.readFileSync(publicKeyPath(profile), "utf-8"), `keep the replaced software device public key for profile ${profile}`);
665
+ } catch (err) {
666
+ (0, import_liveState.rethrowIfLiveStateRefusal)(err);
667
+ }
668
+ for (const filename of [PRIVATE_KEY_FILENAME, PUBLIC_KEY_FILENAME]) {
669
+ for (const stale of retiredKeyCopies(profile, filename).slice(0, -1)) {
670
+ try {
671
+ (0, import_stateFiles.removeStateFile)(stale, `prune a retired device key copy for profile ${profile}`);
672
+ } catch (err) {
673
+ (0, import_liveState.rethrowIfLiveStateRefusal)(err);
674
+ }
675
+ }
676
+ }
677
+ } catch (err) {
678
+ emit("retire_software_key_failed", false, { profile, message: err?.message ?? "unknown" });
679
+ }
680
+ }
681
+ async function commitDeviceKeyCandidate(profile, candidate) {
682
+ if (_initializing)
683
+ await _initializing;
684
+ await withDeviceIdentityLock(profile, async () => {
685
+ commitRecordLocked(profile, candidate.record, candidate.key);
686
+ });
687
+ }
688
+ function commitRecordLocked(profile, record, key) {
689
+ retireSoftwareKey(profile);
690
+ persistHardwareKey(profile, record);
691
+ clearEnrollmentMarker(profile);
692
+ removePendingHardwareKey(profile);
693
+ _state = { kind: "active", key, profile, envelopeProtected: false };
694
+ _unreadableSeen = null;
695
+ trackActiveKeyFiles(profile);
696
+ emit("upgraded_to_hardware", true, {
697
+ profile,
698
+ thumbprint: key.thumbprint,
699
+ custody: "hardware"
700
+ });
701
+ }
702
+ const PENDING_HARDWARE_FILENAME = "device-key.pending";
703
+ const PENDING_HARDWARE_KEY_MAX_AGE_MS = 10 * 60 * 1e3;
704
+ function pendingHardwareKeyPath(profile) {
705
+ return import_node_path.default.join(profileConfigDir(profile), PENDING_HARDWARE_FILENAME);
706
+ }
707
+ function removePendingHardwareKey(profile) {
708
+ try {
709
+ (0, import_stateFiles.removeStateFile)(pendingHardwareKeyPath(profile), `remove the pending hardware key for profile ${profile}`);
710
+ } catch (err) {
711
+ (0, import_liveState.rethrowIfLiveStateRefusal)(err);
712
+ if (err.code !== "ENOENT") {
713
+ emit("pending_hardware_key_clear_failed", false, { profile, message: err?.message ?? "unknown" });
714
+ }
715
+ }
716
+ }
717
+ async function persistPendingHardwareKey(profile, candidate) {
718
+ await withDeviceIdentityLock(profile, async () => {
719
+ (0, import_stateFiles.atomicWrite0600)(pendingHardwareKeyPath(profile), (0, import_deviceKeyHardware.serializeHardwareKeyRecord)(candidate.record), `park a pending hardware key for profile ${profile}`);
720
+ });
721
+ }
722
+ async function commitPendingHardwareKey(profile, thumbprint) {
723
+ if (_initializing)
724
+ await _initializing;
725
+ return withDeviceIdentityLock(profile, async () => {
726
+ const file = pendingHardwareKeyPath(profile);
727
+ let raw;
728
+ let modifiedAt;
729
+ try {
730
+ raw = import_node_fs.default.readFileSync(file, "utf-8");
731
+ modifiedAt = import_node_fs.default.statSync(file).mtimeMs;
732
+ } catch {
733
+ return "no_pending";
734
+ }
735
+ const record = (0, import_deviceKeyHardware.parseHardwareKeyRecord)(raw);
736
+ if (!record)
737
+ return "unreadable";
738
+ if (Date.now() - modifiedAt > PENDING_HARDWARE_KEY_MAX_AGE_MS) {
739
+ removePendingHardwareKey(profile);
740
+ return "expired";
741
+ }
742
+ if ((0, import_deviceKey.jwkThumbprint)(record.publicJwk) !== thumbprint)
743
+ return "mismatch";
744
+ commitRecordLocked(profile, record, (0, import_deviceKeyHardware.hardwareDeviceKeyHandle)(record));
745
+ return "committed";
746
+ });
747
+ }
748
+ async function discardPendingHardwareKey(profile) {
749
+ await withDeviceIdentityLock(profile, async () => {
750
+ removePendingHardwareKey(profile);
751
+ });
449
752
  }
450
753
  function isDeviceIdentityActive() {
754
+ reloadIfRecovered();
451
755
  return _state.kind === "active";
452
756
  }
453
757
  function getDevicePublicKey() {
758
+ reloadIfRecovered();
454
759
  if (_state.kind !== "active")
455
760
  return null;
456
- return { jwk: _state.keypair.publicJwk, thumbprint: _state.keypair.thumbprint };
761
+ return {
762
+ jwk: _state.key.publicJwk,
763
+ thumbprint: _state.key.thumbprint,
764
+ alg: _state.key.alg,
765
+ custody: _state.key.custody
766
+ };
457
767
  }
458
768
  function describeDeviceIdentityState() {
769
+ reloadIfRecovered();
459
770
  if (_state.kind === "disabled") {
460
771
  return { kind: "disabled", reason: _state.reason };
461
772
  }
462
773
  if (_state.kind === "active") {
463
774
  return {
464
775
  kind: "active",
465
- thumbprint: _state.keypair.thumbprint,
776
+ thumbprint: _state.key.thumbprint,
466
777
  profile: _state.profile,
467
- envelopeProtected: _state.envelopeProtected
778
+ envelopeProtected: _state.envelopeProtected,
779
+ alg: _state.key.alg,
780
+ custody: _state.key.custody,
781
+ canSign: _state.key.custody === "software" ? true : (0, import_deviceKeyHardware.resolveHardwareDeviceKeyHelper)() !== null
468
782
  };
469
783
  }
470
784
  return { kind: "uninit" };
471
785
  }
472
- function signServerRequest(opts) {
473
- if (_state.kind !== "active")
786
+ function describeStoredDeviceKey(profile) {
787
+ try {
788
+ const parsed = JSON.parse(import_node_fs.default.readFileSync(publicKeyPath(profile), "utf-8"));
789
+ if (!(0, import_deviceKey.isDevicePublicJwk)(parsed.publicJwk))
790
+ return { stored: false };
791
+ const alg = parsed.publicJwk.kty === "EC" ? "ES256" : "EdDSA";
792
+ const custody = alg === "ES256" ? "hardware" : "software";
793
+ return {
794
+ stored: true,
795
+ alg,
796
+ custody,
797
+ thumbprint: (0, import_deviceKey.jwkThumbprint)(parsed.publicJwk),
798
+ canSign: custody === "software" ? true : (0, import_deviceKeyHardware.resolveHardwareDeviceKeyHelper)() !== null
799
+ };
800
+ } catch {
801
+ return { stored: false };
802
+ }
803
+ }
804
+ async function signServerRequest(opts) {
805
+ reloadIfRecovered();
806
+ const state = _state;
807
+ if (state.kind !== "active")
474
808
  return null;
475
809
  try {
476
- return (0, import_deviceKey.buildProof)(_state.keypair.privateJwk, _state.keypair.publicJwk, {
810
+ return await (0, import_deviceKey.signProofWith)(state.key, {
477
811
  method: opts.method,
478
812
  url: opts.url,
479
813
  refreshToken: opts.refreshToken,
@@ -481,9 +815,13 @@ function signServerRequest(opts) {
481
815
  });
482
816
  } catch (err) {
483
817
  emit("sign_failed", false, {
484
- profile: _state.profile,
485
- thumbprint: _state.keypair.thumbprint,
818
+ profile: state.profile,
819
+ thumbprint: state.key.thumbprint,
820
+ custody: state.key.custody,
486
821
  method: opts.method,
822
+ // A hardware failure names its cause (helper missing, timed out, key
823
+ // mismatch) so a status line can say what to do about it.
824
+ ...err instanceof import_deviceKeyHardware.HardwareDeviceKeyError ? { hardwareFailure: err.code } : {},
487
825
  message: err?.message ?? "unknown"
488
826
  });
489
827
  return null;
@@ -504,8 +842,9 @@ function __enrollmentBackoffPathForTest(profile) {
504
842
  function clearEnrollmentMarker(profile) {
505
843
  for (const markerPath of [enrollmentMarkerPath(profile), enrollmentBackoffPath(profile)]) {
506
844
  try {
507
- import_node_fs.default.unlinkSync(markerPath);
845
+ (0, import_stateFiles.removeStateFile)(markerPath, `delete the enrollment file ${import_node_path.default.basename(markerPath)}`);
508
846
  } catch (err) {
847
+ (0, import_liveState.rethrowIfLiveStateRefusal)(err);
509
848
  if (err.code !== "ENOENT") {
510
849
  emit("enrollment_marker_clear_failed", false, {
511
850
  profile,
@@ -522,7 +861,7 @@ async function enrollWithServer(opts) {
522
861
  return { enrolled: false, reason: "not_active" };
523
862
  }
524
863
  const profile = _state.profile;
525
- const keypair = _state.keypair;
864
+ const key = _state.key;
526
865
  const markerPath = enrollmentMarkerPath(profile);
527
866
  const backoffPath = enrollmentBackoffPath(profile);
528
867
  try {
@@ -534,27 +873,33 @@ async function enrollWithServer(opts) {
534
873
  try {
535
874
  const raw = JSON.parse(import_node_fs.default.readFileSync(backoffPath, "utf-8"));
536
875
  const retryAfter = Date.parse(raw.retryAfter || "");
537
- if (raw.serverUrl === serverUrl && raw.machineUuid === machineUuid && raw.thumbprint === keypair.thumbprint && Number.isFinite(retryAfter) && retryAfter > Date.now()) {
876
+ if (raw.serverUrl === serverUrl && raw.machineUuid === machineUuid && raw.thumbprint === key.thumbprint && Number.isFinite(retryAfter) && retryAfter > Date.now()) {
538
877
  return { enrolled: false, reason: "backoff_active" };
539
878
  }
540
- import_node_fs.default.unlinkSync(backoffPath);
879
+ (0, import_stateFiles.removeStateFile)(backoffPath, "delete the stale enrollment backoff");
541
880
  } catch (err) {
542
881
  if (err.code !== "ENOENT") {
543
882
  try {
544
- import_node_fs.default.unlinkSync(backoffPath);
545
- } catch {
883
+ (0, import_stateFiles.removeStateFile)(backoffPath, "delete the corrupt enrollment backoff");
884
+ } catch (clearErr) {
885
+ (0, import_liveState.rethrowIfLiveStateRefusal)(clearErr);
546
886
  }
547
887
  }
548
888
  }
549
889
  const base = serverUrl.replace(/\/+$/, "");
550
890
  const htu = `${base}/api/daemon-resources/enroll-device-key`;
551
- const proofJws = (0, import_deviceKey.buildProof)(keypair.privateJwk, keypair.publicJwk, {
552
- method: "POST",
553
- url: htu
554
- });
891
+ let proofJws;
892
+ try {
893
+ proofJws = await (0, import_deviceKey.signProofWith)(key, { method: "POST", url: htu });
894
+ } catch (err) {
895
+ const message = err?.message ?? "unknown";
896
+ emit("enrollment_sign_failed", false, { profile, thumbprint: key.thumbprint, custody: key.custody, message });
897
+ return { enrolled: false, reason: "not_active", message };
898
+ }
555
899
  const body = JSON.stringify({
556
900
  machineUuid,
557
- publicJwkX: keypair.publicJwk.x,
901
+ ...key.publicJwk.kty === "OKP" ? { publicJwkX: key.publicJwk.x } : {},
902
+ publicJwk: key.publicJwk,
558
903
  proofJws
559
904
  });
560
905
  const ownAbort = new AbortController();
@@ -575,7 +920,7 @@ async function enrollWithServer(opts) {
575
920
  const message = err?.message ?? "unknown";
576
921
  emit("enrollment_network_error", false, {
577
922
  profile,
578
- thumbprint: keypair.thumbprint,
923
+ thumbprint: key.thumbprint,
579
924
  serverUrl,
580
925
  message
581
926
  });
@@ -595,13 +940,13 @@ async function enrollWithServer(opts) {
595
940
  } catch {
596
941
  }
597
942
  }
598
- const pinnedToOtherKey = response.status === 409 && enrolledThumbprint !== null && enrolledThumbprint !== keypair.thumbprint;
943
+ const pinnedToOtherKey = response.status === 409 && enrolledThumbprint !== null && enrolledThumbprint !== key.thumbprint;
599
944
  const keyPinnedElsewhere = response.status === 409 && conflictCode === "ENROLLMENT_DUPLICATE_KEY";
600
945
  const isBenignRejection = response.status === 404 && responseText.includes("DAEMON_NOT_FOUND") || response.status === 403 && responseText.includes("OPAQUE_AUTH_REFUSED") || response.status === 409 && !pinnedToOtherKey && !keyPinnedElsewhere;
601
946
  if (pinnedToOtherKey) {
602
947
  emit("enrollment_key_mismatch", false, {
603
948
  profile,
604
- thumbprint: keypair.thumbprint,
949
+ thumbprint: key.thumbprint,
605
950
  enrolledThumbprint,
606
951
  serverUrl,
607
952
  message: "this machine is registered with a different device key than this profile signs with; its refreshes will be refused. An interactive daemon login (skrrd login) re-registers this key."
@@ -609,13 +954,14 @@ async function enrollWithServer(opts) {
609
954
  }
610
955
  if (response.status === 404 && responseText.includes("DAEMON_NOT_FOUND")) {
611
956
  try {
612
- atomicWrite0600(backoffPath, JSON.stringify({
957
+ (0, import_stateFiles.atomicWrite0600)(backoffPath, JSON.stringify({
613
958
  serverUrl,
614
959
  machineUuid,
615
- thumbprint: keypair.thumbprint,
960
+ thumbprint: key.thumbprint,
616
961
  retryAfter: new Date(Date.now() + DAEMON_NOT_FOUND_BACKOFF_MS).toISOString()
617
- }));
962
+ }), "write the enrollment backoff");
618
963
  } catch (err) {
964
+ (0, import_liveState.rethrowIfLiveStateRefusal)(err);
619
965
  emit("enrollment_backoff_write_failed", false, {
620
966
  profile,
621
967
  message: err?.message ?? "unknown"
@@ -624,7 +970,7 @@ async function enrollWithServer(opts) {
624
970
  }
625
971
  emit("enrollment_rejected", false, {
626
972
  profile,
627
- thumbprint: keypair.thumbprint,
973
+ thumbprint: key.thumbprint,
628
974
  serverUrl,
629
975
  status: response.status,
630
976
  body: responseText
@@ -639,20 +985,21 @@ async function enrollWithServer(opts) {
639
985
  const marker = JSON.stringify({
640
986
  enrolledAt: (/* @__PURE__ */ new Date()).toISOString(),
641
987
  serverUrl,
642
- thumbprint: keypair.thumbprint,
988
+ thumbprint: key.thumbprint,
643
989
  machineUuid
644
990
  });
645
991
  try {
646
- atomicWrite0600(markerPath, marker);
992
+ (0, import_stateFiles.atomicWrite0600)(markerPath, marker, "write the enrollment marker");
647
993
  } catch (err) {
994
+ (0, import_liveState.rethrowIfLiveStateRefusal)(err);
648
995
  emit("enrollment_marker_write_failed", false, {
649
996
  profile,
650
- thumbprint: keypair.thumbprint,
997
+ thumbprint: key.thumbprint,
651
998
  message: err?.message ?? "unknown"
652
999
  });
653
1000
  }
654
1001
  try {
655
- import_node_fs.default.unlinkSync(backoffPath);
1002
+ (0, import_stateFiles.removeStateFile)(backoffPath, "delete the enrollment backoff");
656
1003
  } catch (err) {
657
1004
  if (err.code !== "ENOENT") {
658
1005
  emit("enrollment_backoff_clear_failed", false, {
@@ -663,26 +1010,36 @@ async function enrollWithServer(opts) {
663
1010
  }
664
1011
  emit("enrolled_with_server", true, {
665
1012
  profile,
666
- thumbprint: keypair.thumbprint,
1013
+ thumbprint: key.thumbprint,
667
1014
  serverUrl
668
1015
  });
669
- return { enrolled: true, thumbprint: keypair.thumbprint };
1016
+ return { enrolled: true, thumbprint: key.thumbprint };
670
1017
  }
671
1018
  // Annotate the CommonJS export names for ESM import in node:
672
1019
  0 && (module.exports = {
1020
+ DEVICE_KEY_UNREADABLE_CREDENTIAL_HINT,
1021
+ PENDING_HARDWARE_KEY_MAX_AGE_MS,
673
1022
  __enrollmentBackoffPathForTest,
674
1023
  __enrollmentMarkerPathForTest,
675
1024
  __getStateForTest,
676
1025
  __privateKeyPathForTest,
677
1026
  __publicKeyPathForTest,
678
1027
  __setStateForTest,
1028
+ __setUnreadableReloadIntervalForTest,
679
1029
  clearEnrollmentMarker,
1030
+ commitDeviceKeyCandidate,
1031
+ commitPendingHardwareKey,
680
1032
  describeDeviceIdentityState,
681
1033
  describeDeviceKeySplit,
1034
+ describeStoredDeviceKey,
1035
+ discardPendingHardwareKey,
682
1036
  enrollWithServer,
683
1037
  getDevicePublicKey,
684
1038
  initDeviceIdentity,
685
1039
  isDeviceIdentityActive,
1040
+ persistPendingHardwareKey,
1041
+ prepareHardwareKeyCandidate,
686
1042
  resetDeviceIdentity,
687
- signServerRequest
1043
+ signServerRequest,
1044
+ signWithCandidate
688
1045
  });