@pellux/goodvibes-daemon 1.28.19 → 1.28.21

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 (114) hide show
  1. package/CHANGELOG.md +119 -63
  2. package/README.md +24 -19
  3. package/bin/launcher-support.js +5 -5
  4. package/package.json +6 -4
  5. package/scripts/postinstall.js +8 -8
  6. package/src/cli/command-catalog.ts +22 -22
  7. package/src/cli/completion.ts +4 -4
  8. package/src/cli/help.ts +5 -5
  9. package/src/cli/index.ts +3 -3
  10. package/src/cli/parser.ts +2 -2
  11. package/src/cli/surface-catalog.ts +1 -1
  12. package/src/cli/types.ts +2 -2
  13. package/src/cluster/daemon-ws-call.ts +5 -5
  14. package/src/cluster/raw-reply-route.ts +5 -5
  15. package/src/config/checkpoint-settings.ts +7 -7
  16. package/src/config/config-key-guard.ts +22 -0
  17. package/src/config/run-daemon-config-migration.ts +3 -3
  18. package/src/config/secret-config.ts +7 -7
  19. package/src/config/surface.ts +3 -3
  20. package/src/core/pairing-banner.ts +5 -5
  21. package/src/daemon/cli.ts +45 -43
  22. package/src/daemon/config-command.ts +15 -15
  23. package/src/daemon/handlers/context.ts +1 -1
  24. package/src/daemon/handlers/contracts.ts +19 -4
  25. package/src/daemon/handlers/credentials.ts +1 -1
  26. package/src/daemon/handlers/drafts/draft-store.ts +3 -3
  27. package/src/daemon/handlers/drafts/register.ts +4 -4
  28. package/src/daemon/handlers/inbox/aggregator.ts +8 -8
  29. package/src/daemon/handlers/inbox/cursor-store.ts +10 -10
  30. package/src/daemon/handlers/inbox/index.ts +7 -7
  31. package/src/daemon/handlers/inbox/mapping.ts +2 -2
  32. package/src/daemon/handlers/inbox/poller.ts +5 -5
  33. package/src/daemon/handlers/inbox/provider-adapter.ts +8 -8
  34. package/src/daemon/handlers/inbox/providers/discord.ts +6 -6
  35. package/src/daemon/handlers/inbox/providers/email.ts +3 -3
  36. package/src/daemon/handlers/inbox/providers/imap-client.ts +1 -1
  37. package/src/daemon/handlers/inbox/providers/slack.ts +4 -4
  38. package/src/daemon/handlers/index.ts +18 -8
  39. package/src/daemon/handlers/payments/address-store.ts +54 -0
  40. package/src/daemon/handlers/payments/budget-store.ts +356 -0
  41. package/src/daemon/handlers/payments/card-store.ts +486 -0
  42. package/src/daemon/handlers/payments/checkout-handlers.ts +526 -0
  43. package/src/daemon/handlers/payments/index.ts +38 -0
  44. package/src/daemon/handlers/payments/merchant-judge.ts +57 -0
  45. package/src/daemon/handlers/payments/notifier.ts +112 -0
  46. package/src/daemon/handlers/payments/purchase-ledger.ts +108 -0
  47. package/src/daemon/handlers/payments/register.ts +518 -0
  48. package/src/daemon/handlers/register.ts +3 -3
  49. package/src/daemon/handlers/remote/backends/cloud-terminal.ts +9 -1
  50. package/src/daemon/handlers/remote/backends/process-runner.ts +1 -1
  51. package/src/daemon/handlers/remote/backends/ssh.ts +9 -1
  52. package/src/daemon/handlers/remote/backends/types.ts +2 -2
  53. package/src/daemon/handlers/remote/dispatcher.ts +3 -3
  54. package/src/daemon/handlers/remote/index.ts +1 -1
  55. package/src/daemon/handlers/remote/peer-registry.ts +62 -13
  56. package/src/daemon/handlers/routing/inbox-bridge.ts +5 -5
  57. package/src/daemon/handlers/routing/index.ts +1 -1
  58. package/src/daemon/handlers/routing/route-store.ts +1 -1
  59. package/src/daemon/handlers/routing/routing-resolver.ts +3 -3
  60. package/src/daemon/handlers/sqlite-store.ts +9 -9
  61. package/src/daemon/handlers/triage/index.ts +1 -1
  62. package/src/daemon/handlers/triage/integration.ts +3 -3
  63. package/src/daemon/handlers/triage/pipeline.ts +2 -2
  64. package/src/daemon/handlers/triage/scorer.ts +2 -2
  65. package/src/daemon/handlers/triage/tagger/discord.ts +3 -3
  66. package/src/daemon/handlers/triage/tagger/imap.ts +7 -7
  67. package/src/daemon/handlers/triage/tagger/index.ts +1 -1
  68. package/src/daemon/handlers/triage/tagger/shared.ts +3 -3
  69. package/src/daemon/handlers/triage/tagger/slack.ts +1 -1
  70. package/src/daemon/handlers/triage/types.ts +2 -2
  71. package/src/daemon/lifecycle.ts +5 -5
  72. package/src/daemon/local-daemon-state.ts +7 -7
  73. package/src/daemon/pair-command.ts +14 -14
  74. package/src/daemon/provision-wake-model.ts +5 -5
  75. package/src/daemon/send/channels.ts +7 -7
  76. package/src/daemon/send/command.ts +11 -11
  77. package/src/daemon/send/composition.ts +5 -5
  78. package/src/daemon/send/failure-text.ts +6 -6
  79. package/src/daemon/send/inert-text.ts +18 -18
  80. package/src/daemon/send/stdin.ts +3 -3
  81. package/src/daemon/service-commands.ts +32 -32
  82. package/src/daemon/sessions-command.ts +7 -7
  83. package/src/daemon/status-command.ts +22 -22
  84. package/src/daemon/webui-command.ts +14 -14
  85. package/src/runtime/boot-tasks.ts +1 -1
  86. package/src/runtime/browser-checkout-seam-holder.ts +55 -0
  87. package/src/runtime/cluster-composition.ts +9 -9
  88. package/src/runtime/cluster-group-composition.ts +7 -7
  89. package/src/runtime/conversation-rewind-port.ts +8 -8
  90. package/src/runtime/credential-composition.ts +2 -2
  91. package/src/runtime/daemon-handler-composition.ts +61 -4
  92. package/src/runtime/device-posture-composition.ts +10 -10
  93. package/src/runtime/disposal-wiring.ts +8 -8
  94. package/src/runtime/fleet-needs-input-push.ts +4 -4
  95. package/src/runtime/fleet-services.ts +1 -1
  96. package/src/runtime/hosted-session-composition.ts +13 -13
  97. package/src/runtime/index.ts +1 -1
  98. package/src/runtime/knowledge-services.ts +2 -2
  99. package/src/runtime/legacy-daemon-migration.ts +43 -43
  100. package/src/runtime/legacy-daemon-reconcile.ts +30 -30
  101. package/src/runtime/mail-composition.ts +6 -6
  102. package/src/runtime/notification-dispatch.ts +7 -7
  103. package/src/runtime/payments-composition.ts +187 -0
  104. package/src/runtime/plugin-composition.ts +7 -7
  105. package/src/runtime/runtime-services-types.ts +9 -9
  106. package/src/runtime/services.ts +41 -32
  107. package/src/runtime/trigger-services.ts +1 -1
  108. package/src/runtime/trust/checkpoint-eligibility.ts +5 -5
  109. package/src/runtime/trust/trust-gated-approvals.ts +9 -9
  110. package/src/runtime/update-check.ts +4 -4
  111. package/src/runtime/workspace-checkpointing.ts +6 -6
  112. package/src/testing/daemon-fixture.ts +11 -11
  113. package/src/testing/hosted-session-failures.ts +4 -4
  114. package/src/version.ts +2 -2
