@h-ai/deploy 1.0.0-beta.22 → 1.0.0-beta.24

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.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { createHash, randomUUID, randomBytes, X509Certificate, createPublicKey, createPrivateKey } from 'crypto';
2
- import { createReadStream, readFileSync, existsSync, lstatSync, mkdirSync, readdirSync, realpathSync, openSync, constants, fstatSync, readSync, closeSync, fchmodSync, writeFileSync, fsyncSync, renameSync, unlinkSync, chmodSync, mkdtempSync, copyFileSync, rmSync, createWriteStream, statSync } from 'fs';
2
+ import { createReadStream, readFileSync, existsSync, lstatSync, mkdirSync, readdirSync, openSync, constants, fstatSync, readSync, closeSync, fchmodSync, writeFileSync, fsyncSync, renameSync, unlinkSync, chmodSync, mkdtempSync, copyFileSync, rmSync, realpathSync, createWriteStream, statSync } from 'fs';
3
3
  import { resolve, extname, join, dirname, basename, relative, sep } from 'path';
4
4
  import { z } from 'zod';
5
5
  import { core, err, ok, CoreConfigSchema, RequestAccessConfigSchema, TrustedProxiesSchema } from '@h-ai/core';
6
- import { spawn, execFileSync } from 'child_process';
6
+ import { execFileSync, spawn } from 'child_process';
7
7
  import process from 'process';
8
8
  import { LineCounter, parseDocument, parse, stringify, parseAllDocuments } from 'yaml';
9
9
  import { Buffer, constants as constants$1 } from 'buffer';
@@ -22,17 +22,14 @@ import { isIP } from 'net';
22
22
  // src/deploy-artifact.ts
23
23
  var DeploymentArtifactSchema = z.object({
24
24
  /** 清单格式版本,与应用版本和部署环境无关。 */
25
- version: z.union([
26
- z.literal(4),
27
- z.literal(5)
28
- ]),
25
+ version: z.literal(5),
29
26
  /** 主运行服务:独立后端为 serv,独立浏览器站点(静态或 SSR)为 web。 */
30
27
  primaryService: z.enum(["serv", "web"]),
31
28
  /** 应用根 package.json 的版本,用于归档、Compose 与桌面交付;最长 80 字符。 */
32
29
  appVersion: z.string().max(80).regex(/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9a-z-]+(?:\.[0-9a-z-]+)*)?(?:\+[0-9a-z-]+(?:\.[0-9a-z-]+)*)?$/i),
33
30
  /** 本次构建标识;同一应用版本允许重复构建。 */
34
31
  buildId: z.string().max(128).regex(/^[a-z0-9][\w.+-]*$/i),
