@calltelemetry/ct-lab-mcp 0.1.5 → 0.3.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 (179) hide show
  1. package/README.md +29 -1
  2. package/dist/bin/ct-lab-mcp.d.ts +3 -0
  3. package/dist/bin/ct-lab-mcp.d.ts.map +1 -1
  4. package/dist/bin/ct-lab-mcp.js +47 -8
  5. package/dist/bin/ct-lab-mcp.js.map +1 -1
  6. package/dist/src/appliance/commands.d.ts.map +1 -1
  7. package/dist/src/appliance/commands.js +24 -0
  8. package/dist/src/appliance/commands.js.map +1 -1
  9. package/dist/src/appliance/executor.d.ts.map +1 -1
  10. package/dist/src/appliance/executor.js +7 -1
  11. package/dist/src/appliance/executor.js.map +1 -1
  12. package/dist/src/appliance/mock.d.ts +6 -0
  13. package/dist/src/appliance/mock.d.ts.map +1 -1
  14. package/dist/src/appliance/mock.js +27 -6
  15. package/dist/src/appliance/mock.js.map +1 -1
  16. package/dist/src/appliance/types.d.ts +20 -1
  17. package/dist/src/appliance/types.d.ts.map +1 -1
  18. package/dist/src/appliance/types.js.map +1 -1
  19. package/dist/src/canonical/bridge.d.ts +28 -0
  20. package/dist/src/canonical/bridge.d.ts.map +1 -0
  21. package/dist/src/canonical/bridge.js +179 -0
  22. package/dist/src/canonical/bridge.js.map +1 -0
  23. package/dist/src/canonical/cli.d.ts +8 -0
  24. package/dist/src/canonical/cli.d.ts.map +1 -0
  25. package/dist/src/canonical/cli.js +9 -0
  26. package/dist/src/canonical/cli.js.map +1 -0
  27. package/dist/src/canonical/index.d.ts +4 -0
  28. package/dist/src/canonical/index.d.ts.map +1 -0
  29. package/dist/src/canonical/index.js +4 -0
  30. package/dist/src/canonical/index.js.map +1 -0
  31. package/dist/src/canonical/types.d.ts +32 -0
  32. package/dist/src/canonical/types.d.ts.map +1 -0
  33. package/dist/src/canonical/types.js +2 -0
  34. package/dist/src/canonical/types.js.map +1 -0
  35. package/dist/src/credentials/errors.d.ts +16 -0
  36. package/dist/src/credentials/errors.d.ts.map +1 -0
  37. package/dist/src/credentials/errors.js +21 -0
  38. package/dist/src/credentials/errors.js.map +1 -0
  39. package/dist/src/credentials/resolver.d.ts +16 -0
  40. package/dist/src/credentials/resolver.d.ts.map +1 -1
  41. package/dist/src/credentials/resolver.js +104 -10
  42. package/dist/src/credentials/resolver.js.map +1 -1
  43. package/dist/src/credentials/types.d.ts +8 -0
  44. package/dist/src/credentials/types.d.ts.map +1 -1
  45. package/dist/src/diagnostics/collector.d.ts +37 -0
  46. package/dist/src/diagnostics/collector.d.ts.map +1 -0
  47. package/dist/src/diagnostics/collector.js +452 -0
  48. package/dist/src/diagnostics/collector.js.map +1 -0
  49. package/dist/src/diagnostics/types.d.ts +103 -0
  50. package/dist/src/diagnostics/types.d.ts.map +1 -0
  51. package/dist/src/diagnostics/types.js +8 -0
  52. package/dist/src/diagnostics/types.js.map +1 -0
  53. package/dist/src/hypervisors/esxi/client.d.ts +1 -0
  54. package/dist/src/hypervisors/esxi/client.d.ts.map +1 -1
  55. package/dist/src/hypervisors/esxi/client.js +15 -3
  56. package/dist/src/hypervisors/esxi/client.js.map +1 -1
  57. package/dist/src/hypervisors/esxi/mock.d.ts +1 -0
  58. package/dist/src/hypervisors/esxi/mock.d.ts.map +1 -1
  59. package/dist/src/hypervisors/esxi/mock.js +4 -0
  60. package/dist/src/hypervisors/esxi/mock.js.map +1 -1
  61. package/dist/src/hypervisors/manager.d.ts.map +1 -1
  62. package/dist/src/hypervisors/manager.js +37 -3
  63. package/dist/src/hypervisors/manager.js.map +1 -1
  64. package/dist/src/hypervisors/proxmox/client.d.ts +13 -1
  65. package/dist/src/hypervisors/proxmox/client.d.ts.map +1 -1
  66. package/dist/src/hypervisors/proxmox/client.js +485 -4
  67. package/dist/src/hypervisors/proxmox/client.js.map +1 -1
  68. package/dist/src/hypervisors/proxmox/mock.d.ts +1 -0
  69. package/dist/src/hypervisors/proxmox/mock.d.ts.map +1 -1
  70. package/dist/src/hypervisors/proxmox/mock.js +4 -0
  71. package/dist/src/hypervisors/proxmox/mock.js.map +1 -1
  72. package/dist/src/hypervisors/types.d.ts +119 -2
  73. package/dist/src/hypervisors/types.d.ts.map +1 -1
  74. package/dist/src/index.d.ts +1 -0
  75. package/dist/src/index.d.ts.map +1 -1
  76. package/dist/src/index.js +1 -0
  77. package/dist/src/index.js.map +1 -1
  78. package/dist/src/proxmox/mock.d.ts +1 -1
  79. package/dist/src/proxmox/mock.d.ts.map +1 -1
  80. package/dist/src/proxmox/mock.js +103 -1
  81. package/dist/src/proxmox/mock.js.map +1 -1
  82. package/dist/src/server.d.ts +2 -0
  83. package/dist/src/server.d.ts.map +1 -1
  84. package/dist/src/server.js +4 -0
  85. package/dist/src/server.js.map +1 -1
  86. package/dist/src/slot/manager.d.ts +26 -1
  87. package/dist/src/slot/manager.d.ts.map +1 -1
  88. package/dist/src/slot/manager.js +278 -1
  89. package/dist/src/slot/manager.js.map +1 -1
  90. package/dist/src/slot/types.d.ts +49 -0
  91. package/dist/src/slot/types.d.ts.map +1 -1
  92. package/dist/src/slot/types.js +18 -0
  93. package/dist/src/slot/types.js.map +1 -1
  94. package/dist/src/tools/canonical-guard.d.ts +7 -0
  95. package/dist/src/tools/canonical-guard.d.ts.map +1 -0
  96. package/dist/src/tools/canonical-guard.js +38 -0
  97. package/dist/src/tools/canonical-guard.js.map +1 -0
  98. package/dist/src/tools/credential-status.d.ts +29 -0
  99. package/dist/src/tools/credential-status.d.ts.map +1 -0
  100. package/dist/src/tools/credential-status.js +81 -0
  101. package/dist/src/tools/credential-status.js.map +1 -0
  102. package/dist/src/tools/cube-provision.d.ts +288 -0
  103. package/dist/src/tools/cube-provision.d.ts.map +1 -0
  104. package/dist/src/tools/cube-provision.js +892 -0
  105. package/dist/src/tools/cube-provision.js.map +1 -0
  106. package/dist/src/tools/cube-tools.d.ts +137 -0
  107. package/dist/src/tools/cube-tools.d.ts.map +1 -0
  108. package/dist/src/tools/cube-tools.js +292 -0
  109. package/dist/src/tools/cube-tools.js.map +1 -0
  110. package/dist/src/tools/cucm-emulator-tool.d.ts +85 -0
  111. package/dist/src/tools/cucm-emulator-tool.d.ts.map +1 -0
  112. package/dist/src/tools/cucm-emulator-tool.js +191 -0
  113. package/dist/src/tools/cucm-emulator-tool.js.map +1 -0
  114. package/dist/src/tools/cucm-trunk.d.ts +165 -0
  115. package/dist/src/tools/cucm-trunk.d.ts.map +1 -0
  116. package/dist/src/tools/cucm-trunk.js +354 -0
  117. package/dist/src/tools/cucm-trunk.js.map +1 -0
  118. package/dist/src/tools/diagnostics.d.ts +58 -0
  119. package/dist/src/tools/diagnostics.d.ts.map +1 -0
  120. package/dist/src/tools/diagnostics.js +212 -0
  121. package/dist/src/tools/diagnostics.js.map +1 -0
  122. package/dist/src/tools/guest-agent.d.ts +34 -0
  123. package/dist/src/tools/guest-agent.d.ts.map +1 -0
  124. package/dist/src/tools/guest-agent.js +133 -0
  125. package/dist/src/tools/guest-agent.js.map +1 -0
  126. package/dist/src/tools/index.d.ts +11 -0
  127. package/dist/src/tools/index.d.ts.map +1 -1
  128. package/dist/src/tools/index.js +154 -1
  129. package/dist/src/tools/index.js.map +1 -1
  130. package/dist/src/tools/lab-preflight.d.ts +21 -0
  131. package/dist/src/tools/lab-preflight.d.ts.map +1 -0
  132. package/dist/src/tools/lab-preflight.js +22 -0
  133. package/dist/src/tools/lab-preflight.js.map +1 -0
  134. package/dist/src/tools/list-hosts.d.ts +11 -0
  135. package/dist/src/tools/list-hosts.d.ts.map +1 -1
  136. package/dist/src/tools/list-hosts.js.map +1 -1
  137. package/dist/src/tools/list-inventory.d.ts.map +1 -1
  138. package/dist/src/tools/list-inventory.js +21 -0
  139. package/dist/src/tools/list-inventory.js.map +1 -1
  140. package/dist/src/tools/network-tools.d.ts +65 -0
  141. package/dist/src/tools/network-tools.d.ts.map +1 -0
  142. package/dist/src/tools/network-tools.js +200 -0
  143. package/dist/src/tools/network-tools.js.map +1 -0
  144. package/dist/src/tools/provision-tools.d.ts.map +1 -1
  145. package/dist/src/tools/provision-tools.js +26 -0
  146. package/dist/src/tools/provision-tools.js.map +1 -1
  147. package/dist/src/tools/purge-slots.d.ts.map +1 -1
  148. package/dist/src/tools/purge-slots.js +4 -0
  149. package/dist/src/tools/purge-slots.js.map +1 -1
  150. package/dist/src/tools/server-management.d.ts +51 -0
  151. package/dist/src/tools/server-management.d.ts.map +1 -0
  152. package/dist/src/tools/server-management.js +175 -0
  153. package/dist/src/tools/server-management.js.map +1 -0
  154. package/dist/src/tools/slot-tools.d.ts.map +1 -1
  155. package/dist/src/tools/slot-tools.js +7 -0
  156. package/dist/src/tools/slot-tools.js.map +1 -1
  157. package/dist/src/tools/storage-tools.d.ts +177 -0
  158. package/dist/src/tools/storage-tools.d.ts.map +1 -1
  159. package/dist/src/tools/storage-tools.js +420 -0
  160. package/dist/src/tools/storage-tools.js.map +1 -1
  161. package/dist/src/tools/update-cli.d.ts +62 -0
  162. package/dist/src/tools/update-cli.d.ts.map +1 -0
  163. package/dist/src/tools/update-cli.js +142 -0
  164. package/dist/src/tools/update-cli.js.map +1 -0
  165. package/dist/src/tools/vm-action.d.ts +6 -6
  166. package/dist/src/tools/vm-action.d.ts.map +1 -1
  167. package/dist/src/tools/vm-action.js +7 -1
  168. package/dist/src/tools/vm-action.js.map +1 -1
  169. package/dist/src/types/mcp.d.ts +5 -1
  170. package/dist/src/types/mcp.d.ts.map +1 -1
  171. package/dist/src/types/mcp.js +14 -0
  172. package/dist/src/types/mcp.js.map +1 -1
  173. package/dist/src/types/proxmox.d.ts +1 -0
  174. package/dist/src/types/proxmox.d.ts.map +1 -1
  175. package/dist/src/utils/formatters.d.ts +7 -0
  176. package/dist/src/utils/formatters.d.ts.map +1 -1
  177. package/dist/src/utils/formatters.js +48 -5
  178. package/dist/src/utils/formatters.js.map +1 -1
  179. package/package.json +2 -3