@@ -3,13 +3,13 @@
3
3
  * CLI (`src/daemon/service-commands.ts`, the `migrate-service` subcommand)
4
4
  * and by this daemon's own boot-time reconcile (`legacy-daemon-reconcile.ts`).
5
5
  * The terminal app ships an independent implementation of the same
6
- * detect/migrate contract for its own onboarding guided UX not shared
6
+ * detect/migrate contract for its own onboarding guided UX, not shared
7
7
  * code, but the two are meant to agree on what "legacy" means and how a
8
8
  * migration is carried out.
9
9
  *
10
10
  * NAMING, load-bearing: this module's identifiers say "legacy" because the
11
11
  * engine migrates AWAY from the `goodvibes-daemon.service` unit name toward
12
- * the runtime-managed unit that name was used by an older install script/
12
+ * the runtime-managed unit, that name was used by an older install script/
13
13
  * release (scripts/install.sh now creates `goodvibes.service` and treats
14
14
  * `goodvibes-daemon.service` as the retired name it migrates existing hosts
15
15
  * away from; see migrate_legacy_installer_unit there). An already-installed
@@ -18,7 +18,7 @@
18
18
  * never labels it legacy or implies it should be removed unless the user is
19
19
  * explicitly migrating.
20
20
  *
21
- * This lives under `src/runtime/` not `src/daemon/` because both
21
+ * This lives under `src/runtime/`, not `src/daemon/`, because both
22
22
  * `src/daemon/service-commands.ts` (the CLI subcommand) and
23
23
  * `src/runtime/legacy-daemon-reconcile.ts` (the boot-time reconcile) need it,
24
24
  * and `src/runtime/**` is this repository's shared layer both can import.
@@ -37,11 +37,11 @@
37
37
  * - ADOPT-OR-WARN, NEVER KILL. If the legacy unit file is simply absent but
38
38
  * something is already listening on the configured host:port (this dev
39
39
  * host's real case: a manually `nohup`'d daemon with no unit at all),
40
- * that is an unidentified process, not a managed unit nothing to stop
40
+ * that is an unidentified process, not a managed unit, nothing to stop
41
41
  * or disable, and this module never attempts to kill it.
42
42
  * - Every action (legacy stop/disable, unit-file removal, daemon-reload)
43
43
  * goes through the injectable `actionRunner`/`legacyUnitFileRemove` seams
44
- * tests use no code path here bypasses them, so the migration is
44
+ * tests use, no code path here bypasses them, so the migration is
45
45
  * exercised deterministically via fakes and never touches a real running
46
46
  * service in tests.
47
47
  */
@@ -56,7 +56,7 @@ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
56
56
  import { runDaemonConfigMigration } from '../config/run-daemon-config-migration.ts';
57
57
  import { GOODVIBES_DAEMON_SURFACE_ROOT } from '../config/surface.ts';
58
58
 
59
- /** Structurally derived from `PlatformServiceManager`'s own constructor the
59
+ /** Structurally derived from `PlatformServiceManager`'s own constructor, the
60
60
  * SDK's public `platform/daemon` entry point only re-exports the class and
61
61
  * `ManagedServiceStatus`, not the options/definition/action-runner interfaces
62
62
  * by name, so we pull their shapes off the class itself rather than reaching
@@ -66,7 +66,7 @@ type ManagedServiceDefinition = NonNullable<ManagedServiceManagerOptions['defini
66
66
  export type ManagedServiceActionRunner = NonNullable<ManagedServiceManagerOptions['actionRunner']>;
67
67
  type ManagedServiceActionResult = ReturnType<ManagedServiceActionRunner>;
68
68
 
69
- // The one unit name/description this tool manages used by the daemon CLI
69
+ // The one unit name/description this tool manages, used by the daemon CLI
70
70
  // (`goodvibes-daemon install-service|uninstall-service|service-status|migrate-service`).
71
71
  // The terminal app's own onboarding UX builds the exact same service
72
72
  // definition independently, so a migration triggered from either surface
@@ -80,7 +80,7 @@ export const MANAGED_SERVICE_DESCRIPTION = 'GoodVibes daemon (shared session bro
80
80
 
81
81
  /**
82
82
  * Follow-up: resolve the unit name the SDK's `PlatformServiceManager`
83
- * would actually manage, from config alone for callers that need the
83
+ * would actually manage, from config alone, for callers that need the
84
84
  * honest display name BEFORE any manager/status exists (the onboarding
85
85
  * wizard's detection banner resolves this at snapshot-collection time and
86
86
  * carries it on `OnboardingLegacyDaemonSnapshot.trackedServiceName`).
@@ -101,17 +101,17 @@ export function resolveConfiguredServiceName(config: { get(key: string): unknown
101
101
  export interface BuildManagedDaemonServiceManagerParams {
102
102
  readonly binaryPath: string;
103
103
  /**
104
- * The GoodVibes tree home GOODVIBES_HOME-overridable, used to root the
104
+ * The GoodVibes tree home, GOODVIBES_HOME-overridable, used to root the
105
105
  * ConfigManager and the daemon's own `--daemon-home` state directory. NEVER
106
106
  * used for unit-file path resolution: see `unitHomeDir` for that. A unit
107
107
  * path search rooted here would look for `~/.config/systemd/user/` under
108
108
  * whatever GOODVIBES_HOME points at instead of the real login home systemd
109
- * actually reads the same class of bug the boot-time reconcile in
109
+ * actually reads, the same class of bug the boot-time reconcile in
110
110
  * `src/daemon/cli.ts` already guards against with the identical split.
111
111
  */