35
- /** 构建启动时观察到的 Git 来源;历史清单可以省略。 */
32
+ /** 构建启动时观察到的 Git 来源。 */
36
33
  source: z.object({
37
34
  /** 工程 package.json 所在仓库的提交 SHA;无法确定时为 unknown。 */
38
35
  commit: z.string().regex(/^(?:[a-f0-9]{40}|[a-f0-9]{64}|unknown)$/),
@@ -43,8 +40,8 @@ var DeploymentArtifactSchema = z.object({
43
40
  partial: z.boolean().default(false),
44
41
  /** 是否包含浏览器端;仅构建 serv 时也保留完整工程的 Web 拓扑。 */
45
42
  hasWeb: z.boolean().default(false),
46
- /** Web构建的公开挂载目录,无末尾斜杠;省略或空字符串保持域名根目录。增量制品须保留完整拓扑的同一值。 */
47
- webBasePath: z.string().refine((value) => value === "" || /^\/[\w.-]+(?:\/[\w.-]+)*$/.test(value) && !value.split("/").some((segment) => segment === "." || segment === "..")).optional(),
43
+ /** Web 构建的公开挂载目录,无末尾斜杠;空字符串表示域名根目录。增量制品须保留完整拓扑的同一值。 */
44
+ webBasePath: z.string().refine((value) => value === "" || /^\/[\w.-]+(?:\/[\w.-]+)*$/.test(value) && !value.split("/").some((segment) => segment === "." || segment === "..")),
48
45
  /** 工程显式配置的后端端口,部分构建未包含 serv 时仍保留。 */
49
46
  servContainerPort: z.number().int().min(1).max(65535).optional(),
50
47
  /** 工程显式配置的 Web 端口,部分构建未包含 web 镜像时仍保留;缺省使用部署默认值。 */
@@ -59,12 +56,12 @@ var DeploymentArtifactSchema = z.object({
59
56
  imageRef: z.string().regex(/^[a-z0-9][a-z0-9./:_-]*:[a-z0-9_][a-z0-9_.-]*$/).refine((value) => !value.endsWith(":latest")),
60
57
  /** tar 内容的 SHA-256,用于部署前校验。 */
61
58
  sha256: z.string().regex(/^[a-f0-9]{64}$/),
62
- /** 引擎实际构建的镜像 OS/架构;历史清单可以省略。 */
59
+ /** 引擎实际构建的镜像 OS/架构。 */
63
60
  platform: z.string().regex(/^[a-z0-9][a-z0-9_.-]*\/[a-z0-9][a-z0-9_.-]*$/).optional(),
64
61
  /** 容器内部监听端口;服务器公开端口由部署选择。 */
65
62
  containerPort: z.number().int().min(1).max(65535).optional()
66
63
  })).min(1).max(2).refine((images) => new Set(images.map((image) => image.name)).size === images.length)
67
- }).refine((artifact) => artifact.version === 5 ? !!artifact.webBasePath : !artifact.webBasePath).refine((artifact) => !artifact.webBasePath || artifact.hasWeb).refine((artifact) => artifact.images.every((image) => image.file === `${image.name}-${artifact.appVersion}.tar`)).refine((artifact) => artifact.primaryService !== "web" || artifact.hasWeb).refine((artifact) => artifact.images.every((image) => image.name === artifact.primaryService || image.name === "web" && artifact.hasWeb)).refine((artifact) => artifact.partial || artifact.images.some((image) => image.name === artifact.primaryService)).refine((artifact) => artifact.partial || !artifact.hasWeb || artifact.images.some((image) => image.name === "web"));
64
+ }).refine((artifact) => !artifact.webBasePath || artifact.hasWeb).refine((artifact) => artifact.images.every((image) => image.file === `${image.name}-${artifact.appVersion}.tar`)).refine((artifact) => artifact.primaryService !== "web" || artifact.hasWeb).refine((artifact) => artifact.images.every((image) => image.name === artifact.primaryService || image.name === "web" && artifact.hasWeb)).refine((artifact) => artifact.partial || artifact.images.some((image) => image.name === artifact.primaryService)).refine((artifact) => artifact.partial || !artifact.hasWeb || artifact.images.some((image) => image.name === "web"));
68
65
  async function artifactChecksum(file) {
69
66
  const hash = createHash("sha256");
70
67
  for await (const chunk of createReadStream(file))
@@ -189,9 +186,7 @@ var en_US_default = {
189
186
  deploy_uploadPasswordCancelled: "Password input cancelled; no server connection was made.",
190
187
  deploy_uploadPasswordInvalid: "SSH passwords must contain 1\u20131023 UTF-8 bytes without line breaks.",
191
188
  deploy_managedSecretsInvalid: "Invalid generated middleware credential candidates. Rebuild the deployment bundle; server credentials will not be rotated automatically.",
192
- deploy_secretImportFailed: "Legacy credential import was rejected during {phase}; check private input permissions, project/volume identity, live credentials and the deployment lock. No services were updated; inspect the protected state file if commit is unconfirmed",
193
189
  deploy_projectNameRequired: "The root package.json.name is missing or cannot be normalized into an image and deployment resource identity.",
194
- deploy_projectNameOverrideRemoved: "Deployment identity comes from the root package.json.name; remove the obsolete projectName override.",
195
190
  deploy_requestAccessInvalid: "Invalid request access policy ({field}). Check fields, IP addresses, timezones and time windows. For a file reference, use readable YAML no larger than 1 MiB. Fix the configuration and create a new bundle.",
196
191
  deploy_httpsPortsInvalid: "HTTP and HTTPS ports must differ; Let\u2019s Encrypt HTTP-01 requires host port 80.",
197
192
  deploy_httpsCertificateInvalid: "Invalid HTTPS certificate. Check readable PEM files, matching unencrypted private key, validity dates and domain, then repackage.",
@@ -334,9 +329,7 @@ var ja_JP_default = {
334
329
  deploy_uploadPasswordCancelled: "\u30D1\u30B9\u30EF\u30FC\u30C9\u5165\u529B\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F\u3002\u30B5\u30FC\u30D0\u30FC\u306B\u306F\u672A\u63A5\u7D9A\u3067\u3059\u3002",
335
330
  deploy_uploadPasswordInvalid: "SSH \u30D1\u30B9\u30EF\u30FC\u30C9\u306F\u6539\u884C\u3092\u542B\u307E\u306A\u3044 1\u20131023 UTF-8 \u30D0\u30A4\u30C8\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
336
331
  deploy_managedSecretsInvalid: "\u81EA\u5EFA\u30DF\u30C9\u30EB\u30A6\u30A7\u30A2\u306E\u5019\u88DC\u8A8D\u8A3C\u60C5\u5831\u304C\u7121\u52B9\u3067\u3059\u3002\u914D\u7F6E\u30D1\u30C3\u30B1\u30FC\u30B8\u3092\u518D\u751F\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u30B5\u30FC\u30D0\u30FC\u306E\u8A8D\u8A3C\u60C5\u5831\u306F\u81EA\u52D5\u5909\u66F4\u3055\u308C\u307E\u305B\u3093\u3002",
337
- deploy_secretImportFailed: "\u65E7\u8A8D\u8A3C\u60C5\u5831\u306E\u30A4\u30F3\u30DD\u30FC\u30C8\u306F {phase} \u6BB5\u968E\u3067\u62D2\u5426\u3055\u308C\u307E\u3057\u305F\u3002\u5165\u529B\u6A29\u9650\u3001\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3068\u30DC\u30EA\u30E5\u30FC\u30E0\u3001\u5B9F\u969B\u306E\u8A8D\u8A3C\u60C5\u5831\u3001\u30C7\u30D7\u30ED\u30A4\u30ED\u30C3\u30AF\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u30B5\u30FC\u30D3\u30B9\u306F\u66F4\u65B0\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u78BA\u5B9A\u304C\u4E0D\u660E\u306A\u5834\u5408\u306F\u4FDD\u8B77\u3055\u308C\u305F\u72B6\u614B\u30D5\u30A1\u30A4\u30EB\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044",
338
332
  deploy_projectNameRequired: "\u5DE5\u7A0B\u30EB\u30FC\u30C8\u306E package.json.name \u304C\u306A\u3044\u304B\u6B63\u898F\u5316\u3067\u304D\u306A\u3044\u305F\u3081\u3001\u30A4\u30E1\u30FC\u30B8\u3068\u30C7\u30D7\u30ED\u30A4\u30EA\u30BD\u30FC\u30B9\u306E\u8B58\u5225\u540D\u3092\u751F\u6210\u3067\u304D\u307E\u305B\u3093\u3002",
339
- deploy_projectNameOverrideRemoved: "\u30C7\u30D7\u30ED\u30A4\u8B58\u5225\u540D\u306F\u5DE5\u7A0B\u30EB\u30FC\u30C8\u306E package.json.name \u304B\u3089\u53D6\u5F97\u3057\u307E\u3059\u3002\u65E7 projectName \u4E0A\u66F8\u304D\u5F15\u6570\u3092\u524A\u9664\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
340
333
  deploy_requestAccessInvalid: "\u30EA\u30AF\u30A8\u30B9\u30C8\u30A2\u30AF\u30BB\u30B9\u8A2D\u5B9A\uFF08{field}\uFF09\u304C\u7121\u52B9\u3067\u3059\u3002\u30D5\u30A3\u30FC\u30EB\u30C9\u3001IP\u3001\u30BF\u30A4\u30E0\u30BE\u30FC\u30F3\u3001\u6642\u9593\u5E2F\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u30D5\u30A1\u30A4\u30EB\u306F\u8AAD\u307F\u53D6\u308A\u53EF\u80FD\u306A 1 MiB \u4EE5\u4E0B\u306E YAML \u3092\u6307\u5B9A\u3057\u3001\u4FEE\u6B63\u5F8C\u306B\u518D\u30D1\u30C3\u30B1\u30FC\u30B8\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
341
334
  deploy_httpsPortsInvalid: "HTTP \u3068 HTTPS \u306E\u30DD\u30FC\u30C8\u306F\u7570\u306A\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002Let\u2019s Encrypt HTTP-01 \u306B\u306F\u30DB\u30B9\u30C8\u306E\u30DD\u30FC\u30C8 80 \u304C\u5FC5\u8981\u3067\u3059\u3002",
342
335
  deploy_httpsCertificateInvalid: "HTTPS \u8A3C\u660E\u66F8\u304C\u7121\u52B9\u3067\u3059\u3002PEM \u30D5\u30A1\u30A4\u30EB\u3001\u6697\u53F7\u5316\u3055\u308C\u3066\u3044\u306A\u3044\u79D8\u5BC6\u9375\u3068\u306E\u4E00\u81F4\u3001\u6709\u52B9\u671F\u9593\u3001\u30C9\u30E1\u30A4\u30F3\u3092\u78BA\u8A8D\u3057\u3001\u518D\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
@@ -479,9 +472,7 @@ var zh_CN_default = {
479
472
  deploy_uploadPasswordCancelled: "\u5DF2\u53D6\u6D88\u5BC6\u7801\u8F93\u5165\uFF0C\u5C1A\u672A\u8FDE\u63A5\u670D\u52A1\u5668\u3002",
480
473
  deploy_uploadPasswordInvalid: "SSH \u5BC6\u7801\u987B\u4E3A 1\u20131023 \u4E2A UTF-8 \u5B57\u8282\uFF0C\u4E14\u4E0D\u80FD\u5305\u542B\u6362\u884C\u3002",
481
474
  deploy_managedSecretsInvalid: "\u81EA\u5EFA\u4E2D\u95F4\u4EF6\u5019\u9009\u51ED\u636E\u65E0\u6548\uFF0C\u8BF7\u91CD\u65B0\u751F\u6210\u90E8\u7F72\u5305\uFF1B\u670D\u52A1\u5668\u51ED\u636E\u4E0D\u4F1A\u81EA\u52A8\u66F4\u6362\u3002",
482
- deploy_secretImportFailed: "\u65E7\u51ED\u636E\u5BFC\u5165\u5728 {phase} \u9636\u6BB5\u88AB\u62D2\u7EDD\uFF1B\u8BF7\u6838\u5BF9\u79C1\u6709\u8F93\u5165\u6743\u9650\u3001\u9879\u76EE/\u5377\u8EAB\u4EFD\u3001\u5B9E\u9645\u670D\u52A1\u51ED\u636E\u53CA\u90E8\u7F72\u9501\uFF0C\u672A\u6267\u884C\u670D\u52A1\u66F4\u65B0\uFF1B\u63D0\u4EA4\u7ED3\u679C\u4E0D\u660E\u65F6\u8BF7\u6838\u5BF9\u53D7\u4FDD\u62A4\u7684\u72B6\u6001\u6587\u4EF6",
483
475
  deploy_projectNameRequired: "\u5DE5\u7A0B\u6839 package.json.name \u7F3A\u5931\u6216\u65E0\u6CD5\u5F52\u4E00\u5316\uFF0C\u4E0D\u80FD\u751F\u6210\u955C\u50CF\u4E0E\u90E8\u7F72\u8D44\u6E90\u8EAB\u4EFD\u3002",
484
- deploy_projectNameOverrideRemoved: "\u90E8\u7F72\u9879\u76EE\u540D\u7EDF\u4E00\u6765\u81EA\u5DE5\u7A0B\u6839 package.json.name\uFF1B\u8BF7\u79FB\u9664\u65E7 projectName \u8986\u76D6\u53C2\u6570\u3002",
485
476
  deploy_requestAccessInvalid: "\u8BF7\u6C42\u8BBF\u95EE\u7B56\u7565\u914D\u7F6E\u65E0\u6548\uFF08{field}\uFF09\u3002\u8BF7\u68C0\u67E5\u5B57\u6BB5\u3001IP\u3001\u65F6\u533A\u53CA\u65F6\u95F4\u7A97\u53E3\uFF1B\u5982\u5F15\u7528\u6587\u4EF6\uFF0C\u8BF7\u786E\u8BA4\u6587\u4EF6\u53EF\u8BFB\u3001\u683C\u5F0F\u4E3A YAML \u4E14\u4E0D\u8D85\u8FC7 1 MiB\u3002\u4FEE\u6B63\u540E\u91CD\u65B0\u6253\u5305\u3002",
486
477
  deploy_httpsPortsInvalid: "HTTP \u4E0E HTTPS \u7AEF\u53E3\u5FC5\u987B\u4E0D\u540C\uFF1BLet\u2019s Encrypt \u7684 HTTP-01 \u5165\u53E3\u5FC5\u987B\u4F7F\u7528\u5BBF\u4E3B\u673A 80 \u7AEF\u53E3\u3002",
487
478
  deploy_httpsCertificateInvalid: "HTTPS \u8BC1\u4E66\u65E0\u6548\uFF1A\u8BF7\u6838\u5BF9 PEM \u6587\u4EF6\u53EF\u8BFB\u3001\u8BC1\u4E66\u4E0E\u672A\u52A0\u5BC6\u79C1\u94A5\u5339\u914D\u3001\u8BC1\u4E66\u6709\u6548\u671F\u53CA\u57DF\u540D\uFF0C\u7136\u540E\u91CD\u65B0\u6253\u5305\u3002",
@@ -2263,6 +2254,320 @@ var deploy_config_shapes_default = {
2263
2254
  pattern: "^[A-Z_]\\w*$"
2264
2255
  }
2265
2256
  },
2257
+ environmentIds: {
2258
+ default: [],
2259
+ maxItems: 64,
2260
+ type: "array",
2261
+ items: {
2262
+ type: "string",
2263
+ pattern: "^[a-z0-9][\\w-]{0,95}$"
2264
+ }
2265
+ },
2266
+ allowedCapabilities: {
2267
+ default: [],
2268
+ maxItems: 64,
2269
+ type: "array",
2270
+ items: {
2271
+ anyOf: [
2272
+ {
2273
+ type: "string",
2274
+ pattern: "^[a-z0-9][\\w-]{0,95}$"
2275
+ },
2276
+ {
2277
+ type: "string",
2278
+ pattern: "^[\\w.-]{1,96}$"
2279
+ }
2280
+ ]
2281
+ }
2282
+ },
2283
+ defaults: {
2284
+ type: "object",
2285
+ properties: {
2286
+ model: {
2287
+ type: "object",
2288
+ properties: {
2289
+ id: {
2290
+ type: "string",
2291
+ minLength: 1,
2292
+ maxLength: 100,
2293
+ pattern: "^[\\w.-]+$"
2294
+ },
2295
+ reasoningEffort: {
2296
+ type: "string",
2297
+ minLength: 1,
2298
+ maxLength: 100,
2299
+ pattern: "^[\\w.-]+$"
2300
+ },
2301
+ temperature: {
2302
+ type: "number",
2303
+ minimum: 0,
2304
+ maximum: 2
2305
+ },
2306
+ maxOutputTokens: {
2307
+ type: "integer",
2308
+ exclusiveMinimum: 0,
2309
+ maximum: 9007199254740991
2310
+ }
2311
+ },
2312
+ additionalProperties: false
2313
+ },
2314
+ execution: {
2315
+ type: "object",
2316
+ properties: {
2317
+ mode: {
2318
+ type: "string",
2319
+ enum: [
2320
+ "auto",
2321
+ "shared",
2322
+ "user",
2323
+ "task"
2324
+ ]
2325
+ },
2326
+ environmentId: {
2327
+ type: "string",
2328
+ minLength: 1,
2329
+ maxLength: 100,
2330
+ pattern: "^[\\w.-]+$"
2331
+ },
2332
+ resources: {
2333
+ type: "object",
2334
+ properties: {
2335
+ cpu: {
2336
+ type: "number",
2337
+ exclusiveMinimum: 0,
2338
+ maximum: 1024
2339
+ },
2340
+ memoryMiB: {
2341
+ type: "integer",
2342
+ minimum: 16,
2343
+ maximum: 9007199254740991
2344
+ }
2345
+ },
2346
+ additionalProperties: false
2347
+ }
2348
+ },
2349
+ additionalProperties: false
2350
+ },
2351
+ workspaceId: {
2352
+ type: "string",
2353
+ minLength: 1,
2354
+ maxLength: 100,
2355
+ pattern: "^[\\w.-]+$"
2356
+ },
2357
+ context: {
2358
+ type: "object",
2359
+ properties: {
2360
+ fileIds: {
2361
+ maxItems: 64,
2362
+ type: "array",
2363
+ items: {
2364
+ type: "string",
2365
+ minLength: 1,
2366
+ maxLength: 100,
2367
+ pattern: "^[\\w.-]+$"
2368
+ }
2369
+ },
2370
+ knowledgeBaseIds: {
2371
+ maxItems: 64,
2372
+ type: "array",
2373
+ items: {
2374
+ type: "string",
2375
+ minLength: 1,
2376
+ maxLength: 100,
2377
+ pattern: "^[\\w.-]+$"
2378
+ }
2379
+ },
2380
+ skillIds: {
2381
+ maxItems: 64,
2382
+ type: "array",
2383
+ items: {
2384
+ type: "string",
2385
+ minLength: 1,
2386
+ maxLength: 100,
2387
+ pattern: "^[\\w.-]+$"
2388
+ }
2389
+ },
2390
+ memory: {
2391
+ type: "object",
2392
+ properties: {
2393
+ scope: {
2394
+ type: "string",
2395
+ enum: [
2396
+ "none",
2397
+ "user",
2398
+ "space"
2399
+ ]
2400
+ },
2401
+ spaceId: {
2402
+ type: "string",
2403
+ minLength: 1,
2404
+ maxLength: 100,
2405
+ pattern: "^[\\w.-]+$"
2406
+ },
2407
+ write: {
2408
+ type: "string",
2409
+ enum: [
2410
+ "none",
2411
+ "confirm",
2412
+ "on-success"
2413
+ ]
2414
+ }
2415
+ },
2416
+ required: [
2417
+ "scope"
2418
+ ],
2419
+ additionalProperties: false
2420
+ }
2421
+ },
2422
+ additionalProperties: false
2423
+ },
2424
+ tools: {
2425
+ type: "object",
2426
+ properties: {
2427
+ allow: {
2428
+ maxItems: 64,
2429
+ type: "array",
2430
+ items: {
2431
+ type: "string",
2432
+ minLength: 1,
2433
+ maxLength: 100,
2434
+ pattern: "^[\\w.-]+$"
2435
+ }
2436
+ }
2437
+ },
2438
+ additionalProperties: false
2439
+ },
2440
+ capabilities: {
2441
+ maxItems: 64,
2442
+ type: "array",
2443
+ items: {
2444
+ type: "string",
2445
+ minLength: 1,
2446
+ maxLength: 100,
2447
+ pattern: "^[\\w.-]+$"
2448
+ }
2449
+ },
2450
+ permissions: {
2451
+ type: "object",
2452
+ properties: {
2453
+ profile: {
2454
+ type: "string",
2455
+ enum: [
2456
+ "read-only",
2457
+ "workspace",
2458
+ "full-access"
2459
+ ]
2460
+ },
2461
+ approval: {
2462
+ type: "string",
2463
+ enum: [
2464
+ "on-request",
2465
+ "never"
2466
+ ]
2467
+ },
2468
+ ruleIds: {
2469
+ maxItems: 64,
2470
+ type: "array",
2471
+ items: {
2472
+ type: "string",
2473
+ minLength: 1,
2474
+ maxLength: 100,
2475
+ pattern: "^[\\w.-]+$"
2476
+ }
2477
+ }
2478
+ },
2479
+ additionalProperties: false
2480
+ },
2481
+ limits: {
2482
+ type: "object",
2483
+ properties: {
2484
+ queueTimeoutMs: {
2485
+ type: "integer",
2486
+ minimum: 1,
2487
+ maximum: 864e5
2488
+ },
2489
+ runTimeoutMs: {
2490
+ type: "integer",
2491
+ minimum: 1,
2492
+ maximum: 864e5
2493
+ },
2494
+ approvalTimeoutMs: {
2495
+ type: "integer",
2496
+ minimum: 1,
2497
+ maximum: 864e5
2498
+ },
2499
+ maxOutputBytes: {
2500
+ type: "integer",
2501
+ minimum: 1,
2502
+ maximum: 8388608
2503
+ }
2504
+ },
2505
+ additionalProperties: false
2506
+ },
2507
+ output: {
2508
+ type: "object",
2509
+ properties: {
2510
+ format: {
2511
+ type: "string",
2512
+ enum: [
2513
+ "text",
2514
+ "json"
2515
+ ]
2516
+ },
2517
+ schema: {
2518
+ type: "object",
2519
+ propertyNames: {
2520
+ type: "string"
2521
+ },
2522
+ additionalProperties: {
2523
+ $ref: "#/$defs/__schema0"
2524
+ }
2525
+ }
2526
+ },
2527
+ required: [
2528
+ "format"
2529
+ ],
2530
+ additionalProperties: false
2531
+ },
2532
+ native: {
2533
+ type: "object",
2534
+ properties: {
2535
+ adapter: {
2536
+ type: "string",
2537
+ minLength: 1,
2538
+ maxLength: 100,
2539
+ pattern: "^[\\w.-]+$"
2540
+ },
2541
+ options: {
2542
+ type: "object",
2543
+ propertyNames: {
2544
+ type: "string"
2545
+ },
2546
+ additionalProperties: {
2547
+ $ref: "#/$defs/__schema1"
2548
+ }
2549
+ }
2550
+ },
2551
+ required: [
2552
+ "adapter",
2553
+ "options"
2554
+ ],
2555
+ additionalProperties: false
2556
+ },
2557
+ metadata: {
2558
+ type: "object",
2559
+ propertyNames: {
2560
+ type: "string",
2561
+ pattern: "^[\\w.-]{1,48}$"
2562
+ },
2563
+ additionalProperties: {
2564
+ type: "string",
2565
+ maxLength: 128
2566
+ }
2567
+ }
2568
+ },
2569
+ additionalProperties: false
2570
+ },
2266
2571
  mode: {
2267
2572
  type: "string",
2268
2573
  const: "custom-service"
@@ -2354,6 +2659,320 @@ var deploy_config_shapes_default = {
2354
2659
  pattern: "^[A-Z_]\\w*$"
2355
2660
  }
2356
2661
  },
2662
+ environmentIds: {
2663
+ default: [],
2664
+ maxItems: 64,
2665
+ type: "array",
2666
+ items: {
2667
+ type: "string",
2668
+ pattern: "^[a-z0-9][\\w-]{0,95}$"
2669
+ }
2670
+ },
2671
+ allowedCapabilities: {
2672
+ default: [],
2673
+ maxItems: 64,
2674
+ type: "array",
2675
+ items: {
2676
+ anyOf: [
2677
+ {
2678
+ type: "string",
2679
+ pattern: "^[a-z0-9][\\w-]{0,95}$"
2680
+ },
2681
+ {
2682
+ type: "string",
2683
+ pattern: "^[\\w.-]{1,96}$"
2684
+ }
2685
+ ]
2686
+ }
2687
+ },
2688
+ defaults: {
2689
+ type: "object",
2690
+ properties: {
2691
+ model: {
2692
+ type: "object",
2693
+ properties: {
2694
+ id: {
2695
+ type: "string",
2696
+ minLength: 1,
2697
+ maxLength: 100,
2698
+ pattern: "^[\\w.-]+$"
2699
+ },
2700
+ reasoningEffort: {
2701
+ type: "string",
2702
+ minLength: 1,
2703
+ maxLength: 100,
2704
+ pattern: "^[\\w.-]+$"
2705
+ },
2706
+ temperature: {
2707
+ type: "number",
2708
+ minimum: 0,
2709
+ maximum: 2
2710
+ },
2711
+ maxOutputTokens: {
2712
+ type: "integer",
2713
+ exclusiveMinimum: 0,
2714
+ maximum: 9007199254740991
2715
+ }
2716
+ },
2717
+ additionalProperties: false
2718
+ },
2719
+ execution: {
2720
+ type: "object",
2721
+ properties: {
2722
+ mode: {
2723
+ type: "string",
2724
+ enum: [
2725
+ "auto",
2726
+ "shared",
2727
+ "user",
2728
+ "task"
2729
+ ]
2730
+ },
2731
+ environmentId: {
2732
+ type: "string",
2733
+ minLength: 1,
2734
+ maxLength: 100,
2735
+ pattern: "^[\\w.-]+$"
2736
+ },
2737
+ resources: {
2738
+ type: "object",
2739
+ properties: {
2740
+ cpu: {
2741
+ type: "number",
2742
+ exclusiveMinimum: 0,
2743
+ maximum: 1024
2744
+ },
2745
+ memoryMiB: {
2746
+ type: "integer",
2747
+ minimum: 16,
2748
+ maximum: 9007199254740991
2749
+ }
2750
+ },
2751
+ additionalProperties: false
2752
+ }
2753
+ },
2754
+ additionalProperties: false
2755
+ },
2756
+ workspaceId: {
2757
+ type: "string",
2758
+ minLength: 1,
2759
+ maxLength: 100,
2760
+ pattern: "^[\\w.-]+$"
2761
+ },
2762
+ context: {
2763
+ type: "object",
2764
+ properties: {
2765
+ fileIds: {
2766
+ maxItems: 64,
2767
+ type: "array",
2768
+ items: {
2769
+ type: "string",
2770
+ minLength: 1,
2771
+ maxLength: 100,
2772
+ pattern: "^[\\w.-]+$"
2773
+ }
2774
+ },
2775
+ knowledgeBaseIds: {
2776
+ maxItems: 64,
2777
+ type: "array",
2778
+ items: {
2779
+ type: "string",
2780
+ minLength: 1,
2781
+ maxLength: 100,
2782
+ pattern: "^[\\w.-]+$"
2783
+ }
2784
+ },
2785
+ skillIds: {
2786
+ maxItems: 64,
2787
+ type: "array",
2788
+ items: {
2789
+ type: "string",
2790
+ minLength: 1,
2791
+ maxLength: 100,
2792
+ pattern: "^[\\w.-]+$"
2793
+ }
2794
+ },
2795
+ memory: {
2796
+ type: "object",
2797
+ properties: {
2798
+ scope: {
2799
+ type: "string",
2800
+ enum: [
2801
+ "none",
2802
+ "user",
2803
+ "space"
2804
+ ]
2805
+ },
2806
+ spaceId: {
2807
+ type: "string",
2808
+ minLength: 1,
2809
+ maxLength: 100,
2810
+ pattern: "^[\\w.-]+$"
2811
+ },
2812
+ write: {
2813
+ type: "string",
2814
+ enum: [
2815
+ "none",
2816
+ "confirm",
2817
+ "on-success"
2818
+ ]
2819
+ }
2820
+ },
2821
+ required: [
2822
+ "scope"
2823
+ ],
2824
+ additionalProperties: false
2825
+ }
2826
+ },
2827
+ additionalProperties: false
2828
+ },
2829
+ tools: {
2830
+ type: "object",
2831
+ properties: {
2832
+ allow: {
2833
+ maxItems: 64,
2834
+ type: "array",
2835
+ items: {
2836
+ type: "string",
2837
+ minLength: 1,
2838
+ maxLength: 100,
2839
+ pattern: "^[\\w.-]+$"
2840
+ }
2841
+ }
2842
+ },
2843
+ additionalProperties: false
2844
+ },
2845
+ capabilities: {
2846
+ maxItems: 64,
2847
+ type: "array",
2848
+ items: {
2849
+ type: "string",
2850
+ minLength: 1,
2851
+ maxLength: 100,
2852
+ pattern: "^[\\w.-]+$"
2853
+ }
2854
+ },
2855
+ permissions: {
2856
+ type: "object",
2857
+ properties: {
2858
+ profile: {
2859
+ type: "string",
2860
+ enum: [
2861
+ "read-only",
2862
+ "workspace",
2863
+ "full-access"
2864
+ ]
2865
+ },
2866
+ approval: {
2867
+ type: "string",
2868
+ enum: [
2869
+ "on-request",
2870
+ "never"
2871
+ ]
2872
+ },
2873
+ ruleIds: {
2874
+ maxItems: 64,
2875
+ type: "array",
2876
+ items: {
2877
+ type: "string",
2878
+ minLength: 1,
2879
+ maxLength: 100,
2880
+ pattern: "^[\\w.-]+$"
2881
+ }
2882
+ }
2883
+ },
2884
+ additionalProperties: false
2885
+ },
2886
+ limits: {
2887
+ type: "object",
2888
+ properties: {
2889
+ queueTimeoutMs: {
2890
+ type: "integer",
2891
+ minimum: 1,
2892
+ maximum: 864e5
2893
+ },
2894
+ runTimeoutMs: {
2895
+ type: "integer",
2896
+ minimum: 1,
2897
+ maximum: 864e5
2898
+ },
2899
+ approvalTimeoutMs: {
2900
+ type: "integer",
2901
+ minimum: 1,
2902
+ maximum: 864e5
2903
+ },
2904
+ maxOutputBytes: {
2905
+ type: "integer",
2906
+ minimum: 1,
2907
+ maximum: 8388608
2908
+ }
2909
+ },
2910
+ additionalProperties: false
2911
+ },
2912
+ output: {
2913
+ type: "object",
2914
+ properties: {
2915
+ format: {
2916
+ type: "string",
2917
+ enum: [
2918
+ "text",
2919
+ "json"
2920
+ ]
2921
+ },
2922
+ schema: {
2923
+ type: "object",
2924
+ propertyNames: {
2925
+ type: "string"
2926
+ },
2927
+ additionalProperties: {
2928
+ $ref: "#/$defs/__schema0"
2929
+ }
2930
+ }
2931
+ },
2932
+ required: [
2933
+ "format"
2934
+ ],
2935
+ additionalProperties: false
2936
+ },
2937
+ native: {
2938
+ type: "object",
2939
+ properties: {
2940
+ adapter: {
2941
+ type: "string",
2942
+ minLength: 1,
2943
+ maxLength: 100,
2944
+ pattern: "^[\\w.-]+$"
2945
+ },
2946
+ options: {
2947
+ type: "object",
2948
+ propertyNames: {
2949
+ type: "string"
2950
+ },
2951
+ additionalProperties: {
2952
+ $ref: "#/$defs/__schema1"
2953
+ }
2954
+ }
2955
+ },
2956
+ required: [
2957
+ "adapter",
2958
+ "options"
2959
+ ],
2960
+ additionalProperties: false
2961
+ },
2962
+ metadata: {
2963
+ type: "object",
2964
+ propertyNames: {
2965
+ type: "string",
2966
+ pattern: "^[\\w.-]{1,48}$"
2967
+ },
2968
+ additionalProperties: {
2969
+ type: "string",
2970
+ maxLength: 128
2971
+ }
2972
+ }
2973
+ },
2974
+ additionalProperties: false
2975
+ },
2357
2976
  command: {
2358
2977
  type: "string",
2359
2978
  minLength: 1
@@ -2425,6 +3044,320 @@ var deploy_config_shapes_default = {
2425
3044
  pattern: "^[A-Z_]\\w*$"
2426
3045
  }
2427
3046
  },
3047
+ environmentIds: {
3048
+ default: [],
3049
+ maxItems: 64,
3050
+ type: "array",
3051
+ items: {
3052
+ type: "string",
3053
+ pattern: "^[a-z0-9][\\w-]{0,95}$"
3054
+ }
3055
+ },
3056
+ allowedCapabilities: {
3057
+ default: [],
3058
+ maxItems: 64,
3059
+ type: "array",
3060
+ items: {
3061
+ anyOf: [
3062
+ {
3063
+ type: "string",
3064
+ pattern: "^[a-z0-9][\\w-]{0,95}$"
3065
+ },
3066
+ {
3067
+ type: "string",
3068
+ pattern: "^[\\w.-]{1,96}$"
3069
+ }
3070
+ ]
3071
+ }
3072
+ },
3073
+ defaults: {
3074
+ type: "object",
3075
+ properties: {
3076
+ model: {
3077
+ type: "object",
3078
+ properties: {
3079
+ id: {
3080
+ type: "string",
3081
+ minLength: 1,
3082
+ maxLength: 100,
3083
+ pattern: "^[\\w.-]+$"
3084
+ },
3085
+ reasoningEffort: {
3086
+ type: "string",
3087
+ minLength: 1,
3088
+ maxLength: 100,
3089
+ pattern: "^[\\w.-]+$"
3090
+ },
3091
+ temperature: {
3092
+ type: "number",
3093
+ minimum: 0,
3094
+ maximum: 2
3095
+ },
3096
+ maxOutputTokens: {
3097
+ type: "integer",
3098
+ exclusiveMinimum: 0,
3099
+ maximum: 9007199254740991
3100
+ }
3101
+ },
3102
+ additionalProperties: false
3103
+ },
3104
+ execution: {
3105
+ type: "object",
3106
+ properties: {
3107
+ mode: {
3108
+ type: "string",
3109
+ enum: [
3110
+ "auto",
3111
+ "shared",
3112
+ "user",
3113
+ "task"
3114
+ ]
3115
+ },
3116
+ environmentId: {
3117
+ type: "string",
3118
+ minLength: 1,
3119
+ maxLength: 100,
3120
+ pattern: "^[\\w.-]+$"
3121
+ },
3122
+ resources: {
3123
+ type: "object",
3124
+ properties: {
3125
+ cpu: {
3126
+ type: "number",
3127
+ exclusiveMinimum: 0,
3128
+ maximum: 1024
3129
+ },
3130
+ memoryMiB: {
3131
+ type: "integer",
3132
+ minimum: 16,
3133
+ maximum: 9007199254740991
3134
+ }
3135
+ },
3136
+ additionalProperties: false
3137
+ }
3138
+ },
3139
+ additionalProperties: false
3140
+ },
3141
+ workspaceId: {
3142
+ type: "string",
3143
+ minLength: 1,
3144
+ maxLength: 100,
3145
+ pattern: "^[\\w.-]+$"
3146
+ },
3147
+ context: {
3148
+ type: "object",
3149
+ properties: {
3150
+ fileIds: {
3151
+ maxItems: 64,
3152
+ type: "array",
3153
+ items: {
3154
+ type: "string",
3155
+ minLength: 1,
3156
+ maxLength: 100,
3157
+ pattern: "^[\\w.-]+$"
3158
+ }
3159
+ },
3160
+ knowledgeBaseIds: {
3161
+ maxItems: 64,
3162
+ type: "array",
3163
+ items: {
3164
+ type: "string",
3165
+ minLength: 1,
3166
+ maxLength: 100,
3167
+ pattern: "^[\\w.-]+$"
3168
+ }
3169
+ },
3170
+ skillIds: {
3171
+ maxItems: 64,
3172
+ type: "array",
3173
+ items: {
3174
+ type: "string",
3175
+ minLength: 1,
3176
+ maxLength: 100,
3177
+ pattern: "^[\\w.-]+$"
3178
+ }
3179
+ },
3180
+ memory: {
3181
+ type: "object",
3182
+ properties: {
3183
+ scope: {
3184
+ type: "string",
3185
+ enum: [
3186
+ "none",
3187
+ "user",
3188
+ "space"
3189
+ ]
3190
+ },
3191
+ spaceId: {
3192
+ type: "string",
3193
+ minLength: 1,
3194
+ maxLength: 100,
3195
+ pattern: "^[\\w.-]+$"
3196
+ },
3197
+ write: {
3198
+ type: "string",
3199
+ enum: [
3200
+ "none",
3201
+ "confirm",
3202
+ "on-success"
3203
+ ]
3204
+ }
3205
+ },
3206
+ required: [
3207
+ "scope"
3208
+ ],
3209
+ additionalProperties: false
3210
+ }
3211
+ },
3212
+ additionalProperties: false
3213
+ },
3214
+ tools: {
3215
+ type: "object",
3216
+ properties: {
3217
+ allow: {
3218
+ maxItems: 64,
3219
+ type: "array",
3220
+ items: {
3221
+ type: "string",
3222
+ minLength: 1,
3223
+ maxLength: 100,
3224
+ pattern: "^[\\w.-]+$"
3225
+ }
3226
+ }
3227
+ },
3228
+ additionalProperties: false
3229
+ },
3230
+ capabilities: {
3231
+ maxItems: 64,
3232
+ type: "array",
3233
+ items: {
3234
+ type: "string",
3235
+ minLength: 1,
3236
+ maxLength: 100,
3237
+ pattern: "^[\\w.-]+$"
3238
+ }
3239
+ },
3240
+ permissions: {
3241
+ type: "object",
3242
+ properties: {
3243
+ profile: {
3244
+ type: "string",
3245
+ enum: [
3246
+ "read-only",
3247
+ "workspace",
3248
+ "full-access"
3249
+ ]
3250
+ },
3251
+ approval: {
3252
+ type: "string",
3253
+ enum: [
3254
+ "on-request",
3255
+ "never"
3256
+ ]
3257
+ },
3258
+ ruleIds: {
3259
+ maxItems: 64,
3260
+ type: "array",
3261
+ items: {
3262
+ type: "string",
3263
+ minLength: 1,
3264
+ maxLength: 100,
3265
+ pattern: "^[\\w.-]+$"
3266
+ }
3267
+ }
3268
+ },
3269
+ additionalProperties: false
3270
+ },
3271
+ limits: {
3272
+ type: "object",
3273
+ properties: {
3274
+ queueTimeoutMs: {
3275
+ type: "integer",
3276
+ minimum: 1,
3277
+ maximum: 864e5
3278
+ },
3279
+ runTimeoutMs: {
3280
+ type: "integer",
3281
+ minimum: 1,
3282
+ maximum: 864e5
3283
+ },
3284
+ approvalTimeoutMs: {
3285
+ type: "integer",
3286
+ minimum: 1,
3287
+ maximum: 864e5
3288
+ },
3289
+ maxOutputBytes: {
3290
+ type: "integer",
3291
+ minimum: 1,
3292
+ maximum: 8388608
3293
+ }
3294
+ },
3295
+ additionalProperties: false
3296
+ },
3297
+ output: {
3298
+ type: "object",
3299
+ properties: {
3300
+ format: {
3301
+ type: "string",
3302
+ enum: [
3303
+ "text",
3304
+ "json"
3305
+ ]
3306
+ },
3307
+ schema: {
3308
+ type: "object",
3309
+ propertyNames: {
3310
+ type: "string"
3311
+ },
3312
+ additionalProperties: {
3313
+ $ref: "#/$defs/__schema0"
3314
+ }
3315
+ }
3316
+ },
3317
+ required: [
3318
+ "format"
3319
+ ],
3320
+ additionalProperties: false
3321
+ },
3322
+ native: {
3323
+ type: "object",
3324
+ properties: {
3325
+ adapter: {
3326
+ type: "string",
3327
+ minLength: 1,
3328
+ maxLength: 100,
3329
+ pattern: "^[\\w.-]+$"
3330
+ },
3331
+ options: {
3332
+ type: "object",
3333
+ propertyNames: {
3334
+ type: "string"
3335
+ },
3336
+ additionalProperties: {
3337
+ $ref: "#/$defs/__schema1"
3338
+ }
3339
+ }
3340
+ },
3341
+ required: [
3342
+ "adapter",
3343
+ "options"
3344
+ ],
3345
+ additionalProperties: false
3346
+ },
3347
+ metadata: {
3348
+ type: "object",
3349
+ propertyNames: {
3350
+ type: "string",
3351
+ pattern: "^[\\w.-]{1,48}$"
3352
+ },
3353
+ additionalProperties: {
3354
+ type: "string",
3355
+ maxLength: 128
3356
+ }
3357
+ }
3358
+ },
3359
+ additionalProperties: false
3360
+ },
2428
3361
  command: {
2429
3362
  type: "string",
2430
3363
  minLength: 1
@@ -2627,6 +3560,12 @@ var deploy_config_shapes_default = {
2627
3560
  type: "integer",
2628
3561
  exclusiveMinimum: 0,
2629
3562
  maximum: 9007199254740991
3563
+ },
3564
+ workerIdleTimeoutMs: {
3565
+ default: 3e5,
3566
+ type: "integer",
3567
+ minimum: 1e3,
3568
+ maximum: 864e5
2630
3569
  }
2631
3570
  },
2632
3571
  additionalProperties: false
@@ -2855,9 +3794,14 @@ var deploy_config_shapes_default = {
2855
3794
  enum: [
2856
3795
  "stdio",
2857
3796
  "http",
2858
- "sse"
3797
+ "sse",
3798
+ "job"
2859
3799
  ]
2860
3800
  },
3801
+ environmentId: {
3802
+ type: "string",
3803
+ pattern: "^[\\w.-]{1,128}$"
3804
+ },
2861
3805
  command: {
2862
3806
  type: "string",
2863
3807
  minLength: 1
@@ -2883,15 +3827,54 @@ var deploy_config_shapes_default = {
2883
3827
  type: "string"
2884
3828
  }
2885
3829
  },
2886
- headerEnv: {
2887
- default: {},
3830
+ headerEnv: {
3831
+ default: {},
3832
+ type: "object",
3833
+ propertyNames: {
3834
+ type: "string"
3835
+ },
3836
+ additionalProperties: {
3837
+ type: "string"
3838
+ }
3839
+ },
3840
+ allowedTools: {
3841
+ minItems: 1,
3842
+ maxItems: 256,
3843
+ type: "array",
3844
+ items: {
3845
+ type: "string",
3846
+ minLength: 1,
3847
+ maxLength: 128
3848
+ }
3849
+ },
3850
+ timeoutMs: {
3851
+ type: "integer",
3852
+ minimum: 1,
3853
+ maximum: 3e5
3854
+ },
3855
+ pool: {
2888
3856
  type: "object",
2889
- propertyNames: {
2890
- type: "string"
3857
+ properties: {
3858
+ maxRunning: {
3859
+ type: "integer",
3860
+ minimum: 1,
3861
+ maximum: 1e3
3862
+ },
3863
+ minReady: {
3864
+ type: "integer",
3865
+ minimum: 0,
3866
+ maximum: 1e3
3867
+ },
3868
+ maxTotal: {
3869
+ type: "integer",
3870
+ minimum: 1,
3871
+ maximum: 1e3
3872
+ }
2891
3873
  },
2892
- additionalProperties: {
2893
- type: "string"
2894
- }
3874
+ required: [
3875
+ "maxRunning"
3876
+ ],
3877
+ additionalProperties: false
2895
3878
  }
2896
3879
  },
2897
3880
  required: [
@@ -3516,6 +4499,70 @@ var deploy_config_shapes_default = {
3516
4499
  }
3517
4500
  }
3518
4501
  }
4502
+ },
4503
+ $defs: {
4504
+ __schema0: {
4505
+ anyOf: [
4506
+ {
4507
+ type: "string"
4508
+ },
4509
+ {
4510
+ type: "number"
4511
+ },
4512
+ {
4513
+ type: "boolean"
4514
+ },
4515
+ {
4516
+ type: "null"
4517
+ },
4518
+ {
4519
+ type: "array",
4520
+ items: {
4521
+ $ref: "#/$defs/__schema0"
4522
+ }
4523
+ },
4524
+ {
4525
+ type: "object",
4526
+ propertyNames: {
4527
+ type: "string"
4528
+ },
4529
+ additionalProperties: {
4530
+ $ref: "#/$defs/__schema0"
4531
+ }
4532
+ }
4533
+ ]
4534
+ },
4535
+ __schema1: {
4536
+ anyOf: [
4537
+ {
4538
+ type: "string"
4539
+ },
4540
+ {
4541
+ type: "number"
4542
+ },
4543
+ {
4544
+ type: "boolean"
4545
+ },
4546
+ {
4547
+ type: "null"
4548
+ },
4549
+ {
4550
+ type: "array",
4551
+ items: {
4552
+ $ref: "#/$defs/__schema1"
4553
+ }
4554
+ },
4555
+ {
4556
+ type: "object",
4557
+ propertyNames: {
4558
+ type: "string"
4559
+ },
4560
+ additionalProperties: {
4561
+ $ref: "#/$defs/__schema1"
4562
+ }
4563
+ }
4564
+ ]
4565
+ }
3519
4566
  }
3520
4567
  },
3521
4568
  vecdb: {
@@ -6261,9 +7308,9 @@ function existingFile(path) {
6261
7308
  throw error;
6262
7309
  }
6263
7310
  }
6264
- function writeLocalFile(path, content, privateFile2 = false) {
7311
+ function writeLocalFile(path, content, privateFile = false) {
6265
7312
  const previous = existingFile(path);
6266
- const mode = privateFile2 ? 384 : previous ? Number(previous.mode & 0o777n) : 420;
7313
+ const mode = privateFile ? 384 : previous ? Number(previous.mode & 0o777n) : 420;
6267
7314
  const temporary = join(dirname(path), `.${basename(path)}.${randomUUID()}.tmp`);
6268
7315
  let fd;
6269
7316
  let owned;
@@ -6470,7 +7517,7 @@ function isSecret(name, value) {
6470
7517
  }
6471
7518
  function generatedVariables(values) {
6472
7519
  const generated = {};
6473
- const groups3 = /* @__PURE__ */ new Map();
7520
+ const groups2 = /* @__PURE__ */ new Map();
6474
7521
  const structures = /* @__PURE__ */ new Set();
6475
7522
  for (const [name, value] of Object.entries(values)) {
6476
7523
  if (value === void 0)
@@ -6482,9 +7529,9 @@ function generatedVariables(values) {
6482
7529
  const module = /^HAI_([A-Z0-9]+)(?:_|$)/.exec(name)?.[1]?.toLowerCase();
6483
7530
  if (!module || !shapes[module])
6484
7531
  continue;
6485
- const group = groups3.get(module) ?? {};
7532
+ const group = groups2.get(module) ?? {};
6486
7533
  group[name] = text;
6487
- groups3.set(module, group);
7534
+ groups2.set(module, group);
6488
7535
  try {
6489
7536
  const parsed = JSON.parse(text);
6490
7537
  if (parsed !== null && typeof parsed === "object")
@@ -6492,7 +7539,7 @@ function generatedVariables(values) {
6492
7539
  } catch {
6493
7540
  }
6494
7541
  }
6495
- for (const [module, group] of groups3) {
7542
+ for (const [module, group] of groups2) {
6496
7543
  const structuredNames = Object.keys(group).filter((name) => structures.has(name));
6497
7544
  if (!structuredNames.length)
6498
7545
  continue;
@@ -6539,7 +7586,7 @@ function generatedVariables(values) {
6539
7586
  }
6540
7587
  return generated;
6541
7588
  }
6542
- function updateFile(path, values, privateFile2) {
7589
+ function updateFile(path, values, privateFile) {
6543
7590
  let content = existsSync(path) ? readLocalFile(path).toString("utf8") : "# Local private deployment configuration; do not commit.\n";
6544
7591
  for (const [name, value] of Object.entries(values)) {
6545
7592
  if (!/^[A-Z][A-Z0-9_]*$/.test(name))
@@ -6563,15 +7610,15 @@ ${line}
6563
7610
  content = withoutParent.slice(0, child.index) + parent[0].replace(/\r?\n?$/, "\n") + withoutParent.slice(child.index);
6564
7611
  }
6565
7612
  }
6566
- writeLocalFile(path, content, privateFile2);
7613
+ writeLocalFile(path, content, privateFile);
6567
7614
  }
6568
- function removeOverrides(path, names, privateFile2 = true) {
7615
+ function removeOverrides(path, names, privateFile = true) {
6569
7616
  if (!existsSync(path) || names.length === 0)
6570
7617
  return;
6571
7618
  let content = readLocalFile(path).toString("utf8");
6572
7619
  for (const name of names)
6573
7620
  content = content.replace(new RegExp(`^[\\t ]*(?:export[\\t ]+)?${name}[\\t ]*=[\\t ]*(?:'[^']*'|"(?:[^"\\\\]|\\\\.)*"|\`[^\`]*\`|[^\\r\\n]*)[^\\r\\n]*(?:\\r?\\n|$)`, "gm"), "");
6574
- writeLocalFile(path, content, privateFile2);
7621
+ writeLocalFile(path, content, privateFile);
6575
7622
  }
6576
7623
  function saveDeploymentEnvironment(environment, values, options = {}) {
6577
7624
  try {
@@ -7134,47 +8181,6 @@ async function scanApp(inputDir, options) {
7134
8181
  }
7135
8182
  }
7136
8183
 
7137
- // src/container/deploy-compose-identity.ts
7138
- function createComposeContainerLookupScript() {
7139
- const labelFormat = [
7140
- "com.docker.compose.project",
7141
- "com.docker.compose.service",
7142
- "io.podman.compose.project",
7143
- "io.podman.compose.service"
7144
- ].map((key) => `{{index .Config.Labels \`${key}\`}}`).join("|");
7145
- return String.raw`compose_container_ids() (
7146
- if [ "$#" -ne 4 ]; then printf 'Compose container lookup requires runtime, project, service and selector.\n' >&2; exit 1; fi
7147
- lookup_runtime=$1
7148
- lookup_project=$2
7149
- lookup_service=$3
7150
- lookup_selector=$4
7151
- case "$lookup_runtime" in docker|podman) ;; *) printf 'Invalid Compose lookup runtime.\n' >&2; exit 1;; esac
7152
- case "$lookup_project" in ''|*[!A-Za-z0-9_.-]*) printf 'Invalid Compose project identity.\n' >&2; exit 1;; esac
7153
- case "$lookup_service" in ''|*[!A-Za-z0-9_.-]*) printf 'Invalid Compose service identity.\n' >&2; exit 1;; esac
7154
- case "$lookup_selector" in -q|-aq) ;; *) printf 'Invalid Compose container selector.\n' >&2; exit 1;; esac
7155
- lookup_ids=''
7156
- for lookup_namespace in com.docker.compose io.podman.compose; do
7157
- lookup_found=$("$lookup_runtime" ps "$lookup_selector" --filter "label=$lookup_namespace.project=$lookup_project" --filter "label=$lookup_namespace.service=$lookup_service") || { printf 'Cannot query Compose containers.\n' >&2; exit 1; }
7158
- printf '%s\n' "$lookup_found" | LC_ALL=C awk 'NF {if(NF!=1 || $0!~/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/) exit 1}' || { printf 'Invalid Compose container identifiers.\n' >&2; exit 1; }
7159
- lookup_ids=$(printf '%s\n%s\n' "$lookup_ids" "$lookup_found" | LC_ALL=C awk 'NF && !seen[$0]++ {print}') || exit 1
7160
- done
7161
- for lookup_id in $lookup_ids; do
7162
- lookup_labels=$("$lookup_runtime" inspect --format '${labelFormat}' "$lookup_id") || { printf 'Cannot inspect Compose container labels.\n' >&2; exit 1; }
7163
- printf '%s\n' "$lookup_labels" | LC_ALL=C awk -F'|' -v project="$lookup_project" -v service="$lookup_service" '
7164
- NR!=1 || NF!=4 {exit 1}
7165
- {
7166
- docker=($1!="" && $1!="<no value>") || ($2!="" && $2!="<no value>")
7167
- podman=($3!="" && $3!="<no value>") || ($4!="" && $4!="<no value>")
7168
- if((!docker && !podman) || (docker && ($1!=project || $2!=service)) || (podman && ($3!=project || $4!=service))) exit 1
7169
- }
7170
- END {if(NR!=1) exit 1}
7171
- ' || { printf 'Container labels do not confirm the requested Compose project and service.\n' >&2; exit 1; }
7172
- done
7173
- if [ -n "$lookup_ids" ]; then printf '%s\n' "$lookup_ids"; fi
7174
- )
7175
- `;
7176
- }
7177
-
7178
8184
  // src/container/deploy-json.ts
7179
8185
  function createDeploymentJsonTool() {
7180
8186
  return String.raw`function fail(message) {
@@ -7499,7 +8505,7 @@ deployment_metadata_init() {
7499
8505
  deployment_metadata_file() {
7500
8506
  metadata_directory=$1
7501
8507
  metadata_name=$2
7502
- case "$metadata_name" in layout|health-paths|image-list|dependencies|managed-project|managed-bindings|managed-volumes|managed-app-keys|unmanaged-app-keys|state-backends|storage-routing|web-base-path|node-runtime|proxy-service|https) ;; *) printf 'Unknown deployment metadata field.\n' >&2; return 1;; esac
8508
+ case "$metadata_name" in layout|health-paths|image-list|dependencies|managed-bindings|managed-volumes|managed-app-keys|unmanaged-app-keys|state-backends|storage-routing|web-base-path|node-runtime|proxy-service|https) ;; *) printf 'Unknown deployment metadata field.\n' >&2; return 1;; esac
7503
8509
  test -f "$metadata_directory/manifest.json" && test ! -L "$metadata_directory/manifest.json" || return 1
7504
8510
  metadata_identity=$(printf '%s' "$metadata_directory" | sha256sum | awk '{print $1}')
7505
8511
  metadata_cached="$metadata_work/$metadata_identity-$metadata_name"
@@ -7596,8 +8602,6 @@ function createManagedSecretAssets(projectName, nativeEnvironment, compose) {
7596
8602
  }
7597
8603
  }
7598
8604
  return {
7599
- "managed-project": `hai-${projectName}
7600
- `,
7601
8605
  "managed-candidates.env": candidates.length ? `${candidates.join("\n")}
7602
8606
  ` : "",
7603
8607
  "managed-bindings": bindings.size ? `${[...bindings].join("\n")}
@@ -7607,29 +8611,6 @@ function createManagedSecretAssets(projectName, nativeEnvironment, compose) {
7607
8611
  "resolve-managed-secrets.sh": createManagedSecretScript()
7608
8612
  };
7609
8613
  }
7610
- function createManagedSecretValidationScript() {
7611
- return String.raw`valid_values() {
7612
- version=$(awk -F= '$1=="FORMAT_VERSION" {print $2}' "$1")
7613
- if [ "$version" = 2 ]; then
7614
- LC_ALL=C awk -F= '
7615
- NF {
7616
- if(NF!=2 || seen[$1]++) exit 1
7617
- if($1=="FORMAT_VERSION") {if($2!="2") exit 1; next}
7618
- if($1=="PROJECT") {if($2!~/^[A-Za-z0-9][A-Za-z0-9_.-]*$/) exit 1; project=1; next}
7619
- if($1~/^VOLUME_[0-9]+$/) {if($2!~/^(DB_PASSWORD|STORAGE_PASSWORD|LDAP_PASSWORD):[A-Za-z0-9][A-Za-z0-9_.-]*$/) exit 1; next}
7620
- if($1!~/^(DB_PASSWORD|STORAGE_PASSWORD|LDAP_PASSWORD)$/ || $2!~/^[0-9a-f]+$/ || length($2)%2 || length($2)>8192) exit 1
7621
- for(i=1;i<=length($2);i+=2) {b=substr($2,i,2); if(b~/^[01]/ || b=="7f") exit 1}
7622
- }
7623
- END {if(!project) exit 1}
7624
- ' "$1"
7625
- return
7626
- fi
7627
- LC_ALL=C awk -F= '
7628
- NF {if(NF!=2 || $1!~/^(DB_PASSWORD|STORAGE_PASSWORD|LDAP_PASSWORD)$/ || $2!~/^[A-Za-z0-9_-]+$/ || length($2)!=32 || seen[$1]++) exit 1}
7629
- ' "$1"
7630
- }
7631
- `;
7632
- }
7633
8614
  function createManagedSecretScript() {
7634
8615
  return String.raw`#!/bin/sh
7635
8616
  # 由 apply.sh 在项目部署锁内调用;长期凭据只保存在服务器,不依赖打包机。
@@ -7644,12 +8625,15 @@ test -d "$root" && test ! -L "$root" && test -d "$release/.hai" && test ! -L "$r
7644
8625
  @@MANIFEST_FUNCTIONS@@
7645
8626
  deployment_metadata_init
7646
8627
  trap deployment_metadata_cleanup EXIT
7647
- managed_project=$(deployment_metadata_file "$release" managed-project)
7648
8628
  managed_bindings=$(deployment_metadata_file "$release" managed-bindings)
7649
8629
  managed_volumes=$(deployment_metadata_file "$release" managed-volumes)
7650
8630
  test -s "$candidate_file" || exit 0
7651
8631
  fail() { printf '%s\n' "$*" >&2; exit 1; }
7652
- @@MANAGED_VALUES_VALIDATOR@@
8632
+ valid_values() {
8633
+ LC_ALL=C awk -F= '
8634
+ NF {if(NF!=2 || $1!~/^(DB_PASSWORD|STORAGE_PASSWORD|LDAP_PASSWORD)$/ || $2!~/^[A-Za-z0-9_-]+$/ || length($2)!=32 || seen[$1]++) exit 1}
8635
+ ' "$1"
8636
+ }
7653
8637
  value_of() { awk -F= -v key="$2" '$1==key {print $2}' "$1"; }
7654
8638
  test ! -L "$state" || fail "Invalid managed middleware credential state: $state"
7655
8639
  valid_values "$candidate_file" || fail 'Invalid managed middleware credential candidates; rebuild the deployment bundle.'
@@ -7660,22 +8644,6 @@ if [ -f "$state" ]; then
7660
8644
  else
7661
8645
  : > "$next"
7662
8646
  fi
7663
- format=$(value_of "$next" FORMAT_VERSION)
7664
- if [ "$format" = 2 ]; then
7665
- test "$(value_of "$next" PROJECT)" = "$(cat "$managed_project")" || fail 'Managed middleware credential project mismatch.'
7666
- known=$("$runtime" volume ls -q) || fail 'Cannot check persistent volumes; managed middleware credentials were not changed.'
7667
- while IFS=' ' read -r volume_group volume; do
7668
- test -n "$volume" || continue
7669
- binding="$volume_group:$volume"
7670
- if ! awk -F= -v binding="$binding" '$1~/^VOLUME_[0-9]+$/ && $2==binding {found=1} END {exit !found}' "$next"; then
7671
- if printf '%s\n' "$known" | grep -Fxq "$volume"; then
7672
- fail 'Existing persistent volume is not bound to the imported credential state. Import its verified identity first.'
7673
- fi
7674
- index=$(awk -F= '$1~/^VOLUME_[0-9]+$/ {n=substr($1,8)+0; if(n>max)max=n} END {print max+1}' "$next")
7675
- printf 'VOLUME_%s=%s\n' "$index" "$binding" >> "$next"
7676
- fi
7677
- done < "$managed_volumes"
7678
- fi
7679
8647
  volumes_checked=false
7680
8648
  known_volumes=''
7681
8649
  while IFS='=' read -r name candidate; do
@@ -7692,64 +8660,24 @@ while IFS='=' read -r name candidate; do
7692
8660
  fail "Missing $name in $state for existing persistent volume $volume. Restore the server credential state before retrying."
7693
8661
  fi
7694
8662
  done < "$managed_volumes"
7695
- value=$candidate
7696
- if [ "$format" = 2 ]; then value=$(printf '%s' "$candidate" | od -An -tx1 | tr -d ' \n'); fi
7697
- printf '%s=%s\n' "$name" "$value" >> "$next"
8663
+ printf '%s=%s\n' "$name" "$candidate" >> "$next"
7698
8664
  fi
7699
8665
  done < "$candidate_file"
7700
8666
  while IFS='=' read -r name candidate; do
7701
8667
  test -n "$name" || continue
7702
8668
  value=$(value_of "$next" "$name")
7703
8669
  for file in config/.env.runtime .hai/middleware-runtime.env; do
7704
- compose_environment=false
7705
- if [ "$file" = .hai/middleware-runtime.env ]; then compose_environment=true; fi
7706
- HAI_DEPLOY_SECRET_CANDIDATE="$candidate" HAI_DEPLOY_SECRET_VALUE="$value" LC_ALL=C awk -v group="$name" -v format="$format" -v compose_environment="$compose_environment" '
8670
+ HAI_DEPLOY_SECRET_CANDIDATE="$candidate" HAI_DEPLOY_SECRET_VALUE="$value" LC_ALL=C awk -v group="$name" '
7707
8671
  FILENAME==ARGV[1] {if($1==group) {keys[$2]=$3}; next}
7708
8672
  function replace_literal(line, needle, value, result, at) {
7709
8673
  result=""
7710
8674
  while((at=index(line,needle))>0) {result=result substr(line,1,at-1) value; line=substr(line,at+length(needle))}
7711
8675
  return result line
7712
8676
  }
7713
- function decode_hex(hex, result, i, high, low) {
7714
- for(i=1;i<=length(hex);i+=2) {high=index("0123456789abcdef",substr(hex,i,1))-1; low=index("0123456789abcdef",substr(hex,i+1,1))-1; result=result sprintf("%c",high*16+low)}
7715
- return result
7716
- }
7717
- function json_escape(value, apostrophe, result, i, ch) {
7718
- for(i=1;i<=length(value);i++) {
7719
- ch=substr(value,i,1)
7720
- if(ch=="\\") result=result "\\\\"
7721
- else if(ch=="\"") result=result "\\\""
7722
- else if(ch==sprintf("%c",39) && apostrophe) result=result "\\u0027"
7723
- else result=result ch
7724
- }
7725
- return result
7726
- }
7727
- function uri_escape(value, result, i, ch, j, ordinal) {
7728
- for(i=1;i<=length(value);i++) {
7729
- ch=substr(value,i,1)
7730
- if(ch~/[A-Za-z0-9_.~-]/) result=result ch
7731
- else {ordinal=0; for(j=1;j<=255;j++) if(sprintf("%c",j)==ch) {ordinal=j; break}; result=result sprintf("%%%02X",ordinal)}
7732
- }
7733
- return result
7734
- }
7735
8677
  {
7736
8678
  equal=index($0,"="); key=substr($0,1,equal-1); scoped=0
7737
8679
  for(prefix in keys) if(key==prefix || (keys[prefix]=="prefix" && index(key,prefix "_")==1)) scoped=1
7738
- if(scoped && format==2 && index($0,ENVIRON["HAI_DEPLOY_SECRET_CANDIDATE"])>0) {
7739
- candidate=ENVIRON["HAI_DEPLOY_SECRET_CANDIDATE"]; value=decode_hex(ENVIRON["HAI_DEPLOY_SECRET_VALUE"])
7740
- literal=substr($0,equal+1); quote=sprintf("%c",39)
7741
- json=substr(literal,1,1)==quote && (substr(literal,2,1)=="{" || substr(literal,2,1)=="[")
7742
- if(json) literal=substr(literal,2,length(literal)-2)
7743
- literal=replace_literal(literal,":" candidate "@",":" uri_escape(value) "@")
7744
- if(json) {literal=replace_literal(literal,candidate,json_escape(value,1)); print key "=" quote literal quote}
7745
- else {
7746
- if(substr(literal,1,1)==quote || substr(literal,1,1)=="\"") literal=substr(literal,2,length(literal)-2)
7747
- literal=replace_literal(literal,candidate,value)
7748
- literal=json_escape(literal,0); if(compose_environment=="true") literal=replace_literal(literal,"$","$")
7749
- print key "=\"" literal "\""
7750
- }
7751
- }
7752
- else if(scoped) print replace_literal($0,ENVIRON["HAI_DEPLOY_SECRET_CANDIDATE"],ENVIRON["HAI_DEPLOY_SECRET_VALUE"])
8680
+ if(scoped) print replace_literal($0,ENVIRON["HAI_DEPLOY_SECRET_CANDIDATE"],ENVIRON["HAI_DEPLOY_SECRET_VALUE"])
7753
8681
  else print
7754
8682
  }
7755
8683
  ' "$managed_bindings" "$release/$file" > "$release/$file.next"
@@ -7759,7 +8687,7 @@ done < "$candidate_file"
7759
8687
  chmod 600 "$next"
7760
8688
  mv "$next" "$state"
7761
8689
  printf '[INFO] Managed middleware credential source: %s (server state; values hidden).\n' "$state"
7762
- `.replace("@@MANAGED_VALUES_VALIDATOR@@", () => createManagedSecretValidationScript()).replace("@@MANIFEST_FUNCTIONS@@", () => createDeploymentManifestFunctions());
8690
+ `.replace("@@MANIFEST_FUNCTIONS@@", () => createDeploymentManifestFunctions());
7763
8691
  }
7764
8692
 
7765
8693
  // src/container/deploy-backend-state.ts
@@ -7822,11 +8750,11 @@ backend_manifest=$(deployment_metadata_file "$release" state-backends)
7822
8750
  regular() { test -f "$1" && test ! -L "$1" || fail "Backend state must be a regular file: $1"; }
7823
8751
  absent_or_regular() { if [ -e "$1" ] || [ -L "$1" ]; then regular "$1"; fi; }
7824
8752
  valid_tuples() {
7825
- LC_ALL=C awk -v legacy="$2" '
8753
+ LC_ALL=C awk '
7826
8754
  NF {
7827
- if((NF!=3 && !(legacy==1 && NF==2)) || roles[$1]++) exit 1
8755
+ if(NF!=3 || roles[$1]++) exit 1
7828
8756
  if(!(($1=="db" && $2~/^(postgresql|mysql)$/) || ($1=="storage" && $2~/^(rustfs|minio)$/) || ($1=="vecdb" && $2~/^(qdrant|pgvector|chroma)$/))) exit 1
7829
- if(NF==3 && ($3!~/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/ || volumes[$3]++)) exit 1
8757
+ if($3!~/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/ || volumes[$3]++) exit 1
7830
8758
  }
7831
8759
  ' "$1"
7832
8760
  }
@@ -7877,45 +8805,6 @@ known=$("$runtime" volume ls -q) || fail 'Cannot list persistent volumes; no bac
7877
8805
  printf '%s\n' "$known" | LC_ALL=C awk 'NF {if(NF!=1 || $1!~/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/ || seen[$1]++) exit 1}' || fail 'Invalid persistent volume listing.'
7878
8806
  exists_volume() { printf '%s\n' "$known" | grep -Fxq "$1"; }
7879
8807
  ledger_tuple() { printf '%s\n' "$ledger" | awk -v volume="$1" '$3==volume {print}'; }
7880
- @@MANAGED_VALUES_VALIDATOR@@
7881
- @@COMPOSE_CONTAINER_LOOKUP@@
7882
- # 状态补证:卷挂载和镜像均取自同项目、同角色的唯一实际容器。
7883
- container_binding() {
7884
- binding_service=$1
7885
- binding_family=$2
7886
- binding_expected=$3
7887
- binding_running=$4
7888
- binding_flags=-aq
7889
- if [ "$binding_running" = true ]; then binding_flags=-q; fi
7890
- binding_containers=$(compose_container_ids "$runtime" "$project" "$binding_service" "$binding_flags") || fail 'Cannot inspect successful deployment containers.'
7891
- printf '%s\n' "$binding_containers" | LC_ALL=C awk 'NR!=1 || NF!=1 || $1!~/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/ {exit 1} END {if(NR!=1) exit 1}' || fail "Cannot prove the previous volume binding for $binding_service. Restore verified backend state."
7892
- if [ "$binding_running" = true ]; then
7893
- binding_status=$("$runtime" inspect --format '{{.State.Running}}' "$binding_containers") || fail 'Cannot verify the imported service is running.'
7894
- test "$binding_status" = true || fail "Imported backend requires its verified original container to be running: $binding_service."
7895
- fi
7896
- binding_mounted=$("$runtime" inspect --format '{{range .Mounts}}{{if eq .Type "volume"}}{{println .Name}}{{end}}{{end}}' "$binding_containers") || fail 'Cannot inspect previous named volume mounts.'
7897
- printf '%s\n' "$binding_mounted" | LC_ALL=C awk 'NR!=1 || NF!=1 || $1!~/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/ {exit 1} END {if(NR!=1) exit 1}' || fail "Ambiguous previous volume binding for $binding_service."
7898
- binding_image=$("$runtime" inspect --format '{{.Config.Image}}' "$binding_containers") || fail 'Cannot identify the previous container image.'
7899
- binding_actual_family=$(printf '%s\n' "$binding_image" | LC_ALL=C awk 'NR!=1 || NF!=1 {exit 1} {image=$0; sub(/^.*\//,"",image); sub(/[:@].*$/,"",image); if(image=="postgres") image="postgresql"; print image}') || fail 'Invalid previous container image identity.'
7900
- test "$binding_actual_family" = "$binding_family" || fail "Actual previous container image does not confirm its backend family: $binding_service."
7901
- if [ -n "$binding_expected" ] && [ "$binding_expected" != "$binding_mounted" ]; then fail "Actual previous volume differs from its backend manifest: $binding_service."; fi
7902
- printf '%s\n' "$binding_mounted"
7903
- }
7904
- confirmed_import() {
7905
- import_service=$1
7906
- import_family=$2
7907
- import_volume=$3
7908
- import_state="$root/managed-secrets.env"
7909
- test -f "$import_state" && test ! -L "$import_state" || return 1
7910
- valid_values "$import_state" || return 1
7911
- test "$(awk -F= '$1=="FORMAT_VERSION" {print $2}' "$import_state")" = 2 || return 1
7912
- test "$(awk -F= '$1=="PROJECT" {print $2}' "$import_state")" = "$project" || return 1
7913
- case "$import_service" in db|vecdb) import_group=DB_PASSWORD;; storage) import_group=STORAGE_PASSWORD;; *) return 1;; esac
7914
- awk -F= -v group="$import_group" -v binding="$import_group:$import_volume" '$1==group && $2!="" {credential=1} $1~/^VOLUME_[0-9]+$/ && $2==binding {volume=1} END {exit !(credential && volume)}' "$import_state" || return 1
7915
- # 密码不证明目录格式;受控导入还须与唯一运行原容器的实际卷及镜像家族共同吻合。
7916
- import_actual=$(container_binding "$import_service" "$import_family" "$import_volume" true) || return 1
7917
- test "$import_actual" = "$import_volume"
7918
- }
7919
8808
  snapshot_previous=$previous
7920
8809
  if [ -z "$snapshot_previous" ]; then snapshot_previous=-; fi
7921
8810
  if [ "$mode" = recover ]; then
@@ -7933,7 +8822,7 @@ RECOVERY_TUPLES
7933
8822
  exit 0
7934
8823
  fi
7935
8824
  regular "$backend_manifest"
7936
- valid_tuples "$backend_manifest" 0 || fail 'Invalid backend manifest; rebuild the deployment bundle.'
8825
+ valid_tuples "$backend_manifest" || fail 'Invalid backend manifest; rebuild the deployment bundle.'
7937
8826
  active=''
7938
8827
  if [ -n "$previous" ]; then
7939
8828
  baseline="$root/releases/$previous"
@@ -7942,7 +8831,7 @@ if [ -n "$previous" ]; then
7942
8831
  baseline_services=$(deployment_services_file "$baseline")
7943
8832
  previous_manifest=$(deployment_metadata_file "$baseline" state-backends)
7944
8833
  regular "$previous_manifest"
7945
- valid_tuples "$previous_manifest" 0 || fail 'Invalid successful backend manifest.'
8834
+ valid_tuples "$previous_manifest" || fail 'Invalid successful backend manifest.'
7946
8835
  LC_ALL=C awk 'NF {if(NF!=1 || $1!~/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/ || seen[$1]++) exit 1}' "$baseline_services" || fail 'Invalid successful deployment services.'
7947
8836
  for active_service in db storage vecdb; do
7948
8837
  if ! grep -Fxq "$active_service" "$baseline_services"; then continue; fi
@@ -7955,28 +8844,18 @@ if [ -n "$previous" ]; then
7955
8844
  printf '%s %s %s\n' "$active_service" "$active_family" "$active_volume" | LC_ALL=C awk 'NF!=3 || !(($1=="db" && $2~/^(postgresql|mysql)$/) || ($1=="storage" && $2~/^(rustfs|minio)$/) || ($1=="vecdb" && $2~/^(qdrant|pgvector|chroma)$/)) {exit 1}' || fail "Unknown installed backend for $active_service."
7956
8845
  exists_volume "$active_volume" || fail "Active persistent volume is missing: $active_volume. Restore its backup instead of creating an empty replacement."
7957
8846
  confirmed=$(ledger_tuple "$active_volume")
7958
- test -z "$confirmed" || test "$confirmed" = "$active_service $active_family $active_volume" || fail "Active persistent backend conflicts with the protected ledger: $active_service."
7959
- if [ -z "$confirmed" ]; then
7960
- proven_volume=$(container_binding "$active_service" "$active_family" "$active_volume" false)
7961
- test "$proven_volume" = "$active_volume" || fail "Cannot confirm the active persistent volume: $active_service."
7962
- fi
8847
+ test "$confirmed" = "$active_service $active_family $active_volume" || fail "Active persistent backend is missing from or conflicts with the protected ledger: $active_service. Restore verified backend state."
7963
8848
  active=$(printf '%s\n%s %s %s\n' "$active" "$active_service" "$active_family" "$active_volume" | sed '/^$/d')
7964
8849
  done
7965
8850
  fi
7966
- # 各个旧 Compose 片段不能把同一实际卷重复绑定给不同有状态角色。
7967
- printf '%s\n' "$active" | valid_tuples /dev/stdin 0 || fail 'Conflicting successful persistent volume bindings.'
8851
+ # 同一实际卷不能绑定给多个有状态服务。
8852
+ printf '%s\n' "$active" | valid_tuples /dev/stdin || fail 'Conflicting successful persistent volume bindings.'
7968
8853
  # 合并时只替换同一卷的无数据预认领,不能按本次拓扑裁剪历史绑定。
7969
8854
  merged=$ledger
7970
8855
  merge_tuple() {
7971
8856
  merged=$(printf '%s\n' "$merged" | awk -v volume="$3" 'NF && $3!=volume {print}')
7972
8857
  merged=$(printf '%s\n%s %s %s\n' "$merged" "$1" "$2" "$3" | sed '/^$/d')
7973
8858
  }
7974
- while IFS=' ' read -r active_service active_family active_volume; do
7975
- test -n "$active_service" || continue
7976
- merge_tuple "$active_service" "$active_family" "$active_volume"
7977
- done <<ACTIVE_TUPLES
7978
- $active
7979
- ACTIVE_TUPLES
7980
8859
  while IFS=' ' read -r candidate_service candidate_family candidate_volume; do
7981
8860
  test -n "$candidate_service" || continue
7982
8861
  prior_active=$(printf '%s\n' "$active" | awk -v service="$candidate_service" '$1==service {print}')
@@ -7984,8 +8863,6 @@ while IFS=' ' read -r candidate_service candidate_family candidate_volume; do
7984
8863
  test -z "$prior_active" || test "$prior_active" = "$tuple" || fail "Persistent backend $candidate_service changed or its active volume binding changed. Use a separate deployment identity and migrate or restore data explicitly."
7985
8864
  if exists_volume "$candidate_volume"; then
7986
8865
  confirmed=$(ledger_tuple "$candidate_volume")
7987
- if [ -z "$confirmed" ]; then confirmed=$(printf '%s\n' "$active" | awk -v volume="$candidate_volume" '$3==volume {print}'); fi
7988
- if [ -z "$confirmed" ] && confirmed_import "$candidate_service" "$candidate_family" "$candidate_volume"; then confirmed=$tuple; fi
7989
8866
  test "$confirmed" = "$tuple" || fail "Existing persistent volume has an unknown or incompatible backend: $candidate_service. Restore verified backend state; in-place reuse is refused."
7990
8867
  else
7991
8868
  users=$("$runtime" ps -aq --filter "volume=$candidate_volume") || fail 'Cannot check persistent volume users.'
@@ -8005,7 +8882,7 @@ mv -f -- "$snapshot.next" "$snapshot"
8005
8882
  (set -C; printf '%s\n' "$state_content" > "$state.next") || fail 'Cannot stage the persistent backend ledger.'
8006
8883
  chmod 600 "$state.next"
8007
8884
  mv -f -- "$state.next" "$state"
8008
- `.replace("@@PROJECT@@", () => projectName).replace("@@MANAGED_VALUES_VALIDATOR@@", () => createManagedSecretValidationScript()).replace("@@COMPOSE_CONTAINER_LOOKUP@@", () => createComposeContainerLookupScript()).replace("@@MANIFEST_FUNCTIONS@@", () => createDeploymentManifestFunctions());
8885
+ `.replace("@@PROJECT@@", () => projectName).replace("@@MANIFEST_FUNCTIONS@@", () => createDeploymentManifestFunctions());
8009
8886
  }
8010
8887
  var nativeJob = String.raw`
8011
8888
  using System;
@@ -8204,7 +9081,7 @@ async function groupExited(pid) {
8204
9081
  async function helper(command, args, timeoutMs) {
8205
9082
  if (timeoutMs <= 0)
8206
9083
  return false;
8207
- return new Promise((resolve9) => {
9084
+ return new Promise((resolve8) => {
8208
9085
  const child = spawn(command, args, { windowsHide: true, shell: false, stdio: "ignore" });
8209
9086
  let settled = false;
8210
9087
  let timer;
@@ -8214,7 +9091,7 @@ async function helper(command, args, timeoutMs) {
8214
9091
  settled = true;
8215
9092
  if (timer)
8216
9093
  clearTimeout(timer);
8217
- resolve9(success);
9094
+ resolve8(success);
8218
9095
  };
8219
9096
  timer = globalThis.setTimeout(() => {
8220
9097
  child.kill("SIGKILL");
@@ -8367,7 +9244,7 @@ ${deployM(state.localTermination === "confirmed" ? "deploy_localStopped" : state
8367
9244
  ext: { commandExecution: state }
8368
9245
  });
8369
9246
  try {
8370
- return await new Promise((resolve9) => {
9247
+ return await new Promise((resolve8) => {
8371
9248
  if (typeof command !== "string" || !command || command.includes("\0") || args.some((argument) => typeof argument !== "string" || argument.includes("\0")))
8372
9249
  throw new TypeError("Invalid command arguments");
8373
9250
  if (options.input !== void 0 && typeof options.input !== "string")
@@ -8410,7 +9287,7 @@ ${deployM(state.localTermination === "confirmed" ? "deploy_localStopped" : state
8410
9287
  settled = true;
8411
9288
  if (timer)
8412
9289
  clearTimeout(timer);
8413
- resolve9(result);
9290
+ resolve8(result);
8414
9291
  };
8415
9292
  const interrupt = (reason, message) => {
8416
9293
  interrupted = true;
@@ -8597,6 +9474,47 @@ watch_certificate &
8597
9474
  `;
8598
9475
  }
8599
9476
 
9477
+ // src/container/deploy-compose-identity.ts
9478
+ function createComposeContainerLookupScript() {
9479
+ const labelFormat = [
9480
+ "com.docker.compose.project",
9481
+ "com.docker.compose.service",
9482
+ "io.podman.compose.project",
9483
+ "io.podman.compose.service"
9484
+ ].map((key) => `{{index .Config.Labels \`${key}\`}}`).join("|");
9485
+ return String.raw`compose_container_ids() (
9486
+ if [ "$#" -ne 4 ]; then printf 'Compose container lookup requires runtime, project, service and selector.\n' >&2; exit 1; fi
9487
+ lookup_runtime=$1
9488
+ lookup_project=$2
9489
+ lookup_service=$3
9490
+ lookup_selector=$4
9491
+ case "$lookup_runtime" in docker|podman) ;; *) printf 'Invalid Compose lookup runtime.\n' >&2; exit 1;; esac
9492
+ case "$lookup_project" in ''|*[!A-Za-z0-9_.-]*) printf 'Invalid Compose project identity.\n' >&2; exit 1;; esac
9493
+ case "$lookup_service" in ''|*[!A-Za-z0-9_.-]*) printf 'Invalid Compose service identity.\n' >&2; exit 1;; esac
9494
+ case "$lookup_selector" in -q|-aq) ;; *) printf 'Invalid Compose container selector.\n' >&2; exit 1;; esac
9495
+ lookup_ids=''
9496
+ for lookup_namespace in com.docker.compose io.podman.compose; do
9497
+ lookup_found=$("$lookup_runtime" ps "$lookup_selector" --filter "label=$lookup_namespace.project=$lookup_project" --filter "label=$lookup_namespace.service=$lookup_service") || { printf 'Cannot query Compose containers.\n' >&2; exit 1; }
9498
+ printf '%s\n' "$lookup_found" | LC_ALL=C awk 'NF {if(NF!=1 || $0!~/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/) exit 1}' || { printf 'Invalid Compose container identifiers.\n' >&2; exit 1; }
9499
+ lookup_ids=$(printf '%s\n%s\n' "$lookup_ids" "$lookup_found" | LC_ALL=C awk 'NF && !seen[$0]++ {print}') || exit 1
9500
+ done
9501
+ for lookup_id in $lookup_ids; do
9502
+ lookup_labels=$("$lookup_runtime" inspect --format '${labelFormat}' "$lookup_id") || { printf 'Cannot inspect Compose container labels.\n' >&2; exit 1; }
9503
+ printf '%s\n' "$lookup_labels" | LC_ALL=C awk -F'|' -v project="$lookup_project" -v service="$lookup_service" '
9504
+ NR!=1 || NF!=4 {exit 1}
9505
+ {
9506
+ docker=($1!="" && $1!="<no value>") || ($2!="" && $2!="<no value>")
9507
+ podman=($3!="" && $3!="<no value>") || ($4!="" && $4!="<no value>")
9508
+ if((!docker && !podman) || (docker && ($1!=project || $2!=service)) || (podman && ($3!=project || $4!=service))) exit 1
9509
+ }
9510
+ END {if(NR!=1) exit 1}
9511
+ ' || { printf 'Container labels do not confirm the requested Compose project and service.\n' >&2; exit 1; }
9512
+ done
9513
+ if [ -n "$lookup_ids" ]; then printf '%s\n' "$lookup_ids"; fi
9514
+ )
9515
+ `;
9516
+ }
9517
+
8600
9518
  // src/container/deploy-runtime-script.ts
8601
9519
  function createRuntimeConfigurationScript(primaryService) {
8602
9520
  return String.raw`configuration_mount() {
@@ -9256,7 +10174,7 @@ if [ "$partial" = true ]; then
9256
10174
  fi
9257
10175
  ;;
9258
10176
  esac
9259
- # 目录属于完整Web拓扑;旧版本缺省为根目录,服务增量不能意外丢失或改变既有目录。
10177
+ # Web 挂载目录由完整拓扑确定,服务增量必须匹配已部署的目录。
9260
10178
  current_web_base=$(deployment_json read metadata.web-base-path string "$source_dir/manifest.json")
9261
10179
  previous_web_base=$(deployment_json read metadata.web-base-path string "$root/releases/$previous/manifest.json")
9262
10180
  test "$current_web_base" = "$previous_web_base" || { printf 'Web base path changed. Match the installed prefix for partial updates, or create a full deployment.\n' >&2; exit 1; }
@@ -10152,7 +11070,7 @@ async function createDeploymentBundle(options) {
10152
11070
  const metadata = {
10153
11071
  "layout": `${config.expose.hostPort}:${options.containerPort}:${options.hasWebConfig ? options.proxyService === "gateway" ? "gateway" : "true" : "false"}:${artifact.primaryService}:${options.hasWebConfig ? await artifactChecksum(join(directory, "web.conf")) : ""}${httpsFingerprint ? `:${httpsFingerprint}` : ""}
10154
11072
  `,
10155
- "web-base-path": `${artifact.webBasePath ?? ""}
11073
+ "web-base-path": `${artifact.webBasePath}
10156
11074
  `,
10157
11075
  "health-paths": `${options.healthPaths.join("\n")}
10158
11076
  `,
@@ -10165,7 +11083,6 @@ async function createDeploymentBundle(options) {
10165
11083
  "node-runtime": String(nodeRuntime),
10166
11084
  "proxy-service": options.hasWebConfig ? options.proxyService ?? "web" : "",
10167
11085
  "https": options.httpsAssets?.https ?? "",
10168
- "managed-project": managed["managed-project"],
10169
11086
  // 未选应用时只带容器原生绑定,保持当前发布的应用连接归属。
10170
11087
  "managed-bindings": updatesApp ? managed["managed-bindings"] : managed["managed-bindings"].replace(/^.* prefix\n/gm, ""),
10171
11088
  "managed-volumes": managed["managed-volumes"],
@@ -11470,7 +12387,7 @@ function makeDeploymentId() {
11470
12387
  return `${core.time.formatDate(/* @__PURE__ */ new Date(), "YYYYMMDD-HHmmss")}-${randomBytes(3).toString("hex")}`;
11471
12388
  }
11472
12389
  function sleep(ms) {
11473
- return new Promise((resolve9) => setTimeout(resolve9, ms));
12390
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
11474
12391
  }
11475
12392
  function createDockerSshProvider(config, container) {
11476
12393
  const ssh = config.ssh ? createSshExecutor(config.ssh) : void 0;
@@ -11502,16 +12419,16 @@ function createDockerSshProvider(config, container) {
11502
12419
  for (let attempt = 0; attempt < HEALTH_MAX_ATTEMPTS; attempt++) {
11503
12420
  try {
11504
12421
  if (certificate) {
11505
- const healthy = await new Promise((resolve9) => {
12422
+ const healthy = await new Promise((resolve8) => {
11506
12423
  const request$1 = request(url, {
11507
12424
  ca: [...rootCertificates, certificate],
11508
12425
  timeout: 5e3
11509
12426
  }, (response) => {
11510
12427
  response.resume();
11511
- resolve9(response.statusCode !== void 0 && response.statusCode >= 200 && response.statusCode < 300);
12428
+ resolve8(response.statusCode !== void 0 && response.statusCode >= 200 && response.statusCode < 300);
11512
12429
  });
11513
- request$1.on("error", () => resolve9(false));
11514
- request$1.on("close", () => resolve9(false));
12430
+ request$1.on("error", () => resolve8(false));
12431
+ request$1.on("close", () => resolve8(false));
11515
12432
  request$1.on("timeout", () => request$1.destroy());
11516
12433
  request$1.end();
11517
12434
  });
@@ -11562,7 +12479,7 @@ function createDockerSshProvider(config, container) {
11562
12479
  const webContainerPort = web?.containerPort ?? artifact.webContainerPort ?? 8080;
11563
12480
  const host = config.ssh?.host;
11564
12481
  const url = https ? `https://${https.domain}${https.port === 443 ? "" : `:${https.port}`}` : host ? `http://${host.includes(":") ? `[${host}]` : host}:${hostPort}` : `http://SERVER_HOST:${hostPort}`;
11565
- const webBasePath = artifact.webBasePath ?? "";
12482
+ const webBasePath = artifact.webBasePath;
11566
12483
  const applicationUrl = `${url}${webBasePath ? `${webBasePath}/` : ""}`;
11567
12484
  const standaloneWebConfig = primaryService === "web" && !scan.isSvelteKit && (!!webBasePath || !!https);
11568
12485
  const healthPaths = hasWebProxy ? ["/_hai/web-health", `${webBasePath}/`] : scan.isSvelteKit ? [`${webBasePath}/`] : primaryService === "serv" ? [] : standaloneWebConfig ? ["/health", `${webBasePath}/`] : ["/health"];
@@ -12340,7 +13257,7 @@ async function pollDeploymentStatus(token, deploymentId) {
12340
13257
  if (deployment.readyState === "ERROR" || deployment.readyState === "CANCELED") {
12341
13258
  return "error";
12342
13259
  }
12343
- await new Promise((resolve9) => setTimeout(resolve9, POLL_INTERVAL));
13260
+ await new Promise((resolve8) => setTimeout(resolve8, POLL_INTERVAL));
12344
13261
  }
12345
13262
  return "building";
12346
13263
  }
@@ -13161,8 +14078,6 @@ var deploy = {
13161
14078
  if (!currentProvider && !currentContainerProvider) {
13162
14079
  return notInitialized.result();
13163
14080
  }
13164
- if (options && "projectName" in options && options.projectName !== void 0)
13165
- return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_projectNameOverrideRemoved"));
13166
14081
  logger12.info("Starting deployment", { appDir, environment: options?.environment });
13167
14082
  const scanOptions = { ...options, environment: options?.environment ?? currentConfig?.environment };
13168
14083
  const scanResult = await scanApp(appDir, scanOptions);
@@ -13896,8 +14811,6 @@ function describeMiddlewareBackend(backend) {
13896
14811
  return deployM(backend.purpose);
13897
14812
  }
13898
14813
  async function createDeploymentPlan(appDir, input, options = {}) {
13899
- if ("projectName" in options && options.projectName !== void 0)
13900
- return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_projectNameOverrideRemoved"));
13901
14814
  const parsed = DeployConfigSchema.safeParse(input);
13902
14815
  if (!parsed.success) {
13903
14816
  return err({
@@ -14098,389 +15011,7 @@ async function createDeploymentPlan(appDir, input, options = {}) {
14098
15011
  return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_artifactInvalid"));
14099
15012
  }
14100
15013
  }
14101
- var nameSchema = z.string().regex(/^[a-z0-9][\w.-]{0,127}$/i);
14102
- var passwordSchema = z.string().min(1).max(1024).refine((value) => Buffer.from(value, "utf8").toString("utf8") === value && [...value].every((character) => character.codePointAt(0) >= 32 && character.codePointAt(0) !== 127));
14103
- var importSchema = z.strictObject({
14104
- version: z.literal(1),
14105
- projectName: nameSchema,
14106
- credentials: z.strictObject({
14107
- DB_PASSWORD: passwordSchema.optional(),
14108
- STORAGE_PASSWORD: passwordSchema.optional(),
14109
- LDAP_PASSWORD: passwordSchema.optional()
14110
- }),
14111
- services: z.array(z.strictObject({
14112
- service: z.enum(["db", "vecdb", "storage", "ldap"]),
14113
- container: nameSchema,
14114
- kind: z.enum(["postgresql", "mysql", "s3", "ldap"]),
14115
- volumes: z.array(nameSchema).min(1).max(8),
14116
- username: z.string().min(1).max(128).default("hai"),
14117
- database: z.string().min(1).max(128).default("hai"),
14118
- bucket: z.string().regex(/^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/).default("hai"),
14119
- accessKeyId: z.string().min(1).max(128).default("hai-storage"),
14120
- region: z.string().regex(/^[a-z0-9-]+$/).default("us-east-1"),
14121
- bindDn: z.string().min(1).max(512).default("cn=admin,dc=example,dc=org")
14122
- })).min(1).max(4)
14123
- });
14124
- var groups2 = {
14125
- db: "DB_PASSWORD",
14126
- vecdb: "DB_PASSWORD",
14127
- storage: "STORAGE_PASSWORD",
14128
- ldap: "LDAP_PASSWORD"
14129
- };
14130
- var importCommitScript = String.raw`(async()=>{const fs=require('node:fs'),path=require('node:path'),crypto=require('node:crypto');const root=process.argv[1];let temporary;
14131
- try{process.stdout.write('LOCKED\n');let input='';for await(const chunk of process.stdin){input+=chunk;if(input.length>65536)throw Error()}
14132
- if(!input)return;const {content}=JSON.parse(input);if(typeof content!=='string'||!content.startsWith('FORMAT_VERSION=2\n'))throw Error();
14133
- const state=path.join(root,'managed-secrets.env');try{fs.lstatSync(state);throw Error('exists')}catch(error){if(error.code!=='ENOENT')throw error}
14134
- temporary=path.join(root,'.managed-secrets-'+crypto.randomUUID()+'.next');const fd=fs.openSync(temporary,fs.constants.O_WRONLY|fs.constants.O_CREAT|fs.constants.O_EXCL|fs.constants.O_NOFOLLOW,0o600);
14135
- try{fs.writeFileSync(fd,content);fs.fsyncSync(fd)}finally{fs.closeSync(fd)}fs.renameSync(temporary,state);temporary=undefined;
14136
- const directory=fs.openSync(root,fs.constants.O_RDONLY);try{fs.fsyncSync(directory)}finally{fs.closeSync(directory)}process.stdout.write('COMMITTED\n');
14137
- }catch{process.exitCode=1}finally{if(temporary)try{fs.unlinkSync(temporary)}catch{}}})()`;
14138
- var s3VerificationScript = String.raw`(async()=>{try{
14139
- const {createHash,createHmac}=require('node:crypto');let input='';for await(const chunk of process.stdin)input+=chunk;
14140
- const {endpoint,bucket,accessKeyId,secret,region}=JSON.parse(input);const url=new URL('/'+encodeURIComponent(bucket),endpoint);
14141
- const hash=value=>createHash('sha256').update(value).digest('hex');const hmac=(key,value)=>createHmac('sha256',key).update(value).digest();
14142
- const date=new Date().toISOString().replace(/[:-]|\.\d{3}/g,'');const day=date.slice(0,8);const payload=hash('');
14143
- const headers='host:'+url.host+'\nx-amz-content-sha256:'+payload+'\nx-amz-date:'+date+'\n';const signed='host;x-amz-content-sha256;x-amz-date';
14144
- const scope=day+'/'+region+'/s3/aws4_request';const canonical=['HEAD',url.pathname,'',headers,signed,payload].join('\n');
14145
- const key=hmac(hmac(hmac(hmac('AWS4'+secret,day),region),'s3'),'aws4_request');const signature=hmac(key,['AWS4-HMAC-SHA256',date,scope,hash(canonical)].join('\n')).toString('hex');
14146
- const response=await fetch(url,{method:'HEAD',redirect:'manual',signal:AbortSignal.timeout(10000),headers:{'x-amz-date':date,'x-amz-content-sha256':payload,Authorization:'AWS4-HMAC-SHA256 Credential='+accessKeyId+'/'+scope+', SignedHeaders='+signed+', Signature='+signature}});
14147
- await response.body?.cancel();if(response.ok)process.stdout.write('VERIFIED');else process.exitCode=1;
14148
- }catch{process.exitCode=1}})()`;
14149
- async function importDeploymentSecrets(options) {
14150
- let lock;
14151
- let commitAttempted = false;
14152
- let phase = "input";
14153
- try {
14154
- if (!["docker", "podman"].includes(options.runtime))
14155
- throw new Error("input");
14156
- const root = resolve(options.directory);
14157
- if (realpathSync(root) !== root || !lstatSync(root).isDirectory() || process.platform !== "win32" && lstatSync(root).mode & 18)
14158
- throw new Error("input");
14159
- const input = privateFile(resolve(options.inputFile));
14160
- const parsed = importSchema.safeParse(JSON.parse(input));
14161
- if (!parsed.success) {
14162
- return err({
14163
- ...HaiDeployError.CONFIG_ERROR,
14164
- message: deployM("deploy_secretImportFailed", {
14165
- params: { phase }
14166
- }),
14167
- ext: {
14168
- phase,
14169
- state: "unchanged",
14170
- fields: parsed.error.issues.map((issue) => issue.path.join("."))
14171
- }
14172
- });
14173
- }
14174
- const config = parsed.data;
14175
- const project = `hai-${config.projectName}`;
14176
- const selectedGroups = [
14177
- ...new Set(config.services.map((service) => groups2[service.service]))
14178
- ].sort();
14179
- if (new Set(config.services.map((service) => service.service)).size !== config.services.length || Object.keys(config.credentials).some((group) => !selectedGroups.includes(group)) || selectedGroups.some((group) => !config.credentials[group])) {
14180
- throw new Error("input");
14181
- }
14182
- const secrets = Object.values(config.credentials).filter((value) => typeof value === "string");
14183
- const stateFile = join(root, "managed-secrets.env");
14184
- const lockFile = join(root, ".deploy.lock");
14185
- phase = "lock";
14186
- if (options.dryRun === false) {
14187
- if (process.platform !== "linux")
14188
- throw new Error("lock");
14189
- if (existsSync(lockFile) && (lstatSync(lockFile).isSymbolicLink() || !lstatSync(lockFile).isFile() || lstatSync(lockFile).nlink !== 1))
14190
- throw new Error("lock");
14191
- lock = await acquireImportLock(lockFile, root);
14192
- }
14193
- phase = "identity";
14194
- const volumes = [];
14195
- for (const service of config.services) {
14196
- const kindValid = service.service === "storage" ? service.kind === "s3" : service.service === "ldap" ? service.kind === "ldap" : ["postgresql", "mysql"].includes(service.kind);
14197
- if (!kindValid)
14198
- throw new Error("identity");
14199
- if (["postgresql", "mysql"].includes(service.kind) && (service.username !== "hai" || service.database !== "hai") || service.kind === "s3" && (service.accessKeyId !== "hai-storage" || service.bucket !== "hai" || service.region !== "us-east-1") || service.kind === "ldap" && service.bindDn !== "cn=admin,dc=example,dc=org") {
14200
- throw new Error("identity");
14201
- }
14202
- const identity = await inspectContainer(options.runtime, service.container, secrets);
14203
- if (identity.project !== project || identity.service !== service.service || !identity.running)
14204
- throw new Error("identity");
14205
- for (const volume of service.volumes) {
14206
- if (!identity.volumes.includes(volume))
14207
- throw new Error("identity");
14208
- volumes.push({ group: groups2[service.service], name: volume });
14209
- }
14210
- phase = "verification";
14211
- const password = config.credentials[groups2[service.service]];
14212
- let verified = false;
14213
- if (service.kind === "s3") {
14214
- const helper2 = `hai-${config.projectName}-app`;
14215
- const application = await inspectContainer(options.runtime, helper2, secrets);
14216
- if (application.project !== project || application.service !== "app" || !application.running || !application.networks.some((network) => identity.networks.includes(network)))
14217
- throw new Error("identity");
14218
- const result = await runCommand(options.runtime, [
14219
- "exec",
14220
- "-i",
14221
- helper2,
14222
- "node",
14223
- "-e",
14224
- s3VerificationScript
14225
- ], {
14226
- input: JSON.stringify({
14227
- endpoint: `http://${service.container}:9000`,
14228
- bucket: service.bucket,
14229
- accessKeyId: service.accessKeyId,
14230
- secret: password,
14231
- region: service.region
14232
- }),
14233
- secrets,
14234
- timeoutMs: 15e3,
14235
- maxCaptureChars: 1024
14236
- });
14237
- verified = result.success && result.data.stdout.trim() === "VERIFIED";
14238
- } else {
14239
- const args = service.kind === "ldap" ? [
14240
- "exec",
14241
- "-i",
14242
- service.container,
14243
- "ldapwhoami",
14244
- "-x",
14245
- "-H",
14246
- "ldap://127.0.0.1:389",
14247
- "-D",
14248
- service.bindDn,
14249
- "-y",
14250
- "/dev/stdin"
14251
- ] : [
14252
- "exec",
14253
- "-i",
14254
- service.container,
14255
- "sh",
14256
- "-c",
14257
- service.kind === "postgresql" ? 'IFS= read -r PGPASSWORD; export PGPASSWORD; exec psql --no-psqlrc -h "$3" -U "$1" -d "dbname=$2 require_auth=scram-sha-256,md5,password" -Atc "SELECT 1"' : 'IFS= read -r MYSQL_PWD; export MYSQL_PWD; exec mysql -h 127.0.0.1 -u "$1" "$2" -Nse "SELECT 1"',
14258
- "hai-secret-check",
14259
- service.username,
14260
- service.database,
14261
- service.container
14262
- ];
14263
- const result = await runCommand(options.runtime, args, {
14264
- input: service.kind === "ldap" ? password : `${password}
14265
- `,
14266
- secrets,
14267
- timeoutMs: 15e3,
14268
- maxCaptureChars: 1024
14269
- });
14270
- verified = result.success && !result.data.stdoutTruncated && (service.kind === "ldap" ? result.data.stdout.trim().startsWith("dn:") : result.data.stdout.trim() === "1");
14271
- if (verified && service.kind === "mysql") {
14272
- const rootCheck = await runCommand(options.runtime, [
14273
- ...args.slice(0, -3),
14274
- "root",
14275
- service.database,
14276
- service.container
14277
- ], {
14278
- input: `${password}
14279
- `,
14280
- secrets,
14281
- timeoutMs: 15e3,
14282
- maxCaptureChars: 1024
14283
- });
14284
- verified = rootCheck.success && !rootCheck.data.stdoutTruncated && rootCheck.data.stdout.trim() === "1";
14285
- const invalidPassword = `hai-import-invalid-${randomUUID()}`;
14286
- for (const username of [service.username, "root"]) {
14287
- if (!verified)
14288
- break;
14289
- const negative = await runCommand(options.runtime, [
14290
- ...args.slice(0, -3),
14291
- username,
14292
- service.database,
14293
- service.container
14294
- ], {
14295
- input: `${invalidPassword}
14296
- `,
14297
- secrets: [...secrets, invalidPassword],
14298
- timeoutMs: 15e3,
14299
- maxCaptureChars: 1024
14300
- });
14301
- verified = !negative.success && /ERROR 1045 \(28000\)/.test(negative.error.message);
14302
- }
14303
- }
14304
- }
14305
- if (!verified)
14306
- throw new Error("verification");
14307
- phase = "identity";
14308
- }
14309
- phase = "state";
14310
- const uniqueVolumes = [
14311
- ...new Map(volumes.map((volume) => [`${volume.group}:${volume.name}`, volume])).values()
14312
- ].sort((a, b) => `${a.group}:${a.name}`.localeCompare(`${b.group}:${b.name}`));
14313
- const lines = [
14314
- "FORMAT_VERSION=2",
14315
- `PROJECT=${project}`,
14316
- ...selectedGroups.map((group) => `${group}=${Buffer.from(config.credentials[group], "utf8").toString("hex")}`),
14317
- ...uniqueVolumes.map((volume, index) => `VOLUME_${index + 1}=${volume.group}:${volume.name}`)
14318
- ];
14319
- let changed = false;
14320
- if (existsSync(stateFile) || isSymbolicLink(stateFile)) {
14321
- const existing = readImportedState(privateFile(stateFile));
14322
- if (existing.FORMAT_VERSION !== "2" || existing.PROJECT !== project || selectedGroups.some((group) => existing[group] !== Buffer.from(config.credentials[group], "utf8").toString("hex")) || uniqueVolumes.some((volume) => !Object.entries(existing).some(([key, value]) => /^VOLUME_\d+$/.test(key) && value === `${volume.group}:${volume.name}`)))
14323
- throw new Error("state");
14324
- } else if (options.dryRun === false) {
14325
- commitAttempted = true;
14326
- if (!lock || !await lock.commit(`${lines.join("\n")}
14327
- `))
14328
- throw new Error("lock");
14329
- changed = true;
14330
- }
14331
- return ok({
14332
- projectName: config.projectName,
14333
- stateVersion: 2,
14334
- groups: selectedGroups,
14335
- volumes: uniqueVolumes.map((volume) => volume.name),
14336
- changed
14337
- });
14338
- } catch {
14339
- return err({
14340
- ...HaiDeployError.CONFIG_ERROR,
14341
- message: deployM("deploy_secretImportFailed", {
14342
- params: { phase }
14343
- }),
14344
- ext: {
14345
- phase,
14346
- state: commitAttempted ? "unknown" : "unchanged"
14347
- }
14348
- });
14349
- } finally {
14350
- await lock?.release();
14351
- }
14352
- }
14353
- function readImportedState(text) {
14354
- const result = {};
14355
- for (const line of text.trimEnd().split("\n")) {
14356
- const match = /^(FORMAT_VERSION|PROJECT|DB_PASSWORD|STORAGE_PASSWORD|LDAP_PASSWORD|VOLUME_[1-9]\d*)=([^=]+)$/.exec(line);
14357
- if (!match || Object.hasOwn(result, match[1]))
14358
- throw new Error("state");
14359
- const key = match[1];
14360
- const value = match[2];
14361
- if (key.endsWith("_PASSWORD")) {
14362
- if (!/^(?:[a-f0-9]{2})+$/.test(value))
14363
- throw new Error("state");
14364
- const decoded = Buffer.from(value, "hex").toString("utf8");
14365
- if (!passwordSchema.safeParse(decoded).success || Buffer.from(decoded, "utf8").toString("hex") !== value)
14366
- throw new Error("state");
14367
- } else if (key.startsWith("VOLUME_") && !/^(?:DB_PASSWORD|STORAGE_PASSWORD|LDAP_PASSWORD):[a-zA-Z0-9][\w.-]*$/.test(value)) {
14368
- throw new Error("state");
14369
- }
14370
- result[key] = value;
14371
- }
14372
- if (result.FORMAT_VERSION !== "2" || !nameSchema.safeParse(result.PROJECT).success)
14373
- throw new Error("state");
14374
- return result;
14375
- }
14376
- function isSymbolicLink(path) {
14377
- try {
14378
- return lstatSync(path).isSymbolicLink();
14379
- } catch {
14380
- return false;
14381
- }
14382
- }
14383
- function privateFile(path) {
14384
- return readLocalFile(path, { maxBytes: 65536, private: true }).toString("utf8");
14385
- }
14386
- async function inspectContainer(runtime, container, secrets) {
14387
- const result = await runCommand(runtime, [
14388
- "inspect",
14389
- "--format",
14390
- "{{json .Config.Labels}}\n{{json .Mounts}}\n{{json .State.Running}}\n{{json .NetworkSettings.Networks}}",
14391
- container
14392
- ], { secrets, timeoutMs: 1e4, maxCaptureChars: 32768 });
14393
- if (!result.success || result.data.stdoutTruncated)
14394
- throw new Error("identity");
14395
- const [labelsText, mountsText, runningText, networksText] = result.data.stdout.trim().split("\n");
14396
- const labels = z.record(z.string(), z.string()).parse(JSON.parse(labelsText));
14397
- const mounts = z.array(z.object({
14398
- Type: z.string(),
14399
- Name: z.string().optional()
14400
- })).parse(JSON.parse(mountsText));
14401
- const networks = z.record(z.string(), z.object({
14402
- NetworkID: z.string()
14403
- })).parse(JSON.parse(networksText) ?? {});
14404
- const dockerProject = labels["com.docker.compose.project"] || void 0;
14405
- const podmanProject = labels["io.podman.compose.project"] || void 0;
14406
- const dockerService = labels["com.docker.compose.service"] || void 0;
14407
- const podmanService = labels["io.podman.compose.service"] || void 0;
14408
- if (Boolean(dockerProject) !== Boolean(dockerService) || Boolean(podmanProject) !== Boolean(podmanService) || dockerProject && podmanProject && dockerProject !== podmanProject || dockerService && podmanService && dockerService !== podmanService) {
14409
- throw new Error("identity");
14410
- }
14411
- return {
14412
- project: dockerProject ?? podmanProject,
14413
- service: dockerService ?? podmanService,
14414
- running: runningText === "true",
14415
- volumes: mounts.flatMap((mount) => mount.Type === "volume" && mount.Name ? [mount.Name] : []),
14416
- networks: Object.values(networks).map((network) => network.NetworkID)
14417
- };
14418
- }
14419
- async function acquireImportLock(path, root) {
14420
- return new Promise((resolveLock, reject) => {
14421
- const child = spawn("flock", [
14422
- "-n",
14423
- path,
14424
- process.execPath,
14425
- "-e",
14426
- importCommitScript,
14427
- root
14428
- ], {
14429
- stdio: ["pipe", "pipe", "ignore"],
14430
- windowsHide: true
14431
- });
14432
- let acquired = false;
14433
- let exited = false;
14434
- let output = "";
14435
- const finished = new Promise((done) => child.once("close", () => {
14436
- exited = true;
14437
- done();
14438
- }));
14439
- child.stdin.on("error", () => {
14440
- });
14441
- const timer = setTimeout(() => {
14442
- child.stdin.end();
14443
- child.kill();
14444
- reject(new Error("lock"));
14445
- }, 5e3);
14446
- child.once("error", () => {
14447
- clearTimeout(timer);
14448
- reject(new Error("lock"));
14449
- });
14450
- child.once("exit", () => {
14451
- if (!acquired) {
14452
- clearTimeout(timer);
14453
- reject(new Error("lock"));
14454
- }
14455
- });
14456
- child.stdout.on("data", (data) => {
14457
- output += data.toString();
14458
- if (acquired || !output.includes("\n"))
14459
- return;
14460
- if (!output.startsWith("LOCKED\n") || exited) {
14461
- child.stdin.end();
14462
- reject(new Error("lock"));
14463
- return;
14464
- }
14465
- acquired = true;
14466
- clearTimeout(timer);
14467
- resolveLock({
14468
- commit: async (content) => {
14469
- if (exited)
14470
- return false;
14471
- child.stdin.end(JSON.stringify({ content }));
14472
- await finished;
14473
- return child.exitCode === 0 && output === "LOCKED\nCOMMITTED\n";
14474
- },
14475
- release: async () => {
14476
- child.stdin.end();
14477
- await finished;
14478
- }
14479
- });
14480
- });
14481
- });
14482
- }
14483
15014
 
14484
- export { DEPLOY_MIDDLEWARE, DeployConfigSchema, DeploymentArtifactSchema, HaiDeployError, ProviderConfigSchema, artifactChecksum, canKeepMiddleware, createDeploymentPlan, deploy, describeMiddlewareBackend, getDeployCommandState, getDeployRecovery, importDeploymentSecrets, loadDeploymentConfig, loadDeploymentEnvironment, mergeEnvironmentLayers, normalizeProjectName, parseMiddlewareField, readDeploymentArtifact, resolveDownloadProxy, resolveImageReference, saveDeploymentEnvironment, saveMiddlewareEnvironment, validateMiddlewareField };
15015
+ export { DEPLOY_MIDDLEWARE, DeployConfigSchema, DeploymentArtifactSchema, HaiDeployError, ProviderConfigSchema, artifactChecksum, canKeepMiddleware, createDeploymentPlan, deploy, describeMiddlewareBackend, getDeployCommandState, getDeployRecovery, loadDeploymentConfig, loadDeploymentEnvironment, mergeEnvironmentLayers, normalizeProjectName, parseMiddlewareField, readDeploymentArtifact, resolveDownloadProxy, resolveImageReference, saveDeploymentEnvironment, saveMiddlewareEnvironment, validateMiddlewareField };
14485
15016
  //# sourceMappingURL=index.js.map
14486
15017
  //# sourceMappingURL=index.js.map