@sema-agent/settings-schema 11.0.0 → 11.2.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 +148 -1
- package/README.md +17 -3
- package/bin/settings-schema.mjs +7 -20
- package/conformance/VERSION +1 -1
- package/conformance/cases/c06-legacy-governance-lift/expected.json +1 -1
- package/conformance/cases/c20-governance-absent-lifts/expected.json +1 -1
- package/conformance/cases/c67-s784-retired-field-loud/meta.json +1 -1
- package/dist/atomic-write.d.ts +14 -0
- package/dist/atomic-write.js +90 -0
- package/dist/cli.d.ts +31 -6
- package/dist/cli.js +50 -124
- package/dist/config-dir-io.d.ts +2 -0
- package/dist/config-dir-io.js +13 -0
- package/dist/config-dir-migration.d.ts +64 -0
- package/dist/config-dir-migration.js +167 -0
- package/dist/file-edit.d.ts +3 -3
- package/dist/file-edit.js +6 -16
- package/dist/file-store.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/local-load.d.ts +1 -1
- package/dist/local-load.js +4 -14
- package/dist/node.d.ts +2 -0
- package/dist/node.js +3 -0
- package/dist/one-time-migrations.d.ts +6 -0
- package/dist/one-time-migrations.js +12 -2
- package/dist/previous-web-admin-env.d.ts +15 -0
- package/dist/previous-web-admin-env.js +20 -0
- package/dist/remote-exec.js +3 -3
- package/dist/retired-envs.js +13 -2
- package/dist/retired-form.d.ts +7 -2
- package/dist/retired-form.js +11 -3
- package/dist/runtime-lift-targets.d.ts +7 -0
- package/dist/runtime-lift-targets.js +7 -0
- package/dist/scheduler-store-node.d.ts +4 -3
- package/dist/scheduler-store-node.js +7 -8
- package/dist/shell-quote.d.ts +1 -0
- package/dist/shell-quote.js +14 -0
- package/dist/types.d.ts +9 -1
- package/dist/types.js +71 -18
- package/dist/web-admin-credential.d.ts +1 -1
- package/dist/web-admin-credential.js +3 -3
- package/dist/web-admin-env.d.ts +2 -1
- package/dist/web-admin-env.js +2 -1
- package/dist/web-admin-session-url.d.ts +17 -0
- package/dist/web-admin-session-url.js +64 -0
- package/package.json +1 -1
package/dist/node.d.ts
CHANGED
|
@@ -10,3 +10,5 @@ export * from "./bundle.js";
|
|
|
10
10
|
export * from "./local-load.js";
|
|
11
11
|
export * from "./scheduler-store-node.js";
|
|
12
12
|
export * from "./prompts-digest.js";
|
|
13
|
+
export { migrateConfigDirectory, type ConfigDirectoryIo, type ConfigDirectoryMigration, type ConfigDirectoryRefusal, type MigrateConfigDirectoryOptions } from "./config-dir-migration.js";
|
|
14
|
+
export { nodeConfigDirectoryIo } from "./config-dir-io.js";
|
package/dist/node.js
CHANGED
|
@@ -10,3 +10,6 @@ export * from "./bundle.js";
|
|
|
10
10
|
export * from "./local-load.js";
|
|
11
11
|
export * from "./scheduler-store-node.js"; // R7 self-wake scheduler store — fs binding of the ./scheduler-store contract
|
|
12
12
|
export * from "./prompts-digest.js"; // prompts 下发包的内容摘要(4.0.0 从 scheduler-store-node 搬来 —— 与调度存储无关;出口路径不变)
|
|
13
|
+
// 目录形的一次性迁移(读目录 → 迁 → 整域终判 → 原子写 → 删 retired 域文件)—— 语义只此一处,命令行是它的渲染器、引擎起服调同一只。显式列名:该模块的包内私有件(出门形/拼路径)不外泄。
|
|
14
|
+
export { migrateConfigDirectory } from "./config-dir-migration.js";
|
|
15
|
+
export { nodeConfigDirectoryIo } from "./config-dir-io.js"; // 上面那只函数的真 I/O 绑定:node:fs + 本包唯一的原子写器(config-document 模式规则)
|
|
@@ -9,6 +9,12 @@ export type WireRetirementId = (typeof WIRE_RETIREMENT_IDS)[number];
|
|
|
9
9
|
export interface OneTimeMigrationWindow {
|
|
10
10
|
/** 数据迁移 id(闭集 `ONE_TIME_MIGRATION_IDS`)或 wire 删码位 id(闭集 {@link WIRE_RETIREMENT_IDS});两集合起来每个 id 恰一行。 */
|
|
11
11
|
readonly id: OneTimeMigrationId | WireRetirementId;
|
|
12
|
+
/** 旧形在读侧的待遇(闭集,每词一义;「读写两面遇旧形一律响亮拒,拒句点名迁移 id」这条律的例外只在这一格里写):
|
|
13
|
+
* · `refused` — 读写两面拒,拒句点名本行 id(经 `migrationSignpost` 铸;钉:每个 `refused` 行的 id 在 `src/` 至少一处铸点,
|
|
14
|
+
* 且非 `refused` 行零铸点);
|
|
15
|
+
* · `rewritten-on-read` — 读时改写、不拒(SS-M3:调度店的下一个写者落盘),没有拒句可点名;
|
|
16
|
+
* · `not-stored` — 没有存储形(wire 删码位),谈不上拒。 */
|
|
17
|
+
readonly oldForm: "refused" | "rewritten-on-read" | "not-stored";
|
|
12
18
|
/** 引入这条迁移的版本。 */
|
|
13
19
|
readonly introducedIn: string;
|
|
14
20
|
/** 该版本在 npm 上的发布日(UTC 日历日 YYYY-MM-DD)。`null` = 未发布形,由抬 `package.json` 到 `introducedIn` 的发布准备提交回填。 */
|
|
@@ -33,6 +33,7 @@ import { outgoingPath, redactSecrets } from "./config-fns.js";
|
|
|
33
33
|
import { LANE_ENV_OWNER, STORE_ENV_KEYS, isLaneEnvKey } from "./execution-lane-index.js";
|
|
34
34
|
import { EXECUTION_LANE_WORDS, MEMORY_ENGINE_BACKENDS, STORE_BACKENDS } from "./execution-lanes.js";
|
|
35
35
|
import { INTEGER } from "./integer.js";
|
|
36
|
+
import { RUNTIME_LIFT_TARGETS } from "./runtime-lift-targets.js";
|
|
36
37
|
import { DOMAIN_SCHEMAS, HOSTS_ARRAY_MAX, HOST_NAME_MAX, NON_SECRET_URL, isDomainName, isRetiredDomainName } from "./types.js";
|
|
37
38
|
import { workerRowVerdict } from "./worker-row-verdict.js";
|
|
38
39
|
import { unwrapSchema } from "./zod-unwrap.js";
|
|
@@ -45,6 +46,7 @@ export const WIRE_RETIREMENT_IDS = ["SS-W1"];
|
|
|
45
46
|
export const ONE_TIME_MIGRATIONS = [
|
|
46
47
|
{
|
|
47
48
|
id: "SS-M1",
|
|
49
|
+
oldForm: "refused",
|
|
48
50
|
introducedIn: "11.0.0",
|
|
49
51
|
releasedOn: "2026-10-10",
|
|
50
52
|
removableAfter: "2026-11-09",
|
|
@@ -60,6 +62,7 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
60
62
|
},
|
|
61
63
|
{
|
|
62
64
|
id: "SS-M2",
|
|
65
|
+
oldForm: "refused",
|
|
63
66
|
introducedIn: "11.0.0",
|
|
64
67
|
releasedOn: "2026-10-10",
|
|
65
68
|
removableAfter: "2026-11-09",
|
|
@@ -71,6 +74,7 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
71
74
|
},
|
|
72
75
|
{
|
|
73
76
|
id: "SS-M3",
|
|
77
|
+
oldForm: "rewritten-on-read",
|
|
74
78
|
introducedIn: "11.0.0",
|
|
75
79
|
releasedOn: "2026-10-10",
|
|
76
80
|
removableAfter: "2026-11-09",
|
|
@@ -83,6 +87,7 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
83
87
|
},
|
|
84
88
|
{
|
|
85
89
|
id: "SS-M4",
|
|
90
|
+
oldForm: "refused",
|
|
86
91
|
introducedIn: "11.0.0",
|
|
87
92
|
releasedOn: "2026-10-10",
|
|
88
93
|
removableAfter: "2026-11-09",
|
|
@@ -95,6 +100,7 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
95
100
|
},
|
|
96
101
|
{
|
|
97
102
|
id: "SS-M5",
|
|
103
|
+
oldForm: "refused",
|
|
98
104
|
introducedIn: "11.0.0",
|
|
99
105
|
releasedOn: "2026-10-10",
|
|
100
106
|
removableAfter: "2026-11-09",
|
|
@@ -104,6 +110,7 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
104
110
|
},
|
|
105
111
|
{
|
|
106
112
|
id: "SS-M6",
|
|
113
|
+
oldForm: "refused",
|
|
107
114
|
introducedIn: "11.0.0",
|
|
108
115
|
releasedOn: "2026-10-10",
|
|
109
116
|
removableAfter: "2026-11-09",
|
|
@@ -115,17 +122,19 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
115
122
|
},
|
|
116
123
|
{
|
|
117
124
|
id: "SS-M7",
|
|
125
|
+
oldForm: "refused",
|
|
118
126
|
introducedIn: "11.0.0",
|
|
119
127
|
releasedOn: "2026-10-10",
|
|
120
128
|
removableAfter: "2026-11-09",
|
|
121
129
|
rewrites: "every NON_SECRET_URL seat in a domain document (derived from the domain schemas, not listed by hand — today models.models[].baseUrl, " +
|
|
122
130
|
"mcp.servers[].transport.url, execution.sessionMirror.engineUrl, workers.workers[].coords.gitApiBaseUrl): `#` and everything after it are removed " +
|
|
123
131
|
"(a fragment never goes on the wire)",
|
|
124
|
-
removal: "the next major after the window: delete the SS-M7 step and its schema walk",
|
|
132
|
+
removal: "the next major after the window: delete the SS-M7 step and its schema walk; the NON_SECRET_URL fragment rule loses its SS-M7 clause",
|
|
125
133
|
afterRemoval: "the NON_SECRET_URL shape judge, which refuses a fragment (`#` included) on read and write",
|
|
126
134
|
},
|
|
127
135
|
{
|
|
128
136
|
id: "SS-M9",
|
|
137
|
+
oldForm: "refused",
|
|
129
138
|
introducedIn: "11.0.0",
|
|
130
139
|
releasedOn: "2026-10-10",
|
|
131
140
|
removableAfter: "2026-11-09",
|
|
@@ -139,6 +148,7 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
139
148
|
{
|
|
140
149
|
// 一条 wire 位的删码登记,不是数据迁移(id 在 WIRE_RETIREMENT_IDS,不在收据闭集)。
|
|
141
150
|
id: "SS-W1",
|
|
151
|
+
oldForm: "not-stored",
|
|
142
152
|
introducedIn: "11.0.0",
|
|
143
153
|
releasedOn: "2026-10-10",
|
|
144
154
|
removableAfter: "2026-11-09",
|
|
@@ -257,7 +267,7 @@ function stepRuntimeDomain(out, coverage, receipts) {
|
|
|
257
267
|
}
|
|
258
268
|
if (!isPlainObject(raw))
|
|
259
269
|
return refuse("the stored runtime document is not a JSON object — the scope does not read today either; fix it by hand");
|
|
260
|
-
if (coverage === "partial" && !(
|
|
270
|
+
if (coverage === "partial" && !RUNTIME_LIFT_TARGETS.every((target) => has(out, target))) {
|
|
261
271
|
return refuse("a runtime document migrates only together with its scope's governance and limits documents (coverage \"scope\") — on its own there is no telling whether governance was ever set");
|
|
262
272
|
}
|
|
263
273
|
const parsed = RUNTIME_10X.safeParse(raw);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 上一代 web-admin 连接名(`SEMA_REGISTRY_*`)—— **包内私有**(不进主桶、不进 exports 表):上一代名字面只在这里写一次,
|
|
3
|
+
* 两个读者各取所需:墓碑表 `RETIRED_ENVS`(旧名 → `WEB_ADMIN_ENV` 同键的现名)与保留 env 名表(渲染器在改名窗里对还读上一代名的引擎
|
|
4
|
+
* 仍写的三名)。键与 {@link WEB_ADMIN_ENV} 同键 —— 现名与上一代名按键配对,去向从 `WEB_ADMIN_ENV` 取、不手抄。
|
|
5
|
+
*
|
|
6
|
+
* 断言令牌(`assertionToken` / `assertionTokenFile`)是上一代之后才有的两键,没有上一代名;映射型 `Exclude` 掉它们,所以 `WEB_ADMIN_ENV`
|
|
7
|
+
* 日后再加键时,这里必须当场表态(有上一代名就写,没有就加进 `Exclude`)—— 否则编译红。
|
|
8
|
+
*/
|
|
9
|
+
import { WEB_ADMIN_ENV } from "./web-admin-env.js";
|
|
10
|
+
/** 有上一代名的键(= `WEB_ADMIN_ENV` 去掉断言令牌两键)。 */
|
|
11
|
+
export type PreviousWebAdminEnvKey = Exclude<keyof typeof WEB_ADMIN_ENV, "assertionToken" | "assertionTokenFile">;
|
|
12
|
+
/** 上一代名,按 `WEB_ADMIN_ENV` 的键配对。 */
|
|
13
|
+
export declare const PREVIOUS_WEB_ADMIN_ENV: {
|
|
14
|
+
readonly [K in PreviousWebAdminEnvKey]: string;
|
|
15
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 上一代 web-admin 连接名(`SEMA_REGISTRY_*`)—— **包内私有**(不进主桶、不进 exports 表):上一代名字面只在这里写一次,
|
|
3
|
+
* 两个读者各取所需:墓碑表 `RETIRED_ENVS`(旧名 → `WEB_ADMIN_ENV` 同键的现名)与保留 env 名表(渲染器在改名窗里对还读上一代名的引擎
|
|
4
|
+
* 仍写的三名)。键与 {@link WEB_ADMIN_ENV} 同键 —— 现名与上一代名按键配对,去向从 `WEB_ADMIN_ENV` 取、不手抄。
|
|
5
|
+
*
|
|
6
|
+
* 断言令牌(`assertionToken` / `assertionTokenFile`)是上一代之后才有的两键,没有上一代名;映射型 `Exclude` 掉它们,所以 `WEB_ADMIN_ENV`
|
|
7
|
+
* 日后再加键时,这里必须当场表态(有上一代名就写,没有就加进 `Exclude`)—— 否则编译红。
|
|
8
|
+
*/
|
|
9
|
+
import { WEB_ADMIN_ENV } from "./web-admin-env.js";
|
|
10
|
+
/** 上一代名,按 `WEB_ADMIN_ENV` 的键配对。 */
|
|
11
|
+
export const PREVIOUS_WEB_ADMIN_ENV = {
|
|
12
|
+
url: "SEMA_REGISTRY_URL",
|
|
13
|
+
token: "SEMA_REGISTRY_TOKEN",
|
|
14
|
+
tokenFile: "SEMA_REGISTRY_TOKEN_FILE",
|
|
15
|
+
worker: "SEMA_REGISTRY_WORKER",
|
|
16
|
+
liveUrl: "SEMA_REGISTRY_LIVE_URL",
|
|
17
|
+
webUrl: "SEMA_REGISTRY_WEB_URL",
|
|
18
|
+
dryRun: "SEMA_REGISTRY_DRY_RUN",
|
|
19
|
+
adminToken: "SEMA_REGISTRY_ADMIN_TOKEN",
|
|
20
|
+
};
|
package/dist/remote-exec.js
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import { z } from "zod";
|
|
30
30
|
import { INTEGER } from "./integer.js";
|
|
31
|
-
import { ENV_NAME, K8S_QUANTITY,
|
|
31
|
+
import { ENV_NAME, K8S_QUANTITY, NON_SECRET_URL_NO_MIGRATION, TCP_PORT, containsSecretToken, hasNul } from "./types.js";
|
|
32
32
|
import { retiredLaneWordRefusal } from "./execution-lanes.js";
|
|
33
33
|
/** A non-empty exec-bound string (an image / path / shell / arg the adapter feeds to spawn()/fs). Rejects a NUL
|
|
34
34
|
* byte uniformly — a NUL validates here but makes Node throw at spawn/boot (fail-OPEN → runtime crash). R20. */
|
|
@@ -84,7 +84,7 @@ export const RemoteExecSpec = z.discriminatedUnion("provider", [
|
|
|
84
84
|
/** Sandbox container image (service: K8S_SANDBOX_IMAGE). Required so a run is reproducible. */
|
|
85
85
|
image: execStr,
|
|
86
86
|
/** Kube API URL (service: K8S_API_URL); absent → in-cluster service account. NO embedded credentials. */
|
|
87
|
-
apiUrl:
|
|
87
|
+
apiUrl: NON_SECRET_URL_NO_MIGRATION.optional(),
|
|
88
88
|
/** env-NAME holding the kube bearer token (service: K8S_TOKEN). NEVER the token. */
|
|
89
89
|
tokenEnv: ENV_NAME.optional(),
|
|
90
90
|
/** env-NAME holding the base64 CA cert (service: K8S_CA_CERT_B64). NEVER the cert bytes. */
|
|
@@ -102,7 +102,7 @@ export const RemoteExecSpec = z.discriminatedUnion("provider", [
|
|
|
102
102
|
* "all three of endpoint/access/secret required" gate). */
|
|
103
103
|
s3Snapshot: z
|
|
104
104
|
.object({
|
|
105
|
-
endpoint:
|
|
105
|
+
endpoint: NON_SECRET_URL_NO_MIGRATION, // S3/MinIO endpoint — creds go in accessKeyEnv/secretKeyEnv, never the URL
|
|
106
106
|
bucket: execStr.optional(),
|
|
107
107
|
accessKeyEnv: ENV_NAME,
|
|
108
108
|
secretKeyEnv: ENV_NAME,
|
package/dist/retired-envs.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* 退役 env 名的**墓碑表** —— 一张表,本包是它的唯一属主:引擎(server)起服时按它拒启、目录 wire 的 `retired[]` 由它投影,
|
|
3
3
|
* 壳在起本机引擎之前、web-admin 在给 worker 渲染 env 之前读同一张(谁都不再手抄、不再从别人的构建产物里正则抽)。
|
|
4
4
|
*
|
|
5
|
-
* **进表的判据(一条)
|
|
6
|
-
* 改名的那一版只登记现名,旧名等引擎换读现名之后的那一版再进表。
|
|
5
|
+
* **进表的判据(一条):没有带着本表的引擎还读这个名。** 更老的引擎内带的是更老的契约版本,看不见这张表,它们读不读某个名由它们自己的版本定;
|
|
6
|
+
* 一个名字只要还有带着本表的引擎把它当活旋钮读,进表就等于让照旧配置的部署拒启 —— 改名的那一版只登记现名,旧名等引擎换读现名之后的那一版再进表。
|
|
7
7
|
*
|
|
8
8
|
* 🪦 **墓碑是永久的响亮拒,不设到期**。一行墓碑就是那句拒启句本身,成本一行;删掉它 = 旧名重新被悄悄忽略。
|
|
9
9
|
* ⇒ 本表只增不减,**唯一的删除条件是这个名字要被赋予新含义**:那一次提交同时删掉这一行,并在发布说明里写明旧名改作何用。
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
*
|
|
26
26
|
* 行形 = 字面元组(第三元是族词),一行一个旧名。纯数据 + 纯函数,零 IO,browser-safe。
|
|
27
27
|
*/
|
|
28
|
+
import { PREVIOUS_WEB_ADMIN_ENV } from "./previous-web-admin-env.js";
|
|
28
29
|
import { WEB_ADMIN_ENV } from "./web-admin-env.js";
|
|
29
30
|
/** 墓碑拒启码(机读;拒句方括号里)。 */
|
|
30
31
|
export const RETIRED_ENV_REFUSAL_CODE = "config.env_key_retired";
|
|
@@ -38,6 +39,16 @@ export const RETIRED_ENVS = [
|
|
|
38
39
|
["TIDB_DATABASE", "MYSQL_DATABASE", "renamed"],
|
|
39
40
|
["TIDB_POOL_SIZE", "MYSQL_POOL_SIZE", "renamed"],
|
|
40
41
|
["ANTHROPIC_BASEURL", "ANTHROPIC_BASE_URL", "renamed"],
|
|
42
|
+
// web-admin 连接名上一代的 `SEMA_REGISTRY_*` 八名,直指同键的现名(一跳;旧名取上一代名表、去向取 `WEB_ADMIN_ENV`,本行不写任何这类字面)。
|
|
43
|
+
// 引擎换读现名之后才进表:读侧同代要求 —— 引擎至少是换读现名的那一版,更老的引擎仍把它们当活旋钮读。
|
|
44
|
+
[PREVIOUS_WEB_ADMIN_ENV.url, WEB_ADMIN_ENV.url, "renamed"],
|
|
45
|
+
[PREVIOUS_WEB_ADMIN_ENV.token, WEB_ADMIN_ENV.token, "renamed"],
|
|
46
|
+
[PREVIOUS_WEB_ADMIN_ENV.tokenFile, WEB_ADMIN_ENV.tokenFile, "renamed"],
|
|
47
|
+
[PREVIOUS_WEB_ADMIN_ENV.worker, WEB_ADMIN_ENV.worker, "renamed"],
|
|
48
|
+
[PREVIOUS_WEB_ADMIN_ENV.liveUrl, WEB_ADMIN_ENV.liveUrl, "renamed"],
|
|
49
|
+
[PREVIOUS_WEB_ADMIN_ENV.webUrl, WEB_ADMIN_ENV.webUrl, "renamed"],
|
|
50
|
+
[PREVIOUS_WEB_ADMIN_ENV.dryRun, WEB_ADMIN_ENV.dryRun, "renamed"],
|
|
51
|
+
[PREVIOUS_WEB_ADMIN_ENV.adminToken, WEB_ADMIN_ENV.adminToken, "renamed"],
|
|
41
52
|
// web-admin 连接名两代前的 `CONFIG_CENTER_*` / `REGISTRY_ADMIN_TOKEN`,直指现名(一跳)。
|
|
42
53
|
["CONFIG_CENTER_URL", WEB_ADMIN_ENV.url, "renamed"],
|
|
43
54
|
["CONFIG_CENTER_TOKEN", WEB_ADMIN_ENV.token, "renamed"],
|
package/dist/retired-form.d.ts
CHANGED
|
@@ -9,9 +9,14 @@
|
|
|
9
9
|
* 墓碑留下 —— 到那时拒句里的迁移 id 由删码那一车改写(登记表那一行的 `removal` 点名了这件事)。
|
|
10
10
|
*/
|
|
11
11
|
import { z } from "zod";
|
|
12
|
-
/** 迁移命令行的唯一拼写:`npx` 全名形 —— 没装本包的运维粘贴即可跑(裸 bin 名在没装本包的机器上是 `command not found`)。参数是一个 `config.d` 目录。
|
|
12
|
+
/** 迁移命令行的唯一拼写:`npx` 全名形 —— 没装本包的运维粘贴即可跑(裸 bin 名在没装本包的机器上是 `command not found`)。参数是一个 `config.d` 目录。
|
|
13
|
+
* 印出命令的地方只有下面两只:带目录占位的模板 {@link MIGRATE_COMMAND_TEMPLATE}(USAGE、迁移指路句)与带真路径的 {@link migrateCommandFor}(指路到某个具体目录)。 */
|
|
13
14
|
export declare const MIGRATE_COMMAND: "npx @sema-agent/settings-schema migrate";
|
|
14
|
-
/**
|
|
15
|
+
/** 命令加目录占位 —— 用法行与迁移指路句里印的形。`<config.d directory>` 是元变量(读者替换成自己的目录),不是参数,所以不引用。 */
|
|
16
|
+
export declare const MIGRATE_COMMAND_TEMPLATE: "npx @sema-agent/settings-schema migrate <config.d directory>";
|
|
17
|
+
/** 指到某个具体目录的命令:路径过 `shellQuote`(含空格 / 特殊字符才加引号)—— 印出来的句子可以直接粘贴去跑。 */
|
|
18
|
+
export declare function migrateCommandFor(configDir: string): string;
|
|
19
|
+
/** 拒句尾的迁移指路:存量由哪一条一次性迁移改写、两种跑法(子路径的函数 / 目录命令;目录是 `config.d` 目录本身,不是它的上一层)。`before` = 旧形不再合法的那一版。
|
|
15
20
|
* 刻意短:它挂在 zod issue 句尾,读侧逐条容错时每一条被丢的条目都要过一遍出门脱敏。 */
|
|
16
21
|
export declare function migrationSignpost(migration: string, before: string): string;
|
|
17
22
|
/** 一个retired 形的整句:`<form> was retired in <版本> — now <今天住哪>; <指路>`。 */
|
package/dist/retired-form.js
CHANGED
|
@@ -9,12 +9,20 @@
|
|
|
9
9
|
* 墓碑留下 —— 到那时拒句里的迁移 id 由删码那一车改写(登记表那一行的 `removal` 点名了这件事)。
|
|
10
10
|
*/
|
|
11
11
|
import { z } from "zod";
|
|
12
|
-
|
|
12
|
+
import { shellQuote } from "./shell-quote.js";
|
|
13
|
+
/** 迁移命令行的唯一拼写:`npx` 全名形 —— 没装本包的运维粘贴即可跑(裸 bin 名在没装本包的机器上是 `command not found`)。参数是一个 `config.d` 目录。
|
|
14
|
+
* 印出命令的地方只有下面两只:带目录占位的模板 {@link MIGRATE_COMMAND_TEMPLATE}(USAGE、迁移指路句)与带真路径的 {@link migrateCommandFor}(指路到某个具体目录)。 */
|
|
13
15
|
export const MIGRATE_COMMAND = "npx @sema-agent/settings-schema migrate";
|
|
14
|
-
/**
|
|
16
|
+
/** 命令加目录占位 —— 用法行与迁移指路句里印的形。`<config.d directory>` 是元变量(读者替换成自己的目录),不是参数,所以不引用。 */
|
|
17
|
+
export const MIGRATE_COMMAND_TEMPLATE = `${MIGRATE_COMMAND} <config.d directory>`;
|
|
18
|
+
/** 指到某个具体目录的命令:路径过 `shellQuote`(含空格 / 特殊字符才加引号)—— 印出来的句子可以直接粘贴去跑。 */
|
|
19
|
+
export function migrateCommandFor(configDir) {
|
|
20
|
+
return `${MIGRATE_COMMAND} ${shellQuote(configDir)}`;
|
|
21
|
+
}
|
|
22
|
+
/** 拒句尾的迁移指路:存量由哪一条一次性迁移改写、两种跑法(子路径的函数 / 目录命令;目录是 `config.d` 目录本身,不是它的上一层)。`before` = 旧形不再合法的那一版。
|
|
15
23
|
* 刻意短:它挂在 zod issue 句尾,读侧逐条容错时每一条被丢的条目都要过一遍出门脱敏。 */
|
|
16
24
|
export function migrationSignpost(migration, before) {
|
|
17
|
-
return `data stored before ${before}: run the one-time migration ${migration} (@sema-agent/settings-schema/one-time-migrations, or ${
|
|
25
|
+
return `data stored before ${before}: run the one-time migration ${migration} (@sema-agent/settings-schema/one-time-migrations, or ${MIGRATE_COMMAND_TEMPLATE})`;
|
|
18
26
|
}
|
|
19
27
|
export function retiredFormSentence(form, t) {
|
|
20
28
|
return `${form} was retired in ${t.retiredIn} — now ${t.now}; ${migrationSignpost(t.migration, t.retiredIn)}`;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 包内私有(不进主桶、不进 exports 表):一次性迁移 SS-M1 把 retired 的 `runtime` 域迁出时**写入的目标域** —— 治理三闸去 `governance`,五个限额键去 `limits`。
|
|
3
|
+
* SS-M1 要判「目标域从未设过」还是「设过、设过的赢」,所以它需要同时看见这两份文档:`coverage: "scope"` 的意思就是「缺席的域从未设过」,
|
|
4
|
+
* 而 `"partial"` 只在两份都给了的时候才迁(否则拒)。谁要把一组文档按「覆盖面」交给 SS-M1(目录迁移),覆盖面含这两个域才算一个 scope;一个域也不能少 ——
|
|
5
|
+
* 少了,迁出的旧值会覆盖覆盖面外已有的那份。一处写名字,SS-M1 的偏集检查与目录迁移的覆盖面判定都读它;一道钉拿真跑 SS-M1 的读数核这个常量。
|
|
6
|
+
*/
|
|
7
|
+
export declare const RUNTIME_LIFT_TARGETS: readonly ["governance", "limits"];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 包内私有(不进主桶、不进 exports 表):一次性迁移 SS-M1 把 retired 的 `runtime` 域迁出时**写入的目标域** —— 治理三闸去 `governance`,五个限额键去 `limits`。
|
|
3
|
+
* SS-M1 要判「目标域从未设过」还是「设过、设过的赢」,所以它需要同时看见这两份文档:`coverage: "scope"` 的意思就是「缺席的域从未设过」,
|
|
4
|
+
* 而 `"partial"` 只在两份都给了的时候才迁(否则拒)。谁要把一组文档按「覆盖面」交给 SS-M1(目录迁移),覆盖面含这两个域才算一个 scope;一个域也不能少 ——
|
|
5
|
+
* 少了,迁出的旧值会覆盖覆盖面外已有的那份。一处写名字,SS-M1 的偏集检查与目录迁移的覆盖面判定都读它;一道钉拿真跑 SS-M1 的读数核这个常量。
|
|
6
|
+
*/
|
|
7
|
+
export const RUNTIME_LIFT_TARGETS = ["governance", "limits"];
|
|
@@ -9,9 +9,10 @@ import { type SchedulerParseWarning, type SchedulerReadOptions, type SchedulerRe
|
|
|
9
9
|
*/
|
|
10
10
|
export declare function loadSchedulerStore(path: string, opts: SchedulerReadOptions): SchedulerStoreRead;
|
|
11
11
|
/**
|
|
12
|
-
* Persist ATOMICALLY: `mkdir -p` the parent, write a uniquely-named `.tmp`
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* Persist ATOMICALLY: `mkdir -p` the parent, then the package's one atomic writer (`./atomic-write`: a uniquely-named `.tmp`
|
|
13
|
+
* sibling, then `rename` over the target — atomic on POSIX, so a concurrent reader never sees a partial file). The file mode
|
|
14
|
+
* is the `scheduler-store` class's (0o600 — owner-only, the records may carry principal/session identifiers; every save
|
|
15
|
+
* restores it). Throws on IO failure (caller maps to a domain error).
|
|
15
16
|
*/
|
|
16
17
|
export declare function saveSchedulerStore(path: string, records: SchedulerRecord[]): void;
|
|
17
18
|
/** A filesystem-backed {@link SchedulerStore} bound to a path (convenience for the daemon/backend constructors).
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Store path = caller-supplied; both sides agree on `SEMA_CONFIG_DIR` → `~/.sema/scheduled_tasks.json`.
|
|
8
8
|
*/
|
|
9
|
-
import { mkdirSync, readFileSync
|
|
9
|
+
import { mkdirSync, readFileSync } from 'node:fs';
|
|
10
10
|
import { dirname } from 'node:path';
|
|
11
|
-
import {
|
|
11
|
+
import { writeFileAtomic } from './atomic-write.js';
|
|
12
12
|
import { parseSchedulerStore, serializeSchedulerStore } from './scheduler-store.js';
|
|
13
13
|
/**
|
|
14
14
|
* Load the durable store. A MISSING file (ENOENT) ⇒ no records, no warnings (first-run/never-written case); any OTHER
|
|
@@ -31,15 +31,14 @@ export function loadSchedulerStore(path, opts) {
|
|
|
31
31
|
return parseSchedulerStore(text, opts);
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
-
* Persist ATOMICALLY: `mkdir -p` the parent, write a uniquely-named `.tmp`
|
|
35
|
-
*
|
|
36
|
-
*
|
|
34
|
+
* Persist ATOMICALLY: `mkdir -p` the parent, then the package's one atomic writer (`./atomic-write`: a uniquely-named `.tmp`
|
|
35
|
+
* sibling, then `rename` over the target — atomic on POSIX, so a concurrent reader never sees a partial file). The file mode
|
|
36
|
+
* is the `scheduler-store` class's (0o600 — owner-only, the records may carry principal/session identifiers; every save
|
|
37
|
+
* restores it). Throws on IO failure (caller maps to a domain error).
|
|
37
38
|
*/
|
|
38
39
|
export function saveSchedulerStore(path, records) {
|
|
39
40
|
mkdirSync(dirname(path), { recursive: true });
|
|
40
|
-
|
|
41
|
-
writeFileSync(tmp, serializeSchedulerStore(records), { mode: 0o600 });
|
|
42
|
-
renameSync(tmp, path); // atomic over the target
|
|
41
|
+
writeFileAtomic(path, serializeSchedulerStore(records), 'scheduler-store');
|
|
43
42
|
}
|
|
44
43
|
/** A filesystem-backed {@link SchedulerStore} bound to a path (convenience for the daemon/backend constructors).
|
|
45
44
|
*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function shellQuote(arg: string): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 包内私有(不进主桶、不进 exports 表):**一只** shell 引用 —— 命令行印出来、要让人粘贴去跑的命令里,路径一律过它。
|
|
3
|
+
*
|
|
4
|
+
* POSIX 单引号规则:整串包进 `'…'`,串里的 `'` 写成 `'\''`(结束引号 + 转义的引号 + 重新开引号);单引号里除 `'` 外一切字面,所以 `$` / 反引号 / 反斜杠 / 空格 /
|
|
5
|
+
* 换行都不再被 shell 解释。**只有安全字符集里的串原样返回、不加引号**:ASCII 字母数字与 `_ @ + : , . / -`。其余一律引用 —— 包括行首的 `~`(否则被展开成家目录)、
|
|
6
|
+
* `=`(zsh 里行首 `=cmd` 展开成命令路径)、`%`、通配符与非 ASCII(保守:引用永远安全,多包一层引号不会让命令失效)。空串写成 `''`(否则命令少一个参数)。
|
|
7
|
+
* 目标是 POSIX shell(sh / bash / zsh);Windows cmd.exe 不认单引号,不在范围内。
|
|
8
|
+
*/
|
|
9
|
+
const SHELL_SAFE = /^[A-Za-z0-9_@+:,./-]+$/;
|
|
10
|
+
export function shellQuote(arg) {
|
|
11
|
+
if (SHELL_SAFE.test(arg))
|
|
12
|
+
return arg;
|
|
13
|
+
return `'${arg.replaceAll("'", "'\\''")}'`;
|
|
14
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -62,10 +62,18 @@ export declare function urlQueryFragmentHasSecret(u: URL): boolean;
|
|
|
62
62
|
/** A URL handed to `fetch` that carries no credential: judged by THE URL shape judge ({@link httpUrlFlaws}, `./url-shape`) — a
|
|
63
63
|
* hierarchical http(s) URL with neither userinfo nor a fragment (an empty `#` included, 11.0.0) — and with no secret-shaped value
|
|
64
64
|
* in its path or query ({@link urlQueryFragmentHasSecret}). One issue per flaw, its message the judge's rule sentence (no value
|
|
65
|
-
* echoed); `params.flaw` carries the rule word.
|
|
65
|
+
* echoed; a fragment's also names its migration, {@link nonSecretUrlSentence}); `params.flaw` carries the rule word.
|
|
66
|
+
*
|
|
67
|
+
* This is the seat of every URL in a stored DOMAIN document (models `baseUrl`, mcp `url`, `sessionMirror.engineUrl`, workers
|
|
68
|
+
* `gitApiBaseUrl`). A URL outside the domain documents — `remote-exec.json`'s endpoints, the web-admin credential envelope's
|
|
69
|
+
* `issuer` — uses {@link NON_SECRET_URL_NO_MIGRATION}.
|
|
66
70
|
*
|
|
67
71
|
* The value is kept EXACTLY as written: nothing here trims or normalizes it (zod's own `.url()` would hand back a rewritten string). */
|
|
68
72
|
export declare const NON_SECRET_URL: z.ZodString;
|
|
73
|
+
/** {@link NON_SECRET_URL}'s judge for a seat no one-time migration reaches (not a domain document): identical rules and rule
|
|
74
|
+
* sentences, but a fragment's refusal carries no migration signpost — pointing at a migration that cannot rewrite the value would
|
|
75
|
+
* send the operator to a command that does nothing for it. */
|
|
76
|
+
export declare const NON_SECRET_URL_NO_MIGRATION: z.ZodString;
|
|
69
77
|
/** Validate a free-form `extraBody`: returns true = UNSAFE (reject). A GETTER-FREE, descriptor-based, bounded
|
|
70
78
|
* walk that rejects — WITHOUT ever invoking a getter (a malicious getter could throw a secret-bearing message,
|
|
71
79
|
* or return different values across the validate vs persist JSON.stringify calls) — anything that isn't plain
|
package/dist/types.js
CHANGED
|
@@ -24,6 +24,7 @@ import { isLoopbackAuthHost } from "./loopback.js";
|
|
|
24
24
|
import { httpUrlFlaws, httpUrlRule } from "./url-shape.js"; // 11.0.0: THE URL shape judge // 11.0.0: the one narrow「本机」predicate (auth relaxation)
|
|
25
25
|
import { secretScanForms } from "./secret-scan.js"; // package-private normalization — the one set of forms both scanners read (not in the barrel)
|
|
26
26
|
import { WEB_ADMIN_ENV } from "./web-admin-env.js"; // the three connection keys the config renderer writes are reserved worker env
|
|
27
|
+
import { PREVIOUS_WEB_ADMIN_ENV } from "./previous-web-admin-env.js"; // the previous generation's names of those three (the renderer still writes them for engines that read them)
|
|
27
28
|
import { retiredEnvRefusal } from "./retired-envs.js"; // the worker env write gate asks the retired-env tombstone table too (one table, one refusal sentence)
|
|
28
29
|
import { Sha256Digest } from "./sha256.js"; // the one `sha256:<hex>` digest form
|
|
29
30
|
import { EXECUTION_LANES, EXECUTION_LANE_WORDS, MEMORY_ENGINE_BACKENDS, SESSION_BACKENDS, STORE_BACKENDS, retiredLaneWordRefusal } from "./execution-lanes.js"; // 7.0.0 M-R2: the lane / store vocabulary (one exported table)
|
|
@@ -168,20 +169,52 @@ export function urlQueryFragmentHasSecret(u) {
|
|
|
168
169
|
* after it is dropped with it — the engine and server refuse a model route root that carries one, so a stored one only fails
|
|
169
170
|
* later, on the worker). A query is a legitimate request parameter (`?api-version=…`) and stays. */
|
|
170
171
|
const NON_SECRET_URL_FORBIDS = ["userinfo", "fragment"];
|
|
172
|
+
/** The refusal sentence of one flaw at a {@link NON_SECRET_URL} seat: the judge's rule sentence, and — for the one flaw that stored data
|
|
173
|
+
* can carry from before 11.0.0 (a fragment) at a seat the one-time migration reaches — the signpost to the migration that strips
|
|
174
|
+
* it, joined the way the other signposted refusals join it (`<rule>; <signpost>`). Closed over the judge's flaw words: a new word is
|
|
175
|
+
* a compile error here. */
|
|
176
|
+
function nonSecretUrlSentence(flaw, reachedByMigration) {
|
|
177
|
+
switch (flaw) {
|
|
178
|
+
case "fragment":
|
|
179
|
+
return reachedByMigration ? `${httpUrlRule(flaw)}; ${migrationSignpost("SS-M7", "11.0.0")}` : httpUrlRule(flaw);
|
|
180
|
+
case "not-http-url":
|
|
181
|
+
case "userinfo":
|
|
182
|
+
case "query":
|
|
183
|
+
return httpUrlRule(flaw);
|
|
184
|
+
default: {
|
|
185
|
+
const unhandled = flaw;
|
|
186
|
+
throw new Error("nonSecretUrlSentence: unhandled flaw word", { cause: unhandled });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** One judge, two seat kinds — the only difference is the fragment refusal's migration signpost. `reachedByMigration` = the seat sits in a
|
|
191
|
+
* stored domain document, which the one-time migration SS-M7 rewrites (it finds its seats by this schema's identity, so "carries the
|
|
192
|
+
* signpost" and "is rewritten by SS-M7" cannot drift apart). */
|
|
193
|
+
function createNonSecretUrl(reachedByMigration) {
|
|
194
|
+
return z.string().superRefine((s, ctx) => {
|
|
195
|
+
const flaws = httpUrlFlaws(s, NON_SECRET_URL_FORBIDS);
|
|
196
|
+
for (const flaw of flaws)
|
|
197
|
+
ctx.addIssue({ code: "custom", message: nonSecretUrlSentence(flaw, reachedByMigration), params: { flaw } });
|
|
198
|
+
if (flaws.length === 0 && urlQueryFragmentHasSecret(new URL(s))) {
|
|
199
|
+
ctx.addIssue({ code: "custom", message: "must not carry a secret-shaped value in its path or query — secrets go in the *Env (env-NAME) fields" });
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
171
203
|
/** A URL handed to `fetch` that carries no credential: judged by THE URL shape judge ({@link httpUrlFlaws}, `./url-shape`) — a
|
|
172
204
|
* hierarchical http(s) URL with neither userinfo nor a fragment (an empty `#` included, 11.0.0) — and with no secret-shaped value
|
|
173
205
|
* in its path or query ({@link urlQueryFragmentHasSecret}). One issue per flaw, its message the judge's rule sentence (no value
|
|
174
|
-
* echoed); `params.flaw` carries the rule word.
|
|
206
|
+
* echoed; a fragment's also names its migration, {@link nonSecretUrlSentence}); `params.flaw` carries the rule word.
|
|
207
|
+
*
|
|
208
|
+
* This is the seat of every URL in a stored DOMAIN document (models `baseUrl`, mcp `url`, `sessionMirror.engineUrl`, workers
|
|
209
|
+
* `gitApiBaseUrl`). A URL outside the domain documents — `remote-exec.json`'s endpoints, the web-admin credential envelope's
|
|
210
|
+
* `issuer` — uses {@link NON_SECRET_URL_NO_MIGRATION}.
|
|
175
211
|
*
|
|
176
212
|
* The value is kept EXACTLY as written: nothing here trims or normalizes it (zod's own `.url()` would hand back a rewritten string). */
|
|
177
|
-
export const NON_SECRET_URL =
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
ctx.addIssue({ code: "custom", message: "must not carry a secret-shaped value in its path or query — secrets go in the *Env (env-NAME) fields" });
|
|
183
|
-
}
|
|
184
|
-
});
|
|
213
|
+
export const NON_SECRET_URL = createNonSecretUrl(true);
|
|
214
|
+
/** {@link NON_SECRET_URL}'s judge for a seat no one-time migration reaches (not a domain document): identical rules and rule
|
|
215
|
+
* sentences, but a fragment's refusal carries no migration signpost — pointing at a migration that cannot rewrite the value would
|
|
216
|
+
* send the operator to a command that does nothing for it. */
|
|
217
|
+
export const NON_SECRET_URL_NO_MIGRATION = createNonSecretUrl(false);
|
|
185
218
|
/** Validate a free-form `extraBody`: returns true = UNSAFE (reject). A GETTER-FREE, descriptor-based, bounded
|
|
186
219
|
* walk that rejects — WITHOUT ever invoking a getter (a malicious getter could throw a secret-bearing message,
|
|
187
220
|
* or return different values across the validate vs persist JSON.stringify calls) — anything that isn't plain
|
|
@@ -1613,13 +1646,22 @@ const WorkerSecretRefs = z.object({
|
|
|
1613
1646
|
* · **删** `MEMORY_BACKEND`(引擎从不读的死名,渲染器不再产出)与 `TIDB_*` 族(引擎的墓碑名,见之拒启;结构字段不再产出它们)。
|
|
1614
1647
|
* MYSQL_* / PG_* 两族归 `runtime.store.db` + `secretRefs.dbPasswordEnv`。
|
|
1615
1648
|
*
|
|
1616
|
-
* `env` / `secretEnv`
|
|
1617
|
-
*
|
|
1649
|
+
* `env` / `secretEnv` 的写闸问**两张表**,各答一个问题,互相独立:
|
|
1650
|
+
* · 本表答「规格作者可不可以自己设这个名」—— 不可以:它归某个结构字段,或归渲染器 / 编排器写,或与渲染器 / 编排器写的名互斥(拒句指路到那个写位);
|
|
1651
|
+
* · 墓碑表 `RETIRED_ENVS`(`./retired-envs`)答「带着墓碑表的引擎还读不读这个名」—— 不读、见之拒启(拒句就是墓碑句 `retiredEnvRefusal` 本身,引擎起服、壳、web-admin 读同一张表同一句)。
|
|
1652
|
+
* 两个问题的读者不同,答案可以同时为「是」。换名窗里就是这样:更老的引擎内带更老的契约、看不见墓碑表,仍读上一代名,渲染器因此对它们仍写上一代的 URL / WORKER / TOKEN
|
|
1653
|
+
* (TOKEN_FILE 与 TOKEN 互斥,同组同入)—— 这四名对第一个问题答「不可以自设」(本表),对第二个问题答「不读」(墓碑表),写闸交两句:墓碑句在前、保留句在后,各说各的事。
|
|
1654
|
+
* 这是**窗期**形。窗关 —— 渲染器不再对任何目标引擎写上一代名、此后没有引擎要读它们 —— 这四名离开本表,两表回到零交集,那才是终态;窗期交集是个写死的闭集,
|
|
1655
|
+
* 有钉盯着,窗关时那一钉翻回零交集。
|
|
1618
1656
|
*
|
|
1619
|
-
* 11.0.0
|
|
1620
|
-
*
|
|
1621
|
-
*
|
|
1622
|
-
*
|
|
1657
|
+
* 11.0.0:`ANTHROPIC_BASEURL`(墓碑名)换成现名 `ANTHROPIC_BASE_URL`;渲染器写的 web-admin 连接三键入表,同 fleet 成员身份两键的「渲染器写、不可配」。
|
|
1658
|
+
* **规则一条:渲染器写的名字是保留名**(运维不可在 `env` / `secretEnv` 里自写)。改名窗里渲染器按目标引擎版本写名,对还读上一代名的引擎写的是
|
|
1659
|
+
* 上一代的 URL / WORKER / TOKEN 三名({@link PREVIOUS_WEB_ADMIN_ENV}),所以这三名与现名三键同一组、保留句同一句(指路同一处)。
|
|
1660
|
+
* 上一代八名都是墓碑(引擎换读现名之后,新引擎见之拒启);其中上面几名因窗期而同时留在本表,另外几名(LIVE_URL / WEB_URL / DRY_RUN / ADMIN_TOKEN)没有谁写、也不与谁互斥,只是墓碑。
|
|
1661
|
+
*
|
|
1662
|
+
* 两把凭证的文件键与断言令牌值键也是保留名(同一条规则):断言令牌(`wat_`)的值键归编排器注入;两个文件键(主凭证 `tokenFile`、断言令牌 `assertionTokenFile`)
|
|
1663
|
+
* 只给不经编排器的手接引擎、由运维直设,不经 worker 规格。文件键与同一份凭证的值键互斥(`judgeWebAdminCredentialDeclaration`:一对里两个都设 ⇒ 拒启),
|
|
1664
|
+
* 规格里写了任一个就与注入撞,拒句点名各自的出路({@link WEB_ADMIN_ASSERTION_SEAT}、{@link WEB_ADMIN_OPERATOR_FILE_SEAT});上一代的 `tokenFile` 与老三名同组同入。
|
|
1623
1665
|
*/
|
|
1624
1666
|
/** web-admin 连接三键的唯一写位(渲染器;没有 WorkerSpec 字段)—— 现名与上一代名指路同一句。 */
|
|
1625
1667
|
const WEB_ADMIN_CONNECTION_SEAT = {
|
|
@@ -1627,6 +1669,12 @@ const WEB_ADMIN_CONNECTION_SEAT = {
|
|
|
1627
1669
|
worker: "(set by the config renderer from the worker's name — not configurable)",
|
|
1628
1670
|
token: "(set by the config renderer from the worker's pull token — not configurable)",
|
|
1629
1671
|
};
|
|
1672
|
+
/** web-admin 凭证的出路:值键由编排器注入;文件键由运维直设(只给不经编排器的手接引擎),不进 worker 规格。没有 WorkerSpec 字段能写它们。
|
|
1673
|
+
* 两把凭证(主凭证 `token` / `tokenFile`、断言令牌 `assertionToken` / `assertionTokenFile`)同一条规则 —— 文件键与同一份凭证的值键互斥,所以文件键同一句出路。 */
|
|
1674
|
+
const WEB_ADMIN_ASSERTION_SEAT = {
|
|
1675
|
+
token: "(injected by the orchestrator from the worker's assertion token — not configurable)",
|
|
1676
|
+
};
|
|
1677
|
+
const WEB_ADMIN_OPERATOR_FILE_SEAT = "(set directly by the operator on a hand-wired engine that is not managed by the orchestrator — never in a worker spec — not configurable)";
|
|
1630
1678
|
const RESERVED_ENV_FIELD = Object.freeze({
|
|
1631
1679
|
PORT: "(fixed at 8090 — not configurable)", MODEL_PROVIDER: "model.provider", MODEL_ID: "model.modelId",
|
|
1632
1680
|
MODEL_REASONING: "model.reasoning", MODEL_CHEAP_ID: "model.cheapId", MODEL_MAX_TOKENS: "model.maxTokens",
|
|
@@ -1650,9 +1698,14 @@ const RESERVED_ENV_FIELD = Object.freeze({
|
|
|
1650
1698
|
[FLEET_SCOPE_ENV]: "(set by the config renderer from the worker's scope — not configurable)",
|
|
1651
1699
|
// ── web-admin 连接三键:渲染器给每台 worker 写 web-admin 根 URL、worker 名与该 worker 的拉取令牌 —— 没有 WorkerSpec 字段能写它们;
|
|
1652
1700
|
// 现名与渲染器对老引擎写的上一代名同一组(见表头注)
|
|
1653
|
-
[WEB_ADMIN_ENV.url]: WEB_ADMIN_CONNECTION_SEAT.url,
|
|
1654
|
-
[WEB_ADMIN_ENV.worker]: WEB_ADMIN_CONNECTION_SEAT.worker,
|
|
1655
|
-
[WEB_ADMIN_ENV.token]: WEB_ADMIN_CONNECTION_SEAT.token,
|
|
1701
|
+
[WEB_ADMIN_ENV.url]: WEB_ADMIN_CONNECTION_SEAT.url, [PREVIOUS_WEB_ADMIN_ENV.url]: WEB_ADMIN_CONNECTION_SEAT.url,
|
|
1702
|
+
[WEB_ADMIN_ENV.worker]: WEB_ADMIN_CONNECTION_SEAT.worker, [PREVIOUS_WEB_ADMIN_ENV.worker]: WEB_ADMIN_CONNECTION_SEAT.worker,
|
|
1703
|
+
[WEB_ADMIN_ENV.token]: WEB_ADMIN_CONNECTION_SEAT.token, [PREVIOUS_WEB_ADMIN_ENV.token]: WEB_ADMIN_CONNECTION_SEAT.token,
|
|
1704
|
+
// ── web-admin 凭证的文件键与断言令牌值键:文件键由运维直设、与同一份凭证的值键互斥 —— 规格里写任一个都与注入撞(主凭证与断言令牌同一规则)。
|
|
1705
|
+
// 上一代的 TOKEN_FILE 与老三名同组同入(换名窗里还读上一代名的引擎读它)。
|
|
1706
|
+
[WEB_ADMIN_ENV.tokenFile]: WEB_ADMIN_OPERATOR_FILE_SEAT, [PREVIOUS_WEB_ADMIN_ENV.tokenFile]: WEB_ADMIN_OPERATOR_FILE_SEAT,
|
|
1707
|
+
[WEB_ADMIN_ENV.assertionToken]: WEB_ADMIN_ASSERTION_SEAT.token,
|
|
1708
|
+
[WEB_ADMIN_ENV.assertionTokenFile]: WEB_ADMIN_OPERATOR_FILE_SEAT,
|
|
1656
1709
|
REQUIRE_EXECUTION_LANE: "runtime.execution.lane (the renderer emits REQUIRE_EXECUTION_LANE=true for every declared lane)",
|
|
1657
1710
|
...Object.fromEntries([...LANE_ENV_OWNER].map(([name, owner]) => [name, laneEnvSeat(name, owner)])),
|
|
1658
1711
|
});
|
|
@@ -32,7 +32,7 @@ export declare const WEB_ADMIN_CREDENTIAL_ENVELOPE_KIND: "user-session";
|
|
|
32
32
|
* 文件**取当前值,不用内存旧值(另一个写者可能刚轮换过)。
|
|
33
33
|
* · `expiresAt` —— 访问令牌到期时刻(RFC 3339)。
|
|
34
34
|
* · `issuer` —— 签发它的 web-admin 的**基础 URL**(与读方连 web-admin 的根 URL 同形,可带路径前缀):续签只向同源的 web-admin 发,
|
|
35
|
-
* 同不同源由读方比。URL 形判据与 `remoteExec` 的端点同一只 {@link
|
|
35
|
+
* 同不同源由读方比。URL 形判据与 `remoteExec` 的端点同一只 {@link NON_SECRET_URL_NO_MIGRATION}(http(s)、有 host、userinfo 与
|
|
36
36
|
* query / fragment 里不许夹带凭据)—— 本包不另写一份更窄的 URL 律。
|
|
37
37
|
*/
|
|
38
38
|
export declare const WebAdminCredentialEnvelope: z.ZodObject<{
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { z } from "zod";
|
|
23
23
|
import { looksLikeJwt } from "./api/auth-bridge.js";
|
|
24
|
-
import {
|
|
24
|
+
import { NON_SECRET_URL_NO_MIGRATION } from "./types.js";
|
|
25
25
|
import { WEB_ADMIN_ENV } from "./web-admin-env.js";
|
|
26
26
|
/** 信封 `kind` 的闭集今只有一员(`SEALED_BOX_ALG` 同式的字面常量:schema 的字面与读方的比较都引它)。 */
|
|
27
27
|
export const WEB_ADMIN_CREDENTIAL_ENVELOPE_KIND = "user-session";
|
|
@@ -50,7 +50,7 @@ const TokenString = z.string().regex(TOKEN_CHARS, "must be a token: printable AS
|
|
|
50
50
|
* 文件**取当前值,不用内存旧值(另一个写者可能刚轮换过)。
|
|
51
51
|
* · `expiresAt` —— 访问令牌到期时刻(RFC 3339)。
|
|
52
52
|
* · `issuer` —— 签发它的 web-admin 的**基础 URL**(与读方连 web-admin 的根 URL 同形,可带路径前缀):续签只向同源的 web-admin 发,
|
|
53
|
-
* 同不同源由读方比。URL 形判据与 `remoteExec` 的端点同一只 {@link
|
|
53
|
+
* 同不同源由读方比。URL 形判据与 `remoteExec` 的端点同一只 {@link NON_SECRET_URL_NO_MIGRATION}(http(s)、有 host、userinfo 与
|
|
54
54
|
* query / fragment 里不许夹带凭据)—— 本包不另写一份更窄的 URL 律。
|
|
55
55
|
*/
|
|
56
56
|
export const WebAdminCredentialEnvelope = z
|
|
@@ -59,7 +59,7 @@ export const WebAdminCredentialEnvelope = z
|
|
|
59
59
|
accessToken: TokenString.refine(looksLikeJwt, "must be a JWT (three dot-separated segments) — a worker pull token never goes into an envelope"),
|
|
60
60
|
refreshToken: TokenString,
|
|
61
61
|
expiresAt: RFC3339_DATE_TIME,
|
|
62
|
-
issuer:
|
|
62
|
+
issuer: NON_SECRET_URL_NO_MIGRATION,
|
|
63
63
|
})
|
|
64
64
|
.strict();
|
|
65
65
|
/** 信封自己的字段名(schema 的键 —— reason 里只许出现这几个名字,不许出现用户写的键)。 */
|
package/dist/web-admin-env.d.ts
CHANGED
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
* · `adminToken` —— 引擎以管理身份写 web-admin 时用的令牌(起服上报执行公钥)。
|
|
18
18
|
*
|
|
19
19
|
* 渲染器给每台 worker 写的三键(`url` / `worker` / `token`)同时是 worker env 的**保留名**(`RESERVED_WORKER_ENV`):运维在 worker 规格的
|
|
20
|
-
* `env`
|
|
20
|
+
* `env` 里写它们,会与渲染器写的值打架。断言令牌的值键 `assertionToken` 也是保留名(由编排器注入);两把凭证的文件键(`tokenFile` / `assertionTokenFile`)同样是保留名:
|
|
21
|
+
* 只给不经编排器的手接引擎、由运维直设,与同一份凭证的值键互斥,规格里写任一个都与注入撞。
|
|
21
22
|
*/
|
|
22
23
|
export declare const WEB_ADMIN_ENV: {
|
|
23
24
|
readonly url: "SEMA_WEB_ADMIN_URL";
|
package/dist/web-admin-env.js
CHANGED
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
* · `adminToken` —— 引擎以管理身份写 web-admin 时用的令牌(起服上报执行公钥)。
|
|
18
18
|
*
|
|
19
19
|
* 渲染器给每台 worker 写的三键(`url` / `worker` / `token`)同时是 worker env 的**保留名**(`RESERVED_WORKER_ENV`):运维在 worker 规格的
|
|
20
|
-
* `env`
|
|
20
|
+
* `env` 里写它们,会与渲染器写的值打架。断言令牌的值键 `assertionToken` 也是保留名(由编排器注入);两把凭证的文件键(`tokenFile` / `assertionTokenFile`)同样是保留名:
|
|
21
|
+
* 只给不经编排器的手接引擎、由运维直设,与同一份凭证的值键互斥,规格里写任一个都与注入撞。
|
|
21
22
|
*/
|
|
22
23
|
export const WEB_ADMIN_ENV = {
|
|
23
24
|
url: "SEMA_WEB_ADMIN_URL",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** 一个会话在 web-admin 里的坐标:它在哪台 worker 上、会话 id 是什么。 */
|
|
2
|
+
export interface WebAdminSessionTarget {
|
|
3
|
+
readonly worker: string;
|
|
4
|
+
readonly sessionId: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* 铸一个会话的绝对网页链接。`webBaseUrl` 必须是干净的绝对 http(s) 基址(无 userinfo / query / fragment;可带路径前缀;尾斜杠折掉);
|
|
8
|
+
* `worker` 与 `sessionId` 必填,逐位 `encodeURIComponent`(含 `&` `#` `=` `?` 的 id 不会串位)。坏输入一律抛,不铸半截链接;
|
|
9
|
+
* 含孤立代理项的串(坏 UTF-16)由 `encodeURIComponent` 抛 `URIError`,不静默替换。
|
|
10
|
+
*/
|
|
11
|
+
export declare function webAdminSessionUrl(webBaseUrl: string, target: WebAdminSessionTarget): string;
|
|
12
|
+
/**
|
|
13
|
+
* {@link webAdminSessionUrl} 的配对解析:从一个链接读回 `worker` / `sessionId`。输入可以是绝对 URL 串、相对串(`/chat?…`)、`URL` 或
|
|
14
|
+
* `URLSearchParams`(聊天页读当前地址栏的形)。**宽读**:缺的那一位不出现在结果里(空值等于缺席);重复参数取第一个(同 `searchParams.get`);
|
|
15
|
+
* 路径与别的查询参数不看。串解析不出来(不是 URL)⇒ 抛 `TypeError`,不静默当成「没有参数」。
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseWebAdminSessionUrl(link: string | URL | URLSearchParams): Partial<WebAdminSessionTarget>;
|