@westbayberry/dg 1.3.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/LICENSE +1 -201
  2. package/NOTICE +1 -4
  3. package/README.md +293 -0
  4. package/dist/api/analyze.js +210 -0
  5. package/dist/audit/deep.js +180 -0
  6. package/dist/audit/detectors.js +247 -0
  7. package/dist/audit/events.js +41 -0
  8. package/dist/audit/rules.js +426 -0
  9. package/dist/audit-ui/AuditApp.js +39 -0
  10. package/dist/audit-ui/components/AuditHeader.js +24 -0
  11. package/dist/audit-ui/components/AuditResultsView.js +307 -0
  12. package/dist/audit-ui/components/DeepStatusRow.js +11 -0
  13. package/dist/audit-ui/export.js +85 -0
  14. package/dist/audit-ui/format.js +34 -0
  15. package/dist/audit-ui/launch.js +34 -0
  16. package/dist/auth/device-login.js +271 -0
  17. package/dist/auth/env-token.js +6 -0
  18. package/dist/auth/login-app.js +156 -0
  19. package/dist/auth/store.js +147 -0
  20. package/dist/bin/dg.js +71 -0
  21. package/dist/commands/audit.js +357 -0
  22. package/dist/commands/completion.js +116 -0
  23. package/dist/commands/config.js +99 -0
  24. package/dist/commands/doctor.js +39 -0
  25. package/dist/commands/explain.js +100 -0
  26. package/dist/commands/guard-commit.js +158 -0
  27. package/dist/commands/help.js +74 -0
  28. package/dist/commands/licenses.js +435 -0
  29. package/dist/commands/login.js +81 -0
  30. package/dist/commands/logout.js +37 -0
  31. package/dist/commands/router.js +98 -0
  32. package/dist/commands/scan.js +18 -0
  33. package/dist/commands/service.js +475 -0
  34. package/dist/commands/setup.js +302 -0
  35. package/dist/commands/status.js +115 -0
  36. package/dist/commands/suggest.js +35 -0
  37. package/dist/commands/types.js +4 -0
  38. package/dist/commands/unavailable.js +11 -0
  39. package/dist/commands/uninstall.js +111 -0
  40. package/dist/commands/update.js +210 -0
  41. package/dist/commands/verify.js +151 -0
  42. package/dist/commands/version.js +22 -0
  43. package/dist/commands/wrap.js +55 -0
  44. package/dist/config/settings.js +302 -0
  45. package/dist/install-ui/LiveInstall.js +24 -0
  46. package/dist/install-ui/block-render.js +83 -0
  47. package/dist/install-ui/live-install-app.js +48 -0
  48. package/dist/install-ui/prompt.js +24 -0
  49. package/dist/launcher/classify.js +116 -0
  50. package/dist/launcher/env.js +53 -0
  51. package/dist/launcher/live-install.js +50 -0
  52. package/dist/launcher/output-redaction.js +77 -0
  53. package/dist/launcher/preflight-prompt.js +139 -0
  54. package/dist/launcher/resolve-real-binary.js +73 -0
  55. package/dist/launcher/run.js +417 -0
  56. package/dist/policy/evaluate.js +128 -0
  57. package/dist/presentation/mode.js +52 -0
  58. package/dist/presentation/theme.js +29 -0
  59. package/dist/proxy/buffer-budget.js +64 -0
  60. package/dist/proxy/ca.js +126 -0
  61. package/dist/proxy/classify-host.js +26 -0
  62. package/dist/proxy/enforcement.js +102 -0
  63. package/dist/proxy/metadata-map.js +336 -0
  64. package/dist/proxy/server.js +909 -0
  65. package/dist/proxy/upstream-proxy.js +102 -0
  66. package/dist/proxy/worker.js +39 -0
  67. package/dist/publish-set/collect.js +51 -0
  68. package/dist/publish-set/no-exec-shell.js +19 -0
  69. package/dist/publish-set/npm.js +109 -0
  70. package/dist/publish-set/pack.js +36 -0
  71. package/dist/publish-set/pypi.js +59 -0
  72. package/dist/runtime/cli.js +17 -0
  73. package/dist/runtime/first-run.js +60 -0
  74. package/dist/runtime/node-version.js +58 -0
  75. package/dist/runtime/nudges.js +105 -0
  76. package/dist/scan/analyze-worker.js +21 -0
  77. package/dist/scan/collect.js +153 -0
  78. package/dist/scan/command.js +159 -0
  79. package/dist/scan/discovery.js +209 -0
  80. package/dist/scan/render.js +240 -0
  81. package/dist/scan/scanner-report.js +82 -0
  82. package/dist/scan/staged.js +173 -0
  83. package/dist/scan/types.js +1 -0
  84. package/dist/scan-ui/LegacyApp.js +156 -0
  85. package/dist/scan-ui/alt-screen.js +84 -0
  86. package/dist/scan-ui/api-aliases.js +1 -0
  87. package/dist/scan-ui/components/ErrorView.js +23 -0
  88. package/dist/scan-ui/components/InteractiveResultsView.js +1166 -0
  89. package/dist/scan-ui/components/ProgressBar.js +89 -0
  90. package/dist/scan-ui/components/ProjectSelector.js +62 -0
  91. package/dist/scan-ui/components/ScoreHeader.js +20 -0
  92. package/dist/scan-ui/components/SetupBanner.js +13 -0
  93. package/dist/scan-ui/components/Spinner.js +4 -0
  94. package/dist/scan-ui/format-helpers.js +40 -0
  95. package/dist/scan-ui/hooks/useExpandAnimation.js +40 -0
  96. package/dist/scan-ui/hooks/useScan.js +113 -0
  97. package/dist/scan-ui/hooks/useTerminalSize.js +24 -0
  98. package/dist/scan-ui/launch.js +27 -0
  99. package/dist/scan-ui/logo.js +91 -0
  100. package/dist/scan-ui/shims.js +30 -0
  101. package/dist/security/sanitize.js +28 -0
  102. package/dist/service/state.js +837 -0
  103. package/dist/service/trust-store.js +234 -0
  104. package/dist/service/worker.js +88 -0
  105. package/dist/setup/git-hook.js +244 -0
  106. package/dist/setup/optional-support.js +58 -0
  107. package/dist/setup/plan.js +899 -0
  108. package/dist/state/cleanup-registry.js +60 -0
  109. package/dist/state/index.js +5 -0
  110. package/dist/state/locks.js +161 -0
  111. package/dist/state/paths.js +24 -0
  112. package/dist/state/sessions.js +170 -0
  113. package/dist/state/store.js +50 -0
  114. package/dist/telemetry/events.js +40 -0
  115. package/dist/util/git.js +20 -0
  116. package/dist/util/tty-prompt.js +43 -0
  117. package/dist/verify/local.js +400 -0
  118. package/dist/verify/package-check.js +240 -0
  119. package/dist/verify/preflight.js +698 -0
  120. package/dist/verify/render.js +184 -0
  121. package/dist/verify/types.js +1 -0
  122. package/package.json +33 -50
  123. package/dist/index.mjs +0 -54141
  124. package/dist/postinstall.mjs +0 -731
  125. package/dist/python-hook/dg_pip_hook.pth +0 -1
  126. package/dist/python-hook/dg_pip_hook.py +0 -130
