@sema-agent/settings-schema 10.1.1 → 11.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +469 -1
- package/README.md +144 -66
- package/bin/settings-schema.mjs +10 -12
- package/conformance/VERSION +1 -1
- package/conformance/baseline.json +3 -1
- package/conformance/cases/c01-entry-salvage-then-typo/expected.json +3 -1
- package/conformance/cases/c02-hosts-clamp-stacked/expected.json +12 -5
- package/conformance/cases/c02-hosts-clamp-stacked/meta.json +1 -1
- package/conformance/cases/c03-literal-dotted-top-key/expected.json +22 -2
- package/conformance/cases/c04-all-bad-collection-not-salvaged/expected.json +3 -1
- package/conformance/cases/c06-legacy-governance-lift/expected.json +11 -101
- package/conformance/cases/c06-legacy-governance-lift/meta.json +1 -1
- package/conformance/cases/c07-hooks-is-not-a-config-domain/expected.json +3 -1
- package/conformance/cases/c08-limits-unknown-carried-64/expected.json +203 -2
- package/conformance/cases/c09-limits-unknown-carried-65/expected.json +204 -2
- package/conformance/cases/c10-record-key-secret-redacted-twice/expected.json +3 -1
- package/conformance/cases/c11-unicode-unknown-keys/expected.json +20 -2
- package/conformance/cases/c12-prototype-name-top-keys/expected.json +3 -1
- package/conformance/cases/c13-models-field-null/expected.json +3 -1
- package/conformance/cases/c14-models-field-empty-array/expected.json +6 -1
- package/conformance/cases/c15-models-field-number/expected.json +3 -1
- package/conformance/cases/c16-deep-nesting-205/expected.json +14 -2
- package/conformance/cases/c17-empty-domain-file/expected.json +3 -1
- package/conformance/cases/c18-wire-envelope-not-an-object/expected.json +3 -1
- package/conformance/cases/c19-wire-envelope-no-version/expected.json +3 -1
- package/conformance/cases/c20-governance-absent-lifts/expected.json +11 -86
- package/conformance/cases/c20-governance-absent-lifts/meta.json +1 -1
- package/conformance/cases/c21-governance-empty-object-wins/expected.json +6 -5
- package/conformance/cases/c21-governance-empty-object-wins/input.json +1 -4
- package/conformance/cases/c21-governance-empty-object-wins/meta.json +1 -1
- package/conformance/cases/c23-mixed-validity-models/expected.json +3 -1
- package/conformance/cases/c24-valid-entries-with-unknown-keys/expected.json +22 -2
- package/conformance/cases/c25-published-roundtrip-zero-warnings/expected.json +6 -1
- package/conformance/cases/c26-hosts-array-1024/expected.json +3 -1
- package/conformance/cases/c27-hosts-array-1025/expected.json +6 -7175
- package/conformance/cases/c27-hosts-array-1025/meta.json +1 -1
- package/conformance/cases/c28-large-document-over-1mb/expected.json +14 -2
- package/conformance/cases/c29-salvage-then-domain-refine-fails/expected.json +3 -1
- package/conformance/cases/c30-version-triple-a-first/expected.json +6 -1
- package/conformance/cases/c31-version-triple-b-changed/expected.json +6 -1
- package/conformance/cases/c32-version-triple-a-restored/expected.json +6 -1
- package/conformance/cases/c33-limits-nested-carried-key/expected.json +18 -2
- package/conformance/cases/c34-skills-content-vs-manifest/expected.json +19 -3
- package/conformance/cases/c35-portable-domain-file-is-null/expected.json +3 -1
- package/conformance/cases/c36-skills-entry-without-content/expected.json +6 -2
- package/conformance/cases/c37-skills-field-null/expected.json +6 -2
- package/conformance/cases/c38-remote-exec-is-a-non-domain-file/expected.json +3 -1
- package/conformance/cases/c39-hidden-file-in-config-d/expected.json +3 -1
- package/conformance/cases/c40-s712-identity-only-what-reads-out/expected.json +11 -4
- package/conformance/cases/c41-s792-encoded-pem-identity/expected.json +9 -3
- package/conformance/cases/c42-s792-unknown-key-secret-redacted/expected.json +39 -2
- package/conformance/cases/c43-s792-form-and-deep-encoded-pem/expected.json +4 -1
- package/conformance/cases/c44-s792-utf8-escape-glued-token/expected.json +3 -1
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/expected.json +19 -10
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/meta.json +1 -1
- package/conformance/cases/{c46-s811-host-name-url-userinfo-characterization → c46-s811-host-name-url-userinfo-withheld}/expected.json +12 -5
- package/conformance/cases/c46-s811-host-name-url-userinfo-withheld/meta.json +9 -0
- package/conformance/cases/{c47-s811-roles-key-url-userinfo-characterization → c47-s811-roles-key-url-userinfo-withheld}/expected.json +23 -3
- package/conformance/cases/{c47-s811-roles-key-url-userinfo-characterization → c47-s811-roles-key-url-userinfo-withheld}/input.json +6 -0
- package/conformance/cases/c47-s811-roles-key-url-userinfo-withheld/meta.json +10 -0
- package/conformance/cases/c48-s935-model-headers-transported/expected.json +6 -1
- package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/expected.json +3 -1
- package/conformance/cases/c50-s784-lane-host-legal/expected.json +3 -1
- package/conformance/cases/c51-s784-lane-e2b-legal/expected.json +3 -1
- package/conformance/cases/c52-s784-lane-k8s-legal/expected.json +3 -1
- package/conformance/cases/c53-s784-lane-ssh-legal/expected.json +3 -1
- package/conformance/cases/c54-s784-lane-adb-legal/expected.json +3 -1
- package/conformance/cases/c55-s784-lane-local-docker-legal/expected.json +3 -1
- package/conformance/cases/c56-s784-lane-device-legal/expected.json +3 -1
- package/conformance/cases/c57-s784-g1-enabled-no-lane/expected.json +3 -1
- package/conformance/cases/c58-s784-g2-multitenant-host/expected.json +3 -1
- package/conformance/cases/c59-s784-g3-k8s-missing-image/expected.json +3 -1
- package/conformance/cases/c60-s784-g3-e2b-missing-key/expected.json +3 -1
- package/conformance/cases/c61-s784-g3-other-lane-key/expected.json +3 -1
- package/conformance/cases/c62-s784-g7a-session-tidb/expected.json +3 -1
- package/conformance/cases/c63-s784-host-two-replicas/expected.json +3 -1
- package/conformance/cases/c64-s784-multitenant-local-store/expected.json +3 -1
- package/conformance/cases/c65-s784-pg-without-db/expected.json +3 -1
- package/conformance/cases/c66-s784-memory-backend-mismatch/expected.json +3 -1
- package/conformance/cases/c67-s784-retired-field-loud/expected.json +3 -1
- package/conformance/cases/c67-s784-retired-field-loud/meta.json +1 -1
- package/conformance/cases/c68-s784-device-local-store/expected.json +3 -1
- package/conformance/cases/c69-deep-encoded-token-past-every-reading/expected.json +3 -1
- package/conformance/cases/c70-rewritten-token-in-front-of-encoded-pem-header/expected.json +3 -1
- package/conformance/cases/c71-one-scanner-write-reads-display-forms/expected.json +119 -0
- package/conformance/cases/c71-one-scanner-write-reads-display-forms/input.json +24 -0
- package/conformance/cases/c71-one-scanner-write-reads-display-forms/meta.json +10 -0
- package/conformance/cases/c72-dangling-reference-write-seat-refuses/expected.json +111 -0
- package/conformance/cases/c72-dangling-reference-write-seat-refuses/input.json +26 -0
- package/conformance/cases/c72-dangling-reference-write-seat-refuses/meta.json +6 -0
- package/conformance/cases/cc01-mcp-toolfaces-non-object/expected.json +3 -1
- package/conformance/cases/cc02-mcp-source-key-is-transported/expected.json +3 -1
- package/conformance/cases/cc03-model-compat-unknown-subkey/expected.json +6 -1
- package/conformance/cases/cli01-model-name-nfc-vs-nfd/expected.json +11 -1
- package/conformance/cases/cli02-control-and-zero-width-chars/expected.json +11 -1
- package/conformance/cases/cli03-credential-pasted-into-unknown-key/expected.json +16 -2
- package/conformance/cases/cli04-env-value-with-newline/expected.json +3 -1
- package/conformance/cases/cli05-long-and-prefix-colliding-names/expected.json +6 -1
- package/conformance/cases/t01-absent-domain-key-not-null/expected.json +3 -1
- package/conformance/cases/t03-prototype-name-key-not-swallowed/expected.json +29 -2
- package/conformance/cases/t04-secret-literal-never-in-warning-value/expected.json +3 -1
- package/conformance/cases/z01-every-domain-empty-object/expected.json +8 -4
- package/conformance/cases/z01-every-domain-empty-object/input.json +0 -1
- package/conformance/cases/z01-every-domain-empty-object/meta.json +1 -1
- package/conformance/cases/z02-every-domain-unknown-key/expected.json +163 -23
- package/conformance/cases/z02-every-domain-unknown-key/input.json +0 -3
- package/conformance/cases/z02-every-domain-unknown-key/meta.json +1 -1
- package/dist/api/auth-bridge.d.ts +51 -181
- package/dist/api/auth-bridge.js +58 -33
- package/dist/api/auth.d.ts +37 -146
- package/dist/api/auth.js +16 -26
- package/dist/api/feedback.d.ts +87 -0
- package/dist/api/feedback.js +66 -0
- package/dist/api/pull-token.d.ts +32 -0
- package/dist/api/pull-token.js +88 -0
- package/dist/api/scopes.d.ts +82 -174
- package/dist/api/scopes.js +23 -18
- package/dist/atomic-write.d.ts +14 -0
- package/dist/atomic-write.js +60 -0
- package/dist/bundle.d.ts +15 -7
- package/dist/bundle.js +28 -27
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +149 -80
- package/dist/config-d-file.d.ts +14 -0
- package/dist/config-d-file.js +67 -0
- package/dist/config-fns.d.ts +87 -186
- package/dist/config-fns.js +207 -262
- package/dist/conformance/contract-adapter.d.ts +9 -2
- package/dist/conformance/contract-adapter.js +15 -7
- package/dist/conformance/corpus.d.ts +3 -0
- package/dist/conformance/corpus.js +5 -0
- package/dist/conformance/derive.d.ts +3 -0
- package/dist/conformance/index.d.ts +2 -2
- package/dist/conformance/index.js +2 -2
- package/dist/conformance/run.js +67 -22
- package/dist/conformance/types.d.ts +31 -15
- package/dist/conformance/types.js +5 -0
- package/dist/conformance/warnings.d.ts +1 -1
- package/dist/conformance/warnings.js +1 -4
- package/dist/cross-domain.d.ts +66 -22
- package/dist/cross-domain.js +164 -93
- package/dist/cross-session.d.ts +29 -11
- package/dist/default-model.d.ts +43 -0
- package/dist/default-model.js +34 -0
- package/dist/execution-lane-index.d.ts +4 -8
- package/dist/execution-lane-index.js +4 -8
- package/dist/execution-lanes.d.ts +31 -8
- package/dist/execution-lanes.js +23 -5
- package/dist/file-edit.d.ts +3 -3
- package/dist/file-edit.js +11 -18
- package/dist/file-store.d.ts +7 -6
- package/dist/file-store.js +37 -50
- package/dist/fleet.d.ts +364 -762
- package/dist/fleet.js +277 -224
- package/dist/hooks.d.ts +1151 -4737
- package/dist/hooks.js +59 -15
- package/dist/index.d.ts +10 -4
- package/dist/index.js +13 -7
- package/dist/integer.d.ts +11 -0
- package/dist/integer.js +11 -0
- package/dist/issue-path.d.ts +10 -0
- package/dist/issue-path.js +10 -0
- package/dist/local-load.d.ts +1 -1
- package/dist/local-load.js +4 -14
- package/dist/loopback.d.ts +26 -0
- package/dist/loopback.js +54 -0
- package/dist/migration-receipt.d.ts +42 -0
- package/dist/migration-receipt.js +12 -0
- package/dist/one-time-migrations.d.ts +62 -0
- package/dist/one-time-migrations.js +828 -0
- package/dist/prompts-digest.d.ts +1 -1
- package/dist/prompts-digest.js +3 -2
- package/dist/reader.d.ts +2 -2
- package/dist/remote-exec.d.ts +45 -187
- package/dist/remote-exec.js +16 -10
- package/dist/resolve-roster.d.ts +18 -3
- package/dist/resolve-roster.js +28 -25
- package/dist/retired-envs.d.ts +24 -0
- package/dist/retired-envs.js +114 -0
- package/dist/retired-form.d.ts +31 -0
- package/dist/retired-form.js +28 -0
- package/dist/safety-merge-spec.d.ts +131 -191
- package/dist/safety-merge-spec.js +18 -14
- package/dist/scheduler-lifetime.d.ts +5 -0
- package/dist/scheduler-lifetime.js +7 -0
- package/dist/scheduler-store-migration.d.ts +14 -0
- package/dist/scheduler-store-migration.js +14 -0
- package/dist/scheduler-store-node.d.ts +28 -12
- package/dist/scheduler-store-node.js +32 -23
- package/dist/scheduler-store.d.ts +47 -14
- package/dist/scheduler-store.js +32 -61
- package/dist/secret-refs.d.ts +3 -3
- package/dist/secret-refs.js +1 -1
- package/dist/secret-scan.d.ts +14 -24
- package/dist/secret-scan.js +45 -23
- package/dist/sha256-core.d.ts +16 -0
- package/dist/sha256-core.js +138 -0
- package/dist/sha256.d.ts +6 -1
- package/dist/sha256.js +8 -98
- package/dist/skills-manifest.js +1 -1
- package/dist/types.d.ts +1509 -13536
- package/dist/types.js +526 -438
- package/dist/url-shape.d.ts +34 -0
- package/dist/url-shape.js +61 -0
- package/dist/web-admin-credential.d.ts +96 -0
- package/dist/{center-credential.js → web-admin-credential.js} +49 -46
- package/dist/web-admin-env.d.ts +33 -0
- package/dist/web-admin-env.js +33 -0
- package/dist/worker-row-verdict.d.ts +16 -0
- package/dist/worker-row-verdict.js +31 -0
- package/dist/workers-rows.d.ts +34 -0
- package/dist/workers-rows.js +42 -0
- package/dist/zod-unwrap.d.ts +11 -0
- package/dist/zod-unwrap.js +25 -0
- package/package.json +17 -9
- package/conformance/cases/c46-s811-host-name-url-userinfo-characterization/meta.json +0 -6
- package/conformance/cases/c47-s811-roles-key-url-userinfo-characterization/meta.json +0 -6
- package/conformance/migrate-workers/m01-g5-k8s-env-lifted/expected.json +0 -49
- package/conformance/migrate-workers/m01-g5-k8s-env-lifted/input.json +0 -26
- package/conformance/migrate-workers/m01-g5-k8s-env-lifted/meta.json +0 -4
- package/conformance/migrate-workers/m02-g5-idempotent/expected.json +0 -38
- package/conformance/migrate-workers/m02-g5-idempotent/input.json +0 -33
- package/conformance/migrate-workers/m02-g5-idempotent/meta.json +0 -5
- package/conformance/migrate-workers/m03-g5-refused-no-lane/expected.json +0 -71
- package/conformance/migrate-workers/m03-g5-refused-no-lane/input.json +0 -42
- package/conformance/migrate-workers/m03-g5-refused-no-lane/meta.json +0 -4
- package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/expected.json +0 -52
- package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/input.json +0 -30
- package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/meta.json +0 -4
- package/conformance/migrate-workers/m05-row-verdict-reserved-env/expected.json +0 -82
- package/conformance/migrate-workers/m05-row-verdict-reserved-env/input.json +0 -68
- package/conformance/migrate-workers/m05-row-verdict-reserved-env/meta.json +0 -4
- package/dist/center-credential.d.ts +0 -116
- package/dist/issue-text.d.ts +0 -10
- package/dist/issue-text.js +0 -8
- package/dist/migrate-workers.d.ts +0 -71
- package/dist/migrate-workers.js +0 -351
- package/dist/migrate.d.ts +0 -255
- package/dist/migrate.js +0 -542
- /package/conformance/cases/{c46-s811-host-name-url-userinfo-characterization → c46-s811-host-name-url-userinfo-withheld}/input.json +0 -0
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 中心(config-center 产品)凭证的**文件形契约** —— S-698(design/325 §1)。
|
|
3
|
-
*
|
|
4
|
-
* 引擎向中心出站时用的凭证由部署声明(见 server `src/center-credential.ts`,单属主);声明成文件时
|
|
5
|
-
* (`SEMA_REGISTRY_TOKEN_FILE`),文件内容有两种**种类**,不是一种语义的两种拼法:
|
|
6
|
-
*
|
|
7
|
-
* · **裸行**(一个静态令牌,种类与今天相同):首行 = 静态 bearer,只收可打印 ASCII({@link TokenString} 的律)。它是用户 JWT 还是 worker
|
|
8
|
-
* 拉取令牌,由读方用 `looksLikeJwt`(`./api/auth-bridge`)自判 —— 本文件不重判种类。
|
|
9
|
-
* · **信封**(新):一个 JSON 对象,形 = {@link CenterCredentialEnvelope}。今只有一员 `kind:"user-session"`
|
|
10
|
-
* (人的登录会话:可轮换的访问令牌 + 单次有效的刷新令牌 + 到期时刻 + 签发它的中心根 URL)。worker 拉取
|
|
11
|
-
* 令牌**不进信封**:它不轮换、不属于人,保持裸行。
|
|
12
|
-
*
|
|
13
|
-
* 判别律(一条,{@link parseCenterCredentialFile} 是它的**唯一**实现):内容 `trim()` 后以 `{` 开头 ⇒ 只按
|
|
14
|
-
* 信封读,读不成 = `unreadable`(**不回落**成裸行 —— 一个 `{` 开头的裸 token 不存在,回落只会把一段坏 JSON
|
|
15
|
-
* 当 bearer 发出去);否则裸行;空 = `unreadable`。同一份内容只有一种读法(零双读)。
|
|
16
|
-
*
|
|
17
|
-
* 声明面(S-742,本文件尾段):两个声明键名 {@link CENTER_CREDENTIAL_ENV} 与二选一判官
|
|
18
|
-
* {@link judgeCenterCredentialDeclaration} —— 规则此前只活在 server,管理面 / 壳各自手抄键名。
|
|
19
|
-
*
|
|
20
|
-
* 纯契约:零 IO(读文件、写文件、续签都是消费方的事),browser-safe,与本包其余部分同一条纯度律。
|
|
21
|
-
*/
|
|
22
|
-
import { z } from "zod";
|
|
23
|
-
/** 信封 `kind` 的闭集今只有一员(`SEALED_BOX_ALG` 同式的字面常量:schema 的字面与读方的比较都引它)。 */
|
|
24
|
-
export declare const CENTER_CREDENTIAL_ENVELOPE_KIND: "user-session";
|
|
25
|
-
/**
|
|
26
|
-
* 中心凭证**信封**(`SEMA_REGISTRY_TOKEN_FILE` 指向的文件是一个 JSON 对象时的唯一形)。`.strict()`:
|
|
27
|
-
* 多一个键就不是这份契约(拼错的 `refresh_token` 被静默剥掉,续签就会拿着缺席的刷新令牌去撞中心)。
|
|
28
|
-
*
|
|
29
|
-
* · `accessToken` —— 出站 bearer(中心签的用户 JWT,形判 `looksLikeJwt`);读方拿它做身份分箱的方式与裸行
|
|
30
|
-
* `user-jwt` 逐字同。令牌形与裸行同一条律({@link TokenString})。
|
|
31
|
-
* · `refreshToken` —— 单次有效、严格轮换(`POST /api/v1/auth/token/refresh`,见 `./api/auth`);续签前**重读
|
|
32
|
-
* 文件**取当前值,不用内存旧值(另一个写者可能刚轮换过)。
|
|
33
|
-
* · `expiresAt` —— 访问令牌到期时刻(RFC 3339)。
|
|
34
|
-
* · `issuer` —— 签发它的中心的**基础 URL**(与读方 `configCenter.url` 同形,可带路径前缀):续签只向同源的中心发,
|
|
35
|
-
* 同不同源由读方比。URL 形判据与 `remoteExec` 的端点同一只 {@link NON_SECRET_URL}(http(s)、有 host、userinfo 与
|
|
36
|
-
* query / fragment 里不许夹带凭据)—— 本包不另写一份更窄的 URL 律。
|
|
37
|
-
*/
|
|
38
|
-
export declare const CenterCredentialEnvelope: z.ZodObject<{
|
|
39
|
-
kind: z.ZodLiteral<"user-session">;
|
|
40
|
-
accessToken: z.ZodEffects<z.ZodString, string, string>;
|
|
41
|
-
refreshToken: z.ZodString;
|
|
42
|
-
expiresAt: z.ZodString;
|
|
43
|
-
issuer: z.ZodEffects<z.ZodString, string, string>;
|
|
44
|
-
}, "strict", z.ZodTypeAny, {
|
|
45
|
-
kind: "user-session";
|
|
46
|
-
issuer: string;
|
|
47
|
-
accessToken: string;
|
|
48
|
-
refreshToken: string;
|
|
49
|
-
expiresAt: string;
|
|
50
|
-
}, {
|
|
51
|
-
kind: "user-session";
|
|
52
|
-
issuer: string;
|
|
53
|
-
accessToken: string;
|
|
54
|
-
refreshToken: string;
|
|
55
|
-
expiresAt: string;
|
|
56
|
-
}>;
|
|
57
|
-
export type CenterCredentialEnvelope = z.infer<typeof CenterCredentialEnvelope>;
|
|
58
|
-
/** {@link parseCenterCredentialFile} 的三态结果(判别键 `kind`)。`unreadable.reason` 只说**哪里**不对
|
|
59
|
-
* (信封字段名 / zod 问题码 / 未知键**个数**),从不回显文件内容 —— 文件里装的就是凭据。 */
|
|
60
|
-
export type CenterCredentialFile = {
|
|
61
|
-
kind: "bare";
|
|
62
|
-
bearer: string;
|
|
63
|
-
} | {
|
|
64
|
-
kind: "envelope";
|
|
65
|
-
envelope: CenterCredentialEnvelope;
|
|
66
|
-
} | {
|
|
67
|
-
kind: "unreadable";
|
|
68
|
-
reason: string;
|
|
69
|
-
};
|
|
70
|
-
/**
|
|
71
|
-
* 凭证文件内容 → 三态({@link CenterCredentialFile})。判别律见模块头注,本函数是它的唯一实现:
|
|
72
|
-
*
|
|
73
|
-
* 1. `trim()`(含 BOM);空 ⇒ `unreadable("empty")`。
|
|
74
|
-
* 2. 以 `{` 开头 ⇒ 只按 {@link CenterCredentialEnvelope} 读:JSON 不成立或 schema 不过 ⇒ `unreadable`
|
|
75
|
-
* (**不回落**裸行)。
|
|
76
|
-
* 3. 否则 ⇒ 首行(第一个 CR 或 LF 之前,再剥一次空白)。它是令牌({@link TokenString} 的律)⇒ `bare`;
|
|
77
|
-
* 不是 ⇒ `unreadable`(放不进 Authorization 头的行不是 bearer)。多行文件只认首行。
|
|
78
|
-
*
|
|
79
|
-
* 纯函数、零 IO;读方负责读文件(以及文件不可读时自己的那条 `unreadable` 臂)。
|
|
80
|
-
*/
|
|
81
|
-
export declare function parseCenterCredentialFile(text: string): CenterCredentialFile;
|
|
82
|
-
/**
|
|
83
|
-
* 中心凭证的两个**声明键名**(部署 env)。一处定义、三方 import(server 读、管理面给 worker 渲染、壳给本机
|
|
84
|
-
* 引擎写)—— 键名是 wire 面,手抄的键名会静默漂。
|
|
85
|
-
*
|
|
86
|
-
* · `token`(`SEMA_REGISTRY_TOKEN`)—— 值即凭证(部署注入;读方在进程生命期内读一次)。
|
|
87
|
-
* · `tokenFile`(`SEMA_REGISTRY_TOKEN_FILE`)—— 凭证文件的路径,读方**每次取值都读文件**(轮换 / 换账号只需
|
|
88
|
-
* 改写文件,不重启);文件内容的读法见 {@link parseCenterCredentialFile}。
|
|
89
|
-
*/
|
|
90
|
-
export declare const CENTER_CREDENTIAL_ENV: {
|
|
91
|
-
readonly token: "SEMA_REGISTRY_TOKEN";
|
|
92
|
-
readonly tokenFile: "SEMA_REGISTRY_TOKEN_FILE";
|
|
93
|
-
};
|
|
94
|
-
/** {@link judgeCenterCredentialDeclaration} 的结果:凭证在哪里声明。`env` 形带**值** —— 它就是凭据,只在读方
|
|
95
|
-
* 自己的凭证模块里流转,不进日志。 */
|
|
96
|
-
export type CenterCredentialDeclaration = {
|
|
97
|
-
from: "env";
|
|
98
|
-
token: string;
|
|
99
|
-
} | {
|
|
100
|
-
from: "file";
|
|
101
|
-
path: string;
|
|
102
|
-
} | {
|
|
103
|
-
from: "absent";
|
|
104
|
-
};
|
|
105
|
-
/**
|
|
106
|
-
* 读两个声明键,判凭证在哪里声明 —— 二选一规则的**唯一**实现:
|
|
107
|
-
*
|
|
108
|
-
* · 空串 = 未设(`undefined` 同);其余一律算「设了」(不 trim —— 与规则上提前 server 的读法逐字同)。
|
|
109
|
-
* · 两个都设 ⇒ **throw**(读方拒启):两个写者写同一个语义面,哪个赢都是猜。拒句点名两个键、不回显值。
|
|
110
|
-
* · 只设 `tokenFile` ⇒ `{from:"file", path}`,`path` **原样**:相对路径按 cwd 解析是读方的事(纯契约不碰
|
|
111
|
-
* `node:path`,也不知道读方的 cwd)。
|
|
112
|
-
* · 只设 `token` ⇒ `{from:"env", token}`;都不设 ⇒ `{from:"absent"}`。
|
|
113
|
-
*
|
|
114
|
-
* 只读这两个键,别的 env 一概不看。
|
|
115
|
-
*/
|
|
116
|
-
export declare function judgeCenterCredentialDeclaration(env: Readonly<Record<string, string | undefined>>): CenterCredentialDeclaration;
|
package/dist/issue-text.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The ONE rendering of a zod issue into a sentence that may leave in a report which promises to name fields, never values (the
|
|
3
|
-
* workers migration's `refused[].reason`, the `migrate-workers` CLI report). Internal — not on any package subpath.
|
|
4
|
-
*
|
|
5
|
-
* zod's own messages name the expected shape, not the input, with one exception: the default `invalid_enum_value` message ends in
|
|
6
|
-
* `, received '<the value>'`. That issue is re-worded from its options alone (the closed set it expected); every other issue keeps
|
|
7
|
-
* its message as is. The result still goes through the usual secret redaction where the caller applies it.
|
|
8
|
-
*/
|
|
9
|
-
import type { z } from "zod";
|
|
10
|
-
export declare function issueText(issue: z.ZodIssue): string;
|
package/dist/issue-text.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* workers 域的**一次性迁移**(7.0.0,M-R2 WorkerSpec 结构块)—— 持久面唯一允许的过渡,窗口关了就整文件删除
|
|
3
|
-
* (连同 `types.ts` 里「删除项读到即拒」那一只预处理)。纯函数、无 I/O、不改入参;文件形的入口是
|
|
4
|
-
* `npx @sema-agent/settings-schema migrate-workers <file>`(`bin/settings-schema.mjs`,同一只函数;命令拼写的唯一正本 = `MIGRATE_WORKERS_COMMAND`)。
|
|
5
|
-
*
|
|
6
|
-
* ## 谁跑、在哪
|
|
7
|
-
* 读前抬升:在 `parseDomain("workers", raw)` / `DOMAIN_SCHEMAS.workers.safeParse` **之前**对**原始 JSON** 跑 —— workers 是
|
|
8
|
-
* 「读即抛」的 gate 域,6.x 存下来的行在 7.0.0 schema 上整域读不出来(删除项响亮拒、enabled 的 worker 缺两块)。中心在它唯一的
|
|
9
|
-
* workers 读口跑一次,结果写回;CLI 给文件后端 / 离线用。
|
|
10
|
-
*
|
|
11
|
-
* ## 规则(每个旧键恰好一种去处)
|
|
12
|
-
* · **车道**:`env.REMOTE_EXEC` 是车道词 ⇒ `runtime.execution.lane`;同车道的 env 键 ⇒ `coords`,同车道的 `secretEnv` 密钥 ⇒
|
|
13
|
-
* `secretRefs`(都按导出表 `EXECUTION_LANES` 的栏判)。别车道的键、`REQUIRE_EXECUTION_LANE` ⇒ **dropped**(引擎只读被点名车道
|
|
14
|
-
* 那一臂;声明了车道就恒渲 true)—— 今天就是惰性的。**enabled 且无 `REMOTE_EXEC` ⇒ refused**:那是「无手照回 completed」的
|
|
15
|
-
* 病例本身,迁移不替运维猜车道。停用且无 `REMOTE_EXEC` ⇒ 不写车道,残留车道键 dropped。
|
|
16
|
-
* · **店**:`sessionBackend` ⇒ `store.session`(`tidb` ⇒ `mysql`,且 `store.backend` 由此为 `mysql`);`memoryBackend` ⇒ **dropped**
|
|
17
|
-
* (它从未到达引擎);`coords.tidbHost` 在 ⇒ `store.backend = mysql`,`coords.tidb*` + `runtime.db` ⇒ `store.db`(缺端口 ⇒ 引擎
|
|
18
|
-
* `MYSQL_PORT` 的缺省 3306),`secretRefs.tidbPasswordEnv` ⇒ `secretRefs.dbPasswordEnv`;没有 host ⇒ `store.backend = local`
|
|
19
|
-
* (设计稿 §3.4:推一次、写死),零散坐标惰性 ⇒ dropped。`env` 逃生口里的 `DB_BACKEND` / `MEMORY_ENGINE` /
|
|
20
|
-
* `MEMORY_ENGINE_BACKEND`(渲染器以前不渲,运维只能写在那里)⇒ 搬进 `store`,显式的 `DB_BACKEND` 优先。停用且旧形没点名任何店
|
|
21
|
-
* (无 host、无 `DB_BACKEND`、无 `tidb` 词)⇒ 不写店,旧字段 dropped。
|
|
22
|
-
* · **表达不了 ⇒ refused**:值写错了栏(密钥以字面值写在 env、坐标来自宿主密钥)、不认识的词、新旧两形并存、`DB_BACKEND=pg`
|
|
23
|
-
* 配 MySQL 族坐标 —— 迁移不搬值进密钥位、不读宿主密钥、不替运维挑赢家、不换库。
|
|
24
|
-
*
|
|
25
|
-
* ## 终判(逐行:迁过的行与没动的行同一只)
|
|
26
|
-
* 每一只**认得出名字**的 worker —— 迁过的、没动的一样 —— 都过读侧自己的判据:**只含这一行的 workers 域**读不读得进
|
|
27
|
-
* (`WorkersConfig`,就是 `parseDomain("workers")` 用的那一只 schema)。读不进 ⇒ refused,拒因是 schema 自己的 issue(例:多租户 + host
|
|
28
|
-
* 车道、点了车道却缺必需坐标、`env` 里设了保留名、`replicas` 与 `autoscaling` 并存)。迁移不复述任何一条写闸规则,也不另抄保留名表:
|
|
29
|
-
* 判据只有读侧那一只。所以 `doc` 里每一只认得出名字的 worker 单独都读得进。
|
|
30
|
-
*
|
|
31
|
-
* 整域读侧仍是**最后围栏**,只剩两类归它:认不出名字的条目(不是对象 / 没有字符串名字 —— 迁移不判形,原样留在 `doc`)与跨行规则
|
|
32
|
-
* (名字 / 路由前缀唯一 —— 两行单独都读得进,合起来才不行,迁移不替运维挑留哪一行)。
|
|
33
|
-
*
|
|
34
|
-
* refused 的 worker **不进**输出 `doc`(中心不加载它、在列表标红点名 `field`;CLI 有 refused 就一字不写),但随 `refused[].original`
|
|
35
|
-
* 逐字带回:`doc` ∪ `refused[].original` 覆盖输入的全部 worker,中心写回前按 `index` 放回(或另存),运维的 spec 不会随迁移消失。
|
|
36
|
-
* 拒因 / dropped 只点名字段,不回显值。
|
|
37
|
-
*
|
|
38
|
-
* ## 幂等
|
|
39
|
-
* 已迁形且逐行读得进(没有任何旧键、该有的块都在)⇒ 原样(同一引用)返回、`changed = []`。所以再跑一次 ⇒ 零变化、`doc` 字节相等。
|
|
40
|
-
*/
|
|
41
|
-
import { type ConfigPath } from "./config-fns.js";
|
|
42
|
-
/** 一个被丢掉的旧字段(引擎本就不读它 —— 通知一行,不是错误)。`field` 相对该 worker 条目;`index` = 它在输入 `workers` 里的下标。 */
|
|
43
|
-
export interface WorkersMigrationDrop {
|
|
44
|
-
readonly name: string;
|
|
45
|
-
readonly index: number;
|
|
46
|
-
readonly field: ConfigPath;
|
|
47
|
-
}
|
|
48
|
-
/** 一只迁不了的 worker(不进输出 `doc`)。`field` 指到要人改的那一格(相对该 worker 条目;终判拒时是读侧 schema issue 的路径 ——
|
|
49
|
-
* 迁过的行指迁出形里的格,没动的行指存下来那一行的格),
|
|
50
|
-
* `reason` 只点名字段、不回显值。`original` = 输入里那一行**逐字**(同一个值,未拷贝未改写)——
|
|
51
|
-
* 🔴 **写回 `doc` 之前必须把每个 `refused[].original` 按 `index` 放回(或另存)**,否则这只 worker 的 spec 随写回从存储里消失,
|
|
52
|
-
* 运维看到的是「worker 不见了」而不是「worker 被标红」。它是调用方自己的数据,原样带回;要记日志,按调用方记那一行的规矩记。 */
|
|
53
|
-
export interface WorkersMigrationRefusal {
|
|
54
|
-
readonly name: string;
|
|
55
|
-
readonly index: number;
|
|
56
|
-
readonly field: ConfigPath;
|
|
57
|
-
readonly reason: string;
|
|
58
|
-
readonly original: unknown;
|
|
59
|
-
}
|
|
60
|
-
/** `migrateWorkersDomain` 的结果。`changed` = 被改写的 worker 名(含只丢了旧字段的)。 */
|
|
61
|
-
export interface WorkersDomainMigration {
|
|
62
|
-
readonly doc: unknown;
|
|
63
|
-
readonly changed: readonly string[];
|
|
64
|
-
readonly dropped: readonly WorkersMigrationDrop[];
|
|
65
|
-
readonly refused: readonly WorkersMigrationRefusal[];
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* 迁移一份 workers 域**原始**文档(`{ workers: [...] }`)。不是这个形 ⇒ 原样返回(判形是 `parseDomain` 的事,迁移不抛)。
|
|
69
|
-
* 规则、幂等与 refused 的处置见文件头。
|
|
70
|
-
*/
|
|
71
|
-
export declare function migrateWorkersDomain(doc: unknown): WorkersDomainMigration;
|
package/dist/migrate-workers.js
DELETED
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* workers 域的**一次性迁移**(7.0.0,M-R2 WorkerSpec 结构块)—— 持久面唯一允许的过渡,窗口关了就整文件删除
|
|
3
|
-
* (连同 `types.ts` 里「删除项读到即拒」那一只预处理)。纯函数、无 I/O、不改入参;文件形的入口是
|
|
4
|
-
* `npx @sema-agent/settings-schema migrate-workers <file>`(`bin/settings-schema.mjs`,同一只函数;命令拼写的唯一正本 = `MIGRATE_WORKERS_COMMAND`)。
|
|
5
|
-
*
|
|
6
|
-
* ## 谁跑、在哪
|
|
7
|
-
* 读前抬升:在 `parseDomain("workers", raw)` / `DOMAIN_SCHEMAS.workers.safeParse` **之前**对**原始 JSON** 跑 —— workers 是
|
|
8
|
-
* 「读即抛」的 gate 域,6.x 存下来的行在 7.0.0 schema 上整域读不出来(删除项响亮拒、enabled 的 worker 缺两块)。中心在它唯一的
|
|
9
|
-
* workers 读口跑一次,结果写回;CLI 给文件后端 / 离线用。
|
|
10
|
-
*
|
|
11
|
-
* ## 规则(每个旧键恰好一种去处)
|
|
12
|
-
* · **车道**:`env.REMOTE_EXEC` 是车道词 ⇒ `runtime.execution.lane`;同车道的 env 键 ⇒ `coords`,同车道的 `secretEnv` 密钥 ⇒
|
|
13
|
-
* `secretRefs`(都按导出表 `EXECUTION_LANES` 的栏判)。别车道的键、`REQUIRE_EXECUTION_LANE` ⇒ **dropped**(引擎只读被点名车道
|
|
14
|
-
* 那一臂;声明了车道就恒渲 true)—— 今天就是惰性的。**enabled 且无 `REMOTE_EXEC` ⇒ refused**:那是「无手照回 completed」的
|
|
15
|
-
* 病例本身,迁移不替运维猜车道。停用且无 `REMOTE_EXEC` ⇒ 不写车道,残留车道键 dropped。
|
|
16
|
-
* · **店**:`sessionBackend` ⇒ `store.session`(`tidb` ⇒ `mysql`,且 `store.backend` 由此为 `mysql`);`memoryBackend` ⇒ **dropped**
|
|
17
|
-
* (它从未到达引擎);`coords.tidbHost` 在 ⇒ `store.backend = mysql`,`coords.tidb*` + `runtime.db` ⇒ `store.db`(缺端口 ⇒ 引擎
|
|
18
|
-
* `MYSQL_PORT` 的缺省 3306),`secretRefs.tidbPasswordEnv` ⇒ `secretRefs.dbPasswordEnv`;没有 host ⇒ `store.backend = local`
|
|
19
|
-
* (设计稿 §3.4:推一次、写死),零散坐标惰性 ⇒ dropped。`env` 逃生口里的 `DB_BACKEND` / `MEMORY_ENGINE` /
|
|
20
|
-
* `MEMORY_ENGINE_BACKEND`(渲染器以前不渲,运维只能写在那里)⇒ 搬进 `store`,显式的 `DB_BACKEND` 优先。停用且旧形没点名任何店
|
|
21
|
-
* (无 host、无 `DB_BACKEND`、无 `tidb` 词)⇒ 不写店,旧字段 dropped。
|
|
22
|
-
* · **表达不了 ⇒ refused**:值写错了栏(密钥以字面值写在 env、坐标来自宿主密钥)、不认识的词、新旧两形并存、`DB_BACKEND=pg`
|
|
23
|
-
* 配 MySQL 族坐标 —— 迁移不搬值进密钥位、不读宿主密钥、不替运维挑赢家、不换库。
|
|
24
|
-
*
|
|
25
|
-
* ## 终判(逐行:迁过的行与没动的行同一只)
|
|
26
|
-
* 每一只**认得出名字**的 worker —— 迁过的、没动的一样 —— 都过读侧自己的判据:**只含这一行的 workers 域**读不读得进
|
|
27
|
-
* (`WorkersConfig`,就是 `parseDomain("workers")` 用的那一只 schema)。读不进 ⇒ refused,拒因是 schema 自己的 issue(例:多租户 + host
|
|
28
|
-
* 车道、点了车道却缺必需坐标、`env` 里设了保留名、`replicas` 与 `autoscaling` 并存)。迁移不复述任何一条写闸规则,也不另抄保留名表:
|
|
29
|
-
* 判据只有读侧那一只。所以 `doc` 里每一只认得出名字的 worker 单独都读得进。
|
|
30
|
-
*
|
|
31
|
-
* 整域读侧仍是**最后围栏**,只剩两类归它:认不出名字的条目(不是对象 / 没有字符串名字 —— 迁移不判形,原样留在 `doc`)与跨行规则
|
|
32
|
-
* (名字 / 路由前缀唯一 —— 两行单独都读得进,合起来才不行,迁移不替运维挑留哪一行)。
|
|
33
|
-
*
|
|
34
|
-
* refused 的 worker **不进**输出 `doc`(中心不加载它、在列表标红点名 `field`;CLI 有 refused 就一字不写),但随 `refused[].original`
|
|
35
|
-
* 逐字带回:`doc` ∪ `refused[].original` 覆盖输入的全部 worker,中心写回前按 `index` 放回(或另存),运维的 spec 不会随迁移消失。
|
|
36
|
-
* 拒因 / dropped 只点名字段,不回显值。
|
|
37
|
-
*
|
|
38
|
-
* ## 幂等
|
|
39
|
-
* 已迁形且逐行读得进(没有任何旧键、该有的块都在)⇒ 原样(同一引用)返回、`changed = []`。所以再跑一次 ⇒ 零变化、`doc` 字节相等。
|
|
40
|
-
*/
|
|
41
|
-
import { formatConfigPath, redactSecrets } from "./config-fns.js";
|
|
42
|
-
import { LANE_ENV_OWNER, STORE_ENV_KEYS, isLaneEnvKey } from "./execution-lane-index.js";
|
|
43
|
-
import { EXECUTION_LANE_WORDS, MEMORY_ENGINE_BACKENDS, STORE_BACKENDS } from "./execution-lanes.js";
|
|
44
|
-
import { issueText } from "./issue-text.js";
|
|
45
|
-
import { WorkersConfig } from "./types.js";
|
|
46
|
-
function isRec(v) {
|
|
47
|
-
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
48
|
-
}
|
|
49
|
-
/** 6.x 删除项:`[容器, 键]`。 */
|
|
50
|
-
const SIX_X_STORE_FIELDS = [
|
|
51
|
-
["runtime", "sessionBackend"],
|
|
52
|
-
["runtime", "memoryBackend"],
|
|
53
|
-
["runtime", "db"],
|
|
54
|
-
["coords", "tidbHost"],
|
|
55
|
-
["coords", "tidbPort"],
|
|
56
|
-
["coords", "tidbUser"],
|
|
57
|
-
["secretRefs", "tidbPasswordEnv"],
|
|
58
|
-
];
|
|
59
|
-
/** 引擎 `MEMORY_ENGINE` 的两族词(server `src/config.ts` :3011–3012,比较前 trim + 小写)。 */
|
|
60
|
-
const MEMORY_ENGINE_ON_WORDS = ["on", "true", "1", "yes", "enabled"];
|
|
61
|
-
const MEMORY_ENGINE_OFF_WORDS = ["off", "false", "0", "no", "disabled"];
|
|
62
|
-
/** 引擎 `MYSQL_PORT` 的缺省(server `src/config.ts` :1818)—— 旧形没写端口时引擎连的就是它。 */
|
|
63
|
-
const MYSQL_DEFAULT_PORT = 3306;
|
|
64
|
-
function isLaneWord(v) {
|
|
65
|
-
return typeof v === "string" && EXECUTION_LANE_WORDS.some((w) => w === v);
|
|
66
|
-
}
|
|
67
|
-
function isStoreWord(v) {
|
|
68
|
-
return typeof v === "string" && STORE_BACKENDS.some((w) => w === v);
|
|
69
|
-
}
|
|
70
|
-
function isMemoryBackendWord(v) {
|
|
71
|
-
return typeof v === "string" && MEMORY_ENGINE_BACKENDS.some((w) => w === v);
|
|
72
|
-
}
|
|
73
|
-
/** 拒因里只点名形如词的值(短、小写);其余一律不回显(值可能是粘错位置的凭据)。 */
|
|
74
|
-
function describeWord(v) {
|
|
75
|
-
return typeof v === "string" && /^[a-z0-9][a-z0-9-]{0,31}$/.test(v) ? `"${v}"` : "(value not shown)";
|
|
76
|
-
}
|
|
77
|
-
const UNCHANGED = { kind: "unchanged" };
|
|
78
|
-
function migrateEntry(raw, index) {
|
|
79
|
-
if (!isRec(raw) || typeof raw.name !== "string")
|
|
80
|
-
return UNCHANGED; // 不是一只可辨认的 worker:判形是 parseDomain 的事
|
|
81
|
-
const name = raw.name;
|
|
82
|
-
const refuse = (field, reason) => ({ kind: "refused", refusal: { name, index, field, reason: redactSecrets(reason), original: raw } });
|
|
83
|
-
const enabled = raw.enabled !== false;
|
|
84
|
-
const has = (container, key) => isRec(container) && Object.hasOwn(container, key);
|
|
85
|
-
const laneKeysOf = (rec) => (isRec(rec) ? Object.keys(rec).filter(isLaneEnvKey) : []);
|
|
86
|
-
const storeKeysOf = (rec) => (isRec(rec) ? STORE_ENV_KEYS.filter((k) => Object.hasOwn(rec, k)) : []);
|
|
87
|
-
const envLaneKeys = laneKeysOf(raw.env);
|
|
88
|
-
const secretLaneKeys = laneKeysOf(raw.secretEnv);
|
|
89
|
-
const envStoreKeys = storeKeysOf(raw.env);
|
|
90
|
-
const secretStoreKeys = storeKeysOf(raw.secretEnv);
|
|
91
|
-
const oldStoreFields = SIX_X_STORE_FIELDS.filter(([c, k]) => has(raw[c], k));
|
|
92
|
-
const hasExecution = has(raw.runtime, "execution");
|
|
93
|
-
const hasStore = has(raw.runtime, "store");
|
|
94
|
-
const laneTouched = envLaneKeys.length + secretLaneKeys.length > 0;
|
|
95
|
-
const storeTouched = oldStoreFields.length + envStoreKeys.length + secretStoreKeys.length > 0;
|
|
96
|
-
if (hasExecution && laneTouched) {
|
|
97
|
-
return refuse(["runtime", "execution"], `declares runtime.execution AND execution-lane keys in env / secretEnv (${[...envLaneKeys, ...secretLaneKeys].join(", ")}) — keep one form; the migration does not pick a winner`);
|
|
98
|
-
}
|
|
99
|
-
// 新形的店字段有两处:`runtime.store` 与 `secretRefs.dbPasswordEnv`(gpt-6 腿 F1:只看前者时,手填过新口令引用、又留着旧
|
|
100
|
-
// `tidbPasswordEnv` 的行会被旧引用静默覆盖)。任一在场 ∧ 还有旧店字段 ⇒ 两形并存,不挑赢家。
|
|
101
|
-
const newStoreFields = [...(hasStore ? ["runtime.store"] : []), ...(has(raw.secretRefs, "dbPasswordEnv") ? ["secretRefs.dbPasswordEnv"] : [])];
|
|
102
|
-
if (newStoreFields.length > 0 && storeTouched) {
|
|
103
|
-
const old = [...oldStoreFields.map(([c, k]) => `${c}.${k}`), ...envStoreKeys.map((k) => `env.${k}`), ...secretStoreKeys.map((k) => `secretEnv.${k}`)];
|
|
104
|
-
return refuse(["runtime", "store"], `declares the 7.0.0 store fields (${newStoreFields.join(", ")}) AND 6.x store fields (${old.join(", ")}) — keep one form; the migration does not pick a winner`);
|
|
105
|
-
}
|
|
106
|
-
const secretStoreKey = secretStoreKeys[0];
|
|
107
|
-
if (secretStoreKey !== undefined)
|
|
108
|
-
return refuse(["secretEnv", secretStoreKey], `${secretStoreKey} is set from a host secret — the migration cannot read it; declare runtime.store by hand`);
|
|
109
|
-
if (has(raw.secretEnv, "REMOTE_EXEC"))
|
|
110
|
-
return refuse(["secretEnv", "REMOTE_EXEC"], "the execution lane is set from a host secret — the migration cannot read it; declare runtime.execution.lane by hand");
|
|
111
|
-
const needLane = !hasExecution && (enabled || laneTouched);
|
|
112
|
-
const needStore = !hasStore && (enabled || storeTouched);
|
|
113
|
-
if (!needLane && !needStore) {
|
|
114
|
-
const refusal = rowVerdict(raw, "stored");
|
|
115
|
-
return refusal === undefined ? UNCHANGED : refuse(refusal.field, refusal.reason);
|
|
116
|
-
}
|
|
117
|
-
const w = structuredClone(raw);
|
|
118
|
-
const runtime = isRec(w.runtime) ? w.runtime : {};
|
|
119
|
-
w.runtime = runtime;
|
|
120
|
-
const env = isRec(w.env) ? w.env : {};
|
|
121
|
-
const secretEnv = isRec(w.secretEnv) ? w.secretEnv : {};
|
|
122
|
-
const coords = isRec(w.coords) ? w.coords : {};
|
|
123
|
-
const secretRefs = isRec(w.secretRefs) ? w.secretRefs : {};
|
|
124
|
-
const dropped = [];
|
|
125
|
-
const drop = (container, slot, key) => {
|
|
126
|
-
delete container[key];
|
|
127
|
-
dropped.push({ name, index, field: [slot, key] });
|
|
128
|
-
};
|
|
129
|
-
// ── 车道 ─────────────────────────────────────────────────────────────────────────────────────────
|
|
130
|
-
if (needLane) {
|
|
131
|
-
const rx = env.REMOTE_EXEC;
|
|
132
|
-
if (rx === undefined || rx === "") {
|
|
133
|
-
if (enabled) {
|
|
134
|
-
return refuse(["env", "REMOTE_EXEC"], `an enabled worker names no execution lane (env.REMOTE_EXEC is unset) — it boots with no hands and still reports tasks completed. ` +
|
|
135
|
-
`The migration does not guess a lane: set runtime.execution.lane (${EXECUTION_LANE_WORDS.join(" | ")}) by hand, or disable the worker`);
|
|
136
|
-
}
|
|
137
|
-
for (const k of envLaneKeys)
|
|
138
|
-
drop(env, "env", k); // 停用且没点名车道 ⇒ 每个车道键都是惰性的
|
|
139
|
-
for (const k of secretLaneKeys)
|
|
140
|
-
drop(secretEnv, "secretEnv", k);
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
if (!isLaneWord(rx)) {
|
|
144
|
-
return refuse(["env", "REMOTE_EXEC"], `env.REMOTE_EXEC ${describeWord(rx)} is not an execution lane (${EXECUTION_LANE_WORDS.join(" | ")}) — the engine refuses to start with it; set runtime.execution.lane by hand`);
|
|
145
|
-
}
|
|
146
|
-
const lane = rx;
|
|
147
|
-
const laneCoords = {};
|
|
148
|
-
const laneSecretRefs = {};
|
|
149
|
-
for (const k of envLaneKeys) {
|
|
150
|
-
if (k === "REMOTE_EXEC") {
|
|
151
|
-
delete env[k];
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
const owner = LANE_ENV_OWNER.get(k);
|
|
155
|
-
if (owner === undefined || owner.lane !== lane) {
|
|
156
|
-
drop(env, "env", k); // REQUIRE_EXECUTION_LANE(声明了车道就恒渲 true)与别车道的键(引擎只读被点名那一臂)
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
const v = env[k];
|
|
160
|
-
if (owner.secret) {
|
|
161
|
-
return refuse(["env", k], `${k} is a secret of lane "${lane}" but env carries it as a literal value — the migration does not move a value into a secret slot: put it in the host secret source and reference it from runtime.execution.secretRefs.${k}`);
|
|
162
|
-
}
|
|
163
|
-
if (typeof v !== "string")
|
|
164
|
-
return refuse(["env", k], `env.${k} is not a string`);
|
|
165
|
-
laneCoords[k] = v;
|
|
166
|
-
delete env[k];
|
|
167
|
-
}
|
|
168
|
-
for (const k of secretLaneKeys) {
|
|
169
|
-
const owner = LANE_ENV_OWNER.get(k);
|
|
170
|
-
if (owner === undefined || owner.lane !== lane) {
|
|
171
|
-
drop(secretEnv, "secretEnv", k);
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
const v = secretEnv[k];
|
|
175
|
-
if (!owner.secret) {
|
|
176
|
-
return refuse(["secretEnv", k], `${k} is a non-secret coordinate of lane "${lane}" but is set from a host secret — the migration cannot read the value; write it in runtime.execution.coords.${k} by hand`);
|
|
177
|
-
}
|
|
178
|
-
if (typeof v !== "string")
|
|
179
|
-
return refuse(["secretEnv", k], `secretEnv.${k} is not a string`);
|
|
180
|
-
laneSecretRefs[k] = v;
|
|
181
|
-
delete secretEnv[k];
|
|
182
|
-
}
|
|
183
|
-
runtime.execution = { lane, coords: laneCoords, secretRefs: laneSecretRefs };
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
// ── 店 ───────────────────────────────────────────────────────────────────────────────────────────
|
|
187
|
-
if (needStore) {
|
|
188
|
-
if (Object.hasOwn(runtime, "memoryBackend"))
|
|
189
|
-
drop(runtime, "runtime", "memoryBackend"); // 从未到达引擎
|
|
190
|
-
const sessionRaw = runtime.sessionBackend;
|
|
191
|
-
const host = coords.tidbHost;
|
|
192
|
-
const hostSet = typeof host === "string" && host !== "";
|
|
193
|
-
const dbBackendRaw = env.DB_BACKEND;
|
|
194
|
-
const dbBackendSet = dbBackendRaw !== undefined && dbBackendRaw !== "";
|
|
195
|
-
const named = hostSet || dbBackendSet || sessionRaw === "tidb";
|
|
196
|
-
const sqlCoordFields = [
|
|
197
|
-
[runtime, "runtime", "db"],
|
|
198
|
-
[coords, "coords", "tidbHost"],
|
|
199
|
-
[coords, "coords", "tidbPort"],
|
|
200
|
-
[coords, "coords", "tidbUser"],
|
|
201
|
-
[secretRefs, "secretRefs", "tidbPasswordEnv"],
|
|
202
|
-
];
|
|
203
|
-
if (!enabled && !named) {
|
|
204
|
-
// 停用、旧形没点名任何店 ⇒ 不替它发明一个;旧字段全是惰性的
|
|
205
|
-
if (Object.hasOwn(runtime, "sessionBackend"))
|
|
206
|
-
drop(runtime, "runtime", "sessionBackend");
|
|
207
|
-
for (const [c, slot, k] of sqlCoordFields)
|
|
208
|
-
if (Object.hasOwn(c, k))
|
|
209
|
-
drop(c, slot, k);
|
|
210
|
-
for (const k of STORE_ENV_KEYS)
|
|
211
|
-
if (Object.hasOwn(env, k))
|
|
212
|
-
drop(env, "env", k);
|
|
213
|
-
}
|
|
214
|
-
else {
|
|
215
|
-
let session;
|
|
216
|
-
if (sessionRaw === undefined)
|
|
217
|
-
session = "auto"; // 6.x 的缺省
|
|
218
|
-
else if (sessionRaw === "tidb")
|
|
219
|
-
session = "mysql";
|
|
220
|
-
else if (sessionRaw === "memory" || sessionRaw === "auto")
|
|
221
|
-
session = sessionRaw;
|
|
222
|
-
else
|
|
223
|
-
return refuse(["runtime", "sessionBackend"], `runtime.sessionBackend ${describeWord(sessionRaw)} is not a 6.x session backend (tidb | memory | auto)`);
|
|
224
|
-
delete runtime.sessionBackend;
|
|
225
|
-
let backend;
|
|
226
|
-
if (dbBackendSet) {
|
|
227
|
-
if (!isStoreWord(dbBackendRaw))
|
|
228
|
-
return refuse(["env", "DB_BACKEND"], `env.DB_BACKEND ${describeWord(dbBackendRaw)} is not a store backend (${STORE_BACKENDS.join(" | ")}) — the engine refuses to start with it`);
|
|
229
|
-
backend = dbBackendRaw;
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
backend = hostSet || sessionRaw === "tidb" ? "mysql" : "local";
|
|
233
|
-
}
|
|
234
|
-
delete env.DB_BACKEND;
|
|
235
|
-
const sqlCoordsPresent = sqlCoordFields.filter(([c, , k]) => Object.hasOwn(c, k));
|
|
236
|
-
const store = { backend, session };
|
|
237
|
-
if (backend === "mysql") {
|
|
238
|
-
if (hostSet) {
|
|
239
|
-
const port = coords.tidbPort;
|
|
240
|
-
store.db = {
|
|
241
|
-
host,
|
|
242
|
-
port: port === undefined ? MYSQL_DEFAULT_PORT : port,
|
|
243
|
-
...(Object.hasOwn(coords, "tidbUser") ? { user: coords.tidbUser } : {}),
|
|
244
|
-
...(Object.hasOwn(runtime, "db") ? { database: runtime.db } : {}),
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
if (Object.hasOwn(secretRefs, "tidbPasswordEnv"))
|
|
248
|
-
secretRefs.dbPasswordEnv = secretRefs.tidbPasswordEnv;
|
|
249
|
-
for (const [c, , k] of sqlCoordsPresent)
|
|
250
|
-
delete c[k];
|
|
251
|
-
}
|
|
252
|
-
else if (backend === "pg") {
|
|
253
|
-
if (sqlCoordsPresent.length > 0) {
|
|
254
|
-
return refuse(["env", "DB_BACKEND"], `env.DB_BACKEND=pg but the SQL coordinates are the MySQL family (coords.tidb* / runtime.db / secretRefs.tidbPasswordEnv, rendered as MYSQL_*) — the migration does not move a MySQL server's coordinates onto pg; declare runtime.store by hand`);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
else {
|
|
258
|
-
for (const [c, slot, k] of sqlCoordsPresent)
|
|
259
|
-
drop(c, slot, k); // 非 SQL 店:SQL 坐标在引擎里是「声明不用」的惰性件
|
|
260
|
-
}
|
|
261
|
-
const memory = {};
|
|
262
|
-
const engineRaw = env.MEMORY_ENGINE;
|
|
263
|
-
if (engineRaw !== undefined) {
|
|
264
|
-
const word = typeof engineRaw === "string" ? engineRaw.trim().toLowerCase() : undefined;
|
|
265
|
-
if (word !== undefined && MEMORY_ENGINE_ON_WORDS.includes(word))
|
|
266
|
-
memory.engine = "on";
|
|
267
|
-
else if (word !== undefined && MEMORY_ENGINE_OFF_WORDS.includes(word))
|
|
268
|
-
memory.engine = "off";
|
|
269
|
-
else if (word !== "")
|
|
270
|
-
return refuse(["env", "MEMORY_ENGINE"], "env.MEMORY_ENGINE is not an on/off word — the engine refuses to start with it");
|
|
271
|
-
delete env.MEMORY_ENGINE;
|
|
272
|
-
}
|
|
273
|
-
const memoryBackendRaw = env.MEMORY_ENGINE_BACKEND;
|
|
274
|
-
if (memoryBackendRaw !== undefined) {
|
|
275
|
-
if (!isMemoryBackendWord(memoryBackendRaw)) {
|
|
276
|
-
return refuse(["env", "MEMORY_ENGINE_BACKEND"], `env.MEMORY_ENGINE_BACKEND ${describeWord(memoryBackendRaw)} is not a memory backend (${MEMORY_ENGINE_BACKENDS.join(" | ")}) — the engine refuses to start with it`);
|
|
277
|
-
}
|
|
278
|
-
memory.backend = memoryBackendRaw;
|
|
279
|
-
delete env.MEMORY_ENGINE_BACKEND;
|
|
280
|
-
}
|
|
281
|
-
if (Object.keys(memory).length > 0)
|
|
282
|
-
store.memory = memory;
|
|
283
|
-
runtime.store = store;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
// 被搬空的 env / secretEnv 整键去掉(它们是可选字段;留一个 `{}` 只是噪声)
|
|
287
|
-
if (isRec(w.env) && Object.keys(env).length === 0 && isRec(raw.env) && Object.keys(raw.env).length > 0)
|
|
288
|
-
delete w.env;
|
|
289
|
-
if (isRec(w.secretEnv) && Object.keys(secretEnv).length === 0 && isRec(raw.secretEnv) && Object.keys(raw.secretEnv).length > 0)
|
|
290
|
-
delete w.secretEnv;
|
|
291
|
-
const refusal = rowVerdict(w, "migrated");
|
|
292
|
-
if (refusal !== undefined)
|
|
293
|
-
return refuse(refusal.field, refusal.reason);
|
|
294
|
-
return { kind: "migrated", entry: w, dropped };
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* 逐行**终判** —— 迁过的行与没动的行同一只(见文件头「终判」):只含这一行的 workers 域读不读得进,判据就是读侧的 `WorkersConfig`
|
|
298
|
-
* (逐行的规则无论写在 `WorkerSpec` 里还是写在域层,都在;跨行规则一行时不触发)。读得进 ⇒ `undefined`;读不进 ⇒ 第一条 issue 的
|
|
299
|
-
* 路径(相对该行)+ 至多三条 issue 的句子(经 `issueText`:不回显输入值)。`stage` 只决定拒因说的是哪一份形(迁出形 / 存储形)。
|
|
300
|
-
*/
|
|
301
|
-
function rowVerdict(row, stage) {
|
|
302
|
-
const verdict = WorkersConfig.safeParse({ workers: [row] });
|
|
303
|
-
if (verdict.success)
|
|
304
|
-
return undefined;
|
|
305
|
-
const issues = verdict.error.issues;
|
|
306
|
-
const shown = issues.slice(0, 3).map((i) => `${formatConfigPath(rowRelative(i.path))}: ${issueText(i)}`);
|
|
307
|
-
const more = issues.length > shown.length ? ` (+${issues.length - shown.length} more)` : "";
|
|
308
|
-
const subject = stage === "migrated" ? "after migration the worker" : "the stored worker";
|
|
309
|
-
const first = issues[0];
|
|
310
|
-
return { field: first === undefined ? [] : rowRelative(first.path), reason: `${subject} is not a valid WorkerSpec — ${shown.join("; ")}${more}` };
|
|
311
|
-
}
|
|
312
|
-
/** 一只单行域里的 issue 路径 → 相对那一行(去掉 `["workers", 0]` 前缀;不在那一行下的 issue 原样)。 */
|
|
313
|
-
function rowRelative(path) {
|
|
314
|
-
return path[0] === "workers" && path[1] === 0 ? path.slice(2) : path;
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* 迁移一份 workers 域**原始**文档(`{ workers: [...] }`)。不是这个形 ⇒ 原样返回(判形是 `parseDomain` 的事,迁移不抛)。
|
|
318
|
-
* 规则、幂等与 refused 的处置见文件头。
|
|
319
|
-
*/
|
|
320
|
-
export function migrateWorkersDomain(doc) {
|
|
321
|
-
if (!isRec(doc) || !Array.isArray(doc.workers))
|
|
322
|
-
return { doc, changed: [], dropped: [], refused: [] };
|
|
323
|
-
const workers = doc.workers;
|
|
324
|
-
const kept = [];
|
|
325
|
-
const changed = [];
|
|
326
|
-
const dropped = [];
|
|
327
|
-
const refused = [];
|
|
328
|
-
workers.forEach((entry, index) => {
|
|
329
|
-
const out = migrateEntry(entry, index);
|
|
330
|
-
switch (out.kind) {
|
|
331
|
-
case "unchanged":
|
|
332
|
-
kept.push(entry);
|
|
333
|
-
break;
|
|
334
|
-
case "migrated":
|
|
335
|
-
kept.push(out.entry);
|
|
336
|
-
changed.push(String(out.entry.name));
|
|
337
|
-
dropped.push(...out.dropped);
|
|
338
|
-
break;
|
|
339
|
-
case "refused":
|
|
340
|
-
refused.push(out.refusal);
|
|
341
|
-
break;
|
|
342
|
-
default: {
|
|
343
|
-
const unhandled = out;
|
|
344
|
-
throw new Error(`migrateWorkersDomain: unhandled outcome ${JSON.stringify(unhandled)}`);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
if (changed.length === 0 && refused.length === 0)
|
|
349
|
-
return { doc, changed: [], dropped: [], refused: [] };
|
|
350
|
-
return { doc: { ...doc, workers: kept }, changed, dropped, refused };
|
|
351
|
-
}
|