@@ -0,0 +1,28 @@
1
+ import { CanonicalCommandResult, LabPreflightInput, LabPreflightResult } from "./types.js";
2
+ export interface CanonicalLabBridgeOptions {
3
+ cliPath?: string;
4
+ inventoryFile?: string;
5
+ args?: string[];
6
+ timeoutMs?: number;
7
+ required?: boolean;
8
+ runner?: (args: string[]) => Promise<Pick<CanonicalCommandResult, "exitCode" | "stdout" | "stderr">>;
9
+ }
10
+ export declare class CanonicalBridgeError extends Error {
11
+ readonly code = "CANONICAL_INVENTORY_UNAVAILABLE";
12
+ constructor(message: string);
13
+ }
14
+ export declare class CanonicalLabBridge {
15
+ private readonly cliPath?;
16
+ private readonly inventoryFile?;
17
+ private readonly prefixArgs;
18
+ private readonly timeoutMs;
19
+ private readonly required;
20
+ private readonly runner?;
21
+ constructor(options?: CanonicalLabBridgeOptions);
22
+ get isConfigured(): boolean;
23
+ /** The canonical CLI owns identity and lease policy in this mode. */
24
+ get isRequired(): boolean;
25
+ run(args: string[]): Promise<CanonicalCommandResult>;
26
+ preflight(input: LabPreflightInput): Promise<LabPreflightResult>;
27
+ }
28
+ //# sourceMappingURL=bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../../src/canonical/bridge.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAK3F,MAAM,WAAW,yBAAyB;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,sBAAsB,EAAE,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC;CACtG;AAED,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,qCAAqC;gBAEtC,OAAO,EAAE,MAAM;CAI5B;AAiBD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAW;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAsC;gBAElD,OAAO,GAAE,yBAA8B;IASnD,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED,qEAAqE;IACrE,IAAI,UAAU,IAAI,OAAO,CAExB;IAEK,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC;IA6CpD,SAAS,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;CA8FvE"}
@@ -0,0 +1,179 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { defaultRedactor } from "../utils/redactor.js";
4
+ const execFileAsync = promisify(execFile);
5
+ export class CanonicalBridgeError extends Error {
6
+ code = "CANONICAL_INVENTORY_UNAVAILABLE";
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = "CanonicalBridgeError";
10
+ }
11
+ }
12
+ function redact(text) {
13
+ let result = defaultRedactor.redactString(text);
14
+ // Preserve machine-readable evidence while redacting JSON object keys
15
+ // recursively (plain string regexes cannot safely cover escaped values).
16
+ try {
17
+ const parsed = JSON.parse(text);
18
+ result = JSON.stringify(defaultRedactor.redactObject(parsed));
19
+ }
20
+ catch {
21
+ // Non-JSON CLI output is already covered by the string redactor above.
22
+ }
23
+ return result
24
+ .replace(/(["']?(?:DOPPLER_TOKEN|LAB_[A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD)|password|passwd|token|secret|private_key|privateKey)["']?)\s*([:=])\s*(["']?)[^\s,"'}]+\3/gi, "$1$2[REDACTED]")
25
+ .replace(/(authorization\s*:\s*bearer\s+)["']?[^\s"']+["']?/gi, "$1[REDACTED]");
26
+ }
27
+ export class CanonicalLabBridge {
28
+ cliPath;
29
+ inventoryFile;
30
+ prefixArgs;
31
+ timeoutMs;
32
+ required;
33
+ runner;
34
+ constructor(options = {}) {
35
+ this.cliPath = options.cliPath || process.env.CT_LAB_CANONICAL_CLI;
36
+ this.inventoryFile = options.inventoryFile || process.env.LAB_INVENTORY_FILE;
37
+ this.prefixArgs = options.args || [];
38
+ this.timeoutMs = options.timeoutMs ?? Number(process.env.CT_LAB_CANONICAL_TIMEOUT_MS || 30_000);
39
+ this.required = options.required ?? process.env.CT_LAB_CANONICAL_REQUIRED === "1";
40
+ this.runner = options.runner;
41
+ }
42
+ get isConfigured() {
43
+ return Boolean(this.runner || this.cliPath);
44
+ }
45
+ /** The canonical CLI owns identity and lease policy in this mode. */
46
+ get isRequired() {
47
+ return this.required;
48
+ }
49
+ async run(args) {
50
+ if (!this.isConfigured) {
51
+ throw new CanonicalBridgeError("canonical lab CLI is not configured");
52
+ }
53
+ const command = [
54
+ ...(this.inventoryFile ? ["--file", this.inventoryFile] : []),
55
+ ...args,
56
+ ];
57
+ if (this.runner) {
58
+ const result = await this.runner(command);
59
+ return {
60
+ command,
61
+ exitCode: result.exitCode,
62
+ stdout: redact(result.stdout),
63
+ stderr: redact(result.stderr),
64
+ };
65
+ }
66
+ try {
67
+ const result = await execFileAsync(this.cliPath, [...this.prefixArgs, ...command], {
68
+ env: process.env,
69
+ timeout: this.timeoutMs,
70
+ maxBuffer: 4 * 1024 * 1024,
71
+ encoding: "utf8",
72
+ });
73
+ return {
74
+ command,
75
+ exitCode: 0,
76
+ stdout: redact(result.stdout),
77
+ stderr: redact(result.stderr),
78
+ };
79
+ }
80
+ catch (error) {
81
+ const exitCode = typeof error?.code === "number" ? error.code : 1;
82
+ return {
83
+ command,
84
+ exitCode,
85
+ stdout: redact(String(error?.stdout || "")),
86
+ stderr: redact(String(error?.stderr || error?.message || "")),
87
+ timedOut: Boolean(error?.killed),
88
+ };
89
+ }
90
+ }
91
+ async preflight(input) {
92
+ const base = {
93
+ assetId: input.assetId,
94
+ linearIssue: input.linearIssue,
95
+ evidence: {},
96
+ };
97
+ if (!this.isConfigured) {
98
+ if (this.required) {
99
+ return {
100
+ ...base,
101
+ status: "blocked",
102
+ blockedAt: "configuration",
103
+ checks: { inventory: "blocked", identity: "not_run", ownership: "not_run", releaseLine: "not_run" },
104
+ message: "canonical lab CLI is not configured",
105
+ };
106
+ }
107
+ throw new CanonicalBridgeError("canonical lab CLI is not configured");
108
+ }
109
+ const inventory = await this.run(["validate"]);
110
+ base.evidence.inventory = inventory;
111
+ if (inventory.exitCode !== 0) {
112
+ return {
113
+ ...base,
114
+ status: "blocked",
115
+ blockedAt: "inventory",
116
+ checks: { inventory: "blocked", identity: "not_run", ownership: "not_run", releaseLine: "not_run" },
117
+ message: inventory.stderr || inventory.stdout || "canonical inventory validation failed",
118
+ command: inventory.command,
119
+ };
120
+ }
121
+ const identity = await this.run(["vm", "status", "--asset-id", input.assetId]);
122
+ base.evidence.identity = identity;
123
+ if (identity.exitCode !== 0) {
124
+ return {
125
+ ...base,
126
+ status: "blocked",
127
+ blockedAt: "identity",
128
+ checks: { inventory: "passed", identity: "blocked", ownership: "not_run", releaseLine: "not_run" },
129
+ message: identity.stderr || identity.stdout || "canonical VM identity validation failed",
130
+ command: identity.command,
131
+ };
132
+ }
133
+ const ownership = await this.run(["context", "--asset-id", input.assetId]);
134
+ base.evidence.ownership = ownership;
135
+ let ownsIssue = false;
136
+ try {
137
+ const payload = JSON.parse(ownership.stdout);
138
+ ownsIssue = (payload.linear_context || []).some((entry) => entry.key?.toUpperCase() === input.linearIssue.toUpperCase() && entry.status === "available");
139
+ }
140
+ catch {
141
+ ownsIssue = false;
142
+ }
143
+ if (ownership.exitCode !== 0 || !ownsIssue) {
144
+ return {
145
+ ...base,
146
+ status: "blocked",
147
+ blockedAt: "ownership",
148
+ checks: { inventory: "passed", identity: "passed", ownership: "blocked", releaseLine: "not_run" },
149
+ message: ownership.stderr || "canonical asset is not provably owned by the supplied Linear issue",
150
+ command: ownership.command,
151
+ };
152
+ }
153
+ const releaseLine = await this.run([
154
+ "fleet",
155
+ "versions",
156
+ "--asset-id",
157
+ input.assetId,
158
+ "--format",
159
+ "json",
160
+ ]);
161
+ base.evidence.releaseLine = releaseLine;
162
+ if (releaseLine.exitCode !== 0) {
163
+ return {
164
+ ...base,
165
+ status: "blocked",
166
+ blockedAt: "releaseLine",
167
+ checks: { inventory: "passed", identity: "passed", ownership: "passed", releaseLine: "blocked" },
168
+ message: releaseLine.stderr || releaseLine.stdout || "release-line qualification failed",
169
+ command: releaseLine.command,
170
+ };
171
+ }
172
+ return {
173
+ ...base,
174
+ status: "passed",
175
+ checks: { inventory: "passed", identity: "passed", ownership: "passed", releaseLine: "passed" },
176
+ };
177
+ }
178
+ }
179
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../../../src/canonical/bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { CanonicalLabBridge } from "./bridge.js";
2
+ import { LabPreflightInput, LabPreflightResult } from "./types.js";
3
+ export interface CanonicalPreflightResponse {
4
+ exitCode: 0 | 2;
5
+ result: LabPreflightResult;
6
+ }
7
+ export declare function runCanonicalPreflight(input: LabPreflightInput, bridge?: CanonicalLabBridge): Promise<CanonicalPreflightResponse>;
8
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/canonical/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEnE,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC;IAChB,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAED,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,iBAAiB,EACxB,MAAM,qBAA6C,GAClD,OAAO,CAAC,0BAA0B,CAAC,CAMrC"}
@@ -0,0 +1,9 @@
1
+ import { CanonicalLabBridge } from "./bridge.js";
2
+ export async function runCanonicalPreflight(input, bridge = new CanonicalLabBridge({ required: true })) {
3
+ const result = await bridge.preflight(input);
4
+ return {
5
+ exitCode: result.status === "passed" ? 0 : 2,
6
+ result,
7
+ };
8
+ }
9
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../src/canonical/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAQjD,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,KAAwB,EACxB,MAAM,GAAG,IAAI,kBAAkB,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAEnD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC7C,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5C,MAAM;KACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from "./types.js";
2
+ export * from "./bridge.js";
3
+ export * from "./cli.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/canonical/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from "./types.js";
2
+ export * from "./bridge.js";
3
+ export * from "./cli.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/canonical/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC"}
@@ -0,0 +1,32 @@
1
+ export interface CanonicalCommandResult {
2
+ command: string[];
3
+ exitCode: number;
4
+ stdout: string;
5
+ stderr: string;
6
+ timedOut?: boolean;
7
+ }
8
+ export interface LabPreflightInput {
9
+ assetId: string;
10
+ linearIssue: string;
11
+ }
12
+ export interface LabPreflightResult {
13
+ status: "passed" | "blocked";
14
+ assetId: string;
15
+ linearIssue: string;
16
+ checks: {
17
+ inventory: "passed" | "blocked";
18
+ identity: "passed" | "blocked" | "not_run";
19
+ ownership: "passed" | "blocked" | "not_run";
20
+ releaseLine: "passed" | "blocked" | "not_run";
21
+ };
22
+ blockedAt?: "inventory" | "identity" | "ownership" | "releaseLine" | "configuration";
23
+ message?: string;
24
+ command?: string[];
25
+ evidence: {
26
+ inventory?: CanonicalCommandResult;
27
+ identity?: CanonicalCommandResult;
28
+ ownership?: CanonicalCommandResult;
29
+ releaseLine?: CanonicalCommandResult;
30
+ };
31
+ }
32
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/canonical/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE;QACN,SAAS,EAAE,QAAQ,GAAG,SAAS,CAAC;QAChC,QAAQ,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;QAC3C,SAAS,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;QAC5C,WAAW,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;KAC/C,CAAC;IACF,SAAS,CAAC,EAAE,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,eAAe,CAAC;IACrF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE;QACR,SAAS,CAAC,EAAE,sBAAsB,CAAC;QACnC,QAAQ,CAAC,EAAE,sBAAsB,CAAC;QAClC,SAAS,CAAC,EAAE,sBAAsB,CAAC;QACnC,WAAW,CAAC,EAAE,sBAAsB,CAAC;KACtC,CAAC;CACH"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/canonical/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Structured credential resolution failures.
3
+ *
4
+ * These errors exist to make one thing impossible: silently booting the MCP
5
+ * server (or executing a tool) against synthetic/mock hypervisor and
6
+ * appliance state without the caller having explicitly opted in. See QA-395.
7
+ */
8
+ export type CredentialResolutionFailureReason = "no_credentials_found" | "doppler_unreachable" | "partial_credentials";
9
+ export declare class CredentialResolutionError extends Error {
10
+ readonly code = "CREDENTIAL_RESOLUTION_ERROR";
11
+ readonly reason: CredentialResolutionFailureReason;
12
+ readonly remediation: string;
13
+ readonly missing: string[];
14
+ constructor(message: string, reason: CredentialResolutionFailureReason, remediation: string, missing?: string[]);
15
+ }
16
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/credentials/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,iCAAiC,GACzC,sBAAsB,GACtB,qBAAqB,GACrB,qBAAqB,CAAC;AAE1B,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,IAAI,iCAAiC;IAC9C,QAAQ,CAAC,MAAM,EAAE,iCAAiC,CAAC;IACnD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;gBAGzB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,iCAAiC,EACzC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,MAAM,EAAO;CAQzB"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Structured credential resolution failures.
3
+ *
4
+ * These errors exist to make one thing impossible: silently booting the MCP
5
+ * server (or executing a tool) against synthetic/mock hypervisor and
6
+ * appliance state without the caller having explicitly opted in. See QA-395.
7
+ */
8
+ export class CredentialResolutionError extends Error {
9
+ code = "CREDENTIAL_RESOLUTION_ERROR";
10
+ reason;
11
+ remediation;
12
+ missing;
13
+ constructor(message, reason, remediation, missing = []) {
14
+ super(message);
15
+ this.name = "CredentialResolutionError";
16
+ this.reason = reason;
17
+ this.remediation = remediation;
18
+ this.missing = missing;
19
+ }
20
+ }
21
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/credentials/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IACzC,IAAI,GAAG,6BAA6B,CAAC;IACrC,MAAM,CAAoC;IAC1C,WAAW,CAAS;IACpB,OAAO,CAAW;IAE3B,YACE,OAAe,EACf,MAAyC,EACzC,WAAmB,EACnB,UAAoB,EAAE;QAEtB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;QACxC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF"}
@@ -10,8 +10,24 @@ export declare class CredentialResolver {
10
10
  private readonly defaultLabProtectedVmids;
11
11
  private readonly defaultMiniProtectedVmids;
12
12
  private readonly defaultEsxiProtectedVms;
13
+ /**
14
+ * Returns true only when mock mode was EXPLICITLY requested by the caller,
15
+ * either to force it immediately or to permit it as a fallback when real
16
+ * resolution is incomplete/unavailable. Mock mode must never engage
17
+ * implicitly — see QA-395 (ct-lab-mcp fabricated success against
18
+ * nonexistent hosts when Doppler resolution silently failed).
19
+ */
20
+ private isForceMock;
21
+ private isMockPermitted;
13
22
  /**
14
23
  * Resolve all lab credentials through the 3-tier cascade.
24
+ *
25
+ * Mock substitution (full OR partial, i.e. filling in a single missing
26
+ * host) NEVER happens implicitly. If real resolution is incomplete and
27
+ * the caller has not explicitly permitted mock mode (forceMock,
28
+ * CT_LAB_MCP_ALLOW_MOCK=1, CT_LAB_MOCK_MODE=true, PROXMOX_MOCK_MODE=true,
29
+ * or NODE_ENV=test-mock), this throws CredentialResolutionError instead of
30
+ * returning a synthetic success. See QA-395.
15
31
  */
16
32
  resolve(options?: ResolutionOptions): Promise<CredentialResolutionResult>;
17
33
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../../src/credentials/resolver.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAKL,iBAAiB,EACjB,0BAA0B,EAE3B,MAAM,YAAY,CAAC;AAKpB,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAwB;IACjE,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAgB;IAC1D,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAQtC;IAEF;;OAEG;IACG,OAAO,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,0BAA0B,CAAC;IA4HnF;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAmGhC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA0F7B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAuE5B,OAAO,CAAC,gBAAgB;IAiBxB,OAAO,CAAC,qBAAqB;IAwB7B,OAAO,CAAC,kBAAkB;IAiB1B,OAAO,CAAC,uBAAuB;IAQ/B,OAAO,CAAC,kBAAkB;CAwB3B;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC"}
1
+ {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../../src/credentials/resolver.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAKL,iBAAiB,EACjB,0BAA0B,EAE3B,MAAM,YAAY,CAAC;AAcpB,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAwB;IACjE,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAgB;IAC1D,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAQtC;IAEF;;;;;;OAMG;IACH,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,eAAe;IASvB;;;;;;;;;OASG;IACG,OAAO,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,0BAA0B,CAAC;IAuLnF;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAmGhC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA0F7B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IA+F5B,OAAO,CAAC,gBAAgB;IAiBxB,OAAO,CAAC,qBAAqB;IAwB7B,OAAO,CAAC,kBAAkB;IAiB1B,OAAO,CAAC,uBAAuB;IAQ/B,OAAO,CAAC,kBAAkB;CAwB3B;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC"}
@@ -8,6 +8,13 @@
8
8
  import { DopplerClient } from "./doppler.js";
9
9
  import { registerSecret } from "../utils/redactor.js";
10
10
  import { logger } from "../utils/logger.js";
11
+ import { CredentialResolutionError } from "./errors.js";
12
+ const ALLOW_MOCK_REMEDIATION = "This is intentional: ct-lab-mcp never silently substitutes fabricated " +
13
+ "hypervisor/appliance state for missing credentials (QA-395). Either " +
14
+ "supply real credentials (LAB_PROXMOX_*, LAB_PROXMOX_MINIS_*, GOVC_*/LAB_ESXI_*, " +
15
+ "LAB_VM_CREDENTIALS_*) directly or via DOPPLER_TOKEN (+ DOPPLER_PROJECT/DOPPLER_CONFIG), " +
16
+ "or explicitly opt into offline mock mode with CT_LAB_MCP_ALLOW_MOCK=1 " +
17
+ "(or --force-mock / forceMock) if this is a genuine test/offline run.";
11
18
  export class CredentialResolver {
12
19
  defaultLabProtectedVmids = [100, 101, 102, 104];
13
20
  defaultMiniProtectedVmids = [];
@@ -20,15 +27,39 @@ export class CredentialResolver {
20
27
  "k8s-master",
21
28
  "esxi-template-base"
22
29
  ];
30
+ /**
31
+ * Returns true only when mock mode was EXPLICITLY requested by the caller,
32
+ * either to force it immediately or to permit it as a fallback when real
33
+ * resolution is incomplete/unavailable. Mock mode must never engage
34
+ * implicitly — see QA-395 (ct-lab-mcp fabricated success against
35
+ * nonexistent hosts when Doppler resolution silently failed).
36
+ */
37
+ isForceMock(options, env) {
38
+ return Boolean(options.forceMock ||
39
+ env.CT_LAB_MOCK_MODE === "true" ||
40
+ env.PROXMOX_MOCK_MODE === "true" ||
41
+ env.NODE_ENV === "test-mock");
42
+ }
43
+ isMockPermitted(options, env) {
44
+ return (this.isForceMock(options, env) ||
45
+ Boolean(options.allowMock) ||
46
+ env.CT_LAB_MCP_ALLOW_MOCK === "1" ||
47
+ env.CT_LAB_MCP_ALLOW_MOCK === "true");
48
+ }
23
49
  /**
24
50
  * Resolve all lab credentials through the 3-tier cascade.
51
+ *
52
+ * Mock substitution (full OR partial, i.e. filling in a single missing
53
+ * host) NEVER happens implicitly. If real resolution is incomplete and
54
+ * the caller has not explicitly permitted mock mode (forceMock,
55
+ * CT_LAB_MCP_ALLOW_MOCK=1, CT_LAB_MOCK_MODE=true, PROXMOX_MOCK_MODE=true,
56
+ * or NODE_ENV=test-mock), this throws CredentialResolutionError instead of
57
+ * returning a synthetic success. See QA-395.
25
58
  */
26
59
  async resolve(options = {}) {
27
60
  const env = options.env || process.env;
28
- const forceMock = options.forceMock ||
29
- env.CT_LAB_MOCK_MODE === "true" ||
30
- env.PROXMOX_MOCK_MODE === "true" ||
31
- env.NODE_ENV === "test-mock";
61
+ const forceMock = this.isForceMock(options, env);
62
+ const mockPermitted = this.isMockPermitted(options, env);
32
63
  if (forceMock) {
33
64
  logger.info("Mock mode forced via configuration. Utilizing Tier 3 (Mock Fallback).");
34
65
  return this.createMockResult();
@@ -39,12 +70,16 @@ export class CredentialResolver {
39
70
  this.extractProxmoxHostConfig("proxmox-mini", "LAB_PROXMOX_MINI_", env, 4, this.defaultMiniProtectedVmids);
40
71
  const directEsxi = this.extractEsxiHostConfig(env);
41
72
  const directVmCreds = this.extractVmCredentials(env);
42
- // If all essential direct credentials exist, return Tier 1
73
+ // If all essential direct credentials exist, return Tier 1 (fully real, no mock involved)
43
74
  if (directLab && directMini && directEsxi) {
44
75
  logger.info("Resolved full credentials for Proxmox and ESXi hosts from Direct Environment Variables (Tier 1).");
45
76
  const proxmoxHosts = [directLab, directMini];
46
77
  const esxiHosts = [directEsxi];
47
- const vmCredentials = directVmCreds || this.createMockVmCredentials();
78
+ const vmCredentials = directVmCreds || (mockPermitted ? this.createMockVmCredentials() : null);
79
+ if (!vmCredentials) {
80
+ throw new CredentialResolutionError("Proxmox and ESXi hypervisor credentials resolved, but no appliance SSH " +
81
+ "credentials (LAB_VM_CREDENTIALS / LAB_VM_CREDENTIALS_USERNAME+PASSWORD) were found.", "partial_credentials", ALLOW_MOCK_REMEDIATION, ["vmCredentials"]);
82
+ }
48
83
  this.registerAllSecrets(proxmoxHosts, esxiHosts, vmCredentials);
49
84
  return {
50
85
  tier: "direct_env",
@@ -76,6 +111,21 @@ export class CredentialResolver {
76
111
  const dopplerEsxi = directEsxi || this.extractEsxiHostConfig(mergedSecrets);
77
112
  const dopplerVmCreds = directVmCreds || this.extractVmCredentials(mergedSecrets);
78
113
  if (dopplerLab || dopplerMini || dopplerEsxi) {
114
+ const fullyResolved = Boolean(dopplerLab && dopplerMini && dopplerEsxi && dopplerVmCreds);
115
+ if (!fullyResolved && !mockPermitted) {
116
+ const missing = [];
117
+ if (!dopplerLab)
118
+ missing.push("proxmox-lab");
119
+ if (!dopplerMini)
120
+ missing.push("proxmox-mini");
121
+ if (!dopplerEsxi)
122
+ missing.push("esxi");
123
+ if (!dopplerVmCreds)
124
+ missing.push("vmCredentials");
125
+ throw new CredentialResolutionError(`Doppler API resolved credentials for [${[dopplerLab && "proxmox-lab", dopplerMini && "proxmox-mini", dopplerEsxi && "esxi"]
126
+ .filter(Boolean)
127
+ .join(", ") || "none"}] but is missing [${missing.join(", ")}].`, "partial_credentials", ALLOW_MOCK_REMEDIATION, missing);
128
+ }
79
129
  const proxmoxHosts = [];
80
130
  proxmoxHosts.push(dopplerLab || this.createMockProxmoxHost("proxmox-lab", 2, this.defaultLabProtectedVmids));
81
131
  proxmoxHosts.push(dopplerMini || this.createMockProxmoxHost("proxmox-mini", 4, this.defaultMiniProtectedVmids));
@@ -95,13 +145,29 @@ export class CredentialResolver {
95
145
  }
96
146
  }
97
147
  catch (err) {
148
+ if (err instanceof CredentialResolutionError) {
149
+ throw err;
150
+ }
98
151
  const msg = err instanceof Error ? err.message : String(err);
99
- logger.warn(`Doppler API resolution failed: ${msg}. Falling back to partial / Tier 3.`);
152
+ if (!mockPermitted) {
153
+ throw new CredentialResolutionError(`Doppler API resolution failed: ${msg}`, "doppler_unreachable", ALLOW_MOCK_REMEDIATION);
154
+ }
155
+ logger.warn(`Doppler API resolution failed: ${msg}. Mock fallback explicitly permitted; falling back to partial / Tier 3.`);
100
156
  }
101
157
  }
102
158
  // --- Partial Direct Env Fallback ---
103
159
  if (directLab || directMini || directEsxi) {
104
- logger.info("Engaging partial direct environment resolution with mock fallback for missing targets.");
160
+ if (!mockPermitted) {
161
+ const missing = [];
162
+ if (!directLab)
163
+ missing.push("proxmox-lab");
164
+ if (!directMini)
165
+ missing.push("proxmox-mini");
166
+ if (!directEsxi)
167
+ missing.push("esxi");
168
+ throw new CredentialResolutionError(`Partial direct-environment credentials resolved for [${[directLab && "proxmox-lab", directMini && "proxmox-mini", directEsxi && "esxi"].filter(Boolean).join(", ")}] but missing [${missing.join(", ")}].`, "partial_credentials", ALLOW_MOCK_REMEDIATION, missing);
169
+ }
170
+ logger.info("Engaging partial direct environment resolution with mock fallback for missing targets (explicitly permitted).");
105
171
  const proxmoxHosts = [];
106
172
  proxmoxHosts.push(directLab || this.createMockProxmoxHost("proxmox-lab", 2, this.defaultLabProtectedVmids));
107
173
  proxmoxHosts.push(directMini || this.createMockProxmoxHost("proxmox-mini", 4, this.defaultMiniProtectedVmids));
@@ -118,7 +184,11 @@ export class CredentialResolver {
118
184
  };
119
185
  }
120
186
  // --- Tier 3: Mock Fallback Mode ---
121
- logger.info("No hypervisor credentials found. Engaging Tier 3 (In-Memory Mock Fallback).");
187
+ if (!mockPermitted) {
188
+ throw new CredentialResolutionError("No hypervisor or appliance credentials were found in the environment, direct " +
189
+ "config, or Doppler (DOPPLER_TOKEN was not set or not provided).", "no_credentials_found", ALLOW_MOCK_REMEDIATION);
190
+ }
191
+ logger.info("No hypervisor credentials found. Mock fallback explicitly permitted; engaging Tier 3 (In-Memory Mock Fallback).");
122
192
  return this.createMockResult();
123
193
  }
124
194
  /**
@@ -337,13 +407,37 @@ export class CredentialResolver {
337
407
  const port = portRaw ? parseInt(portRaw, 10) : 2222;
338
408
  if (username || password || privateKey) {
339
409
  return {
340
- username: username ? username.trim() : "admin",
410
+ username: username ? username.trim() : "calltelemetry",
341
411
  password: password ? password.trim() : undefined,
342
412
  privateKey: privateKey ? privateKey.trim() : undefined,
343
413
  passphrase: passphrase ? passphrase.trim() : undefined,
344
414
  port: isNaN(port) ? 2222 : port
345
415
  };
346
416
  }
417
+ // 3. Check for specific Doppler secret patterns: LAB_VM_CREDENTIALS_<UUID> or LAB_VM_CT_*
418
+ for (const [key, val] of Object.entries(record)) {
419
+ if (key.startsWith("LAB_VM_CREDENTIALS_") && val && typeof val === "string" && val.startsWith("{")) {
420
+ try {
421
+ const parsed = JSON.parse(val);
422
+ if (parsed?.ssh?.password || parsed?.password) {
423
+ return {
424
+ username: parsed.ssh?.username || parsed.username || "calltelemetry",
425
+ password: parsed.ssh?.password || parsed.password,
426
+ privateKey: parsed.ssh?.privateKey || parsed.privateKey,
427
+ port: parsed.ssh?.port ? Number(parsed.ssh.port) : 2222
428
+ };
429
+ }
430
+ }
431
+ catch { }
432
+ }
433
+ if (key.includes("LAB_VM") && key.includes("PASSWORD") && val) {
434
+ return {
435
+ username: record.LAB_VM_CT_086_STABLE_QA_USERNAME || "calltelemetry",
436
+ password: val.trim(),
437
+ port: 2222
438
+ };
439
+ }
440
+ }
347
441
  return null;
348
442
  }
349
443
  createMockResult() {