@sema-agent/settings-schema 11.2.0 → 12.0.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 +112 -0
- package/README.md +2 -1
- package/conformance/VERSION +1 -1
- package/conformance/cases/c40-s712-identity-only-what-reads-out/expected.json +6 -3
- package/conformance/cases/c41-s792-encoded-pem-identity/expected.json +4 -2
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/expected.json +4 -2
- package/conformance/cases/c46-s811-host-name-url-userinfo-withheld/expected.json +2 -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/conformance/cases/cli01-model-name-nfc-vs-nfd/expected.json +4 -2
- package/conformance/cases/cli02-control-and-zero-width-chars/expected.json +4 -2
- package/dist/api/feedback.js +2 -3
- package/dist/api/scopes.js +2 -1
- package/dist/atomic-write.d.ts +6 -0
- package/dist/atomic-write.js +23 -6
- package/dist/cli.d.ts +8 -4
- package/dist/cli.js +55 -10
- package/dist/config-d-file.d.ts +12 -2
- package/dist/config-d-file.js +36 -9
- package/dist/config-dir-io.js +4 -3
- package/dist/config-dir-migration.d.ts +5 -3
- package/dist/config-dir-migration.js +19 -9
- package/dist/config-fns.d.ts +14 -2
- package/dist/config-fns.js +13 -1
- 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/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 +4 -3
- package/dist/migration-receipt.js +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/one-time-migrations.d.ts +6 -4
- package/dist/one-time-migrations.js +73 -6
- 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/resolve-roster.d.ts +1 -1
- package/dist/resolve-roster.js +3 -3
- package/dist/retired-role-words.d.ts +5 -0
- package/dist/retired-role-words.js +25 -0
- package/dist/rfc3339.d.ts +10 -0
- package/dist/rfc3339.js +12 -0
- package/dist/safety-merge-spec.d.ts +2 -2
- package/dist/types.d.ts +140 -32
- package/dist/types.js +215 -114
- package/dist/web-admin-credential.d.ts +2 -2
- package/dist/web-admin-credential.js +5 -10
- package/dist/write-failure.d.ts +16 -0
- package/dist/write-failure.js +43 -0
- package/package.json +1 -1
|
@@ -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` 与其余信封键原样。
|
|
@@ -34,6 +34,8 @@ import { LANE_ENV_OWNER, STORE_ENV_KEYS, isLaneEnvKey } from "./execution-lane-i
|
|
|
34
34
|
import { EXECUTION_LANE_WORDS, MEMORY_ENGINE_BACKENDS, STORE_BACKENDS } from "./execution-lanes.js";
|
|
35
35
|
import { INTEGER } from "./integer.js";
|
|
36
36
|
import { RUNTIME_LIFT_TARGETS } from "./runtime-lift-targets.js";
|
|
37
|
+
import { RETIRED_ROLE_WORDS } from "./retired-role-words.js";
|
|
38
|
+
import { REMOTE_EXEC_DOCUMENT, RemoteExecSpec } from "./remote-exec.js";
|
|
37
39
|
import { DOMAIN_SCHEMAS, HOSTS_ARRAY_MAX, HOST_NAME_MAX, NON_SECRET_URL, isDomainName, isRetiredDomainName } from "./types.js";
|
|
38
40
|
import { workerRowVerdict } from "./worker-row-verdict.js";
|
|
39
41
|
import { unwrapSchema } from "./zod-unwrap.js";
|
|
@@ -128,10 +130,24 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
128
130
|
removableAfter: "2026-11-09",
|
|
129
131
|
rewrites: "every NON_SECRET_URL seat in a domain document (derived from the domain schemas, not listed by hand — today models.models[].baseUrl, " +
|
|
130
132
|
"mcp.servers[].transport.url, execution.sessionMirror.engineUrl, workers.workers[].coords.gitApiBaseUrl): `#` and everything after it are removed " +
|
|
131
|
-
"(a fragment never goes on the wire)"
|
|
133
|
+
"(a fragment never goes on the wire); the same walk covers config.d/remote-exec.json, derived from its schema the same way " +
|
|
134
|
+
"(today the k8s apiUrl and s3Snapshot.endpoint) — the command-line migrate reads and rewrites it as one more document",
|
|
132
135
|
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",
|
|
133
136
|
afterRemoval: "the NON_SECRET_URL shape judge, which refuses a fragment (`#` included) on read and write",
|
|
134
137
|
},
|
|
138
|
+
{
|
|
139
|
+
id: "SS-M8",
|
|
140
|
+
oldForm: "refused",
|
|
141
|
+
introducedIn: "12.0.0",
|
|
142
|
+
releasedOn: "2026-10-12",
|
|
143
|
+
removableAfter: "2026-11-11",
|
|
144
|
+
rewrites: "models / rosters: role words removed by core 7.49.0: team, synthesize — the keys roles.team / roles.synthesize (models.roles and every " +
|
|
145
|
+
"rosters[].roles) are removed whatever their value (12.0.0 drops both words from ModelRole: no role reads them, and the engine refuses " +
|
|
146
|
+
"them); every other role and key stays as stored",
|
|
147
|
+
removal: "the next major after the window: delete the SS-M8 step; the retired role-word tombstone stays — its SS-M8 clause goes",
|
|
148
|
+
afterRemoval: "the retired role-word tombstone (permanent): a residual roles.team / roles.synthesize is refused on read and write (read: the entry is dropped " +
|
|
149
|
+
"when other roles survive, else models degrades to its default; rosters: the scope fails to read)",
|
|
150
|
+
},
|
|
135
151
|
{
|
|
136
152
|
id: "SS-M9",
|
|
137
153
|
oldForm: "refused",
|
|
@@ -187,6 +203,7 @@ export function migrateStoredDocuments(docs, opts) {
|
|
|
187
203
|
stepHostsBounds(out, receipts);
|
|
188
204
|
stepModelKeyOneOf(out, receipts);
|
|
189
205
|
stepAtMention(out, receipts);
|
|
206
|
+
stepRetiredRoleWords(out, receipts);
|
|
190
207
|
stepModelsDefault(out, receipts);
|
|
191
208
|
stepUrlFragments(out, receipts);
|
|
192
209
|
stepWorkers6x(out, receipts);
|
|
@@ -673,6 +690,45 @@ function stepAtMention(out, receipts) {
|
|
|
673
690
|
out.rosters = { ...rosters, rosters: next };
|
|
674
691
|
}
|
|
675
692
|
}
|
|
693
|
+
// ── SS-M8:退役的角色词 ────────────────────────────────────────────────────────────────────────────────
|
|
694
|
+
/** 一个 `roles` 图上的退役角色词键删掉(值不论 —— 没有角色读它);逐键一张 `dropped-inert` 收据。返回改后的图或 `undefined`(没改 / 不是对象:判形是读者的事)。 */
|
|
695
|
+
function withoutRetiredRoles(roles, document, at, receipts) {
|
|
696
|
+
if (!isPlainObject(roles))
|
|
697
|
+
return undefined;
|
|
698
|
+
const present = RETIRED_ROLE_WORDS.filter((w) => Object.prototype.hasOwnProperty.call(roles, w));
|
|
699
|
+
if (present.length === 0)
|
|
700
|
+
return undefined;
|
|
701
|
+
let next = roles;
|
|
702
|
+
for (const w of present) {
|
|
703
|
+
next = without(next, w);
|
|
704
|
+
receipts.push(receipt("SS-M8", document, [...at, "roles", w], "dropped-inert"));
|
|
705
|
+
}
|
|
706
|
+
return next;
|
|
707
|
+
}
|
|
708
|
+
function stepRetiredRoleWords(out, receipts) {
|
|
709
|
+
const models = out.models;
|
|
710
|
+
if (isPlainObject(models)) {
|
|
711
|
+
const roles = withoutRetiredRoles(models.roles, "models", [], receipts);
|
|
712
|
+
if (roles !== undefined)
|
|
713
|
+
out.models = { ...models, roles };
|
|
714
|
+
}
|
|
715
|
+
const rosters = out.rosters;
|
|
716
|
+
if (isPlainObject(rosters) && Array.isArray(rosters.rosters)) {
|
|
717
|
+
const rows = rosters.rosters;
|
|
718
|
+
let next;
|
|
719
|
+
rows.forEach((row, index) => {
|
|
720
|
+
if (!isPlainObject(row))
|
|
721
|
+
return;
|
|
722
|
+
const roles = withoutRetiredRoles(row.roles, "rosters", ["rosters", index], receipts);
|
|
723
|
+
if (roles === undefined)
|
|
724
|
+
return;
|
|
725
|
+
next ??= [...rows];
|
|
726
|
+
next[index] = { ...row, roles };
|
|
727
|
+
});
|
|
728
|
+
if (next !== undefined)
|
|
729
|
+
out.rosters = { ...rosters, rosters: next };
|
|
730
|
+
}
|
|
731
|
+
}
|
|
676
732
|
// ── SS-M9:models.default → roles.default ──────────────────────────────────────────────────────────────
|
|
677
733
|
/** 迁移前「缺省模型是谁」的生效读法 = server 的缺省解析以结构键 `default` 为第一级,主任务真跑它(N-7 两答复现:`default` = A、`roles.default`
|
|
678
734
|
* = B 时主任务跑 A,core 的 `default` 角色解到 B)。所以并过去的是 `default` 的值:`roles.default` 缺席 ⇒ 搬入;同名 ⇒ 搬入(重复的那一份
|
|
@@ -694,6 +750,14 @@ function stepModelsDefault(out, receipts) {
|
|
|
694
750
|
out.models = { ...without(doc, "default"), roles: { ...roles, default: { model: name } } };
|
|
695
751
|
}
|
|
696
752
|
// ── SS-M7:URL fragment ────────────────────────────────────────────────────────────────────────────────
|
|
753
|
+
/** SS-M7 走查的文档 → 它的 schema(座位从 schema 派生的那一只来源):配置域按 `DOMAIN_SCHEMAS`,`remote-exec` 按 `RemoteExecSpec`;别的文档没有 URL 座位。 */
|
|
754
|
+
function urlSeatSchemaOf(document) {
|
|
755
|
+
if (isDomainName(document))
|
|
756
|
+
return DOMAIN_SCHEMAS[document];
|
|
757
|
+
if (document === REMOTE_EXEC_DOCUMENT)
|
|
758
|
+
return RemoteExecSpec;
|
|
759
|
+
return undefined;
|
|
760
|
+
}
|
|
697
761
|
/** 这只(已展开的)schema 是不是一个 `NON_SECRET_URL` 座位:就是它,或是「先归一、再交给它」的管道(`z.string().trim().pipe(NON_SECRET_URL)`)。 */
|
|
698
762
|
function isUrlSeat(s) {
|
|
699
763
|
return s === NON_SECRET_URL || (s instanceof z.ZodPipe && unwrapSchema(s.out) === NON_SECRET_URL);
|
|
@@ -775,7 +839,8 @@ function stripFragments(schema, value, path, onStrip, depth = 0) {
|
|
|
775
839
|
}
|
|
776
840
|
return value;
|
|
777
841
|
}
|
|
778
|
-
/**
|
|
842
|
+
/** SS-M7 走查的全部 URL 座位的坐标模式(派生;数组 / Record 位写 `*`;域文档以域名起头,`remote-exec.json` 以 `remote-exec` 起头)—— 迁移不用它,
|
|
843
|
+
* 它给钉与说明看「今天派生出哪些座位」。 */
|
|
779
844
|
export function urlSeatPatterns() {
|
|
780
845
|
const out = [];
|
|
781
846
|
const walk = (schema, path, depth) => {
|
|
@@ -813,16 +878,18 @@ export function urlSeatPatterns() {
|
|
|
813
878
|
};
|
|
814
879
|
for (const [d, schema] of Object.entries(DOMAIN_SCHEMAS))
|
|
815
880
|
walk(schema, [d], 0);
|
|
881
|
+
walk(RemoteExecSpec, [REMOTE_EXEC_DOCUMENT], 0);
|
|
816
882
|
return out;
|
|
817
883
|
}
|
|
818
884
|
function stepUrlFragments(out, receipts) {
|
|
819
885
|
for (const d of Object.keys(out)) {
|
|
820
|
-
|
|
886
|
+
const schema = urlSeatSchemaOf(d);
|
|
887
|
+
if (schema === undefined)
|
|
821
888
|
continue;
|
|
822
889
|
const doc = out[d];
|
|
823
890
|
if (doc === undefined)
|
|
824
891
|
continue;
|
|
825
|
-
const next = stripFragments(
|
|
892
|
+
const next = stripFragments(schema, doc, [], (path) => receipts.push(receipt("SS-M7", d, path, "rewritten")));
|
|
826
893
|
if (next !== doc)
|
|
827
894
|
out[d] = next;
|
|
828
895
|
}
|
|
@@ -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/resolve-roster.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import type { AtMention, EffectiveConfig, EffectiveWire, ModelRole, RoleTarget, RosterEntry } from "./types.js";
|
|
14
14
|
import { type RefIssue } from "./cross-domain.js";
|
|
15
15
|
/** primaryModel 标记喂的角色族(§3)。 */
|
|
16
|
-
export declare const ROSTER_PRIMARY_ROLES: readonly ["default"
|
|
16
|
+
export declare const ROSTER_PRIMARY_ROLES: readonly ["default"];
|
|
17
17
|
/** cheapModel 标记喂的角色族(§3)。 */
|
|
18
18
|
export declare const ROSTER_CHEAP_ROLES: readonly ["subagent", "summarize"];
|
|
19
19
|
/** 把 roster 的主/cheap 角色标记派生成 roles 片段(PURE)。显式 roster.roles 逐角色盖过它;它盖过全局
|
package/dist/resolve-roster.js
CHANGED
|
@@ -2,12 +2,12 @@ import { stableHash } from "./hash.js";
|
|
|
2
2
|
import { danglingRefs } from "./cross-domain.js";
|
|
3
3
|
import { toSkillsManifest } from "./skills-manifest.js";
|
|
4
4
|
// ── 主/cheap 角色标记语义(0.6.3, E3)────────────────────────────────
|
|
5
|
-
// service 现有 ModelRoles 口径:default
|
|
5
|
+
// service 现有 ModelRoles 口径:default→主(12.0.0 起唯一;`team` / `synthesize` 随 core 7.49.0 删)、subagent/summarize→cheap。1.8.0 起 `classifier`/`consolidate`
|
|
6
6
|
// 同 `verifier` 一样**不派生**(归 cheap 会盖掉 models.tiers 档位与 core 兜底链)。`verifier` 刻意
|
|
7
7
|
// 不派生(unset→core 的 default 兜底链,与 service 语义一致)。常量导出=单一语义源,service 收敛小单与
|
|
8
8
|
// web-admin 表单同源引用,别第二份。
|
|
9
9
|
/** primaryModel 标记喂的角色族(§3)。 */
|
|
10
|
-
export const ROSTER_PRIMARY_ROLES = ["default"
|
|
10
|
+
export const ROSTER_PRIMARY_ROLES = ["default"];
|
|
11
11
|
/** cheapModel 标记喂的角色族(§3)。 */
|
|
12
12
|
export const ROSTER_CHEAP_ROLES = ["subagent", "summarize"];
|
|
13
13
|
/** 把 roster 的主/cheap 角色标记派生成 roles 片段(PURE)。显式 roster.roles 逐角色盖过它;它盖过全局
|
|
@@ -86,7 +86,7 @@ export function resolveEffectiveForWorker(eff, worker) {
|
|
|
86
86
|
// `{select}`-form (capability-based) roles name no model → kept.
|
|
87
87
|
// Per-role merge (roster overrides global PER ROLE) — NOT a whole-map replace. The types promise "unset
|
|
88
88
|
// roster roles fall back to global models.roles per role"; a whole-map `??` silently dropped the global
|
|
89
|
-
// `subagent`/`
|
|
89
|
+
// `subagent`/`summarize`/… when a roster set only `default`. Round-3 fix. Then drop any dangling {model}.
|
|
90
90
|
// 0.6.3: marker-derived roles (primaryModel/cheapModel semantics) sit BETWEEN global and explicit
|
|
91
91
|
// roster.roles — global < markers < explicit per-role. Markers are subset-validated at PUT; a stale marker
|
|
92
92
|
// after a catalog change still falls through the dangling-{model} drop below (defence-in-depth).
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** 退役的角色词(闭集;逐字)。 */
|
|
2
|
+
export declare const RETIRED_ROLE_WORDS: readonly ["team", "synthesize"];
|
|
3
|
+
/** 一组被判官拒下的角色键 / 角色词里若有退役词 ⇒ 那一句(点名退役词与 SS-M8;同组里别的不认识的词也点名,一个不漏);没有 ⇒ `undefined`
|
|
4
|
+
* (交回 zod 自己的句子)。`ModelRole` 的判句(一个词)与 `roles` 图的未知键判句(一组键)都调这一只。 */
|
|
5
|
+
export declare function retiredRoleWordsRefusal(words: readonly unknown[]): string | undefined;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 包内私有(不进主桶、不进 exports 表):12.0.0 退役的**角色词**墓碑 —— 引擎 core 7.49.0 把 `team` / `synthesize` 从角色表里删了(对未知角色词响亮拒),
|
|
3
|
+
* 本包的 `ModelRole` 随之收成 6 词。墓碑是**拒**,不是第二种读法:判官遇到这两个词不把它们读成任何角色,只说「这是一份没迁的旧形」以及去哪儿迁
|
|
4
|
+
* (一次性迁移 SS-M8,`./one-time-migrations`)。一处写词、一处写句:`ModelRole` 的判句、`roles` 图的未知键判句与 SS-M8 的改写步都读这里。
|
|
5
|
+
* 迁移族按登记表的窗整族删,墓碑留下 —— 到那时句子里的迁移指路由删码那一车去掉(登记表 SS-M8 行的 `removal`)。
|
|
6
|
+
*/
|
|
7
|
+
import { retiredFormSentence } from "./retired-form.js";
|
|
8
|
+
/** 退役的角色词(闭集;逐字)。 */
|
|
9
|
+
export const RETIRED_ROLE_WORDS = ["team", "synthesize"];
|
|
10
|
+
const ROLE_WORDS_RETIRED = {
|
|
11
|
+
retiredIn: "12.0.0",
|
|
12
|
+
now: "no role reads it (core 7.49.0 removed the role and refuses the word)",
|
|
13
|
+
migration: "SS-M8",
|
|
14
|
+
};
|
|
15
|
+
const isRetiredRoleWord = (w) => RETIRED_ROLE_WORDS.some((r) => r === w);
|
|
16
|
+
/** 一组被判官拒下的角色键 / 角色词里若有退役词 ⇒ 那一句(点名退役词与 SS-M8;同组里别的不认识的词也点名,一个不漏);没有 ⇒ `undefined`
|
|
17
|
+
* (交回 zod 自己的句子)。`ModelRole` 的判句(一个词)与 `roles` 图的未知键判句(一组键)都调这一只。 */
|
|
18
|
+
export function retiredRoleWordsRefusal(words) {
|
|
19
|
+
const retired = RETIRED_ROLE_WORDS.filter((r) => words.includes(r));
|
|
20
|
+
if (retired.length === 0)
|
|
21
|
+
return undefined;
|
|
22
|
+
const others = words.filter((w) => !isRetiredRoleWord(w)).map((w) => JSON.stringify(String(w)));
|
|
23
|
+
const head = others.length === 0 ? "" : `unrecognized role ${others.length === 1 ? "word" : "words"} ${others.join(", ")}; `;
|
|
24
|
+
return head + retiredFormSentence(`role word ${retired.join(" / ")}`, ROLE_WORDS_RETIRED);
|
|
25
|
+
}
|
|
@@ -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");
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
import { z } from "zod";
|
|
54
54
|
/** How values from different layers/principals combine for a category. */
|
|
55
55
|
export declare const MergeShape: z.ZodEnum<{
|
|
56
|
-
concat: "concat";
|
|
57
56
|
replace: "replace";
|
|
57
|
+
concat: "concat";
|
|
58
58
|
denyFirst: "denyFirst";
|
|
59
59
|
runAll: "runAll";
|
|
60
60
|
overrideByName: "overrideByName";
|
|
@@ -185,8 +185,8 @@ export declare const TightenRule: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
185
185
|
export type TightenRule = z.infer<typeof TightenRule>;
|
|
186
186
|
export declare const MergeCategorySpec: z.ZodObject<{
|
|
187
187
|
mergeShape: z.ZodEnum<{
|
|
188
|
-
concat: "concat";
|
|
189
188
|
replace: "replace";
|
|
189
|
+
concat: "concat";
|
|
190
190
|
denyFirst: "denyFirst";
|
|
191
191
|
runAll: "runAll";
|
|
192
192
|
overrideByName: "overrideByName";
|