@@ -0,0 +1,837 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { randomUUID } from "node:crypto";
5
+ import { fileURLToPath } from "node:url";
6
+ import { loadUserConfig } from "../config/settings.js";
7
+ import { applyTrustInstall, applyTrustUninstall, readCertificateFingerprints, readServiceTrustRecord, renderTrustStorePlanLines, resolveTrustInstallPlan, TrustStoreError, writeServiceTrustRecord } from "./trust-store.js";
8
+ import { acquireLockSync, cleanupSessionSync, createSessionSync, resolveDgPaths, CLEANUP_REGISTRY_LOCK } from "../state/index.js";
9
+ export const SERVICE_SENTINEL = "dg-service-mode-v1";
10
+ export const TRUST_SENTINEL = "dg-service-trust-v1";
11
+ export const SERVICE_LOCK = "service-control";
12
+ export const SERVICE_LOCK_STALE_MS = 30 * 60 * 1000;
13
+ const EMPTY_SERVICE_STATE = {
14
+ version: 1,
15
+ configured: false,
16
+ running: false,
17
+ trustInstalled: false,
18
+ trust: undefined,
19
+ trustDrift: undefined,
20
+ proxy: undefined,
21
+ lastError: undefined,
22
+ policySyncedAt: undefined,
23
+ configuredAt: undefined,
24
+ startedAt: undefined,
25
+ stoppedAt: undefined,
26
+ trustInstalledAt: undefined,
27
+ trustUninstalledAt: undefined
28
+ };
29
+ export function resolveServicePaths(env = process.env) {
30
+ const paths = resolveDgPaths(env);
31
+ const serviceDir = join(paths.stateDir, "service");
32
+ return {
33
+ paths,
34
+ serviceDir,
35
+ statePath: join(serviceDir, "service.json"),
36
+ trustRecordPath: join(serviceDir, "trust-store.json"),
37
+ runtimePath: join(serviceDir, "runtime.json"),
38
+ logPath: join(serviceDir, "service.log.jsonl")
39
+ };
40
+ }
41
+ export function buildServiceSetupPlan(env) {
42
+ const paths = resolveServicePaths(env);
43
+ return {
44
+ kind: "setup",
45
+ writes: [
46
+ {
47
+ action: "create dg-owned service state directory",
48
+ path: paths.serviceDir
49
+ },
50
+ {
51
+ action: "record explicit service-mode configuration",
52
+ path: paths.statePath
53
+ },
54
+ {
55
+ action: "record dg-owned service writes for uninstall",
56
+ path: paths.paths.cleanupRegistryPath
57
+ }
58
+ ]
59
+ };
60
+ }
61
+ export function buildTrustInstallPlan(env) {
62
+ const paths = resolveServicePaths(env);
63
+ const current = readState(paths);
64
+ const runtime = readRuntime(paths) ?? current.proxy;
65
+ const trustPlan = runtime?.caPath && existsSync(runtime.caPath) ? resolveTrustInstallPlan(runtime.caPath, env) : undefined;
66
+ return {
67
+ kind: "trust-install",
68
+ writes: [
69
+ ...renderTrustStorePlanLines(trustPlan).map((line) => ({
70
+ action: line,
71
+ path: trustPlan?.target ?? paths.trustRecordPath
72
+ })),
73
+ {
74
+ action: "write dg-owned managed trust record after trust-store mutation succeeds",
75
+ path: paths.trustRecordPath
76
+ },
77
+ {
78
+ action: "record trust reversal in cleanup registry",
79
+ path: paths.paths.cleanupRegistryPath
80
+ }
81
+ ]
82
+ };
83
+ }
84
+ export function buildTrustUninstallPlan(env) {
85
+ const paths = resolveServicePaths(env);
86
+ const record = readServiceTrustRecord(paths.trustRecordPath, TRUST_SENTINEL);
87
+ return {
88
+ kind: "trust-uninstall",
89
+ writes: [
90
+ ...(record
91
+ ? [
92
+ {
93
+ action: `remove ${record.provider} trust for certificate SHA-256 ${record.fingerprintSha256}`,
94
+ path: record.target
95
+ }
96
+ ]
97
+ : []),
98
+ {
99
+ action: "remove dg-owned managed trust record",
100
+ path: paths.trustRecordPath
101
+ },
102
+ {
103
+ action: "remove trust cleanup registry entry",
104
+ path: paths.paths.cleanupRegistryPath
105
+ }
106
+ ]
107
+ };
108
+ }
109
+ export function buildServiceUninstallPlan(env) {
110
+ const paths = resolveServicePaths(env);
111
+ return {
112
+ kind: "uninstall",
113
+ writes: [
114
+ {
115
+ action: "stop explicit service mode if running",
116
+ path: paths.statePath
117
+ },
118
+ {
119
+ action: "remove dg-owned managed trust record if present",
120
+ path: paths.trustRecordPath
121
+ },
122
+ {
123
+ action: "remove dg-owned service state directory",
124
+ path: paths.serviceDir
125
+ },
126
+ {
127
+ action: "remove service and trust cleanup registry entries",
128
+ path: paths.paths.cleanupRegistryPath
129
+ }
130
+ ]
131
+ };
132
+ }
133
+ export function renderServicePlan(title, plan) {
134
+ const lines = [
135
+ title,
136
+ "",
137
+ "No service or trust-store state is changed until this plan is confirmed.",
138
+ ...plan.writes.map((write) => `- ${write.action}: ${write.path}`)
139
+ ];
140
+ return `${lines.join("\n")}\n`;
141
+ }
142
+ export function readServiceState(env) {
143
+ const paths = resolveServicePaths(env);
144
+ const state = applyRuntimeDiagnostics(paths, readState(paths));
145
+ return {
146
+ paths,
147
+ state,
148
+ changed: false
149
+ };
150
+ }
151
+ export function configureService(env, now = new Date()) {
152
+ const paths = resolveServicePaths(env);
153
+ return withServiceLock(paths, () => {
154
+ const current = readState(paths);
155
+ const state = {
156
+ ...current,
157
+ configured: true,
158
+ configuredAt: current.configuredAt ?? now.toISOString(),
159
+ policySyncedAt: now.toISOString()
160
+ };
161
+ writeState(paths, state);
162
+ writeLog(paths, "service.configured", now);
163
+ withRegistryLock(paths.paths, () => {
164
+ writeRegistry(paths.paths, mergeRegistry(readRegistry(paths.paths), [registryEntry("service", paths.statePath, SERVICE_SENTINEL, now)]));
165
+ });
166
+ return {
167
+ paths,
168
+ state,
169
+ changed: !current.configured
170
+ };
171
+ });
172
+ }
173
+ export function startService(env, now = new Date()) {
174
+ const paths = resolveServicePaths(env);
175
+ return withServiceLock(paths, () => {
176
+ const current = readState(paths);
177
+ if (!current.configured) {
178
+ throw new ServiceNotConfiguredError();
179
+ }
180
+ const existingRuntime = readRuntime(paths);
181
+ if (current.running && existingRuntime && runtimeHealth(existingRuntime).healthy) {
182
+ const state = {
183
+ ...current,
184
+ running: true,
185
+ proxy: existingRuntime,
186
+ trustDrift: detectTrustDrift(current.trust, existingRuntime),
187
+ lastError: undefined
188
+ };
189
+ writeState(paths, state);
190
+ writeLog(paths, "service.start.noop", now);
191
+ return {
192
+ paths,
193
+ state,
194
+ changed: false
195
+ };
196
+ }
197
+ cleanupRuntime(paths, existingRuntime ?? current.proxy);
198
+ const runtime = startServiceProxyRuntime(paths, env ?? process.env, now);
199
+ const trustRemoval = removeTrustRecordIfDrifted(paths, current.trust, runtime, now);
200
+ const state = {
201
+ ...current,
202
+ running: true,
203
+ startedAt: current.running ? current.startedAt : now.toISOString(),
204
+ policySyncedAt: now.toISOString(),
205
+ proxy: runtime,
206
+ trustInstalled: trustRemoval.removed ? false : current.trustInstalled,
207
+ trust: trustRemoval.removed ? undefined : current.trust,
208
+ trustDrift: undefined,
209
+ trustUninstalledAt: trustRemoval.removed ? now.toISOString() : current.trustUninstalledAt,
210
+ lastError: trustRemoval.message
211
+ };
212
+ writeState(paths, state);
213
+ writeLog(paths, "service.started", now);
214
+ return {
215
+ paths,
216
+ state,
217
+ changed: !current.running
218
+ };
219
+ });
220
+ }
221
+ export function stopService(env, now = new Date()) {
222
+ const paths = resolveServicePaths(env);
223
+ return withServiceLock(paths, () => {
224
+ const current = readState(paths);
225
+ if (!current.configured) {
226
+ return {
227
+ paths,
228
+ state: current,
229
+ changed: false
230
+ };
231
+ }
232
+ const state = {
233
+ ...current,
234
+ running: false,
235
+ stoppedAt: current.running ? now.toISOString() : current.stoppedAt,
236
+ proxy: undefined,
237
+ lastError: undefined
238
+ };
239
+ cleanupRuntime(paths, readRuntime(paths) ?? current.proxy);
240
+ writeState(paths, state);
241
+ writeLog(paths, current.running ? "service.stopped" : "service.stop.noop", now);
242
+ return {
243
+ paths,
244
+ state,
245
+ changed: current.running
246
+ };
247
+ });
248
+ }
249
+ export function restartService(env, now = new Date()) {
250
+ const paths = resolveServicePaths(env);
251
+ return withServiceLock(paths, () => {
252
+ const current = readState(paths);
253
+ if (!current.configured) {
254
+ throw new ServiceNotConfiguredError();
255
+ }
256
+ cleanupRuntime(paths, readRuntime(paths) ?? current.proxy);
257
+ const runtime = startServiceProxyRuntime(paths, env ?? process.env, now);
258
+ const trustRemoval = removeTrustRecordIfDrifted(paths, current.trust, runtime, now);
259
+ const state = {
260
+ ...current,
261
+ running: true,
262
+ startedAt: now.toISOString(),
263
+ stoppedAt: current.running ? now.toISOString() : current.stoppedAt,
264
+ policySyncedAt: now.toISOString(),
265
+ proxy: runtime,
266
+ trustInstalled: trustRemoval.removed ? false : current.trustInstalled,
267
+ trust: trustRemoval.removed ? undefined : current.trust,
268
+ trustDrift: undefined,
269
+ trustUninstalledAt: trustRemoval.removed ? now.toISOString() : current.trustUninstalledAt,
270
+ lastError: trustRemoval.message
271
+ };
272
+ writeState(paths, state);
273
+ writeLog(paths, "service.restarted", now);
274
+ return {
275
+ paths,
276
+ state,
277
+ changed: true
278
+ };
279
+ });
280
+ }
281
+ export function installServiceTrust(env, now = new Date()) {
282
+ const paths = resolveServicePaths(env);
283
+ return withServiceLock(paths, () => {
284
+ const current = readState(paths);
285
+ if (!current.configured) {
286
+ throw new ServiceNotConfiguredError();
287
+ }
288
+ const runtime = readRuntime(paths) ?? current.proxy;
289
+ if (!runtime?.caPath || !existsSync(runtime.caPath)) {
290
+ throw new ServiceTrustStoreError("dg service trust install requires a running service proxy with an active CA certificate. Run 'dg service start' first.");
291
+ }
292
+ const plan = trustStoreOperation(() => resolveTrustInstallPlan(runtime.caPath, env ?? process.env));
293
+ const existing = readServiceTrustRecord(paths.trustRecordPath, TRUST_SENTINEL);
294
+ if (existing && existing.fingerprintSha256 === plan.fingerprintSha256 && existing.provider === plan.provider && existing.target === plan.target) {
295
+ const state = {
296
+ ...current,
297
+ trustInstalled: true,
298
+ trust: existing,
299
+ trustDrift: undefined,
300
+ trustInstalledAt: current.trustInstalledAt ?? existing.installedAt
301
+ };
302
+ writeState(paths, state);
303
+ writeLog(paths, "service.trust.install.noop", now);
304
+ return {
305
+ paths,
306
+ state,
307
+ changed: false
308
+ };
309
+ }
310
+ if (existing) {
311
+ trustStoreOperation(() => applyTrustUninstall(existing));
312
+ }
313
+ const record = trustStoreOperation(() => applyTrustInstall(plan, now, TRUST_SENTINEL));
314
+ const state = {
315
+ ...current,
316
+ trustInstalled: true,
317
+ trust: record,
318
+ trustDrift: undefined,
319
+ trustInstalledAt: current.trustInstalledAt ?? now.toISOString()
320
+ };
321
+ writeServiceTrustRecord(paths.trustRecordPath, record);
322
+ writeState(paths, state);
323
+ writeLog(paths, "service.trust.installed", now);
324
+ withRegistryLock(paths.paths, () => {
325
+ writeRegistry(paths.paths, mergeRegistry(readRegistry(paths.paths), [registryEntry("trust-store", paths.trustRecordPath, TRUST_SENTINEL, now)]));
326
+ });
327
+ return {
328
+ paths,
329
+ state,
330
+ changed: !current.trustInstalled
331
+ };
332
+ });
333
+ }
334
+ export function uninstallServiceTrust(env, now = new Date()) {
335
+ const paths = resolveServicePaths(env);
336
+ return withServiceLock(paths, () => {
337
+ const current = readState(paths);
338
+ const record = readServiceTrustRecord(paths.trustRecordPath, TRUST_SENTINEL);
339
+ if (record) {
340
+ trustStoreOperation(() => applyTrustUninstall(record));
341
+ }
342
+ const removedTrust = removeFile(paths.trustRecordPath);
343
+ if (!current.configured && !removedTrust) {
344
+ return {
345
+ paths,
346
+ state: current,
347
+ changed: false
348
+ };
349
+ }
350
+ const state = {
351
+ ...current,
352
+ trustInstalled: false,
353
+ trust: undefined,
354
+ trustDrift: undefined,
355
+ trustUninstalledAt: current.trustInstalled || removedTrust ? now.toISOString() : current.trustUninstalledAt
356
+ };
357
+ writeState(paths, state);
358
+ writeLog(paths, current.trustInstalled || removedTrust ? "service.trust.uninstalled" : "service.trust.uninstall.noop", now);
359
+ withRegistryLock(paths.paths, () => {
360
+ writeRegistryIfChanged(paths.paths, readRegistry(paths.paths), [{ kind: "trust-store", path: paths.trustRecordPath }]);
361
+ });
362
+ return {
363
+ paths,
364
+ state,
365
+ changed: current.trustInstalled || removedTrust
366
+ };
367
+ });
368
+ }
369
+ export function uninstallService(env, now = new Date()) {
370
+ const paths = resolveServicePaths(env);
371
+ return withServiceLock(paths, () => {
372
+ const current = readState(paths);
373
+ const removed = [];
374
+ const missing = [];
375
+ cleanupRuntime(paths, readRuntime(paths) ?? current.proxy);
376
+ const record = readServiceTrustRecord(paths.trustRecordPath, TRUST_SENTINEL);
377
+ if (record) {
378
+ trustStoreOperation(() => applyTrustUninstall(record));
379
+ }
380
+ if (existsSync(paths.trustRecordPath)) {
381
+ rmSync(paths.trustRecordPath, {
382
+ force: true
383
+ });
384
+ removed.push(paths.trustRecordPath);
385
+ }
386
+ else {
387
+ missing.push(paths.trustRecordPath);
388
+ }
389
+ if (existsSync(paths.serviceDir)) {
390
+ rmSync(paths.serviceDir, {
391
+ force: true,
392
+ recursive: true
393
+ });
394
+ removed.push(paths.serviceDir);
395
+ }
396
+ else {
397
+ missing.push(paths.serviceDir);
398
+ }
399
+ withRegistryLock(paths.paths, () => {
400
+ writeRegistryIfChanged(paths.paths, readRegistry(paths.paths), [
401
+ {
402
+ kind: "service",
403
+ path: paths.statePath
404
+ },
405
+ {
406
+ kind: "trust-store",
407
+ path: paths.trustRecordPath
408
+ }
409
+ ]);
410
+ });
411
+ return {
412
+ removed,
413
+ missing,
414
+ state: {
415
+ ...current,
416
+ configured: false,
417
+ running: false,
418
+ trustInstalled: false,
419
+ trust: undefined,
420
+ trustDrift: undefined,
421
+ proxy: undefined,
422
+ lastError: undefined,
423
+ stoppedAt: current.running ? now.toISOString() : current.stoppedAt
424
+ }
425
+ };
426
+ });
427
+ }
428
+ export class ServiceNotConfiguredError extends Error {
429
+ constructor() {
430
+ super("Service mode is not configured");
431
+ }
432
+ }
433
+ export class ServiceTrustStoreError extends Error {
434
+ constructor(message) {
435
+ super(message);
436
+ }
437
+ }
438
+ export class ServiceProxyError extends Error {
439
+ constructor(message) {
440
+ super(message);
441
+ this.name = "ServiceProxyError";
442
+ }
443
+ }
444
+ function trustStoreOperation(operation) {
445
+ try {
446
+ return operation();
447
+ }
448
+ catch (error) {
449
+ if (error instanceof TrustStoreError) {
450
+ throw new ServiceTrustStoreError(error.message);
451
+ }
452
+ throw error;
453
+ }
454
+ }
455
+ function withServiceLock(paths, run) {
456
+ const lock = acquireLockSync(paths.paths, SERVICE_LOCK, {
457
+ staleMs: SERVICE_LOCK_STALE_MS
458
+ });
459
+ try {
460
+ return run();
461
+ }
462
+ finally {
463
+ lock.release();
464
+ }
465
+ }
466
+ function withRegistryLock(paths, run) {
467
+ const lock = acquireLockSync(paths, CLEANUP_REGISTRY_LOCK, {
468
+ staleMs: SERVICE_LOCK_STALE_MS
469
+ });
470
+ try {
471
+ return run();
472
+ }
473
+ finally {
474
+ lock.release();
475
+ }
476
+ }
477
+ function readState(paths) {
478
+ try {
479
+ if (!existsSync(paths.statePath)) {
480
+ return EMPTY_SERVICE_STATE;
481
+ }
482
+ const parsed = JSON.parse(readFileSync(paths.statePath, "utf8"));
483
+ if (parsed.version !== 1) {
484
+ return EMPTY_SERVICE_STATE;
485
+ }
486
+ const trust = readServiceTrustRecord(paths.trustRecordPath, TRUST_SENTINEL);
487
+ return {
488
+ version: 1,
489
+ configured: parsed.configured === true,
490
+ running: parsed.running === true,
491
+ trustInstalled: parsed.trustInstalled === true && trust !== undefined,
492
+ trust,
493
+ trustDrift: undefined,
494
+ proxy: serviceProxyRuntime(parsed.proxy),
495
+ lastError: typeof parsed.lastError === "string" ? parsed.lastError : undefined,
496
+ policySyncedAt: parsed.policySyncedAt,
497
+ configuredAt: parsed.configuredAt,
498
+ startedAt: parsed.startedAt,
499
+ stoppedAt: parsed.stoppedAt,
500
+ trustInstalledAt: parsed.trustInstalledAt,
501
+ trustUninstalledAt: parsed.trustUninstalledAt
502
+ };
503
+ }
504
+ catch {
505
+ return EMPTY_SERVICE_STATE;
506
+ }
507
+ }
508
+ function applyRuntimeDiagnostics(paths, state) {
509
+ const runtime = readRuntime(paths) ?? state.proxy;
510
+ if (!runtime) {
511
+ return state;
512
+ }
513
+ const drift = detectTrustDrift(state.trust, runtime);
514
+ if (!state.running) {
515
+ return {
516
+ ...state,
517
+ proxy: runtime,
518
+ trustDrift: drift,
519
+ lastError: "stale service runtime state: runtime file exists while service is stopped"
520
+ };
521
+ }
522
+ const health = runtimeHealth(runtime);
523
+ if (!health.healthy) {
524
+ return {
525
+ ...state,
526
+ running: false,
527
+ proxy: runtime,
528
+ trustDrift: drift,
529
+ lastError: `stale service runtime state: ${health.reason}`
530
+ };
531
+ }
532
+ return {
533
+ ...state,
534
+ proxy: runtime,
535
+ trustDrift: drift
536
+ };
537
+ }
538
+ function runtimeHealth(runtime) {
539
+ if (!processIsAlive(runtime.pid)) {
540
+ return {
541
+ healthy: false,
542
+ reason: `recorded service worker pid ${runtime.pid} is not running`
543
+ };
544
+ }
545
+ if (!healthEndpointReachable(runtime.healthUrl)) {
546
+ return {
547
+ healthy: false,
548
+ reason: `health endpoint is unreachable at ${runtime.healthUrl}`
549
+ };
550
+ }
551
+ return {
552
+ healthy: true,
553
+ reason: undefined
554
+ };
555
+ }
556
+ function healthEndpointReachable(healthUrl) {
557
+ const script = `
558
+ const http = require("node:http");
559
+ const https = require("node:https");
560
+ const target = process.argv[1];
561
+ const client = target.startsWith("https:") ? https : http;
562
+ const request = client.get(target, { timeout: 500 }, (response) => {
563
+ response.resume();
564
+ response.on("end", () => process.exit(response.statusCode === 200 ? 0 : 1));
565
+ });
566
+ request.on("timeout", () => request.destroy(new Error("timeout")));
567
+ request.on("error", () => process.exit(1));
568
+ `;
569
+ const result = spawnSync(process.execPath, ["-e", script, healthUrl], {
570
+ stdio: "ignore",
571
+ timeout: 1_000
572
+ });
573
+ return result.status === 0;
574
+ }
575
+ function detectTrustDrift(trust, runtime) {
576
+ if (!trust || !runtime?.caPath || !existsSync(runtime.caPath)) {
577
+ return undefined;
578
+ }
579
+ try {
580
+ const active = readCertificateFingerprints(runtime.caPath);
581
+ if (active.fingerprintSha256 === trust.fingerprintSha256) {
582
+ return undefined;
583
+ }
584
+ return {
585
+ installedFingerprintSha256: trust.fingerprintSha256,
586
+ activeFingerprintSha256: active.fingerprintSha256,
587
+ message: `Installed service trust fingerprint ${trust.fingerprintSha256} does not match active service CA fingerprint ${active.fingerprintSha256}. Run dg service trust install --yes to trust the active CA.`
588
+ };
589
+ }
590
+ catch {
591
+ return {
592
+ installedFingerprintSha256: trust.fingerprintSha256,
593
+ activeFingerprintSha256: undefined,
594
+ message: `Installed service trust fingerprint ${trust.fingerprintSha256} cannot be compared because the active service CA is unreadable. Run dg service restart before reinstalling trust.`
595
+ };
596
+ }
597
+ }
598
+ function removeTrustRecordIfDrifted(paths, trust, runtime, now) {
599
+ const drift = detectTrustDrift(trust, runtime);
600
+ if (!trust || !drift) {
601
+ return {
602
+ removed: false,
603
+ message: undefined
604
+ };
605
+ }
606
+ trustStoreOperation(() => applyTrustUninstall(trust));
607
+ removeFile(paths.trustRecordPath);
608
+ withRegistryLock(paths.paths, () => {
609
+ writeRegistryIfChanged(paths.paths, readRegistry(paths.paths), [{ kind: "trust-store", path: paths.trustRecordPath }]);
610
+ });
611
+ writeLog(paths, "service.trust.removed-after-ca-drift", now);
612
+ return {
613
+ removed: true,
614
+ message: `${drift.message} The stale dg-owned trust record was removed during service restart.`
615
+ };
616
+ }
617
+ function startServiceProxyRuntime(paths, env, now) {
618
+ const workerPath = env.DG_SERVICE_WORKER_PATH ?? fileURLToPath(new URL("./worker.js", import.meta.url));
619
+ if (!existsSync(workerPath)) {
620
+ throw new ServiceProxyError("service proxy worker is unavailable until the CLI package is built");
621
+ }
622
+ const session = createSessionSync(paths.paths);
623
+ const sessionBootstrapPath = join(session.dir, "session.json");
624
+ writeFileSync(sessionBootstrapPath, `${JSON.stringify(session)}\n`, {
625
+ encoding: "utf8",
626
+ mode: 0o600
627
+ });
628
+ const config = loadUserConfig(env);
629
+ const child = spawn(process.execPath, [workerPath, sessionBootstrapPath, config.api.baseUrl, paths.runtimePath], {
630
+ detached: true,
631
+ env: {
632
+ ...env,
633
+ DG_SERVICE_CLASSIFICATION: JSON.stringify({
634
+ kind: "protected",
635
+ manager: "npm",
636
+ realBinaryName: "npm",
637
+ action: "service-proxy",
638
+ args: []
639
+ })
640
+ },
641
+ stdio: "ignore"
642
+ });
643
+ child.unref();
644
+ const runtime = waitForRuntime(paths, child.pid);
645
+ if (!runtime) {
646
+ if (child.pid) {
647
+ killProcess(child.pid);
648
+ }
649
+ cleanupSessionSync(session);
650
+ throw new ServiceProxyError("service proxy worker did not become healthy");
651
+ }
652
+ void now;
653
+ return runtime;
654
+ }
655
+ function waitForRuntime(paths, pid) {
656
+ const deadline = Date.now() + 2_000;
657
+ while (Date.now() < deadline) {
658
+ const runtime = readRuntime(paths);
659
+ if (runtime && (!pid || runtime.pid === pid) && runtime.proxyUrl && runtime.healthUrl) {
660
+ return runtime;
661
+ }
662
+ sleep(25);
663
+ }
664
+ return undefined;
665
+ }
666
+ function readRuntime(paths) {
667
+ try {
668
+ if (!existsSync(paths.runtimePath)) {
669
+ return undefined;
670
+ }
671
+ const parsed = JSON.parse(readFileSync(paths.runtimePath, "utf8"));
672
+ return serviceProxyRuntime(parsed);
673
+ }
674
+ catch {
675
+ return undefined;
676
+ }
677
+ }
678
+ function serviceProxyRuntime(value) {
679
+ if (typeof value !== "object" || value === null) {
680
+ return undefined;
681
+ }
682
+ const runtime = value;
683
+ if (typeof runtime.pid !== "number" ||
684
+ typeof runtime.proxyUrl !== "string" ||
685
+ typeof runtime.healthUrl !== "string" ||
686
+ typeof runtime.sessionDir !== "string" ||
687
+ typeof runtime.caPath !== "string" ||
688
+ typeof runtime.startedAt !== "string") {
689
+ return undefined;
690
+ }
691
+ return {
692
+ pid: runtime.pid,
693
+ proxyUrl: runtime.proxyUrl,
694
+ healthUrl: runtime.healthUrl,
695
+ sessionDir: runtime.sessionDir,
696
+ caPath: runtime.caPath,
697
+ startedAt: runtime.startedAt
698
+ };
699
+ }
700
+ function cleanupRuntime(paths, runtime) {
701
+ if (runtime?.pid && processIsAlive(runtime.pid)) {
702
+ killProcess(runtime.pid);
703
+ }
704
+ if (runtime?.sessionDir && runtime.sessionDir.startsWith(paths.paths.sessionsDir)) {
705
+ rmSync(runtime.sessionDir, {
706
+ force: true,
707
+ recursive: true
708
+ });
709
+ }
710
+ removeFile(paths.runtimePath);
711
+ }
712
+ function processIsAlive(pid) {
713
+ if (pid <= 0) {
714
+ return false;
715
+ }
716
+ try {
717
+ process.kill(pid, 0);
718
+ return true;
719
+ }
720
+ catch {
721
+ return false;
722
+ }
723
+ }
724
+ function killProcess(pid) {
725
+ try {
726
+ process.kill(pid, "SIGTERM");
727
+ }
728
+ catch {
729
+ return;
730
+ }
731
+ }
732
+ function sleep(ms) {
733
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
734
+ }
735
+ function writeJsonAtomicSync(path, value) {
736
+ mkdirSync(dirname(path), {
737
+ recursive: true,
738
+ mode: 0o700
739
+ });
740
+ const tempPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
741
+ try {
742
+ writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, {
743
+ encoding: "utf8",
744
+ flag: "wx",
745
+ mode: 0o600
746
+ });
747
+ renameSync(tempPath, path);
748
+ }
749
+ catch (error) {
750
+ rmSync(tempPath, {
751
+ force: true
752
+ });
753
+ throw error;
754
+ }
755
+ }
756
+ function writeState(paths, state) {
757
+ writeJsonAtomicSync(paths.statePath, state);
758
+ }
759
+ function writeLog(paths, event, now) {
760
+ mkdirSync(dirname(paths.logPath), {
761
+ recursive: true,
762
+ mode: 0o700
763
+ });
764
+ writeFileSync(paths.logPath, `${JSON.stringify({ event, at: now.toISOString() })}\n`, {
765
+ encoding: "utf8",
766
+ flag: "a",
767
+ mode: 0o600
768
+ });
769
+ }
770
+ function readRegistry(paths) {
771
+ try {
772
+ if (!existsSync(paths.cleanupRegistryPath)) {
773
+ return {
774
+ version: 1,
775
+ entries: []
776
+ };
777
+ }
778
+ const parsed = JSON.parse(readFileSync(paths.cleanupRegistryPath, "utf8"));
779
+ if (parsed.version !== 1 || !Array.isArray(parsed.entries)) {
780
+ throw new Error("unsupported cleanup registry");
781
+ }
782
+ return parsed;
783
+ }
784
+ catch {
785
+ return {
786
+ version: 1,
787
+ entries: []
788
+ };
789
+ }
790
+ }
791
+ function writeRegistry(paths, registry) {
792
+ writeJsonAtomicSync(paths.cleanupRegistryPath, registry);
793
+ }
794
+ function writeRegistryIfChanged(paths, registry, targets) {
795
+ const next = removeRegistryTargets(registry, targets);
796
+ if (!existsSync(paths.cleanupRegistryPath) && registry.entries.length === next.entries.length) {
797
+ return;
798
+ }
799
+ if (registry.entries.length === next.entries.length) {
800
+ return;
801
+ }
802
+ writeRegistry(paths, next);
803
+ }
804
+ function registryEntry(kind, path, sentinel, now) {
805
+ return {
806
+ kind,
807
+ path,
808
+ mode: "mode2",
809
+ sentinel,
810
+ installedAt: now.toISOString(),
811
+ owner: "dg"
812
+ };
813
+ }
814
+ function mergeRegistry(registry, entries) {
815
+ return {
816
+ version: 1,
817
+ entries: [
818
+ ...registry.entries.filter((entry) => !entries.some((next) => entry.kind === next.kind && entry.path === next.path && entry.sentinel === next.sentinel)),
819
+ ...entries
820
+ ]
821
+ };
822
+ }
823
+ function removeRegistryTargets(registry, targets) {
824
+ return {
825
+ version: 1,
826
+ entries: registry.entries.filter((entry) => !targets.some((target) => entry.kind === target.kind && entry.path === target.path))
827
+ };
828
+ }
829
+ function removeFile(path) {
830
+ if (!existsSync(path)) {
831
+ return false;
832
+ }
833
+ rmSync(path, {
834
+ force: true
835
+ });
836
+ return true;
837
+ }