112
112
  readonly homeDir: string;
113
113
  /**
114
- * The LOGIN user's home where `~/.config/systemd/user/` (or the launchd/
114
+ * The LOGIN user's home, where `~/.config/systemd/user/` (or the launchd/
115
115
  * Windows equivalent) actually lives, regardless of any GOODVIBES_HOME/
116
116
  * GOODVIBES_DAEMON_HOME override in effect. Threaded through to
117
117
  * `PlatformServiceManager`'s own `homeDirectory` option, which resolves the
@@ -122,7 +122,7 @@ export interface BuildManagedDaemonServiceManagerParams {
122
122
  readonly unitHomeDir: string;
123
123
  readonly host: string;
124
124
  readonly port: number;
125
- /** Defaults to `homeDir` overridable so tests can scope both to one tempdir. */
125
+ /** Defaults to `homeDir`, overridable so tests can scope both to one tempdir. */
126
126
  readonly workingDirectory?: string | undefined;
127
127
  /** Injected in tests; a real `ConfigManager` rooted at `homeDir` otherwise. */
128
128
  readonly configManager?: ConfigManager | undefined;
@@ -131,12 +131,12 @@ export interface BuildManagedDaemonServiceManagerParams {
131
131
  }
132
132
 
133
133
  /**
134
- * Build the ONE `PlatformServiceManager` this tool manages the single
134
+ * Build the ONE `PlatformServiceManager` this tool manages, the single
135
135
  * source of truth, in this repository, for the unit's definition
136
136
  * (`ExecStart` command/args, name, description). Both
137
137
  * `src/daemon/service-commands.ts` (the CLI) and this daemon's own
138
138
  * boot-time reconcile call this so every path in this repository installs
139
- * the identical unit no risk of consumers drifting apart.
139
+ * the identical unit, no risk of consumers drifting apart.
140
140
  */
