swallowkit 1.1.2 → 2.0.0-alpha.10

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 (262) hide show
  1. package/README.ja.md +61 -7
  2. package/README.md +61 -7
  3. package/dist/cli/commands/add-auth.d.ts.map +1 -1
  4. package/dist/cli/commands/add-auth.js +103 -27
  5. package/dist/cli/commands/add-auth.js.map +1 -1
  6. package/dist/cli/commands/configure-mcp.d.ts +12 -0
  7. package/dist/cli/commands/configure-mcp.d.ts.map +1 -0
  8. package/dist/cli/commands/configure-mcp.js +42 -0
  9. package/dist/cli/commands/configure-mcp.js.map +1 -0
  10. package/dist/cli/commands/dev.d.ts +5 -2
  11. package/dist/cli/commands/dev.d.ts.map +1 -1
  12. package/dist/cli/commands/dev.js +143 -67
  13. package/dist/cli/commands/dev.js.map +1 -1
  14. package/dist/cli/commands/init.d.ts +14 -7
  15. package/dist/cli/commands/init.d.ts.map +1 -1
  16. package/dist/cli/commands/init.js +149 -170
  17. package/dist/cli/commands/init.js.map +1 -1
  18. package/dist/cli/commands/migrations.d.ts.map +1 -1
  19. package/dist/cli/commands/migrations.js +25 -0
  20. package/dist/cli/commands/migrations.js.map +1 -1
  21. package/dist/cli/commands/provision.d.ts +5 -0
  22. package/dist/cli/commands/provision.d.ts.map +1 -1
  23. package/dist/cli/commands/provision.js +278 -55
  24. package/dist/cli/commands/provision.js.map +1 -1
  25. package/dist/cli/commands/scaffold.d.ts +5 -1
  26. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  27. package/dist/cli/commands/scaffold.js +123 -18
  28. package/dist/cli/commands/scaffold.js.map +1 -1
  29. package/dist/cli/index.d.ts +2 -0
  30. package/dist/cli/index.d.ts.map +1 -1
  31. package/dist/cli/index.js +49 -8
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/core/config.d.ts +2 -1
  34. package/dist/core/config.d.ts.map +1 -1
  35. package/dist/core/config.js +19 -0
  36. package/dist/core/config.js.map +1 -1
  37. package/dist/core/operations/create-model.d.ts +2 -0
  38. package/dist/core/operations/create-model.d.ts.map +1 -1
  39. package/dist/core/operations/create-model.js +60 -8
  40. package/dist/core/operations/create-model.js.map +1 -1
  41. package/dist/core/operations/migration-generator.d.ts.map +1 -1
  42. package/dist/core/operations/migration-generator.js +12 -1
  43. package/dist/core/operations/migration-generator.js.map +1 -1
  44. package/dist/core/operations/model-migration.d.ts +22 -0
  45. package/dist/core/operations/model-migration.d.ts.map +1 -0
  46. package/dist/core/operations/model-migration.js +36 -0
  47. package/dist/core/operations/model-migration.js.map +1 -0
  48. package/dist/core/operations/provision-deployment.d.ts +52 -0
  49. package/dist/core/operations/provision-deployment.d.ts.map +1 -0
  50. package/dist/core/operations/provision-deployment.js +164 -0
  51. package/dist/core/operations/provision-deployment.js.map +1 -0
  52. package/dist/core/operations/provision-operations.d.ts +62 -6
  53. package/dist/core/operations/provision-operations.d.ts.map +1 -1
  54. package/dist/core/operations/provision-operations.js +653 -115
  55. package/dist/core/operations/provision-operations.js.map +1 -1
  56. package/dist/core/operations/scaffold-apply.d.ts +1 -0
  57. package/dist/core/operations/scaffold-apply.d.ts.map +1 -1
  58. package/dist/core/operations/scaffold-apply.js +1 -0
  59. package/dist/core/operations/scaffold-apply.js.map +1 -1
  60. package/dist/core/operations/scaffold-machine.d.ts +1 -0
  61. package/dist/core/operations/scaffold-machine.d.ts.map +1 -1
  62. package/dist/core/operations/scaffold-machine.js.map +1 -1
  63. package/dist/core/operations/scaffold-plan.d.ts +2 -0
  64. package/dist/core/operations/scaffold-plan.d.ts.map +1 -1
  65. package/dist/core/operations/scaffold-plan.js +3 -1
  66. package/dist/core/operations/scaffold-plan.js.map +1 -1
  67. package/dist/core/project/agent-instructions.d.ts +2 -1
  68. package/dist/core/project/agent-instructions.d.ts.map +1 -1
  69. package/dist/core/project/agent-instructions.js +15 -0
  70. package/dist/core/project/agent-instructions.js.map +1 -1
  71. package/dist/core/project/boundaries.d.ts +5 -2
  72. package/dist/core/project/boundaries.d.ts.map +1 -1
  73. package/dist/core/project/boundaries.js +51 -20
  74. package/dist/core/project/boundaries.js.map +1 -1
  75. package/dist/core/project/capabilities.d.ts +17 -0
  76. package/dist/core/project/capabilities.d.ts.map +1 -1
  77. package/dist/core/project/capabilities.js +45 -6
  78. package/dist/core/project/capabilities.js.map +1 -1
  79. package/dist/core/project/home-page.d.ts +4 -0
  80. package/dist/core/project/home-page.d.ts.map +1 -0
  81. package/dist/core/project/home-page.js +113 -0
  82. package/dist/core/project/home-page.js.map +1 -0
  83. package/dist/core/project/manifest.d.ts +3 -1
  84. package/dist/core/project/manifest.d.ts.map +1 -1
  85. package/dist/core/project/manifest.js +47 -25
  86. package/dist/core/project/manifest.js.map +1 -1
  87. package/dist/core/project/mcp-config.d.ts +26 -0
  88. package/dist/core/project/mcp-config.d.ts.map +1 -0
  89. package/dist/core/project/mcp-config.js +117 -0
  90. package/dist/core/project/mcp-config.js.map +1 -0
  91. package/dist/core/project/react-project.d.ts +26 -0
  92. package/dist/core/project/react-project.d.ts.map +1 -0
  93. package/dist/core/project/react-project.js +443 -0
  94. package/dist/core/project/react-project.js.map +1 -0
  95. package/dist/core/project/state.d.ts +7 -0
  96. package/dist/core/project/state.d.ts.map +1 -1
  97. package/dist/core/project/state.js +86 -0
  98. package/dist/core/project/state.js.map +1 -1
  99. package/dist/core/project/validation.d.ts.map +1 -1
  100. package/dist/core/project/validation.js +32 -10
  101. package/dist/core/project/validation.js.map +1 -1
  102. package/dist/core/project/workflows.d.ts.map +1 -1
  103. package/dist/core/project/workflows.js +53 -7
  104. package/dist/core/project/workflows.js.map +1 -1
  105. package/dist/core/scaffold/auth-generator.d.ts.map +1 -1
  106. package/dist/core/scaffold/auth-generator.js +26 -27
  107. package/dist/core/scaffold/auth-generator.js.map +1 -1
  108. package/dist/core/scaffold/model-parser.d.ts +23 -0
  109. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  110. package/dist/core/scaffold/model-parser.js +407 -137
  111. package/dist/core/scaffold/model-parser.js.map +1 -1
  112. package/dist/core/scaffold/native-schema-generator.d.ts +13 -0
  113. package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -1
  114. package/dist/core/scaffold/native-schema-generator.js +294 -91
  115. package/dist/core/scaffold/native-schema-generator.js.map +1 -1
  116. package/dist/core/scaffold/openapi-generator.d.ts +5 -0
  117. package/dist/core/scaffold/openapi-generator.d.ts.map +1 -1
  118. package/dist/core/scaffold/openapi-generator.js +53 -3
  119. package/dist/core/scaffold/openapi-generator.js.map +1 -1
  120. package/dist/core/scaffold/react-bff-generator.d.ts +10 -0
  121. package/dist/core/scaffold/react-bff-generator.d.ts.map +1 -0
  122. package/dist/core/scaffold/react-bff-generator.js +252 -0
  123. package/dist/core/scaffold/react-bff-generator.js.map +1 -0
  124. package/dist/core/scaffold/react-ui-generator.d.ts +6 -0
  125. package/dist/core/scaffold/react-ui-generator.d.ts.map +1 -0
  126. package/dist/core/scaffold/react-ui-generator.js +41 -0
  127. package/dist/core/scaffold/react-ui-generator.js.map +1 -0
  128. package/dist/core/scaffold/ui-generator.d.ts +0 -3
  129. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  130. package/dist/core/scaffold/ui-generator.js +68 -7
  131. package/dist/core/scaffold/ui-generator.js.map +1 -1
  132. package/dist/core/verify/index.d.ts +4 -3
  133. package/dist/core/verify/index.d.ts.map +1 -1
  134. package/dist/core/verify/index.js +122 -5
  135. package/dist/core/verify/index.js.map +1 -1
  136. package/dist/index.d.ts +3 -3
  137. package/dist/index.d.ts.map +1 -1
  138. package/dist/index.js +3 -2
  139. package/dist/index.js.map +1 -1
  140. package/dist/machine/contracts.d.ts +1 -0
  141. package/dist/machine/contracts.d.ts.map +1 -1
  142. package/dist/machine/errors.d.ts +3 -2
  143. package/dist/machine/errors.d.ts.map +1 -1
  144. package/dist/machine/errors.js +2 -1
  145. package/dist/machine/errors.js.map +1 -1
  146. package/dist/machine/index.d.ts.map +1 -1
  147. package/dist/machine/index.js +61 -30
  148. package/dist/machine/index.js.map +1 -1
  149. package/dist/mcp/index.d.ts +6 -4
  150. package/dist/mcp/index.d.ts.map +1 -1
  151. package/dist/mcp/index.js +210 -25
  152. package/dist/mcp/index.js.map +1 -1
  153. package/dist/mcp/version-selection.d.ts +55 -0
  154. package/dist/mcp/version-selection.d.ts.map +1 -0
  155. package/dist/mcp/version-selection.js +152 -0
  156. package/dist/mcp/version-selection.js.map +1 -0
  157. package/dist/types/index.d.ts +4 -0
  158. package/dist/types/index.d.ts.map +1 -1
  159. package/dist/utils/azure-cli.d.ts +56 -0
  160. package/dist/utils/azure-cli.d.ts.map +1 -0
  161. package/dist/utils/azure-cli.js +326 -0
  162. package/dist/utils/azure-cli.js.map +1 -0
  163. package/dist/utils/azure-diagnostics.d.ts +16 -0
  164. package/dist/utils/azure-diagnostics.d.ts.map +1 -0
  165. package/dist/utils/azure-diagnostics.js +68 -0
  166. package/dist/utils/azure-diagnostics.js.map +1 -0
  167. package/dist/utils/package-manager.d.ts +2 -2
  168. package/dist/utils/package-manager.d.ts.map +1 -1
  169. package/dist/utils/package-manager.js +6 -3
  170. package/dist/utils/package-manager.js.map +1 -1
  171. package/dist/utils/python-uv.d.ts +5 -7
  172. package/dist/utils/python-uv.d.ts.map +1 -1
  173. package/dist/utils/python-uv.js +50 -24
  174. package/dist/utils/python-uv.js.map +1 -1
  175. package/dist/utils/react-dev.d.ts +4 -0
  176. package/dist/utils/react-dev.d.ts.map +1 -0
  177. package/dist/utils/react-dev.js +35 -0
  178. package/dist/utils/react-dev.js.map +1 -0
  179. package/dist/utils/semver.d.ts +14 -0
  180. package/dist/utils/semver.d.ts.map +1 -0
  181. package/dist/utils/semver.js +61 -0
  182. package/dist/utils/semver.js.map +1 -0
  183. package/package.json +7 -4
  184. package/src/__tests__/__snapshots__/boundaries.test.ts.snap +154 -0
  185. package/src/__tests__/agent-instructions.test.ts +6 -1
  186. package/src/__tests__/agent-loop-phase34.test.ts +143 -8
  187. package/src/__tests__/auth.test.ts +9 -0
  188. package/src/__tests__/azure-cli.test.ts +202 -0
  189. package/src/__tests__/boundaries.test.ts +42 -0
  190. package/src/__tests__/csharp-schema.test.ts +261 -0
  191. package/src/__tests__/dev.test.ts +13 -6
  192. package/src/__tests__/fixtures/az wrapper.cmd +2 -0
  193. package/src/__tests__/fixtures/azure-cli.cjs +153 -0
  194. package/src/__tests__/fixtures/provision-runner.cjs +28 -0
  195. package/src/__tests__/generated-project-matrix.test.ts +789 -0
  196. package/src/__tests__/home-page.test.ts +50 -0
  197. package/src/__tests__/init.test.ts +145 -5
  198. package/src/__tests__/mcp-version.test.ts +262 -0
  199. package/src/__tests__/mcp.test.ts +120 -3
  200. package/src/__tests__/multi-auth.test.ts +115 -4
  201. package/src/__tests__/package-manager.test.ts +17 -2
  202. package/src/__tests__/plan-apply.test.ts +179 -0
  203. package/src/__tests__/provision-runtime.test.ts +722 -0
  204. package/src/__tests__/python-uv.test.ts +50 -14
  205. package/src/__tests__/python-venv.test.ts +48 -0
  206. package/src/__tests__/react-dev.test.ts +72 -0
  207. package/src/__tests__/react.test.ts +838 -0
  208. package/src/__tests__/release-metadata.test.ts +121 -0
  209. package/src/__tests__/schema-analysis.test.ts +329 -0
  210. package/src/cli/commands/add-auth.ts +103 -29
  211. package/src/cli/commands/configure-mcp.ts +54 -0
  212. package/src/cli/commands/dev.ts +145 -73
  213. package/src/cli/commands/init.ts +147 -172
  214. package/src/cli/commands/migrations.ts +22 -0
  215. package/src/cli/commands/provision.ts +294 -56
  216. package/src/cli/commands/scaffold.ts +145 -22
  217. package/src/cli/index.ts +46 -10
  218. package/src/core/config.ts +21 -1
  219. package/src/core/operations/create-model.ts +51 -13
  220. package/src/core/operations/migration-generator.ts +14 -1
  221. package/src/core/operations/model-migration.ts +52 -0
  222. package/src/core/operations/provision-deployment.ts +183 -0
  223. package/src/core/operations/provision-operations.ts +662 -154
  224. package/src/core/operations/scaffold-apply.ts +2 -0
  225. package/src/core/operations/scaffold-machine.ts +1 -0
  226. package/src/core/operations/scaffold-plan.ts +5 -1
  227. package/src/core/project/agent-instructions.ts +17 -1
  228. package/src/core/project/agent-templates/AGENTS.md +113 -15
  229. package/src/core/project/agent-templates/CLAUDE.md +14 -1
  230. package/src/core/project/agent-templates/bff-routes.instructions.md +5 -3
  231. package/src/core/project/agent-templates/copilot-instructions.md +15 -1
  232. package/src/core/project/agent-templates/shared-models.instructions.md +11 -3
  233. package/src/core/project/boundaries.ts +62 -20
  234. package/src/core/project/capabilities.ts +63 -6
  235. package/src/core/project/home-page.ts +114 -0
  236. package/src/core/project/manifest.ts +52 -27
  237. package/src/core/project/mcp-config.ts +84 -0
  238. package/src/core/project/react-project.ts +529 -0
  239. package/src/core/project/state.ts +70 -0
  240. package/src/core/project/validation.ts +32 -10
  241. package/src/core/project/workflows.ts +53 -7
  242. package/src/core/scaffold/auth-generator.ts +26 -27
  243. package/src/core/scaffold/model-parser.ts +413 -129
  244. package/src/core/scaffold/native-schema-generator.ts +319 -103
  245. package/src/core/scaffold/openapi-generator.ts +63 -5
  246. package/src/core/scaffold/react-bff-generator.ts +292 -0
  247. package/src/core/scaffold/react-ui-generator.ts +51 -0
  248. package/src/core/scaffold/ui-generator.ts +70 -8
  249. package/src/core/verify/index.ts +118 -6
  250. package/src/index.ts +3 -1
  251. package/src/machine/contracts.ts +1 -0
  252. package/src/machine/errors.ts +4 -2
  253. package/src/machine/index.ts +67 -36
  254. package/src/mcp/index.ts +221 -31
  255. package/src/mcp/version-selection.ts +163 -0
  256. package/src/types/index.ts +4 -0
  257. package/src/utils/azure-cli.ts +315 -0
  258. package/src/utils/azure-diagnostics.ts +75 -0
  259. package/src/utils/package-manager.ts +7 -4
  260. package/src/utils/python-uv.ts +53 -33
  261. package/src/utils/react-dev.ts +31 -0
  262. package/src/utils/semver.ts +59 -0
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Generate only the Cosmos DB container migration for a model. Used after
3
+ * `scaffold --skip-infra`, when infrastructure changes become allowed.
4
+ */
5
+
6
+ import { resolveModelPath } from "../../cli/commands/scaffold";
7
+ import { MachineCommandError } from "../../machine/errors";
8
+ import { parseModelFile } from "../scaffold/model-parser";
9
+ import { recordSessionOperations } from "../project/artifacts";
10
+ import { FileOperationSession } from "./file-session";
11
+ import { generateModelContainerMigration } from "./migration-generator";
12
+
13
+ export interface ModelMigrationResult {
14
+ model: string;
15
+ dryRun: boolean;
16
+ migration: { version: number; template: string } | null;
17
+ reason?: string;
18
+ operations: Array<{ path: string; action: string }>;
19
+ }
20
+
21
+ export async function addModelContainerMigration(
22
+ modelInput: string,
23
+ options: { dryRun?: boolean; projectRoot?: string } = {}
24
+ ): Promise<ModelMigrationResult> {
25
+ const projectRoot = options.projectRoot ?? process.cwd();
26
+ const modelInfo = await parseModelFile(resolveModelPath(modelInput));
27
+ if (modelInfo.connectorConfig) {
28
+ throw new MachineCommandError(
29
+ "connector-model-has-no-container",
30
+ `${modelInfo.name} uses connector ${modelInfo.connectorConfig.connector}; connector models do not get a Cosmos DB container.`,
31
+ undefined,
32
+ "failed"
33
+ );
34
+ }
35
+
36
+ const session = new FileOperationSession(options.dryRun ? "collect" : "commit", projectRoot);
37
+ const migration = generateModelContainerMigration(modelInfo, session);
38
+ if (!options.dryRun) {
39
+ recordSessionOperations(session.operations, {
40
+ schemaHash: modelInfo.semanticFingerprint,
41
+ sourceModel: modelInfo.name,
42
+ }, projectRoot);
43
+ }
44
+
45
+ return {
46
+ model: modelInfo.name,
47
+ dryRun: Boolean(options.dryRun),
48
+ migration: migration ? { version: migration.version, template: migration.template } : null,
49
+ ...(migration ? {} : { reason: "The container is already owned by the legacy migration baseline." }),
50
+ operations: session.operations.map((operation) => ({ path: operation.path, action: operation.action })),
51
+ };
52
+ }
@@ -0,0 +1,183 @@
1
+ import { runAzCliAsync } from "../../utils/azure-cli";
2
+ import { AzureCliDiagnostic, azureCliDiagnostic, redactAzureText } from "../../utils/azure-diagnostics";
3
+
4
+ export const DEPLOYMENT_QUERY = "{id:id,name:name,state:properties.provisioningState,correlationId:properties.correlationId,templateHash:properties.templateHash,timestamp:properties.timestamp,error:properties.error}";
5
+ export const DEFAULT_PROVISION_WAIT_SECONDS = 300;
6
+ export const MAX_PROVISION_WAIT_SECONDS = 3_600;
7
+ /** Consecutive transient state-read failures tolerated while the human CLI waits for a terminal state. */
8
+ export const UNBOUNDED_WAIT_TRANSIENT_RETRIES = 24;
9
+
10
+ export interface DeploymentReference {
11
+ subscription: string;
12
+ resourceGroup: string;
13
+ name: string;
14
+ id?: string;
15
+ correlationId?: string;
16
+ templateHash?: string;
17
+ /** Do not mistake a previous deployment under the same name for this submission. */
18
+ previousCorrelationId?: string;
19
+ }
20
+
21
+ export interface AzureDeploymentError {
22
+ code: string;
23
+ message: string;
24
+ }
25
+
26
+ export interface DeploymentObservation {
27
+ state: "Accepted" | "Running" | "Succeeded" | "Failed" | "Canceled" | "unknown";
28
+ reference: DeploymentReference;
29
+ reason?: "not-found" | "query-failed" | "invalid-response" | "identity-changed" | "not-yet-observed" | "cancelled";
30
+ errors: AzureDeploymentError[];
31
+ diagnostic: AzureCliDiagnostic;
32
+ }
33
+
34
+ function record(value: unknown): Record<string, unknown> | null {
35
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : null;
36
+ }
37
+
38
+ export function extractAzureErrors(value: unknown, secretValues: readonly string[] = []): AzureDeploymentError[] {
39
+ const result: AzureDeploymentError[] = [];
40
+ const visit = (entry: unknown, depth: number) => {
41
+ if (depth > 8 || result.length >= 20) return;
42
+ if (typeof entry === "string") {
43
+ try { visit(JSON.parse(entry), depth + 1); } catch { /* Only structured Azure errors are projected. */ }
44
+ return;
45
+ }
46
+ const item = record(entry);
47
+ if (!item) return;
48
+ if (typeof item.code === "string" && typeof item.message === "string") {
49
+ result.push({ code: redactAzureText(item.code, secretValues).slice(0, 200), message: redactAzureText(item.message, secretValues).slice(0, 2_000) });
50
+ }
51
+ if (item.error) visit(item.error, depth + 1);
52
+ if (Array.isArray(item.details)) item.details.forEach((detail) => visit(detail, depth + 1));
53
+ };
54
+ visit(value, 0);
55
+ return result;
56
+ }
57
+
58
+ export function deploymentId(reference: DeploymentReference): string {
59
+ return reference.id ?? `/subscriptions/${reference.subscription}/resourceGroups/${reference.resourceGroup}/providers/Microsoft.Resources/deployments/${reference.name}`;
60
+ }
61
+
62
+ export async function queryAzureDeployment(
63
+ reference: DeploymentReference,
64
+ options: { timeoutMs?: number; signal?: AbortSignal; secretValues?: readonly string[] } = {}
65
+ ): Promise<DeploymentObservation> {
66
+ const result = await runAzCliAsync([
67
+ "deployment", "group", "show", "--name", reference.name,
68
+ "--resource-group", reference.resourceGroup, "--subscription", reference.subscription,
69
+ "--query", DEPLOYMENT_QUERY, "-o", "json",
70
+ ], { timeout: options.timeoutMs ?? 30_000, signal: options.signal });
71
+ const diagnostic = azureCliDiagnostic(result, "deployment-state", options.secretValues);
72
+ const unknown = (reason: DeploymentObservation["reason"]): DeploymentObservation => ({
73
+ state: "unknown", reference, reason, errors: [], diagnostic,
74
+ });
75
+ if (result.status !== 0 || result.failureKind) {
76
+ if (result.failureKind === "cancelled") return unknown("cancelled");
77
+ return unknown(/DeploymentNotFound|ResourceGroupNotFound/i.test(`${result.stderr}\n${result.stdout}`) ? "not-found" : "query-failed");
78
+ }
79
+ if (result.stdoutTruncated) return unknown("invalid-response");
80
+ let parsed: Record<string, unknown> | null;
81
+ try { parsed = record(JSON.parse(result.stdout)); } catch { return unknown("invalid-response"); }
82
+ if (!parsed || typeof parsed.id !== "string" || typeof parsed.state !== "string" || typeof parsed.correlationId !== "string") {
83
+ return unknown("invalid-response");
84
+ }
85
+ if (parsed.id.toLowerCase() !== deploymentId(reference).toLowerCase()
86
+ || (reference.correlationId && parsed.correlationId !== reference.correlationId)
87
+ || (reference.templateHash && parsed.templateHash !== reference.templateHash)) {
88
+ return unknown("identity-changed");
89
+ }
90
+ if (reference.previousCorrelationId && parsed.correlationId === reference.previousCorrelationId) {
91
+ return unknown("not-yet-observed");
92
+ }
93
+ const states: Record<string, DeploymentObservation["state"]> = {
94
+ accepted: "Accepted", running: "Running", creating: "Running", updating: "Running",
95
+ succeeded: "Succeeded", failed: "Failed", canceled: "Canceled", cancelled: "Canceled",
96
+ };
97
+ const state = states[parsed.state.toLowerCase()];
98
+ if (!state) return unknown("invalid-response");
99
+ return {
100
+ state,
101
+ reference: {
102
+ ...reference,
103
+ id: parsed.id,
104
+ correlationId: parsed.correlationId,
105
+ ...(typeof parsed.templateHash === "string" ? { templateHash: parsed.templateHash } : {}),
106
+ },
107
+ errors: extractAzureErrors(parsed.error, options.secretValues),
108
+ diagnostic,
109
+ };
110
+ }
111
+
112
+ export async function waitForAzureDeployment(
113
+ reference: DeploymentReference,
114
+ options: {
115
+ deadline: number;
116
+ signal?: AbortSignal;
117
+ pollIntervalMs?: number;
118
+ onObservation?: (observation: DeploymentObservation) => void;
119
+ secretValues?: readonly string[];
120
+ queryTimeoutMs?: number;
121
+ /**
122
+ * Consecutive non-terminal "unknown" reads (query failure, not yet visible) to keep polling through.
123
+ * Required for an unbounded deadline so a missing deployment cannot be polled forever.
124
+ */
125
+ transientRetries?: number;
126
+ }
127
+ ): Promise<DeploymentObservation> {
128
+ let observation: DeploymentObservation;
129
+ let unknownReads = 0;
130
+ do {
131
+ const remaining = options.deadline - Date.now();
132
+ // Even when the wait budget is exhausted, one bounded read can identify an accepted submission.
133
+ observation = await queryAzureDeployment(reference, {
134
+ timeoutMs: remaining > 0 ? Math.min(options.queryTimeoutMs ?? 30_000, remaining) : options.queryTimeoutMs ?? 30_000,
135
+ signal: options.signal,
136
+ secretValues: options.secretValues,
137
+ });
138
+ if (observation.state !== "unknown") reference = observation.reference;
139
+ options.onObservation?.(observation);
140
+ if (observation.state === "Succeeded" || observation.state === "Failed" || observation.state === "Canceled") return observation;
141
+ if (observation.state === "unknown") {
142
+ unknownReads += 1;
143
+ const retryable = observation.reason === "not-yet-observed" || observation.reason === "not-found"
144
+ || (options.transientRetries !== undefined && (observation.reason === "query-failed" || observation.reason === "invalid-response"));
145
+ if (!retryable) return observation;
146
+ if (!Number.isFinite(options.deadline) && unknownReads > (options.transientRetries ?? 0)) return observation;
147
+ } else {
148
+ unknownReads = 0;
149
+ }
150
+ if (Date.now() >= options.deadline || options.signal?.aborted) return observation;
151
+ await new Promise<void>((resolve) => {
152
+ const done = () => {
153
+ clearTimeout(timer);
154
+ options.signal?.removeEventListener("abort", done);
155
+ resolve();
156
+ };
157
+ const timer = setTimeout(done, Math.min(options.pollIntervalMs ?? 5_000, Math.max(options.deadline - Date.now(), 0)));
158
+ options.signal?.addEventListener("abort", done, { once: true });
159
+ if (options.signal?.aborted) done();
160
+ });
161
+ } while (!options.signal?.aborted);
162
+ return observation;
163
+ }
164
+
165
+ export async function queryDeploymentOperationErrors(reference: DeploymentReference, secretValues: readonly string[] = [], signal?: AbortSignal): Promise<{
166
+ errors: AzureDeploymentError[];
167
+ diagnostic: AzureCliDiagnostic;
168
+ }> {
169
+ const result = await runAzCliAsync([
170
+ "deployment", "operation", "group", "list",
171
+ "--resource-group", reference.resourceGroup, "--name", reference.name, "--subscription", reference.subscription,
172
+ "--query", "[?properties.provisioningState=='Failed' || properties.provisioningState=='Canceled'].{error:properties.statusMessage}",
173
+ "-o", "json",
174
+ ], { timeout: 30_000, signal });
175
+ const errors: AzureDeploymentError[] = [];
176
+ if (result.status === 0 && !result.failureKind && !result.stdoutTruncated) {
177
+ try {
178
+ const parsed: unknown = JSON.parse(result.stdout);
179
+ if (Array.isArray(parsed)) parsed.slice(0, 20).forEach((item) => errors.push(...extractAzureErrors(item, secretValues)));
180
+ } catch { /* The separate diagnostic explicitly records an unreadable operations response. */ }
181
+ }
182
+ return { errors: errors.slice(0, 20), diagnostic: azureCliDiagnostic(result, "deployment-operations", secretValues) };
183
+ }