@sema-agent/settings-schema 11.1.0 → 11.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +151 -0
- package/README.md +14 -1
- package/bin/settings-schema.mjs +5 -9
- package/conformance/VERSION +1 -1
- package/conformance/cases/c73-projects-extended-registration-reads/expected.json +150 -0
- package/conformance/cases/c73-projects-extended-registration-reads/input.json +42 -0
- package/conformance/cases/c73-projects-extended-registration-reads/meta.json +6 -0
- package/conformance/cases/c74-projects-over-bound-refused/expected.json +89 -0
- package/conformance/cases/c74-projects-over-bound-refused/input.json +29 -0
- package/conformance/cases/c74-projects-over-bound-refused/meta.json +5 -0
- package/conformance/cases/c75-projects-patch-remote-uniqueness/expected.json +89 -0
- package/conformance/cases/c75-projects-patch-remote-uniqueness/input.json +35 -0
- package/conformance/cases/c75-projects-patch-remote-uniqueness/meta.json +5 -0
- package/dist/api/feedback.js +2 -3
- package/dist/api/scopes.js +2 -1
- package/dist/atomic-write.d.ts +7 -1
- package/dist/atomic-write.js +43 -5
- package/dist/cli.d.ts +34 -6
- package/dist/cli.js +80 -130
- package/dist/config-d-file.d.ts +11 -0
- package/dist/config-d-file.js +35 -1
- package/dist/config-dir-io.d.ts +2 -0
- package/dist/config-dir-io.js +14 -0
- package/dist/config-dir-migration.d.ts +67 -0
- package/dist/config-dir-migration.js +181 -0
- package/dist/cross-domain.d.ts +8 -3
- package/dist/cross-domain.js +23 -13
- package/dist/file-edit.js +7 -5
- package/dist/file-store.js +2 -1
- package/dist/fleet.js +3 -4
- package/dist/hooks.d.ts +14 -1
- package/dist/hooks.js +61 -16
- package/dist/host-path.d.ts +17 -0
- package/dist/host-path.js +22 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/integer.js +2 -1
- package/dist/local-load.d.ts +1 -1
- package/dist/local-load.js +9 -31
- package/dist/migration-receipt.d.ts +3 -2
- package/dist/node.d.ts +2 -0
- package/dist/node.js +3 -0
- package/dist/one-time-migrations.d.ts +5 -3
- package/dist/one-time-migrations.js +21 -7
- package/dist/previous-web-admin-env.d.ts +15 -0
- package/dist/previous-web-admin-env.js +20 -0
- package/dist/quoted-issue.d.ts +41 -0
- package/dist/quoted-issue.js +35 -0
- package/dist/remote-exec.d.ts +3 -0
- package/dist/remote-exec.js +12 -7
- package/dist/retired-envs.js +13 -2
- package/dist/retired-form.d.ts +6 -1
- package/dist/retired-form.js +10 -2
- package/dist/rfc3339.d.ts +10 -0
- package/dist/rfc3339.js +12 -0
- package/dist/runtime-lift-targets.d.ts +7 -0
- package/dist/runtime-lift-targets.js +7 -0
- package/dist/shell-quote.d.ts +1 -0
- package/dist/shell-quote.js +14 -0
- package/dist/types.d.ts +140 -20
- package/dist/types.js +224 -114
- package/dist/web-admin-credential.d.ts +2 -2
- package/dist/web-admin-credential.js +5 -10
- 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
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 叶子模块(纯、浏览器安全):**一只**「主机绝对路径」形判 —— 任务请求的 `cwd`(服务器受理时判)与项目登记里 `folder` / `file`
|
|
3
|
+
* 条目的值读同一只,不各写一份。只判形,不判存在(不碰文件系统)。
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
/** 一条主机绝对路径的长度上界(字符数)。 */
|
|
7
|
+
export declare const ABSOLUTE_HOST_PATH_MAX = 4096;
|
|
8
|
+
/**
|
|
9
|
+
* `p` 是不是一条形上合法的**主机绝对路径**:字符串、非空、≤ {@link ABSOLUTE_HOST_PATH_MAX}、不含 NUL、以 `/` 开头(posix 绝对)、没有 `..` 段
|
|
10
|
+
* (`/` 与 `\` 都算分隔符 —— `path.isAbsolute` 是语法判,`/a/../../etc` 也「绝对」,而读方 `path.resolve` 会把它解到别处;拒掉它,读到的就是写下的那一条)。
|
|
11
|
+
*
|
|
12
|
+
* posix 口径是有意的:读这些路径的主机车道今天跑 posix 树,而这一律在 posix 宿主上与 Node 的 `path.isAbsolute` 逐字同判(`C:\x`、`\\host\share`
|
|
13
|
+
* 在 posix 上不绝对)。哪天主机车道要接 win 树,换的是这一只(按树的家族判),读方跟着同一只走。
|
|
14
|
+
*/
|
|
15
|
+
export declare function isAbsoluteHostPath(p: unknown): p is string;
|
|
16
|
+
/** {@link isAbsoluteHostPath} 的 zod 座位(一条 issue,定句)。 */
|
|
17
|
+
export declare const AbsoluteHostPath: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 叶子模块(纯、浏览器安全):**一只**「主机绝对路径」形判 —— 任务请求的 `cwd`(服务器受理时判)与项目登记里 `folder` / `file`
|
|
3
|
+
* 条目的值读同一只,不各写一份。只判形,不判存在(不碰文件系统)。
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { buildQuotedIssue, fixed } from "./quoted-issue.js";
|
|
7
|
+
/** 一条主机绝对路径的长度上界(字符数)。 */
|
|
8
|
+
export const ABSOLUTE_HOST_PATH_MAX = 4096;
|
|
9
|
+
/**
|
|
10
|
+
* `p` 是不是一条形上合法的**主机绝对路径**:字符串、非空、≤ {@link ABSOLUTE_HOST_PATH_MAX}、不含 NUL、以 `/` 开头(posix 绝对)、没有 `..` 段
|
|
11
|
+
* (`/` 与 `\` 都算分隔符 —— `path.isAbsolute` 是语法判,`/a/../../etc` 也「绝对」,而读方 `path.resolve` 会把它解到别处;拒掉它,读到的就是写下的那一条)。
|
|
12
|
+
*
|
|
13
|
+
* posix 口径是有意的:读这些路径的主机车道今天跑 posix 树,而这一律在 posix 宿主上与 Node 的 `path.isAbsolute` 逐字同判(`C:\x`、`\\host\share`
|
|
14
|
+
* 在 posix 上不绝对)。哪天主机车道要接 win 树,换的是这一只(按树的家族判),读方跟着同一只走。
|
|
15
|
+
*/
|
|
16
|
+
export function isAbsoluteHostPath(p) {
|
|
17
|
+
return typeof p === "string" && p.length > 0 && p.length <= ABSOLUTE_HOST_PATH_MAX && !p.includes("\0") && p.startsWith("/") && !p.split(/[/\\]/).includes("..");
|
|
18
|
+
}
|
|
19
|
+
/** {@link isAbsoluteHostPath} 的 zod 座位(一条 issue,定句)。 */
|
|
20
|
+
export const AbsoluteHostPath = z
|
|
21
|
+
.string()
|
|
22
|
+
.refine(isAbsoluteHostPath, buildQuotedIssue `must be an absolute host path: starts with "/", no ".." segment, no NUL, at most ${fixed(String(ABSOLUTE_HOST_PATH_MAX))} characters`);
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -29,6 +29,7 @@ export * from "./api/pull-token.js"; // scope-bound token FORM + minting rule (o
|
|
|
29
29
|
export * from "./api/feedback.js"; // /bug report wire contract: FeedbackSubmitRequest (POST body) + FeedbackWire / FeedbackListResponse + the two caps, pure/browser-safe
|
|
30
30
|
export * from "./web-admin-env.js"; // WEB_ADMIN_ENV — every env name an engine reads to reach web-admin (one table; a leaf module), pure/browser-safe
|
|
31
31
|
export * from "./web-admin-credential.js"; // S-698 web-admin credential FILE contract: WebAdminCredentialEnvelope (user-session envelope, strict) + parseWebAdminCredentialFile (bare / envelope / unreadable — the one discrimination law) + judgeWebAdminCredentialDeclaration(env, credential) (pick-exactly-one over the credential's WEB_ADMIN_ENV pair; WEB_ADMIN_CREDENTIALS = access | assertion), pure/browser-safe
|
|
32
|
+
export * from "./web-admin-session-url.js"; // webAdminSessionUrl(webBaseUrl, { worker, sessionId }) — THE web-admin session link template (<webBaseUrl>/chat?agent=<worker>&session=<sessionId>) + parseWebAdminSessionUrl(link) its paired reader, pure/browser-safe
|
|
32
33
|
export * from "./retired-envs.js"; // the retired-env TOMBSTONE table (renamed / replaced / removed, closed set) + retiredEnvRefusal(env) (the one refusal sentence) + buildRetiredEnvWire() — engine boot, catalog wire, shell and web-admin read this one table, pure/browser-safe
|
|
33
34
|
// NOTE: the Node-fs-backed helpers (FileConfigStore + the file editor) are NOT in this barrel — they pull
|
|
34
35
|
// `node:fs`/`node:path` and would break a browser/client bundle that only wants the contract (e.g.
|
package/dist/integer.js
CHANGED
|
@@ -8,4 +8,5 @@
|
|
|
8
8
|
* written down here once, instead of being borrowed from the library and changing whenever the library does.
|
|
9
9
|
*/
|
|
10
10
|
import { z } from "zod";
|
|
11
|
-
|
|
11
|
+
import { buildQuotedIssue } from "./quoted-issue.js";
|
|
12
|
+
export const INTEGER = z.number().refine((n) => Number.isInteger(n), buildQuotedIssue `Expected integer, received float`);
|
package/dist/local-load.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type RequiredEnvName } from "./file-edit.js";
|
|
2
2
|
import { type EffectiveReadWarning } from "./config-fns.js";
|
|
3
|
-
import { RemoteExecSpec } from "./remote-exec.js";
|
|
3
|
+
import { type RemoteExecSpec } from "./remote-exec.js";
|
|
4
4
|
import type { EffectiveConfig } from "./types.js";
|
|
5
5
|
/** The remoteExec file path under a config root (under config.d/, beside the per-domain files). */
|
|
6
6
|
export declare function remoteExecPath(root: string): string;
|
package/dist/local-load.js
CHANGED
|
@@ -13,25 +13,12 @@ import path from "node:path";
|
|
|
13
13
|
import { writeFileAtomic } from "./atomic-write.js";
|
|
14
14
|
import { FileConfigStore } from "./file-store.js";
|
|
15
15
|
import { listRequiredEnvNames } from "./file-edit.js";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
* ride the thrown error). A hostile getter/Proxy trap → a fixed message. R14. */
|
|
20
|
-
function parseRemoteExec(raw) {
|
|
21
|
-
let r;
|
|
22
|
-
try {
|
|
23
|
-
r = RemoteExecSpec.safeParse(raw);
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
throw new Error("invalid remoteExec (a value could not be read during validation)");
|
|
27
|
-
}
|
|
28
|
-
if (r.success)
|
|
29
|
-
return r.data;
|
|
30
|
-
throw sanitizeZodError(r.error);
|
|
31
|
-
}
|
|
16
|
+
import {} from "./config-fns.js";
|
|
17
|
+
import { configDDocumentText, judgeRemoteExecText, parseRemoteExecDocument } from "./config-d-file.js";
|
|
18
|
+
import { REMOTE_EXEC_DOCUMENT } from "./remote-exec.js";
|
|
32
19
|
/** The remoteExec file path under a config root (under config.d/, beside the per-domain files). */
|
|
33
20
|
export function remoteExecPath(root) {
|
|
34
|
-
return path.join(root, "config.d",
|
|
21
|
+
return path.join(root, "config.d", `${REMOTE_EXEC_DOCUMENT}.json`);
|
|
35
22
|
}
|
|
36
23
|
/** Read `<root>/config.d/remote-exec.json` → a VALIDATED RemoteExecSpec, or undefined if the file is absent
|
|
37
24
|
* (→ the engine defaults to `host` under CONFIG_PROVIDER=local). Throws (zod) on a present-but-invalid file. */
|
|
@@ -45,28 +32,19 @@ export async function loadRemoteExec(root) {
|
|
|
45
32
|
return undefined;
|
|
46
33
|
throw e;
|
|
47
34
|
}
|
|
48
|
-
const trimmed = text.trim();
|
|
49
35
|
// A present-but-empty file is a corruption/half-write, NOT "use the default" — FAIL CLOSED (an empty file
|
|
50
36
|
// silently falling back to the host lane would be an isolation DOWNGRADE if the user meant local-docker).
|
|
51
|
-
// To get the host default, the file must be ABSENT, not empty.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
let json;
|
|
55
|
-
try {
|
|
56
|
-
json = JSON.parse(trimmed); // sanitized: don't let a SyntaxError echo the raw (possibly secret-bearing) file text
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
throw new Error(`${remoteExecPath(root)} — invalid JSON (delete it for the host default, or write a valid remoteExec spec)`);
|
|
60
|
-
}
|
|
61
|
-
return parseRemoteExec(json);
|
|
37
|
+
// To get the host default, the file must be ABSENT, not empty. The text judge and the sanitized value judge are the ones the
|
|
38
|
+
// directory migration reads this file with (`./config-d-file`).
|
|
39
|
+
return parseRemoteExecDocument(judgeRemoteExecText(remoteExecPath(root), text));
|
|
62
40
|
}
|
|
63
41
|
/** Validate + ATOMICALLY write `<root>/config.d/remote-exec.json` (the package's one atomic writer, `./atomic-write`; never a half file). The
|
|
64
42
|
* client's `config` editor writes the exec backend through this; the engine reads it via loadRemoteExec. */
|
|
65
43
|
export async function writeRemoteExec(root, spec) {
|
|
66
|
-
const parsed =
|
|
44
|
+
const parsed = parseRemoteExecDocument(spec);
|
|
67
45
|
const dir = path.join(root, "config.d");
|
|
68
46
|
await mkdir(dir, { recursive: true });
|
|
69
|
-
writeFileAtomic(remoteExecPath(root),
|
|
47
|
+
writeFileAtomic(remoteExecPath(root), configDDocumentText(parsed), "config-document");
|
|
70
48
|
return parsed;
|
|
71
49
|
}
|
|
72
50
|
/** Resolve a `file:<path>` .env VALUE → the file's trimmed content (the file-NAME secret ref, config-research
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* 一次性迁移的**收据形**与两张闭集(11.0.0)—— 叶子模块(运行期零依赖;只有型导入),由 `./one-time-migrations` 再导出(不进主桶)。
|
|
3
3
|
* 单拆出来只为一件事:调度店的读口(`./scheduler-store`,轻、浏览器安全)要在告警里带收据,不该为一个型把整套域 schema 拉进来。
|
|
4
4
|
*/
|
|
5
|
+
import type { REMOTE_EXEC_DOCUMENT } from "./remote-exec.js";
|
|
5
6
|
import type { DomainName, RetiredDomainName } from "./types.js";
|
|
6
7
|
/** 本版登记的一次性**数据**迁移 id(闭集;每个 id 在 `ONE_TIME_MIGRATIONS` 登记表里恰一行)。收据只许这些 id —— 同表登记的 wire 删码位
|
|
7
8
|
* (`WIRE_RETIREMENT_IDS`,`./one-time-migrations`)不改任何存储形,铸不出收据,不在本集。 */
|
|
@@ -17,8 +18,8 @@ export type OneTimeMigrationId = (typeof ONE_TIME_MIGRATION_IDS)[number];
|
|
|
17
18
|
*/
|
|
18
19
|
export declare const MIGRATION_APPLIED_AS: readonly ["moved", "rewritten", "dropped-shadowed", "dropped-inert", "refused"];
|
|
19
20
|
export type MigrationAppliedAs = (typeof MIGRATION_APPLIED_AS)[number];
|
|
20
|
-
/** 收据指向的那份文档:配置域名、retired 域名(`runtime`),或调度店(`scheduler-store`)。 */
|
|
21
|
-
export type MigrationDocument = DomainName | RetiredDomainName | "scheduler-store";
|
|
21
|
+
/** 收据指向的那份文档:配置域名、retired 域名(`runtime`)、`config.d/remote-exec.json`(`remote-exec`),或调度店(`scheduler-store`)。 */
|
|
22
|
+
export type MigrationDocument = DomainName | RetiredDomainName | typeof REMOTE_EXEC_DOCUMENT | "scheduler-store";
|
|
22
23
|
/** 文档里的段坐标(字符串段 = 对象键,数字段 = 数组下标;与告警坐标同一形)。 */
|
|
23
24
|
export type MigrationPath = readonly (string | number)[];
|
|
24
25
|
interface ReceiptBase {
|
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 ConfigDirectoryDocument, 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 模式规则)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MigrationReceipt, OneTimeMigrationId } from "./migration-receipt.js";
|
|
2
|
+
import { REMOTE_EXEC_DOCUMENT } from "./remote-exec.js";
|
|
2
3
|
import { type DomainName, type RetiredDomainName } from "./types.js";
|
|
3
4
|
export { MIGRATION_APPLIED_AS, ONE_TIME_MIGRATION_IDS, type MigrationAppliedAs, type MigrationDocument, type MigrationPath, type MigrationReceipt, type OneTimeMigrationId } from "./migration-receipt.js";
|
|
4
5
|
export { migrateSchedulerRecords } from "./scheduler-store-migration.js";
|
|
@@ -33,8 +34,8 @@ export declare const ONE_TIME_MIGRATIONS: readonly OneTimeMigrationWindow[];
|
|
|
33
34
|
/** 一组存储文档是不是**一个 scope 的全集**:`"scope"` = 缺席的域就是从未设过;`"partial"` = 缺席的域只是没给(principal 层、回滚史的
|
|
34
35
|
* 单域行、任意子集)。只有 SS-M1 读它。 */
|
|
35
36
|
export type DocumentCoverage = "scope" | "partial";
|
|
36
|
-
/** 一组存储文档(域名 / retired 域名 → 文档)。别的键原样带过、不动。 */
|
|
37
|
-
export type StoredDocuments = Readonly<Partial<Record<DomainName | RetiredDomainName, unknown>>>;
|
|
37
|
+
/** 一组存储文档(域名 / retired 域名 / `remote-exec` → 文档)。别的键原样带过、不动。 */
|
|
38
|
+
export type StoredDocuments = Readonly<Partial<Record<DomainName | RetiredDomainName | typeof REMOTE_EXEC_DOCUMENT, unknown>>>;
|
|
38
39
|
export interface StoredDocumentsMigration {
|
|
39
40
|
/** 迁移后的文档集:没改的文档是**同一引用**;被移除的(retired 域)不在;新建的(例:迁出的 `governance` / `limits`)在。 */
|
|
40
41
|
readonly docs: Readonly<Record<string, unknown>>;
|
|
@@ -58,5 +59,6 @@ export declare function migrateEffectiveSnapshot(effective: unknown): {
|
|
|
58
59
|
readonly effective: unknown;
|
|
59
60
|
readonly receipts: readonly MigrationReceipt[];
|
|
60
61
|
};
|
|
61
|
-
/**
|
|
62
|
+
/** SS-M7 走查的全部 URL 座位的坐标模式(派生;数组 / Record 位写 `*`;域文档以域名起头,`remote-exec.json` 以 `remote-exec` 起头)—— 迁移不用它,
|
|
63
|
+
* 它给钉与说明看「今天派生出哪些座位」。 */
|
|
62
64
|
export declare function urlSeatPatterns(): string[];
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* 同一串字节换意思的改动一律**换表示**(SS-M6 因此是「`atModelAllowlist` → `atMention`」换字段,而不是翻 `[]` 的义)。
|
|
13
13
|
*
|
|
14
14
|
* ## 入口(纯函数:不读环境、不读墙钟、不改入参、**不抬任何版本号**)
|
|
15
|
-
* · {@link migrateStoredDocuments} —— 一组**存储文档**(域名 →
|
|
16
|
-
* skills / plugins)、回滚史里的一条单域行,各调一次;`coverage` 说这组文档是不是一个 scope 的全集(只有 SS-M1 要知道:缺席的
|
|
15
|
+
* · {@link migrateStoredDocuments} —— 一组**存储文档**(域名 → 文档;还有本机的 `remote-exec` —— `config.d/remote-exec.json`,
|
|
16
|
+
* 只有 SS-M7 的座位走查读它)。一个 scope 的全部草稿、一个 principal 层(个人 models / mcp / skills / plugins)、回滚史里的一条单域行,各调一次;`coverage` 说这组文档是不是一个 scope 的全集(只有 SS-M1 要知道:缺席的
|
|
17
17
|
* `governance` 是「从未设过」还是「没给」)。缺的域 = 不动。
|
|
18
18
|
* · {@link migrateEffectiveSnapshot} —— 一份**冻结的 effective**(发布快照、回滚历史里的整份快照、引擎的 last-known-good 文件)。改写规则
|
|
19
19
|
* 与草稿同(零语义变 ⇒ 不越过发布审批:改的是表示,不是已批准的内容);不从草稿重建;`version` / `updatedAt` 与其余信封键原样。
|
|
@@ -33,6 +33,8 @@ 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";
|
|
37
|
+
import { REMOTE_EXEC_DOCUMENT, RemoteExecSpec } from "./remote-exec.js";
|
|
36
38
|
import { DOMAIN_SCHEMAS, HOSTS_ARRAY_MAX, HOST_NAME_MAX, NON_SECRET_URL, isDomainName, isRetiredDomainName } from "./types.js";
|
|
37
39
|
import { workerRowVerdict } from "./worker-row-verdict.js";
|
|
38
40
|
import { unwrapSchema } from "./zod-unwrap.js";
|
|
@@ -127,7 +129,8 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
127
129
|
removableAfter: "2026-11-09",
|
|
128
130
|
rewrites: "every NON_SECRET_URL seat in a domain document (derived from the domain schemas, not listed by hand — today models.models[].baseUrl, " +
|
|
129
131
|
"mcp.servers[].transport.url, execution.sessionMirror.engineUrl, workers.workers[].coords.gitApiBaseUrl): `#` and everything after it are removed " +
|
|
130
|
-
"(a fragment never goes on the wire)"
|
|
132
|
+
"(a fragment never goes on the wire); the same walk covers config.d/remote-exec.json, derived from its schema the same way " +
|
|
133
|
+
"(today the k8s apiUrl and s3Snapshot.endpoint) — the command-line migrate reads and rewrites it as one more document",
|
|
131
134
|
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",
|
|
132
135
|
afterRemoval: "the NON_SECRET_URL shape judge, which refuses a fragment (`#` included) on read and write",
|
|
133
136
|
},
|
|
@@ -266,7 +269,7 @@ function stepRuntimeDomain(out, coverage, receipts) {
|
|
|
266
269
|
}
|
|
267
270
|
if (!isPlainObject(raw))
|
|
268
271
|
return refuse("the stored runtime document is not a JSON object — the scope does not read today either; fix it by hand");
|
|
269
|
-
if (coverage === "partial" && !(
|
|
272
|
+
if (coverage === "partial" && !RUNTIME_LIFT_TARGETS.every((target) => has(out, target))) {
|
|
270
273
|
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");
|
|
271
274
|
}
|
|
272
275
|
const parsed = RUNTIME_10X.safeParse(raw);
|
|
@@ -693,6 +696,14 @@ function stepModelsDefault(out, receipts) {
|
|
|
693
696
|
out.models = { ...without(doc, "default"), roles: { ...roles, default: { model: name } } };
|
|
694
697
|
}
|
|
695
698
|
// ── SS-M7:URL fragment ────────────────────────────────────────────────────────────────────────────────
|
|
699
|
+
/** SS-M7 走查的文档 → 它的 schema(座位从 schema 派生的那一只来源):配置域按 `DOMAIN_SCHEMAS`,`remote-exec` 按 `RemoteExecSpec`;别的文档没有 URL 座位。 */
|
|
700
|
+
function urlSeatSchemaOf(document) {
|
|
701
|
+
if (isDomainName(document))
|
|
702
|
+
return DOMAIN_SCHEMAS[document];
|
|
703
|
+
if (document === REMOTE_EXEC_DOCUMENT)
|
|
704
|
+
return RemoteExecSpec;
|
|
705
|
+
return undefined;
|
|
706
|
+
}
|
|
696
707
|
/** 这只(已展开的)schema 是不是一个 `NON_SECRET_URL` 座位:就是它,或是「先归一、再交给它」的管道(`z.string().trim().pipe(NON_SECRET_URL)`)。 */
|
|
697
708
|
function isUrlSeat(s) {
|
|
698
709
|
return s === NON_SECRET_URL || (s instanceof z.ZodPipe && unwrapSchema(s.out) === NON_SECRET_URL);
|
|
@@ -774,7 +785,8 @@ function stripFragments(schema, value, path, onStrip, depth = 0) {
|
|
|
774
785
|
}
|
|
775
786
|
return value;
|
|
776
787
|
}
|
|
777
|
-
/**
|
|
788
|
+
/** SS-M7 走查的全部 URL 座位的坐标模式(派生;数组 / Record 位写 `*`;域文档以域名起头,`remote-exec.json` 以 `remote-exec` 起头)—— 迁移不用它,
|
|
789
|
+
* 它给钉与说明看「今天派生出哪些座位」。 */
|
|
778
790
|
export function urlSeatPatterns() {
|
|
779
791
|
const out = [];
|
|
780
792
|
const walk = (schema, path, depth) => {
|
|
@@ -812,16 +824,18 @@ export function urlSeatPatterns() {
|
|
|
812
824
|
};
|
|
813
825
|
for (const [d, schema] of Object.entries(DOMAIN_SCHEMAS))
|
|
814
826
|
walk(schema, [d], 0);
|
|
827
|
+
walk(RemoteExecSpec, [REMOTE_EXEC_DOCUMENT], 0);
|
|
815
828
|
return out;
|
|
816
829
|
}
|
|
817
830
|
function stepUrlFragments(out, receipts) {
|
|
818
831
|
for (const d of Object.keys(out)) {
|
|
819
|
-
|
|
832
|
+
const schema = urlSeatSchemaOf(d);
|
|
833
|
+
if (schema === undefined)
|
|
820
834
|
continue;
|
|
821
835
|
const doc = out[d];
|
|
822
836
|
if (doc === undefined)
|
|
823
837
|
continue;
|
|
824
|
-
const next = stripFragments(
|
|
838
|
+
const next = stripFragments(schema, doc, [], (path) => receipts.push(receipt("SS-M7", d, path, "rewritten")));
|
|
825
839
|
if (next !== doc)
|
|
826
840
|
out[d] = next;
|
|
827
841
|
}
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 包内私有叶子模块(运行期零依赖,浏览器安全):**custom issue 的唯一铸点**(全包一只;11.0.0 时它住在 `./types` 里、只管那一个文件)。
|
|
3
|
+
*
|
|
4
|
+
* 律(一条):本包 schema 自己铸的每一条 custom issue —— `ctx.addIssue({ code: "custom", … })`、`.refine(判据, …)`、引用完整性的 `RefIssue`
|
|
5
|
+
* —— 都带 {@link QuotedIssueParams}(句子的固定词 `template` 与值 `operands`,值位是洞),`message` 恒是 {@link renderQuotedSentence} 从它渲出的,
|
|
6
|
+
* 铸点不手写第二遍。读者因此对 custom issue 只有一种读法:读结构化的句子(日志边界逐值打指纹、诊断面把值换成洞),不在「带 params 的读 params、
|
|
7
|
+
* 不带的退回 message」之间分流。没有值的定句一样走这里(`operands` 为空)。源码门在测试 custom-issue-params(扫 `src/` 全部铸点)。
|
|
8
|
+
*
|
|
9
|
+
* 不在本律里的(如实):zod 内建码的 issue —— `.max(n, 句)` / `.regex(re, 句)` 的 `too_big` / `invalid_format`、墓碑字段 `z.never` 的 `invalid_type`、
|
|
10
|
+
* 联合的错误映射 —— 它们的码不是 `custom`,`params` 不是 zod 给它们的槽位;它们的句子是定句(不插配置里的值),读者读 `message`。
|
|
11
|
+
*/
|
|
12
|
+
/** The params of a custom issue whose sentence QUOTES values (S-1075, 11.0.0) —— plain data: the sentence's fixed parts and the
|
|
13
|
+
* values between them, in order, so `message` is exactly {@link renderQuotedSentence} of them. A reader that has to treat the
|
|
14
|
+
* user-written parts of a sentence differently from its fixed words (a log boundary fingerprinting each value, the sanitizer
|
|
15
|
+
* bounding each value's length) reads them HERE instead of guessing where a value starts in the sentence. Every custom issue this
|
|
16
|
+
* package raises carries them (the fixed sentences too — those carry no operands). */
|
|
17
|
+
export interface QuotedIssueParams {
|
|
18
|
+
readonly template: readonly string[];
|
|
19
|
+
readonly operands: readonly (string | number)[];
|
|
20
|
+
}
|
|
21
|
+
/** The one rendering of a quoted sentence: `template[0] + show(operands[0]) + template[1] + … + template[n]`. `show` defaults to
|
|
22
|
+
* the value as written (that is how `message` is minted); a sanitizer / log boundary passes its own. */
|
|
23
|
+
export declare function renderQuotedSentence(p: QuotedIssueParams, show?: (operand: string | number) => string): string;
|
|
24
|
+
/** A custom issue's quoted sentence and its params (what {@link buildQuotedIssue} mints; spread into `ctx.addIssue`, or passed as a
|
|
25
|
+
* `.refine` second argument). */
|
|
26
|
+
export type QuotedIssue = {
|
|
27
|
+
readonly message: string;
|
|
28
|
+
readonly params: QuotedIssueParams;
|
|
29
|
+
};
|
|
30
|
+
/** Fixed sentence text spliced into a quoted sentence (a migration signpost, a closed vocabulary listed as the way out, a field
|
|
31
|
+
* name): it joins the template, it is NOT an operand —— operands are the values the config holds, the only parts a log boundary
|
|
32
|
+
* fingerprints and the sanitizer bounds (S-815). */
|
|
33
|
+
export interface FixedText {
|
|
34
|
+
readonly fixedText: string;
|
|
35
|
+
}
|
|
36
|
+
export declare const fixed: (text: string) => FixedText;
|
|
37
|
+
/** Mint a custom issue's `message` + `params` from a tagged template (`buildQuotedIssue\`duplicate model "${name}"\``): every
|
|
38
|
+
* interpolation is an operand unless wrapped in {@link fixed} (then it is template text). THE minting point of custom issues in this
|
|
39
|
+
* package — a sentence written straight into `message` would put a value into a sentence with no record of where it is, and give
|
|
40
|
+
* readers a second way to read an issue. */
|
|
41
|
+
export declare function buildQuotedIssue(strings: TemplateStringsArray, ...parts: (string | number | FixedText)[]): QuotedIssue;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 包内私有叶子模块(运行期零依赖,浏览器安全):**custom issue 的唯一铸点**(全包一只;11.0.0 时它住在 `./types` 里、只管那一个文件)。
|
|
3
|
+
*
|
|
4
|
+
* 律(一条):本包 schema 自己铸的每一条 custom issue —— `ctx.addIssue({ code: "custom", … })`、`.refine(判据, …)`、引用完整性的 `RefIssue`
|
|
5
|
+
* —— 都带 {@link QuotedIssueParams}(句子的固定词 `template` 与值 `operands`,值位是洞),`message` 恒是 {@link renderQuotedSentence} 从它渲出的,
|
|
6
|
+
* 铸点不手写第二遍。读者因此对 custom issue 只有一种读法:读结构化的句子(日志边界逐值打指纹、诊断面把值换成洞),不在「带 params 的读 params、
|
|
7
|
+
* 不带的退回 message」之间分流。没有值的定句一样走这里(`operands` 为空)。源码门在测试 custom-issue-params(扫 `src/` 全部铸点)。
|
|
8
|
+
*
|
|
9
|
+
* 不在本律里的(如实):zod 内建码的 issue —— `.max(n, 句)` / `.regex(re, 句)` 的 `too_big` / `invalid_format`、墓碑字段 `z.never` 的 `invalid_type`、
|
|
10
|
+
* 联合的错误映射 —— 它们的码不是 `custom`,`params` 不是 zod 给它们的槽位;它们的句子是定句(不插配置里的值),读者读 `message`。
|
|
11
|
+
*/
|
|
12
|
+
/** The one rendering of a quoted sentence: `template[0] + show(operands[0]) + template[1] + … + template[n]`. `show` defaults to
|
|
13
|
+
* the value as written (that is how `message` is minted); a sanitizer / log boundary passes its own. */
|
|
14
|
+
export function renderQuotedSentence(p, show = String) {
|
|
15
|
+
return p.template.reduce((out, part, i) => (i === 0 ? part : `${out}${show(p.operands[i - 1])}${part}`), "");
|
|
16
|
+
}
|
|
17
|
+
export const fixed = (text) => ({ fixedText: text });
|
|
18
|
+
/** Mint a custom issue's `message` + `params` from a tagged template (`buildQuotedIssue\`duplicate model "${name}"\``): every
|
|
19
|
+
* interpolation is an operand unless wrapped in {@link fixed} (then it is template text). THE minting point of custom issues in this
|
|
20
|
+
* package — a sentence written straight into `message` would put a value into a sentence with no record of where it is, and give
|
|
21
|
+
* readers a second way to read an issue. */
|
|
22
|
+
export function buildQuotedIssue(strings, ...parts) {
|
|
23
|
+
const template = [strings[0]];
|
|
24
|
+
const operands = [];
|
|
25
|
+
parts.forEach((part, i) => {
|
|
26
|
+
if (typeof part === "object")
|
|
27
|
+
template[template.length - 1] += part.fixedText + strings[i + 1];
|
|
28
|
+
else {
|
|
29
|
+
operands.push(part);
|
|
30
|
+
template.push(strings[i + 1]);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
const params = { template, operands };
|
|
34
|
+
return { message: renderQuotedSentence(params), params };
|
|
35
|
+
}
|
package/dist/remote-exec.d.ts
CHANGED
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
* ONLY; the service owns the adapter implementations.
|
|
28
28
|
*/
|
|
29
29
|
import { z } from "zod";
|
|
30
|
+
/** The name of this contract's document in a `config.d` directory (`config.d/remote-exec.json`) —— the one spelling: the local reader /
|
|
31
|
+
* writer, the directory migration and the command line name the document by it. It is not a config domain (not in `DOMAIN_SCHEMAS`). */
|
|
32
|
+
export declare const REMOTE_EXEC_DOCUMENT: "remote-exec";
|
|
30
33
|
export declare const RemoteExecSpec: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
31
34
|
provider: z.ZodLiteral<"host">;
|
|
32
35
|
workdir: z.ZodOptional<z.ZodString>;
|
package/dist/remote-exec.js
CHANGED
|
@@ -28,11 +28,15 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import { z } from "zod";
|
|
30
30
|
import { INTEGER } from "./integer.js";
|
|
31
|
-
import {
|
|
31
|
+
import { buildQuotedIssue } from "./quoted-issue.js";
|
|
32
|
+
import { ENV_NAME, K8S_QUANTITY, NON_SECRET_URL, TCP_PORT, containsSecretToken, hasNul } from "./types.js";
|
|
32
33
|
import { retiredLaneWordRefusal } from "./execution-lanes.js";
|
|
34
|
+
/** The name of this contract's document in a `config.d` directory (`config.d/remote-exec.json`) —— the one spelling: the local reader /
|
|
35
|
+
* writer, the directory migration and the command line name the document by it. It is not a config domain (not in `DOMAIN_SCHEMAS`). */
|
|
36
|
+
export const REMOTE_EXEC_DOCUMENT = "remote-exec";
|
|
33
37
|
/** A non-empty exec-bound string (an image / path / shell / arg the adapter feeds to spawn()/fs). Rejects a NUL
|
|
34
38
|
* byte uniformly — a NUL validates here but makes Node throw at spawn/boot (fail-OPEN → runtime crash). R20. */
|
|
35
|
-
const execStr = z.string().min(1).refine((s) => !hasNul(s),
|
|
39
|
+
const execStr = z.string().min(1).refine((s) => !hasNul(s), buildQuotedIssue `must not contain a NUL byte`);
|
|
36
40
|
/** Container knobs of the `local-docker` lane. `network` defaults to `none` at the adapter (the safe posture for
|
|
37
41
|
* untrusted code — see the dual-mode design note). */
|
|
38
42
|
const DOCKER_FIELDS = {
|
|
@@ -83,8 +87,9 @@ export const RemoteExecSpec = z.discriminatedUnion("provider", [
|
|
|
83
87
|
provider: z.literal("k8s"),
|
|
84
88
|
/** Sandbox container image (service: K8S_SANDBOX_IMAGE). Required so a run is reproducible. */
|
|
85
89
|
image: execStr,
|
|
86
|
-
/** Kube API URL (service: K8S_API_URL); absent → in-cluster service account. NO embedded credentials.
|
|
87
|
-
|
|
90
|
+
/** Kube API URL (service: K8S_API_URL); absent → in-cluster service account. NO embedded credentials. A stored fragment is rewritten
|
|
91
|
+
* by the one-time migration SS-M7 (this file is one of its documents), so the refusal names it. */
|
|
92
|
+
apiUrl: NON_SECRET_URL.optional(),
|
|
88
93
|
/** env-NAME holding the kube bearer token (service: K8S_TOKEN). NEVER the token. */
|
|
89
94
|
tokenEnv: ENV_NAME.optional(),
|
|
90
95
|
/** env-NAME holding the base64 CA cert (service: K8S_CA_CERT_B64). NEVER the cert bytes. */
|
|
@@ -102,7 +107,7 @@ export const RemoteExecSpec = z.discriminatedUnion("provider", [
|
|
|
102
107
|
* "all three of endpoint/access/secret required" gate). */
|
|
103
108
|
s3Snapshot: z
|
|
104
109
|
.object({
|
|
105
|
-
endpoint:
|
|
110
|
+
endpoint: NON_SECRET_URL, // S3/MinIO endpoint — creds go in accessKeyEnv/secretKeyEnv, never the URL (a stored fragment: SS-M7, like apiUrl)
|
|
106
111
|
bucket: execStr.optional(),
|
|
107
112
|
accessKeyEnv: ENV_NAME,
|
|
108
113
|
secretKeyEnv: ENV_NAME,
|
|
@@ -117,9 +122,9 @@ export const RemoteExecSpec = z.discriminatedUnion("provider", [
|
|
|
117
122
|
provider: z.literal("ssh"),
|
|
118
123
|
// host + username are persisted to config.d/remote-exec.json — scan for a secret-shaped literal (a secret must go
|
|
119
124
|
// in privateKeyEnv, not the login). R14.
|
|
120
|
-
host: execStr.refine((s) => !containsSecretToken(s),
|
|
125
|
+
host: execStr.refine((s) => !containsSecretToken(s), buildQuotedIssue `no secret literal in ssh host — credentials go in privateKeyEnv`),
|
|
121
126
|
port: TCP_PORT.optional(),
|
|
122
|
-
username: execStr.refine((s) => !containsSecretToken(s),
|
|
127
|
+
username: execStr.refine((s) => !containsSecretToken(s), buildQuotedIssue `no secret literal in ssh username — credentials go in privateKeyEnv`),
|
|
123
128
|
/** env-NAME holding the SSH private key (service: SSH_PRIVATE_KEY). NEVER the key value. */
|
|
124
129
|
privateKeyEnv: ENV_NAME,
|
|
125
130
|
mountPath: execStr.optional(),
|
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,8 +9,13 @@
|
|
|
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";
|
|
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;
|
|
14
19
|
/** 拒句尾的迁移指路:存量由哪一条一次性迁移改写、两种跑法(子路径的函数 / 目录命令;目录是 `config.d` 目录本身,不是它的上一层)。`before` = 旧形不再合法的那一版。
|
|
15
20
|
* 刻意短:它挂在 zod issue 句尾,读侧逐条容错时每一条被丢的条目都要过一遍出门脱敏。 */
|
|
16
21
|
export declare function migrationSignpost(migration: string, before: string): string;
|
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";
|
|
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
|
+
}
|
|
14
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,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 包内私有叶子模块:**一只** RFC 3339 时刻判(从 `./web-admin-credential` 搬出 —— 凭证信封的 `expiresAt` 与项目登记的 `archivedAt` 读同一只,
|
|
3
|
+
* 不各写一份)。
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
/** RFC 3339 `date-time` 的**剖面**:zod 的 `z.iso.datetime({offset:true})` 管日历有效性(月日范围、闰年、时分秒范围),
|
|
7
|
+
* 这里的正则自己判另三处 —— 秒必备、数字偏移必须是 `±hh:mm`、偏移的时 00–23 / 分 00–59(`10:00Z`、`+0800` 与 `+99:99`
|
|
8
|
+
* 都不是 RFC 3339,后者 `Date.parse` 读成 NaN)。这三处的判据在本正则,不随 zod 哪一版收不收而变。大写 `T` / `Z`、不收闰秒 `:60`:读方普遍用
|
|
9
|
+
* `Date.parse` 取时刻,这是那组形里它处处读得准的子集。 */
|
|
10
|
+
export declare const RFC3339_DATE_TIME: z.ZodISODateTime;
|
package/dist/rfc3339.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 包内私有叶子模块:**一只** RFC 3339 时刻判(从 `./web-admin-credential` 搬出 —— 凭证信封的 `expiresAt` 与项目登记的 `archivedAt` 读同一只,
|
|
3
|
+
* 不各写一份)。
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
/** RFC 3339 `date-time` 的**剖面**:zod 的 `z.iso.datetime({offset:true})` 管日历有效性(月日范围、闰年、时分秒范围),
|
|
7
|
+
* 这里的正则自己判另三处 —— 秒必备、数字偏移必须是 `±hh:mm`、偏移的时 00–23 / 分 00–59(`10:00Z`、`+0800` 与 `+99:99`
|
|
8
|
+
* 都不是 RFC 3339,后者 `Date.parse` 读成 NaN)。这三处的判据在本正则,不随 zod 哪一版收不收而变。大写 `T` / `Z`、不收闰秒 `:60`:读方普遍用
|
|
9
|
+
* `Date.parse` 取时刻,这是那组形里它处处读得准的子集。 */
|
|
10
|
+
export const RFC3339_DATE_TIME = z.iso
|
|
11
|
+
.datetime({ offset: true })
|
|
12
|
+
.regex(/T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/, "must be an RFC 3339 date-time: seconds and a Z / ±hh:mm offset (hh 00-23, mm 00-59) are required");
|
|
@@ -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"];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function shellQuote(arg: string): string;
|