141
141
  export function buildManagedDaemonServiceManager(params: BuildManagedDaemonServiceManagerParams): PlatformServiceManager {
142
142
  const workingDirectory = params.workingDirectory ?? params.homeDir;
@@ -152,7 +152,7 @@ export function buildManagedDaemonServiceManager(params: BuildManagedDaemonServi
152
152
  // already configured for hostMode=network / a non-default port) keeps its
153
153
  // endpoint without a unit rewrite. Baking endpoint values here is what
154
154
  // silently re-pinned custom-configured hosts back to the values current at
155
- // install time. scripts/install.sh writes the same shape the two paths
155
+ // install time. scripts/install.sh writes the same shape, the two paths
156
156
  // must produce the identical running daemon (see the installer parity test).
157
157
  // `params.host`/`params.port` remain inputs because the migration engine
158
158
  // still needs them for its read-only port-liveness probe.
@@ -161,15 +161,15 @@ export function buildManagedDaemonServiceManager(params: BuildManagedDaemonServi
161
161
  description: MANAGED_SERVICE_DESCRIPTION,
162
162
  workingDirectory,
163
163
  command: params.binaryPath,
164
- // `--daemon-home` names the daemon's own STATE directory the one holding
165
- // operator-tokens.json, auth-users.json and daemon-settings.json which is
164
+ // `--daemon-home` names the daemon's own STATE directory, the one holding
165
+ // operator-tokens.json, auth-users.json and daemon-settings.json, which is
166
166
  // `<home>/.goodvibes/daemon`. This baked the USER HOME, so a serviced
167
167
  // daemon filed its identity a level above where every reader in this
168
168
  // repository looks: the SDK's platform/config goodvibes-home resolves the flag AS the state
169
169
  // directory, cli/service-posture.ts already writes the state directory into
170
170
  // GOODVIBES_DAEMON_HOME for the unit it installs, and runtime/bootstrap.ts
171
171
  // reads the companion token from the state directory. On a normal machine
172
- // the mismatch is invisible from the outside the daemon simply mints a
172
+ // the mismatch is invisible from the outside, the daemon simply mints a
173
173
  // second operator-tokens.json in the home directory and the client keeps
174
174
  // reading the empty one under .goodvibes/daemon.
175
175
  args: ['--daemon-home', join(params.homeDir, '.goodvibes', 'daemon')],
@@ -179,7 +179,7 @@ export function buildManagedDaemonServiceManager(params: BuildManagedDaemonServi
179
179
  return new PlatformServiceManager(configManager, {
180
180
  workingDirectory,
181
181
  // Unit paths resolve from the LOGIN home, never the (possibly
182
- // GOODVIBES_HOME-relocated) tree home above see `unitHomeDir`'s doc.
182
+ // GOODVIBES_HOME-relocated) tree home above, see `unitHomeDir`'s doc.
183
183
  homeDirectory: params.unitHomeDir,
184
184
  definitionOverride: definition,
185
185
  defaultServiceName: MANAGED_SERVICE_NAME,
@@ -250,7 +250,7 @@ export function legacyUnitPath(homeDir: string): string {
250
250
 
251
251
  export interface DetectLegacyUnitInput {
252
252
  /**
253
- * The LOGIN user's home where the legacy unit file would actually live
253
+ * The LOGIN user's home, where the legacy unit file would actually live
254
254
  * (`~/.config/systemd/user/goodvibes-daemon.service`), never the
255
255
  * GOODVIBES_HOME-overridable tree home. See
256
256
  * `BuildManagedDaemonServiceManagerParams.unitHomeDir` for the identical
@@ -266,7 +266,7 @@ export interface DetectLegacyUnitInput {
266
266
  /**
267
267
  * Read-only detection: does a legacy `goodvibes-daemon.service` unit file
268
268
  * exist, and if so, is it currently active? Never stops, disables, or
269
- * modifies anything a file-existence check plus a read-only
269
+ * modifies anything, a file-existence check plus a read-only
270
270
  * `systemctl --user is-active` query through the injected actionRunner.
271
271
  */
272
272
  export function detectLegacyUnit(input: DetectLegacyUnitInput): LegacyUnitInfo {
@@ -283,8 +283,8 @@ export function detectLegacyUnit(input: DetectLegacyUnitInput): LegacyUnitInfo {
283
283
  /**
284
284
  * The unit name `PlatformServiceManager` is ACTUALLY about to mutate can
285
285
  * differ from `MANAGED_SERVICE_NAME` / `definitionOverride.name`. The SDK's
286
- * internal `resolveServiceName()` used by `install()`, `uninstall()`, and
287
- * `status()` alike to compute the unit file PATH resolves from the
286
+ * internal `resolveServiceName()`, used by `install()`, `uninstall()`, and
287
+ * `status()` alike to compute the unit file PATH, resolves from the
288
288
  * `service.serviceName` CONFIG key first, falling back to the
289
289
  * `defaultServiceName` this module passes only when that key is unset. It
290
290
  * never consults `definitionOverride.name` for the path. So if a host's
@@ -313,11 +313,11 @@ export function resolveManagedUnitName(status: ManagedServiceStatus): string {
313
313
  return basename(status.path).replace(/\.(service|plist)$/, '');
314
314
  }
315
315
 
316
- /** Honest one-line disclosure of the install-script unit's presence/state plus a manual migration hint never auto-acted-on. */
316
+ /** Honest one-line disclosure of the install-script unit's presence/state plus a manual migration hint, never auto-acted-on. */
317
317
  export function legacyUnitNote(legacy: LegacyUnitInfo, trackedServiceName: string): string {
318
318
  const stateWord = legacy.active ? 'installed and RUNNING' : 'installed (not currently active)';
319
319
  return (
320
- `note: a separate service named ${LEGACY_SERVICE_UNIT_NAME}.service is ${stateWord} at ${legacy.path} ` +
320
+ `note: a separate service named ${LEGACY_SERVICE_UNIT_NAME}.service is ${stateWord} at ${legacy.path}; ` +
321
321
  `that unit name was used by an older install script/release, while this tool manages ` +
322
322
  `${trackedServiceName}.service and will not touch the other unit automatically. Keep whichever one you use; running ` +
323
323
  `both would start two daemons competing for the same port. To retire the install-script unit in favor of this ` +
@@ -329,7 +329,7 @@ export function legacyUnitNote(legacy: LegacyUnitInfo, trackedServiceName: strin
329
329
  * Hard ceiling on every systemctl invocation made through a DEFAULT action
330
330
  * runner in this module. The reconcile below runs on the daemon's own startup
331
331
  * path, and `spawnSync` without a timeout blocks the single JS event loop for
332
- * as long as the child runs a wedged user D-Bus (a real incident class on
332
+ * as long as the child runs, a wedged user D-Bus (a real incident class on
333
333
  * this host) would freeze an already-listening daemon indefinitely. A timed-out
334
334
  * call reports `status: null`, which every status check in this module treats
335
335
  * as failure, so a wedge degrades to an honest refusal instead of a hang.
@@ -353,7 +353,7 @@ export function parseMainPid(result: { status?: number | null; stdout?: string |
353
353
  * Read-only, best-effort TCP connect probe used ONLY by the legacy-absent
354
354
  * branch to tell "nothing is listening on this port" apart from "an
355
355
  * unmanaged process (e.g. a manual `nohup`) already owns it." Never used to
356
- * identify or act on that process a positive result only produces a
356
+ * identify or act on that process, a positive result only produces a
357
357
  * warning, never a kill. Tests always inject a fake `portProbe`; this default
358
358
  * is never exercised against a real host in this repo's test suite.
359
359
  */
@@ -376,11 +376,11 @@ export function defaultPortProbe(host: string, port: number, timeoutMs = 750): P
376
376
  export interface RunLegacyDaemonMigrationParams {
377
377
  readonly host: string;
378
378
  readonly port: number;
379
- /** The unit name this tool manages (e.g. 'goodvibes') distinct from LEGACY_SERVICE_UNIT_NAME. */
379
+ /** The unit name this tool manages (e.g. 'goodvibes'), distinct from LEGACY_SERVICE_UNIT_NAME. */
380
380
  readonly trackedServiceName: string;
381
381
  /**
382
382
  * Explicit consent to actually execute the migration. Without it, the
383
- * result is a printed plan only never auto-migrate.
383
+ * result is a printed plan only, never auto-migrate.
384
384
  */
385
385
  readonly confirmMigration?: boolean | undefined;
386
386
  /** Injectable port-liveness check for the legacy-absent branch. Defaults to `defaultPortProbe`. */
@@ -396,7 +396,7 @@ export interface RunLegacyDaemonMigrationParams {
396
396
  * legacy unit. Called immediately before the two mutation calls
397
397
  * (`manager.install()`, and `manager.uninstall()` on the failed-health
398
398
  * rollback path) that would otherwise write to or remove that path. This is
399
- * an internal invariant check, not a normal user-facing error path the
399
+ * an internal invariant check, not a normal user-facing error path, the
400
400
  * pre-flight collision check in `runLegacyDaemonMigration` already returns
401
401
  * before either call site is reached whenever this would trip, so tripping
402
402
  * here means that earlier check regressed, not that the user did anything
@@ -406,7 +406,7 @@ function assertUnitIsNotLegacy(status: ManagedServiceStatus, legacy: LegacyUnitI
406
406
  if (status.path === legacy.path || resolveManagedUnitName(status) === LEGACY_SERVICE_UNIT_NAME) {
407
407
  throw new Error(
408
408
  `refusing to ${action}: the resolved managed unit (${resolveManagedUnitName(status)} at ${status.path}) is the ` +
409
- `install-script ${LEGACY_SERVICE_UNIT_NAME}.service unit this should already have been caught by the pre-flight ` +
409
+ `install-script ${LEGACY_SERVICE_UNIT_NAME}.service unit; this should already have been caught by the pre-flight ` +
410
410
  'collision check in runLegacyDaemonMigration',
411
411
  );
412
412
  }
@@ -431,7 +431,7 @@ export async function runLegacyDaemonMigration(
431
431
  ): Promise<LegacyDaemonMigrationResult> {
432
432
  const { trackedServiceName } = params;
433
433
  // Computed once, up front, and reused for every branch below (this is the
434
- // exact same single call each branch made individually before see the
434
+ // exact same single call each branch made individually before, see the
435
435
  // fix note on `resolveManagedUnitName` for why the name/path it reports
436
436
  // can differ from `trackedServiceName`).
437
437
  const currentStatus = manager.status();
@@ -448,9 +448,9 @@ export async function runLegacyDaemonMigration(
448
448
  `migrate-service: no install-script ${LEGACY_SERVICE_UNIT_NAME}.service unit was found, but something is already ` +
449
449
  `listening on ${params.host}:${params.port}.`,
450
450
  "That looks like a process this tool doesn't manage (for example, a manually-started `nohup` daemon) rather " +
451
- 'than a systemd unit there is nothing here to stop or disable, and this tool will not attempt to kill an ' +
451
+ 'than a systemd unit; there is nothing here to stop or disable, and this tool will not attempt to kill an ' +
452
452
  'unrecognized process.',
453
- 'Stop that process yourself, then re-run migrate-service or install-service once the port is free or, if ' +
453
+ 'Stop that process yourself, then re-run migrate-service or install-service once the port is free; or, if ' +
454
454
  "it's already the daemon you want running, leave it alone: a client surface can still reach it at this " +
455
455
  "host:port directly, with no service unit required for this tool to manage.",
456
456
  ],
@@ -462,7 +462,7 @@ export async function runLegacyDaemonMigration(
462
462
  exitCode: 0,
463
463
  lines: [
464
464
  `migrate-service: no install-script ${LEGACY_SERVICE_UNIT_NAME}.service unit was found and ${params.host}:${params.port} ` +
465
- 'is free there is nothing to migrate.',
465
+ 'is free; there is nothing to migrate.',
466
466
  `Run install-service to set up the managed ${resolvedUnitName}.service directly.`,
467
467
  ],
468
468
  status: currentStatus,
@@ -476,7 +476,7 @@ export async function runLegacyDaemonMigration(
476
476
  lines: [
477
477
  `migrate-service: this host's detected service platform is '${currentStatus.platform}', not systemd, but a ` +
478
478
  `unit file with the install-script name exists at ${legacy.path}.`,
479
- 'That unit is systemd-specific and this tool only knows how to migrate a systemd unit today ' +
479
+ 'That unit is systemd-specific and this tool only knows how to migrate a systemd unit today; ' +
480
480
  'nothing was changed.',
481
481
  ],
482
482
  status: currentStatus,
@@ -486,7 +486,7 @@ export async function runLegacyDaemonMigration(
486
486
  // Before any mutation, confirm the unit PlatformServiceManager is
487
487
  // actually about to install/uninstall isn't the legacy unit itself. This
488
488
  // happens when the host's `service.serviceName` config key is set to the
489
- // legacy unit's own name see `resolveManagedUnitName`'s doc comment for
489
+ // legacy unit's own name, see `resolveManagedUnitName`'s doc comment for
490
490
  // why the SDK resolves mutation paths from that config key rather than
491
491
  // from the definition this engine passes. Without this check, `install()`
492
492
  // below would overwrite the legacy unit file, a failed-health rollback
@@ -516,12 +516,12 @@ export async function runLegacyDaemonMigration(
516
516
  exitCode: 0,
517
517
  lines: [
518
518
  legacyUnitNote(legacy, resolvedUnitName),
519
- 'migrate-service (dry run re-run with confirmation to execute): this would',
519
+ 'migrate-service (dry run, re-run with confirmation to execute): this would',
520
520
  ` 1. install and start the new ${resolvedUnitName}.service unit`,
521
521
  ' 2. verify it comes up healthy (a fresh, honest systemd is-active check)',
522
522
  ` 3. only if that succeeds, stop, disable, and remove the install-script ${LEGACY_SERVICE_UNIT_NAME}.service unit ` +
523
523
  'and run `systemctl --user daemon-reload`',
524
- 'Nothing has been changed. Nothing is migrated automatically re-run with explicit confirmation ' +
524
+ 'Nothing has been changed. Nothing is migrated automatically; re-run with explicit confirmation ' +
525
525
  "(the CLI's -y/--yes flag) to execute this plan.",
526
526
  ],
527
527
  status: currentStatus,
@@ -531,7 +531,7 @@ export async function runLegacyDaemonMigration(
531
531
  // Consented: new-up-then-old-down. The legacy unit is not touched until the
532
532
  // new unit is verified healthy.
533
533
  // Belt-and-braces: the collision check above already returns before
534
- // reaching here whenever the resolved unit is the legacy one this
534
+ // reaching here whenever the resolved unit is the legacy one, this
535
535
  // re-asserts the same invariant right at the mutation site so a future
536
536
  // change to the check above can never silently reopen the hole.
537
537
  assertUnitIsNotLegacy(currentStatus, legacy, 'install the new unit');
@@ -554,7 +554,7 @@ export async function runLegacyDaemonMigration(
554
554
  assertUnitIsNotLegacy(installed, legacy, 'roll back (uninstall) the new unit');
555
555
  const rollback = manager.uninstall();
556
556
  const rollbackNote = rollback.actionError
557
- ? `rolling back the new unit ALSO hit an error (${rollback.actionError}) remove ${installed.path} by hand.`
557
+ ? `rolling back the new unit ALSO hit an error (${rollback.actionError}); remove ${installed.path} by hand.`
558
558
  : 'the newly-written unit has been rolled back (removed).';
559
559
  return {
560
560
  ok: false,
@@ -569,7 +569,7 @@ export async function runLegacyDaemonMigration(
569
569
  };
570
570
  }
571
571
 
572
- // New unit verified healthy now, and only now, retire the legacy unit.
572
+ // New unit verified healthy, now, and only now, retire the legacy unit.
573
573
  const run: ManagedServiceActionRunner = params.actionRunner ?? defaultActionRunner(SYSTEMCTL_TIMEOUT_MS);
574
574
  const stopResult = run('systemctl', ['--user', 'stop', `${LEGACY_SERVICE_UNIT_NAME}.service`]);
575
575
  const disableResult = run('systemctl', ['--user', 'disable', `${LEGACY_SERVICE_UNIT_NAME}.service`]);
@@ -596,7 +596,7 @@ export async function runLegacyDaemonMigration(
596
596
  );
597
597
  }
598
598
  if (removeError) {
599
- lines.push(`note: could not remove the install-script unit file at ${legacy.path}: ${removeError} remove it by hand.`);
599
+ lines.push(`note: could not remove the install-script unit file at ${legacy.path}: ${removeError}; remove it by hand.`);
600
600
  } else {
601
601
  lines.push(`the install-script ${LEGACY_SERVICE_UNIT_NAME}.service unit has been stopped, disabled, and removed.`);
602
602
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The unattended startup reconcile for the redundant install-script
3
- * `goodvibes-daemon.service` unit split out of `legacy-daemon-migration.ts`
3
+ * `goodvibes-daemon.service` unit, split out of `legacy-daemon-migration.ts`
4
4
  * (which owns the shared unit definition, detection, and the CONSENTED
5
5
  * `migrate-service` engine) so each module stays within the architecture
6
6
  * gate's file-size cap. Same layer (`src/runtime/`), same injectable-seam
@@ -24,8 +24,8 @@ type ManagedServiceActionResult = ReturnType<ManagedServiceActionRunner>;
24
24
  /**
25
25
  * The exact marker string scripts/install.sh writes into every unit it
26
26
  * creates (as a `# managed by goodvibes install.sh` comment). The reconcile
27
- * check below keys on it to tell an installer-created legacy unit safe to
28
- * auto-retire apart from a hand-written one that must only ever be reported.
27
+ * check below keys on it to tell an installer-created legacy unit, safe to
28
+ * auto-retire, apart from a hand-written one that must only ever be reported.
29
29
  * Kept in lockstep with `INSTALLER_MARKER` in scripts/install.sh.
30
30
  */
31
31
  export const INSTALLER_UNIT_MARKER = 'managed by goodvibes install.sh';
@@ -35,7 +35,7 @@ export const INSTALLER_UNIT_MARKER = 'managed by goodvibes install.sh';
35
35
  /**
36
36
  * CUMULATIVE ceiling on the reconcile's whole boot-path pass. Per-call
37
37
  * timeouts alone still allow ~5 sequential slow-but-completing calls to stack
38
- * up (a degraded not wedged user bus), blocking the already-listening
38
+ * up (a degraded, not wedged, user bus), blocking the already-listening
39
39
  * daemon's event loop for tens of seconds. One overall deadline covers the
40
40
  * pass: once it is exceeded, every remaining call is skipped (reported as
41
41
  * `status: null`, which the guards treat as unknown → refusal) and the result
@@ -48,7 +48,7 @@ export const RECONCILE_DEADLINE_MS = 8_000;
48
48
  /**
49
49
  * Wrap a runner with the reconcile's cumulative deadline: calls after the
50
50
  * deadline are skipped outright; calls near it get only the remaining budget
51
- * as their per-call timeout (default runner only injected runners are test
51
+ * as their per-call timeout (default runner only, injected runners are test
52
52
  * fakes that answer instantly).
53
53
  */
54
54
  function makeDeadlineBoundRunner(
@@ -62,7 +62,7 @@ function makeDeadlineBoundRunner(
62
62
  const remaining = deadlineMs - (Date.now() - startedAt);
63
63
  if (remaining <= 0) {
64
64
  hit = true;
65
- return { status: null, stdout: '', stderr: 'reconcile time budget exceeded call skipped' } as ManagedServiceActionResult;
65
+ return { status: null, stdout: '', stderr: 'reconcile time budget exceeded; call skipped' } as ManagedServiceActionResult;
66
66
  }
67
67
  if (injected) return injected(command, args);
68
68
  return defaultActionRunner(Math.min(perCallTimeoutMs, remaining))(command, args);
@@ -167,7 +167,7 @@ function defaultReadOwnCgroup(): string {
167
167
  * This runs on daemon startup (no user invocation needed) and, guard-railed:
168
168
  * - Only acts when the canonical unit is CONFIRMED serving: `is-active`
169
169
  * reports active AND its MainPID resolves to a live process. `is-active`
170
- * alone is not proof a Type=simple unit reports active from fork onward,
170
+ * alone is not proof, a Type=simple unit reports active from fork onward,
171
171
  * including the pre-bind window of a daemon that is about to crash-loop.
172
172
  * - NEVER acts from inside the legacy unit itself: if this process IS the
173
173
  * legacy unit's MainPID, or /proc/self/cgroup names the legacy unit,
@@ -175,20 +175,20 @@ function defaultReadOwnCgroup(): string {
175
175
  * code (mid-boot, from inside a blocking spawnSync). Refuses instead.
176
176
  * - NEVER stops a RUNNING legacy daemon: if the legacy unit's MainPID is a
177
177
  * live process, it may be the daemon serving the endpoint clients are
178
- * configured to reach (the wrong-port two-daemon state) the unattended
178
+ * configured to reach (the wrong-port two-daemon state), the unattended
179
179
  * path only retires enabled-but-NOT-running legacy units; a running one
180
180
  * gets a notice pointing at the consented migrate-service.
181
181
  * - Requires the CONFIGURED endpoint (the one clients resolve from
182
- * settings.json, no runtime overrides) to be answering before retiring
182
+ * settings.json, no runtime overrides) to be answering before retiring,
183
183
  * a canonical daemon alive on some other port proves nothing about the
184
184
  * endpoint clients use.
185
185
  * - AUTO-RETIRES only an installer-MARKER-managed legacy unit, and only
186
186
  * removes its unit file AFTER `disable --now` reports success. A NONZERO
187
187
  * exit leaves everything in place and says so; a TIMED-OUT disable
188
- * (status null) has an UNKNOWN outcome the enablement state is
188
+ * (status null) has an UNKNOWN outcome, the enablement state is
189
189
  * re-inspected before anything is printed, and the receipt states what
190
190
  * was actually confirmed, never a blanket claim.
191
- * - A hand-written legacy unit (no marker) is never touched a one-line
191
+ * - A hand-written legacy unit (no marker) is never touched, a one-line
192
192
  * actionable notice. An UNREADABLE unit file is reported as unreadable,
193
193
  * never misdiagnosed as hand-written.
194
194
  * - The whole pass shares ONE cumulative time budget (RECONCILE_DEADLINE_MS)
@@ -214,11 +214,11 @@ export async function reconcileRedundantLegacyUnit(
214
214
  const legacyUnit = `${LEGACY_SERVICE_UNIT_NAME}.service`;
215
215
  const deadlineNote = (): string[] =>
216
216
  deadlineHit()
217
- ? ['note: the reconcile hit its overall time budget remaining checks were skipped; it will re-run at the next daemon start.']
217
+ ? ['note: the reconcile hit its overall time budget: remaining checks were skipped; it will re-run at the next daemon start.']
218
218
  : [];
219
219
 
220
220
  // Guard 1: the canonical unit must report active. (A timed-out, skipped, or
221
- // failed probe e.g. a wedged user bus lands here too and refuses.)
221
+ // failed probe, e.g. a wedged user bus, lands here too and refuses.)
222
222
  const probe = run(systemctl, ['--user', 'is-active', canonicalUnit]);
223
223
  const canonicalActive = (probe.status ?? 1) === 0 && (probe.stdout ?? '').trim() === 'active';
224
224
  if (!canonicalActive) {
@@ -226,7 +226,7 @@ export async function reconcileRedundantLegacyUnit(
226
226
  action: 'noop',
227
227
  reason: 'canonical-not-active',
228
228
  lines: [
229
- `legacy-unit reconcile: a ${legacyUnit} unit file exists at ${path} but ${canonicalUnit} is not confirmably active ` +
229
+ `legacy-unit reconcile: a ${legacyUnit} unit file exists at ${path} but ${canonicalUnit} is not confirmably active; ` +
230
230
  'left untouched (it may be the only daemon).',
231
231
  ...deadlineNote(),
232
232
  ],
@@ -243,7 +243,7 @@ export async function reconcileRedundantLegacyUnit(
243
243
  action: 'noop',
244
244
  reason: 'canonical-mainpid-not-alive',
245
245
  lines: [
246
- `legacy-unit reconcile: ${canonicalUnit} reports active but its MainPID could not be confirmed alive ` +
246
+ `legacy-unit reconcile: ${canonicalUnit} reports active but its MainPID could not be confirmed alive; ` +
247
247
  `left the ${legacyUnit} unit untouched.`,
248
248
  ...deadlineNote(),
249
249
  ],
@@ -256,7 +256,7 @@ export async function reconcileRedundantLegacyUnit(
256
256
  //
257
257
  // The legacy MainPID probe is TRI-STATE, exactly like guards 1-2: a reply
258
258
  // whose status is non-zero or null (bus flap, timeout, skipped-by-deadline)
259
- // does NOT mean "MainPID=0, unit affirmatively stopped" reading it that
259
+ // does NOT mean "MainPID=0, unit affirmatively stopped", reading it that
260
260
  // way let one transient systemctl failure on exactly this call authorize
261
261
  // `disable --now` against a LIVE legacy daemon (and guard 5 cannot catch
262
262
  // it: in the wrong-port state the legacy daemon itself answers the
@@ -271,7 +271,7 @@ export async function reconcileRedundantLegacyUnit(
271
271
  action: 'noop',
272
272
  reason: 'self-supervised-by-legacy',
273
273
  lines: [
274
- `legacy-unit reconcile: this daemon appears to be running UNDER ${legacyUnit} itself refusing to disable ` +
274
+ `legacy-unit reconcile: this daemon appears to be running UNDER ${legacyUnit} itself; refusing to disable ` +
275
275
  `the unit supervising the current process. Migrate from the canonical side instead: goodvibes-daemon migrate-service`,
276
276
  ],
277
277
  };
@@ -282,14 +282,14 @@ export async function reconcileRedundantLegacyUnit(
282
282
  reason: 'legacy-state-unknown',
283
283
  lines: [
284
284
  `legacy-unit reconcile: could not determine whether ${legacyUnit} has a running daemon (the MainPID query ` +
285
- 'failed or timed out) refusing to act on a guess; it will be re-checked at the next daemon start.',
285
+ 'failed or timed out), refusing to act on a guess; it will be re-checked at the next daemon start.',
286
286
  ...deadlineNote(),
287
287
  ],
288
288
  };
289
289
  }
290
290
 
291
291
  // Guard 4: never stop a RUNNING legacy daemon from the unattended path. A
292
- // live legacy MainPID means a second daemon is actually serving something
292
+ // live legacy MainPID means a second daemon is actually serving something,
293
293
  // possibly the endpoint clients resolve from settings.json (the wrong-port
294
294
  // two-daemon state). Retiring an enabled-but-idle unit needs no --now kill;
295
295
  // stopping a serving one needs consent. (A KNOWN reply of MainPID=0 is the
@@ -299,7 +299,7 @@ export async function reconcileRedundantLegacyUnit(
299
299
  action: 'noop',
300
300
  reason: 'legacy-running',
301
301
  lines: [
302
- `legacy-unit reconcile: ${legacyUnit} has a live main process (pid ${legacyPid}) a second daemon is ` +
302
+ `legacy-unit reconcile: ${legacyUnit} has a live main process (pid ${legacyPid}); a second daemon is ` +
303
303
  'actually running, and it may be the one serving the endpoint your clients are configured to reach. ' +
304
304
  'Refusing to stop it unattended; migrate deliberately with: goodvibes-daemon migrate-service',
305
305
  ],
@@ -308,7 +308,7 @@ export async function reconcileRedundantLegacyUnit(
308
308
 
309
309
  // Guard 5: the endpoint clients resolve from settings.json must be answered
310
310
  // before anything is retired. The canonical daemon being alive proves only
311
- // that A daemon runs not that the configured endpoint is served (its unit
311
+ // that A daemon runs, not that the configured endpoint is served (its unit
312
312
  // may pin different launch args).
313
313
  if (input.configuredEndpoint) {
314
314
  const endpointProbe = input.endpointProbe ?? defaultPortProbe;
@@ -319,7 +319,7 @@ export async function reconcileRedundantLegacyUnit(
319
319
  reason: 'configured-endpoint-unserved',
320
320
  lines: [
321
321
  `legacy-unit reconcile: nothing is answering on the CONFIGURED endpoint ` +
322
- `${input.configuredEndpoint.host}:${input.configuredEndpoint.port} the canonical daemon is alive but not ` +
322
+ `${input.configuredEndpoint.host}:${input.configuredEndpoint.port}; the canonical daemon is alive but not ` +
323
323
  `provably serving what clients resolve from settings. Left the ${legacyUnit} unit untouched; check the ` +
324
324
  "canonical unit's launch arguments against the controlPlane settings.",
325
325
  ],
@@ -345,7 +345,7 @@ export async function reconcileRedundantLegacyUnit(
345
345
  reason: 'marker-unreadable',
346
346
  lines: [
347
347
  `note: ${canonicalUnit} is active and a separate ${legacyUnit} exists at ${path}, but its unit file could not ` +
348
- `be read (${readError}) left untouched. Inspect it yourself; if it is redundant, retire it with: ` +
348
+ `be read (${readError}). Left untouched. Inspect it yourself; if it is redundant, retire it with: ` +
349
349
  `systemctl --user disable --now ${legacyUnit} && rm ${path} && systemctl --user daemon-reload`,
350
350
  ],
351
351
  };
@@ -365,14 +365,14 @@ export async function reconcileRedundantLegacyUnit(
365
365
  }
366
366
 
367
367
  // Installer-marker-managed and redundant: disable first, and only remove the
368
- // unit file if the disable actually succeeded otherwise the enablement
368
+ // unit file if the disable actually succeeded, otherwise the enablement
369
369
  // symlink dangles at a deleted file and this tool can never repair it (the
370
370
  // next pass no-ops at the file-exists check).
371
371
  const disableResult = run(systemctl, ['--user', 'disable', '--now', legacyUnit]);
372
372
  const disableStatus = disableResult.status ?? 1;
373
373
  if (disableStatus !== 0) {
374
374
  if (disableResult.status === null) {
375
- // TIMED OUT: the outcome is UNKNOWN, not failed `disable --now`
375
+ // TIMED OUT: the outcome is UNKNOWN, not failed, `disable --now`
376
376
  // removes the enablement symlinks synchronously BEFORE waiting on the
377
377
  // stop job, so the disable may well have taken effect even though the
378
378
  // client was killed. Re-inspect instead of printing a blanket denial.
@@ -393,11 +393,11 @@ export async function reconcileRedundantLegacyUnit(
393
393
  run(systemctl, ['--user', 'daemon-reload']);
394
394
  const lines = [
395
395
  `reconciled: the disable command timed out waiting on the stop job, but re-inspection confirms the ` +
396
- `installer-managed ${legacyUnit} is no longer enabled${removeErrorAfterTimeout ? '' : ` its unit file was removed (${path})`}. ` +
396
+ `installer-managed ${legacyUnit} is no longer enabled${removeErrorAfterTimeout ? '' : `, its unit file was removed (${path})`}. ` +
397
397
  'Its stop may still be completing.',
398
398
  ];
399
399
  if (removeErrorAfterTimeout) {
400
- lines.push(`note: could not remove ${path}: ${removeErrorAfterTimeout} remove it by hand.`);
400
+ lines.push(`note: could not remove ${path}: ${removeErrorAfterTimeout}; remove it by hand.`);
401
401
  }
402
402
  lines.push(...deadlineNote());
403
403
  return { action: 'removed', reason: 'retired', lines };
@@ -406,7 +406,7 @@ export async function reconcileRedundantLegacyUnit(
406
406
  action: 'failed',
407
407
  reason: 'disable-timeout',
408
408
  lines: [
409
- `legacy-unit reconcile: the disable of ${legacyUnit} timed out and its outcome is UNKNOWN the enablement ` +
409
+ `legacy-unit reconcile: the disable of ${legacyUnit} timed out and its outcome is UNKNOWN; the enablement ` +
410
410
  `state could not be re-confirmed. This tool removed nothing; the unit file at ${path} was left in place ` +
411
411
  'and will be re-checked at the next daemon start.',
412
412
  `Verify it yourself: systemctl --user is-enabled ${legacyUnit}`,
@@ -420,7 +420,7 @@ export async function reconcileRedundantLegacyUnit(
420
420
  reason: 'disable-failed',
421
421
  lines: [
422
422
  `legacy-unit reconcile: the disable of the redundant installer-managed ${legacyUnit} reported failure ` +
423
- `(${detail}) this tool removed nothing; the unit file at ${path} was left in place.`,
423
+ `(${detail}); this tool removed nothing; the unit file at ${path} was left in place.`,
424
424
  `Verify its state and retire it yourself: systemctl --user is-enabled ${legacyUnit} ; ` +
425
425
  `systemctl --user disable --now ${legacyUnit} && rm ${path} && systemctl --user daemon-reload`,
426
426
  ],
@@ -441,7 +441,7 @@ export async function reconcileRedundantLegacyUnit(
441
441
  `was disabled${removeError ? '' : ` and removed (${path})`}.`,
442
442
  ];
443
443
  if (removeError) {
444
- lines.push(`note: could not remove ${path}: ${removeError} remove it by hand.`);
444
+ lines.push(`note: could not remove ${path}: ${removeError}; remove it by hand.`);
445
445
  }
446
446
  lines.push(...deadlineNote());
447
447
  return { action: 'removed', reason: 'retired', lines };
@@ -4,7 +4,7 @@
4
4
  * The deps that let the platform register this daemon's `email.*` verbs.
5
5
  *
6
6
  * `calendar.*` and `email.*` are served by the platform now, not by handlers in
7
- * this repository those were deleted when the platform gained an
7
+ * this repository, those were deleted when the platform gained an
8
8
  * implementation the daemon could call. What is left for a product to supply is
9
9
  * the wiring, and it is not optional: without `homeDirectory` the calendar
10
10
  * composition returns null, without these deps the mail one does, and either way
@@ -27,7 +27,7 @@ import { nodeEmailTransport } from '@pellux/goodvibes-sdk/platform/email/node';
27
27
 
28
28
  /** The narrow slices this composition needs; the real managers satisfy them. */
29
29
  interface MailCompositionInput {
30
- readonly configManager: { get(key: never): unknown };
30
+ readonly configManager: { get(key: string): unknown };
31
31
  readonly secretsManager: { get(key: string): Promise<string | null> };
32
32
  }
33
33
 
@@ -36,8 +36,8 @@ interface MailCompositionInput {
36
36
  * `registerGatewayVerbGroups`.
37
37
  *
38
38
  * The settings come from the daemon's own `surfaces.email.*` keys through
39
- * `withSurfaceEmailConfig`, so the keys an operator has already set and that
40
- * the settings modal now shows keep working unchanged, and a not-configured
39
+ * `withSurfaceEmailConfig`, so the keys an operator has already set, and that
40
+ * the settings modal now shows, keep working unchanged, and a not-configured
41
41
  * answer names the keys THIS operator actually has rather than the ones the
42
42
  * service validates internally.
43
43
  */
@@ -45,7 +45,7 @@ export function composeMailDeps(input: MailCompositionInput): {
45
45
  readonly emailServiceDeps: EmailServiceDeps;
46
46
  readonly describeEmailConfigProblem: () => Promise<SurfaceEmailConfigProblem | null>;
47
47
  } {
48
- const getConfig = (key: string): unknown => input.configManager.get(key as never);
48
+ const getConfig = (key: string): unknown => input.configManager.get(key);
49
49
  const emailServiceDeps = withSurfaceEmailConfig({
50
50
  getConfig,
51
51
  secretsManager: input.secretsManager,
@@ -56,7 +56,7 @@ export function composeMailDeps(input: MailCompositionInput): {
56
56
  // raises display confidence only, and commandAuthority is the literal
57
57
  // 'none'.
58
58
  describeSenderClaim: describeSenderClaimNeutrally,
59
- } as never);
59
+ });
60
60
  return {
61
61
  emailServiceDeps,
62
62
  describeEmailConfigProblem: () =>