@sema-agent/server 7.112.0 → 7.114.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/MIGRATION.md +95 -0
- package/USAGE.md +4 -4
- package/dist/approval-ask-audit-store.d.ts +3 -4
- package/dist/approval-ask-audit-store.js +3 -3
- package/dist/approval-card.d.ts +4 -4
- package/dist/boot/coordinators.js +1 -1
- package/dist/boot/memory-boundary.d.ts +2 -1
- package/dist/boot/memory-consolidation.d.ts +1 -1
- package/dist/boot/resolve-spec.js +2 -2
- package/dist/boot/stores.js +4 -6
- package/dist/boot/workflow-orchestration.js +4 -4
- package/dist/config-catalog.js +6 -5
- package/dist/config-invariants.d.ts +27 -0
- package/dist/config-invariants.js +54 -39
- package/dist/config-types.d.ts +5 -11
- package/dist/config-types.js +0 -1
- package/dist/config.d.ts +0 -16
- package/dist/config.js +44 -69
- package/dist/env-facts.d.ts +3 -1
- package/dist/env-facts.js +5 -1
- package/dist/execution-lane-caps.d.ts +54 -30
- package/dist/execution-lane-caps.js +48 -20
- package/dist/hooks/hook-runner.d.ts +87 -7
- package/dist/hooks/hook-runner.js +113 -20
- package/dist/host-decision.d.ts +5 -3
- package/dist/hosted-posture.d.ts +2 -2
- package/dist/http/admission.js +8 -4
- package/dist/http/request-guards.js +2 -2
- package/dist/http/resume-legs.d.ts +9 -10
- package/dist/http/resume-legs.js +64 -18
- package/dist/http/route-ctx.d.ts +38 -8
- package/dist/http/route-ctx.js +4 -0
- package/dist/http/routes/background.js +2 -2
- package/dist/http/routes/capabilities.js +2 -2
- package/dist/http/routes/mcp-reconnect.js +2 -2
- package/dist/http/routes/notify-wake.js +3 -3
- package/dist/http/routes/runs.js +19 -13
- package/dist/http/routes/session-sync.js +6 -6
- package/dist/http/routes/sessions.js +20 -20
- package/dist/http/routes/tasks.js +1 -0
- package/dist/http/send.d.ts +1 -1
- package/dist/http/send.js +1 -1
- package/dist/http/wire-types.d.ts +1 -1
- package/dist/local-data-root.d.ts +14 -0
- package/dist/local-data-root.js +5 -0
- package/dist/observability/corrupt-read-seat.d.ts +46 -66
- package/dist/observability/corrupt-read-seat.js +79 -63
- package/dist/observability/fail-open.d.ts +28 -0
- package/dist/observability/fail-open.js +28 -0
- package/dist/observability/run-terminal.js +39 -0
- package/dist/orchestration/workflow-agent-session-index.d.ts +1 -5
- package/dist/orchestration/workflow-agent-session-index.js +2 -2
- package/dist/orchestration/workflow-notify-journal.d.ts +1 -3
- package/dist/orchestration/workflow-notify-journal.js +2 -2
- package/dist/plugins/approval-ask-store-file.d.ts +1 -4
- package/dist/plugins/approval-ask-store-file.js +2 -2
- package/dist/plugins/file-resume-anchor-store.d.ts +1 -6
- package/dist/plugins/file-resume-anchor-store.js +2 -4
- package/dist/plugins/file-run-store.d.ts +0 -4
- package/dist/plugins/file-run-store.js +1 -3
- package/dist/plugins/remote-env-host.d.ts +0 -35
- package/dist/plugins/remote-env-host.js +0 -8
- package/dist/plugins/remote-env-local-docker.d.ts +5 -13
- package/dist/plugins/remote-env-local-docker.js +2 -4
- package/dist/plugins/send-file-ledger.d.ts +1 -5
- package/dist/plugins/send-file-ledger.js +2 -2
- package/dist/plugins/store-backend.js +8 -9
- package/dist/plugins/user-input-ledger-file.d.ts +1 -5
- package/dist/plugins/user-input-ledger-file.js +2 -2
- package/dist/run-local.d.ts +1 -1
- package/dist/run-local.js +2 -2
- package/dist/runs.js +1 -0
- package/dist/spec-fields.js +0 -1
- package/dist/store-posture.d.ts +7 -7
- package/dist/store-posture.js +0 -1
- package/dist/task-settings.d.ts +13 -0
- package/dist/task-settings.js +3 -0
- package/dist/tool-approval.d.ts +1 -1
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/project.js +2 -3
- package/dist/trace/redact.d.ts +18 -0
- package/dist/trace/redact.js +4 -0
- package/dist/unattended-permission-mode.d.ts +9 -2
- package/dist/unattended-permission-mode.js +5 -1
- package/dist/user-input-ledger.d.ts +24 -5
- package/dist/user-input-ledger.js +46 -11
- package/package.json +4 -3
- package/scripts/retired-mode-seed-drain-local.mjs +317 -0
|
@@ -3,7 +3,7 @@ import { modelProviderRefusal } from "./model-provider.js";
|
|
|
3
3
|
import { modelRouteUrlRefusal } from "./model-route-url.js";
|
|
4
4
|
import { modelRouteEndpointRefusal } from "./model-route-endpoint.js";
|
|
5
5
|
import { defaultModelRefusal, refusedNameMintedRefusal } from "./model-entry-refusal.js";
|
|
6
|
-
import { executionLaneCaps, executionLaneOf, laneCapabilityHolds, REMOTE_EXEC_PROVIDER_WORDS } from "./execution-lane-caps.js";
|
|
6
|
+
import { executionLaneCaps, executionLaneOf, executionLaneRequiredRefusal, laneCapabilityHolds, REMOTE_EXEC_PROVIDER_WORDS } from "./execution-lane-caps.js";
|
|
7
7
|
export const LIVENESS_HEARTBEAT_MS = 30_000;
|
|
8
8
|
const anyServiceAuth = (v) => Boolean(v.authToken) || Object.keys(v.authTokens ?? {}).length > 0;
|
|
9
9
|
const mustNarrowBind = (v) => Boolean(v.allowUnauthedWrites) && !anyServiceAuth(v);
|
|
@@ -16,6 +16,51 @@ function multiTenantSharedLaneRefusal(v) {
|
|
|
16
16
|
return (`REQUIRE_PRINCIPAL=true (multi-tenant posture) with the "${lane}" execution lane runs every tenant's Bash on one shared target — ` +
|
|
17
17
|
`this lane does not separate tenants; set REMOTE_EXEC to ${outlets.join(" / ")}, or unset REQUIRE_PRINCIPAL for a single-user box`);
|
|
18
18
|
}
|
|
19
|
+
export function halfOpenDirectDoorRefusal(v) {
|
|
20
|
+
if (!v.directApprovalDoor)
|
|
21
|
+
return undefined;
|
|
22
|
+
const missing = [];
|
|
23
|
+
if (!v.durableApproval)
|
|
24
|
+
missing.push("DURABLE_APPROVAL=true (the direct door's crypto gate only exists on the durable /decide path)");
|
|
25
|
+
if (v.approvalHmacKeys.length === 0)
|
|
26
|
+
missing.push("APPROVAL_HMAC_KEYS");
|
|
27
|
+
if (v.principalJwtPubkeys.length === 0)
|
|
28
|
+
missing.push("PRINCIPAL_JWT_PUBKEYS");
|
|
29
|
+
if (!v.principalJwtIss)
|
|
30
|
+
missing.push("PRINCIPAL_JWT_ISS");
|
|
31
|
+
if (!v.principalJwtAud)
|
|
32
|
+
missing.push("PRINCIPAL_JWT_AUD");
|
|
33
|
+
if (v.operatorPrincipals.length === 0)
|
|
34
|
+
missing.push("OPERATOR_PRINCIPALS (else isOperator is true-for-all)");
|
|
35
|
+
if (!v.requirePrincipal)
|
|
36
|
+
missing.push("REQUIRE_PRINCIPAL=true (a direct door is multi-tenant; without it an absent principal is anonymous and the self-asserted header governs owner/quota checks)");
|
|
37
|
+
return missing.length > 0 ? `DIRECT_APPROVAL_DOOR=true but D-G anchors are missing: ${missing.join(", ")} — refusing to start a half-open direct door (design/80 D-G boot invariant)` : undefined;
|
|
38
|
+
}
|
|
39
|
+
export function operatorPrincipalsRequiredRefusal(v) {
|
|
40
|
+
return v.durableApproval === true && v.requirePrincipal && v.operatorPrincipals.length === 0
|
|
41
|
+
? "DURABLE_APPROVAL=true + REQUIRE_PRINCIPAL=true but OPERATOR_PRINCIPALS is empty — on a multi-tenant worker the empty operator set makes isOperator() true-for-all, so every authenticated tenant could read every other tenant's pending-approval queue (GET /v1/approvals & siblings). Set OPERATOR_PRINCIPALS, or unset REQUIRE_PRINCIPAL if this is a single-tenant deployment (#157-① boot invariant)"
|
|
42
|
+
: undefined;
|
|
43
|
+
}
|
|
44
|
+
export function bakeOperatorPrincipalsRefusal(v) {
|
|
45
|
+
return v.imageBakes.enabled && v.operatorPrincipals.length === 0
|
|
46
|
+
? "IMAGE_BAKES_ENABLED=true but OPERATOR_PRINCIPALS is empty — the bake door runs build.sh on the privileged build host; an empty operator set makes isOperator() true-for-all (world-writable RCE). Set OPERATOR_PRINCIPALS or disable bakes (IMAGE-API-DESIGN.md §P2.4a boot invariant)"
|
|
47
|
+
: undefined;
|
|
48
|
+
}
|
|
49
|
+
export function bakeRunnerCredentialRefusal(v) {
|
|
50
|
+
const runner = v.imageBakes.runnerPrincipal;
|
|
51
|
+
return v.imageBakes.enabled && !Object.values(v.authTokens ?? {}).includes(runner)
|
|
52
|
+
? `IMAGE_BAKES_ENABLED=true but no SERVICE_AUTH_TOKENS entry maps to the runner principal '${runner}' — the bake-runner authenticates by its credential (token-derived source), so a '<BAKE_RUNNER_TOKEN>=${runner}' SERVICE_AUTH_TOKENS entry is required or claim/ingest/heartbeat all 403 (IMAGE-API-DESIGN.md §P2.12 boot invariant)`
|
|
53
|
+
: undefined;
|
|
54
|
+
}
|
|
55
|
+
export function leaderDurableStoreRefusal(v) {
|
|
56
|
+
return v.leaderEnabled && v.dbBackend !== "mysql" && v.dbBackend !== "pg"
|
|
57
|
+
? `LEADER_ENABLED=true but DB_BACKEND=${v.dbBackend} (no external SQL store) is refused: a leader run is an ` +
|
|
58
|
+
"N-worker fan-out lasting up to 24h that ends in a REAL git push, and without a durable store its registry is " +
|
|
59
|
+
"a per-process Map — a cross-replica GET 404s, a restart forgets the run, and a retried POST starts a SECOND " +
|
|
60
|
+
"push. Point the service at a durable store (DB_BACKEND=mysql|pg), or turn the leader off (unset LEADER_ENABLED) " +
|
|
61
|
+
"(#193 车4 件2 / staleness ledger P1-9+P1-10 boot invariant)"
|
|
62
|
+
: undefined;
|
|
63
|
+
}
|
|
19
64
|
export const CONFIG_CROSS_INVARIANTS = [
|
|
20
65
|
{
|
|
21
66
|
id: "run-stale-heartbeat",
|
|
@@ -39,57 +84,31 @@ export const CONFIG_CROSS_INVARIANTS = [
|
|
|
39
84
|
keys: ["imageBakes", "authTokens"],
|
|
40
85
|
restartReason: "bake-runner-credential",
|
|
41
86
|
bootAnchor: { file: "src/config.ts", marker: "CROSS-INVARIANT:bake-runner-credential" },
|
|
42
|
-
check: ({ next }) =>
|
|
43
|
-
? `IMAGE_BAKES_ENABLED is on but no SERVICE_AUTH_TOKENS entry maps to the runner principal '${next.imageBakes.runnerPrincipal}' — claim/ingest/heartbeat would all 403`
|
|
44
|
-
: undefined,
|
|
87
|
+
check: ({ next }) => bakeRunnerCredentialRefusal(next),
|
|
45
88
|
},
|
|
46
89
|
{
|
|
47
90
|
id: "direct-door-anchors",
|
|
48
91
|
keys: ["directApprovalDoor", "durableApproval", "approvalHmacKeys", "principalJwtPubkeys", "principalJwtIss", "principalJwtAud", "requirePrincipal", "operatorPrincipals"],
|
|
49
92
|
restartReason: "direct-door",
|
|
50
93
|
bootAnchor: { file: "src/config.ts", marker: "CROSS-INVARIANT:direct-door-anchors" },
|
|
51
|
-
check: ({ live, next }) =>
|
|
52
|
-
|
|
53
|
-
const missing = [];
|
|
54
|
-
if (!next.durableApproval)
|
|
55
|
-
missing.push("DURABLE_APPROVAL");
|
|
56
|
-
if (next.approvalHmacKeys.length === 0)
|
|
57
|
-
missing.push("APPROVAL_HMAC_KEYS");
|
|
58
|
-
if (next.principalJwtPubkeys.length === 0)
|
|
59
|
-
missing.push("PRINCIPAL_JWT_PUBKEYS");
|
|
60
|
-
if (!next.principalJwtIss)
|
|
61
|
-
missing.push("PRINCIPAL_JWT_ISS");
|
|
62
|
-
if (!next.principalJwtAud)
|
|
63
|
-
missing.push("PRINCIPAL_JWT_AUD");
|
|
64
|
-
if (next.operatorPrincipals.length === 0)
|
|
65
|
-
missing.push("OPERATOR_PRINCIPALS");
|
|
66
|
-
if (!next.requirePrincipal)
|
|
67
|
-
missing.push("REQUIRE_PRINCIPAL");
|
|
68
|
-
if (missing.length > 0)
|
|
69
|
-
return `a half-open direct approval door is refused: missing ${missing.join(", ")}`;
|
|
70
|
-
}
|
|
71
|
-
return directDoorConjuncts(live) !== directDoorConjuncts(next)
|
|
94
|
+
check: ({ live, next }) => halfOpenDirectDoorRefusal(next) ??
|
|
95
|
+
(directDoorConjuncts(live) !== directDoorConjuncts(next)
|
|
72
96
|
? "the direct-door posture (its conjunction of approval/JWT anchors) is decided at boot — refusing to hot-flip it (restart to change the door)"
|
|
73
|
-
: undefined
|
|
74
|
-
},
|
|
97
|
+
: undefined),
|
|
75
98
|
},
|
|
76
99
|
{
|
|
77
100
|
id: "operator-principals-required",
|
|
78
101
|
keys: ["durableApproval", "requirePrincipal", "operatorPrincipals"],
|
|
79
102
|
restartReason: "operator-principals",
|
|
80
103
|
bootAnchor: { file: "src/config.ts", marker: "CROSS-INVARIANT:operator-principals-required" },
|
|
81
|
-
check: ({ next }) => next
|
|
82
|
-
? "DURABLE_APPROVAL + REQUIRE_PRINCIPAL with an EMPTY OPERATOR_PRINCIPALS makes isOperator() true-for-all — every authenticated tenant could read every other tenant's pending-approval queue"
|
|
83
|
-
: undefined,
|
|
104
|
+
check: ({ next }) => operatorPrincipalsRequiredRefusal(next),
|
|
84
105
|
},
|
|
85
106
|
{
|
|
86
107
|
id: "bake-operator-principals",
|
|
87
108
|
keys: ["imageBakes", "operatorPrincipals"],
|
|
88
109
|
restartReason: "operator-principals",
|
|
89
110
|
bootAnchor: { file: "src/config.ts", marker: "CROSS-INVARIANT:bake-operator-principals" },
|
|
90
|
-
check: ({ next }) => next
|
|
91
|
-
? "IMAGE_BAKES_ENABLED with an EMPTY OPERATOR_PRINCIPALS is a world-writable bake door (the runner runs build.sh on the privileged build host)"
|
|
92
|
-
: undefined,
|
|
111
|
+
check: ({ next }) => bakeOperatorPrincipalsRefusal(next),
|
|
93
112
|
},
|
|
94
113
|
{
|
|
95
114
|
id: "sensitive-write-patterns",
|
|
@@ -111,9 +130,7 @@ export const CONFIG_CROSS_INVARIANTS = [
|
|
|
111
130
|
keys: ["requireExecutionLane", "remoteExec"],
|
|
112
131
|
restartReason: "execution-lane",
|
|
113
132
|
bootAnchor: { file: "src/config.ts", marker: "CROSS-INVARIANT:require-execution-lane" },
|
|
114
|
-
check: ({ next }) => next
|
|
115
|
-
? "REQUIRE_EXECUTION_LANE=true with no execution lane named (REMOTE_EXEC unset/empty and CONFIG_PROVIDER not \"local\") — the deployment asked to refuse this state"
|
|
116
|
-
: undefined,
|
|
133
|
+
check: ({ next }) => executionLaneRequiredRefusal(next),
|
|
117
134
|
},
|
|
118
135
|
{
|
|
119
136
|
id: "leader-host-lane",
|
|
@@ -142,9 +159,7 @@ export const CONFIG_CROSS_INVARIANTS = [
|
|
|
142
159
|
keys: ["leaderEnabled", "dbBackend"],
|
|
143
160
|
restartReason: "leader-lane",
|
|
144
161
|
bootAnchor: { file: "src/config.ts", marker: "CROSS-INVARIANT:leader-durable-store" },
|
|
145
|
-
check: ({ next }) => next
|
|
146
|
-
? `LEADER_ENABLED with DB_BACKEND=${next.dbBackend} (no external SQL store) degrades the leader-run registry to a per-process Map`
|
|
147
|
-
: undefined,
|
|
162
|
+
check: ({ next }) => leaderDurableStoreRefusal(next),
|
|
148
163
|
},
|
|
149
164
|
{
|
|
150
165
|
id: "device-durable-store",
|
package/dist/config-types.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ import type { InfraCostRates } from "./observability/cost-taxonomy.js";
|
|
|
18
18
|
import type { Autonomy, CommandRule } from "./runtime-governance.js";
|
|
19
19
|
import type { CenterCredentialSource } from "./center-credential.js";
|
|
20
20
|
import type { GitApiKind } from "./git-api-kind.js";
|
|
21
|
+
import type { MemoryEngineBackend, StoreBackend as StoreBackendWord } from "@sema-agent/settings-schema/execution-lanes";
|
|
21
22
|
import { DRAIN_GRACE_FLOOR_MS } from "@sema-agent/settings-schema/fleet";
|
|
22
23
|
/** [ref]:记忆向量面的 embedder 坐标(env `MEMORY_EMBEDDER_*` 解析结果;单一属主 = 本类型,
|
|
23
24
|
* `plugins/memory-embedder.ts` 的工厂直接消费它,不复制形状)。语义与三问见
|
|
@@ -54,13 +55,6 @@ export type RetentionMode = (typeof RETENTION_MODES)[number];
|
|
|
54
55
|
*/
|
|
55
56
|
export declare const CONFIG_PROVIDER_WORDS: readonly ["local", "remote"];
|
|
56
57
|
export type ConfigProviderWord = (typeof CONFIG_PROVIDER_WORDS)[number];
|
|
57
|
-
/**
|
|
58
|
-
* S-866 ① —— `DB_BACKEND` 的闭集词表(**一处**:`config.ts` 的枚举解析、配置目录的 enum 行、caps `store.declared` / `store.backend`
|
|
59
|
-
* 三处共读;S-2 设计稿 v1 写的契约包 `STORE_ENGINES` 不存在,搬进契约包是 [ref] A-5 的另票)。`mysql` = 任一 MySQL 协议服务器
|
|
60
|
-
* (MySQL / TiDB / MariaDB);`local` = 本机文件店;`memory` = 纯内存(显式 opt-out,或多租裸 boot 的缺省)。
|
|
61
|
-
*/
|
|
62
|
-
export declare const DB_BACKEND_WORDS: readonly ["mysql", "pg", "local", "memory"];
|
|
63
|
-
export type DbBackendWord = (typeof DB_BACKEND_WORDS)[number];
|
|
64
58
|
/** [ref] §3/§4 —— sweep lane 的节律与灰度档(语义与三问见 {@link ServiceConfigFlat.retentionSweep})。 */
|
|
65
59
|
export interface RetentionSweepConfig {
|
|
66
60
|
/** `audit-only` = 照跑照判、只记 `audit_only` 行,**不调任何破坏性方法**;`enforce` = 真删。默认前者。 */
|
|
@@ -702,7 +696,7 @@ export interface ServiceConfigFlat {
|
|
|
702
696
|
* pg 部署的检索**恒 lexical**(词面 jaccard),向量列空转。配上这几键 = 该部署真正拿到语义检索。
|
|
703
697
|
* ② **半配为何拒启**:三键里少任何一件,「向量面」都无从组装。静默忽略的后果不是功能缺席,而是
|
|
704
698
|
* **operator 以为开了**——检索照常返回结果(词面档),质量差异只在最终答案里,任何日志都不报。
|
|
705
|
-
* 所以照 `MEMORY_SYNC_*` / `
|
|
699
|
+
* 所以照 `MEMORY_SYNC_*` / [ref] 车道必需键(`laneRequiredEnv`)的同款姿势整拒并点名缺哪几键。
|
|
706
700
|
* ③ **维度守卫为何是拒启不是 warn**:向量列写错维度是**静默毒库**形([ref] 裁③)——错长度的向量
|
|
707
701
|
* 既不会让 DB 报错(本仓 pg 记忆表的 embedding 是 `jsonb`,无维度约束),也不会让检索报错(维度
|
|
708
702
|
* 不匹配的行只是悄悄退回词面档),只会让「向量面开着」这句话变成谎。所以坏 DIM(非正整数)在
|
|
@@ -1003,7 +997,7 @@ export interface ServiceConfigFlat {
|
|
|
1003
997
|
* MYSQL_* replacement named (see RETIRED_ENVS in config.ts). `mysql` reads MYSQL_* coords only.
|
|
1004
998
|
* TiDB itself stays fully supported (any MySQL-protocol engine works — memory no longer needs a VECTOR
|
|
1005
999
|
* column since [ref] moved it onto the file engine). Env: DB_BACKEND. */
|
|
1006
|
-
dbBackend:
|
|
1000
|
+
dbBackend: StoreBackendWord;
|
|
1007
1001
|
/** clay 拍(2026-07-27):operator 是否显式设了 DB_BACKEND。main.ts 的降级判据——**默认推导**的
|
|
1008
1002
|
* local(裸 boot)在 mkdir/ensure 失败时降级 memory + warn + gauge(auto-DB 不可达先例);
|
|
1009
1003
|
* **显式** DB_BACKEND=local 失败仍 fail-loud(operator 要了 durable,静默丢=丢数据)。 */
|
|
@@ -1884,7 +1878,7 @@ export interface ServiceConfigFlat {
|
|
|
1884
1878
|
* 结束等待的确实是本仓的窗;其余三臂结束等待的是容量/连接/装配,自报恒缺席。
|
|
1885
1879
|
*
|
|
1886
1880
|
* 🔴 **`deny` 交给引擎的形(S-689)**:部署政策拒对象臂 `{allow:false, settledBy:"policy", reason}` ⇒ 引擎结算
|
|
1887
|
-
* `policy_refused`(`who.party:"none"`)、模型读策略句(「部署政策拒了、此处没有交互审批,别重试、做不需审批的
|
|
1881
|
+
* `policy_refused`(`who.party:"policy"` —— core 7.39.0 #1265 起;此前 `"none"`)、模型读策略句(「部署政策拒了、此处没有交互审批,别重试、做不需审批的
|
|
1888
1882
|
* 部分」)、run 不以「用户拒绝」收束而是继续。修前是裸 `false` —— 引擎契约里那就是「一个人拒了」
|
|
1889
1883
|
* (`human_refused` / `haltedOnUserRejection`),与本键「没有人会来」的自声明正好相反。(b)(c) 两臂照旧带
|
|
1890
1884
|
* `"timeout"` 自报(`approval_window_expired`)。
|
|
@@ -2053,7 +2047,7 @@ export interface ServiceConfigFlat {
|
|
|
2053
2047
|
* 404 there (P1-2: our own repos live on GitHub and were unreviewable). Unknown word = refuse boot. */
|
|
2054
2048
|
gitApiKind: GitApiKind;
|
|
2055
2049
|
/** S3-TOB 设计 §1.3:memory 持久面方言(env MEMORY_ENGINE_BACKEND,缺省 file)。boot-only。 */
|
|
2056
|
-
memoryEngineBackend:
|
|
2050
|
+
memoryEngineBackend: MemoryEngineBackend;
|
|
2057
2051
|
/** CC `workflowSizeGuideline` (advisory, injected into the Workflow tool card via
|
|
2058
2052
|
* core `RunnerDeps.workflowLimits.sizeGuideline`). env WORKFLOW_SIZE_GUIDELINE; unset = absent =
|
|
2059
2053
|
* byte-compat (core treats missing/unrestricted identically). boot-only (per-task card snapshot). */
|
package/dist/config-types.js
CHANGED
|
@@ -2,7 +2,6 @@ import { DRAIN_GRACE_FLOOR_MS } from "@sema-agent/settings-schema/fleet";
|
|
|
2
2
|
export const TASK_WRITE_FACE_ESCALATIONS = ["allow", "refuse"];
|
|
3
3
|
export const RETENTION_MODES = ["audit-only", "enforce"];
|
|
4
4
|
export const CONFIG_PROVIDER_WORDS = ["local", "remote"];
|
|
5
|
-
export const DB_BACKEND_WORDS = ["mysql", "pg", "local", "memory"];
|
|
6
5
|
export const MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC = 300;
|
|
7
6
|
export const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
8
7
|
export const AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS = { minMs: 1_000, maxMs: MAX_TIMER_DELAY_MS };
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type Model, type PathRuleBases, type RuleBehavior } from "@sema-agent/core";
|
|
2
|
-
import { type RemoteExecProvider } from "./execution-lane-caps.js";
|
|
3
2
|
import { type Autonomy } from "./runtime-governance.js";
|
|
4
3
|
import { type RunnerEnvNameAllowlistKnob, type ServerEnvNameAllowlistKnob } from "./env-name-allowlist-knobs.js";
|
|
5
4
|
import type { ServiceConfig, ServiceConfigFlat, ServiceConfigGroups } from "./config-types.js";
|
|
@@ -252,21 +251,6 @@ export declare const configLkgEnabled: () => boolean;
|
|
|
252
251
|
export declare const hostBackgroundShellEnabled: () => boolean;
|
|
253
252
|
/** The `host` exec lane's spool-file stdio form; false ⇒ the pre-1.226 pipe fallback (read per exec). */
|
|
254
253
|
export declare const hostExecSpoolEnabled: () => boolean;
|
|
255
|
-
/**
|
|
256
|
-
* 每条执行车道**必需的 env 键名**([ref] 的 fail-closed 拒启门按它点名缺件;见 `parseOrchestrationDomain`
|
|
257
|
-
* 里那处 `REMOTE_EXEC` 校验)。
|
|
258
|
-
*
|
|
259
|
-
* 键集 = `RemoteExecProvider`(判别联合派生,`execution-lane-caps.ts`):配置面加一条 provider 臂而
|
|
260
|
-
* 这里不加行 = **编译红**(codex R1-F2)。此前是 `Record<string, …>`,漏一行只表现为该词在 boot 期被判
|
|
261
|
-
* 「不是执行车道」—— fail-closed 但把「新车道」讲成「拼错了」,运维照着报文永远查不到成因。
|
|
262
|
-
*
|
|
263
|
-
* 🔴 **S-599:提到模块顶层导出**(此前是 `parseOrchestrationDomain` 的函数内局部)。零语义变,动机是
|
|
264
|
-
* 部署面需要**同一张表**:`deploy/sema-up/lib/lanes.json` 的每行必须登记它那条车道的必需键名,根 /
|
|
265
|
-
* review compose 的 environment 白名单必须把**全部**车道的必需键透传出去 —— 那两处若各手抄一份名单,
|
|
266
|
-
* 加一条车道就会静默分叉(选得到、起不来,而 config 的拒启报文里那个键在 compose 里根本不存在)。
|
|
267
|
-
* 门 = `test/deploy-lanes-contract.test.ts`,它读的是本表而不是任何副本。
|
|
268
|
-
*/
|
|
269
|
-
export declare const REQUIRED_ENVS_BY_LANE: Record<RemoteExecProvider, readonly string[]>;
|
|
270
254
|
/** 名单型 env 旋钮的**唯一**解析器(规则全文见上方 `ENV_NAME_ALLOWLIST_KNOBS` 头注)。`undefined` = 没配(未设 / 空串)。
|
|
271
255
|
* 服务侧旋钮要交本进程的姿态(③);runner 旋钮没有姿态,签名上就不收 —— 读者与行形由叶表的类型一起定,传错是编译红。 */
|
|
272
256
|
export declare function parseEnvNameAllowlist(knob: ServerEnvNameAllowlistKnob, singleUserTurnkey: boolean): readonly string[] | undefined;
|
package/dist/config.js
CHANGED
|
@@ -10,14 +10,17 @@ import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
|
|
|
10
10
|
import { isV2ScopeKey, memoryReplicationCoverage, replicationCovers } from "./memory-scope.js";
|
|
11
11
|
import { DEFAULT_QUESTION_THROTTLE } from "./question.js";
|
|
12
12
|
import { GIT_API_KINDS, isGitApiKind } from "./git-api-kind.js";
|
|
13
|
-
import { CONFIG_PROVIDER_WORDS,
|
|
13
|
+
import { CONFIG_PROVIDER_WORDS, RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC, RESILIENCE_DEFAULTS, TASK_WRITE_FACE_ESCALATIONS, MAX_TIMER_DELAY_MS, AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS, DRAIN_GRACE_FLOOR_MS, DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS, DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS, FIRST_BYTE_TIMEOUT_BOUNDS } from "./config-types.js";
|
|
14
|
+
import { resolveLocalDataRoot } from "./local-data-root.js";
|
|
14
15
|
import { buildWriteProtectionPosture } from "./write-protection.js";
|
|
15
16
|
import { normalizeSshFingerprint } from "./ssh-host-key.js";
|
|
16
17
|
import { buildReadFacePostureNotice, readFaceForPosture } from "./read-face-posture.js";
|
|
17
18
|
import { THINKING_LEVELS, THINKING_FORMATS, buildModelCompat, isThinkingFormat, isThinkingTier } from "./model-compat.js";
|
|
18
19
|
import {} from "./posture-source.js";
|
|
19
20
|
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
20
|
-
import {
|
|
21
|
+
import { bakeOperatorPrincipalsRefusal, bakeRunnerCredentialRefusal, halfOpenDirectDoorRefusal, leaderDurableStoreRefusal, operatorPrincipalsRequiredRefusal } from "./config-invariants.js";
|
|
22
|
+
import { REMOTE_EXEC_PROVIDER_WORDS, executionLaneOf, executionLaneRequiredRefusal, isSingleMachineDefaultLane, laneRequiredEnv } from "./execution-lane-caps.js";
|
|
23
|
+
import { MEMORY_ENGINE_BACKENDS, SESSION_BACKENDS, STORE_BACKENDS } from "@sema-agent/settings-schema/execution-lanes";
|
|
21
24
|
import { AUTONOMY_MODES } from "./runtime-governance.js";
|
|
22
25
|
import { createCenterCredentialSource, readCenterCredentialDeclaration } from "./center-credential.js";
|
|
23
26
|
import { parseCenterCredentialRenewLeadMs } from "./center-credential-renewer.js";
|
|
@@ -483,15 +486,6 @@ function boolEnv(name, def) {
|
|
|
483
486
|
export const configLkgEnabled = () => boolEnv("CONFIG_LKG_ENABLED", true);
|
|
484
487
|
export const hostBackgroundShellEnabled = () => boolEnv("HOST_BG_ENABLED", true);
|
|
485
488
|
export const hostExecSpoolEnabled = () => boolEnv("HOST_EXEC_SPOOL_ENABLED", true);
|
|
486
|
-
export const REQUIRED_ENVS_BY_LANE = {
|
|
487
|
-
e2b: ["E2B_API_KEY"],
|
|
488
|
-
k8s: ["K8S_SANDBOX_IMAGE"],
|
|
489
|
-
ssh: ["SSH_HOST", "SSH_USER", "SSH_PRIVATE_KEY"],
|
|
490
|
-
adb: ["ADB_SERIAL"],
|
|
491
|
-
host: [],
|
|
492
|
-
"local-docker": ["DOCKER_IMAGE"],
|
|
493
|
-
device: [],
|
|
494
|
-
};
|
|
495
489
|
function parseReadDenyPatternsEnv(raw) {
|
|
496
490
|
let arr;
|
|
497
491
|
try {
|
|
@@ -861,7 +855,7 @@ function parseStoreDomain(ctx) {
|
|
|
861
855
|
!!(process.env.MYSQL_HOST || process.env.PG_HOST);
|
|
862
856
|
const bareBoot = !dbBackendSet && !sqlSignal;
|
|
863
857
|
const multiTenantBareBoot = bareBoot && requirePrincipal;
|
|
864
|
-
const dbBackend = enumEnv("DB_BACKEND", bareBoot ? (multiTenantBareBoot ? "memory" : "local") : "mysql",
|
|
858
|
+
const dbBackend = enumEnv("DB_BACKEND", bareBoot ? (multiTenantBareBoot ? "memory" : "local") : "mysql", STORE_BACKENDS);
|
|
865
859
|
if (multiTenantBareBoot) {
|
|
866
860
|
CONFIG_NOTICES.push({
|
|
867
861
|
level: "warn",
|
|
@@ -872,9 +866,9 @@ function parseStoreDomain(ctx) {
|
|
|
872
866
|
});
|
|
873
867
|
}
|
|
874
868
|
const sqlEngineExplicit = !!process.env.DB_BACKEND && dbBackend !== "local" && dbBackend !== "memory";
|
|
875
|
-
const sessionBackendRaw = enumEnv("SESSION_BACKEND", sqlEngineExplicit ? "mysql" : "memory",
|
|
869
|
+
const sessionBackendRaw = enumEnv("SESSION_BACKEND", sqlEngineExplicit ? "mysql" : "memory", SESSION_BACKENDS);
|
|
876
870
|
const sessionBackend = sessionBackendRaw === "mysql" ? "tidb" : sessionBackendRaw;
|
|
877
|
-
const localDataRoot = process.env
|
|
871
|
+
const localDataRoot = resolveLocalDataRoot(process.env, homedir());
|
|
878
872
|
const dbHostSet = dbBackend === "pg" ? !!process.env.PG_HOST : !!process.env.MYSQL_HOST;
|
|
879
873
|
const needsTidb = sessionBackend === "tidb" || (sessionBackend === "auto" && dbHostSet);
|
|
880
874
|
const needsDb = needsTidb;
|
|
@@ -1644,11 +1638,7 @@ function parseMemoryConsolidationDriver() {
|
|
|
1644
1638
|
function parseMemoryDomain(ctx) {
|
|
1645
1639
|
const { requirePrincipal } = ctx;
|
|
1646
1640
|
const memoryEngineEnabled = parseMemoryEngineWord(env("MEMORY_ENGINE", "on"));
|
|
1647
|
-
const
|
|
1648
|
-
if (!["file", "pg", "tidb"].includes(memoryEngineBackendRaw)) {
|
|
1649
|
-
throw new Error(`MEMORY_ENGINE_BACKEND must be file|pg|tidb, got "${memoryEngineBackendRaw}"`);
|
|
1650
|
-
}
|
|
1651
|
-
const memoryEngineBackend = memoryEngineBackendRaw;
|
|
1641
|
+
const memoryEngineBackend = enumEnv("MEMORY_ENGINE_BACKEND", "file", MEMORY_ENGINE_BACKENDS);
|
|
1652
1642
|
const memoryScope = process.env.MEMORY_SCOPE ?? (memoryEngineEnabled && !requirePrincipal ? "local" : undefined);
|
|
1653
1643
|
const memorySyncUrl = process.env.MEMORY_SYNC_URL || undefined;
|
|
1654
1644
|
const memorySyncToken = process.env.MEMORY_SYNC_TOKEN || undefined;
|
|
@@ -1864,12 +1854,12 @@ function parseOrchestrationDomain(ctx) {
|
|
|
1864
1854
|
{
|
|
1865
1855
|
const lane = process.env.REMOTE_EXEC;
|
|
1866
1856
|
if (lane !== undefined && lane !== "") {
|
|
1867
|
-
const
|
|
1868
|
-
if (
|
|
1869
|
-
throw new Error(`REMOTE_EXEC="${lane}" is not an execution lane — valid: ${
|
|
1857
|
+
const word = REMOTE_EXEC_PROVIDER_WORDS.find((w) => w === lane);
|
|
1858
|
+
if (word === undefined) {
|
|
1859
|
+
throw new Error(`REMOTE_EXEC="${lane}" is not an execution lane — valid: ${REMOTE_EXEC_PROVIDER_WORDS.join(" | ")}. ` +
|
|
1870
1860
|
`An unrecognized value used to fall through to the host/in-process lane silently; refusing to start instead (#157).`);
|
|
1871
1861
|
}
|
|
1872
|
-
const missing =
|
|
1862
|
+
const missing = laneRequiredEnv(word).filter((name) => !process.env[name]);
|
|
1873
1863
|
if (missing.length > 0) {
|
|
1874
1864
|
throw new Error(`REMOTE_EXEC=${lane} but required env ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} missing/empty — ` +
|
|
1875
1865
|
`refusing to start rather than silently downgrading to the host/in-process lane (#157; unset REMOTE_EXEC if you meant no sandbox lane).`);
|
|
@@ -2406,54 +2396,39 @@ export function loadConfig() {
|
|
|
2406
2396
|
const integrations = parseIntegrationsDomain({ requirePrincipal, configProvider });
|
|
2407
2397
|
const { directApprovalDoor, durableApproval: dgDurable, approvalHmacKeys: dgHmacKeys } = approval;
|
|
2408
2398
|
const { principalJwtPubkeys, principalJwtIss, principalJwtAud, operatorPrincipals: dgOperators, authTokens } = auth;
|
|
2409
|
-
const { enabled: imageBakesEnabled, runnerPrincipal: bakeRunnerPrincipal } = orchestration.imageBakes;
|
|
2410
2399
|
const directDoorActive = directApprovalDoor && dgDurable && dgHmacKeys.length > 0 && principalJwtPubkeys.length > 0 && !!principalJwtIss && !!principalJwtAud;
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2400
|
+
const doorRefusal = halfOpenDirectDoorRefusal({
|
|
2401
|
+
directApprovalDoor,
|
|
2402
|
+
durableApproval: dgDurable,
|
|
2403
|
+
approvalHmacKeys: dgHmacKeys,
|
|
2404
|
+
principalJwtPubkeys,
|
|
2405
|
+
principalJwtIss,
|
|
2406
|
+
principalJwtAud,
|
|
2407
|
+
requirePrincipal,
|
|
2408
|
+
operatorPrincipals: dgOperators,
|
|
2409
|
+
});
|
|
2410
|
+
if (doorRefusal !== undefined) {
|
|
2411
|
+
throw new Error(doorRefusal);
|
|
2412
|
+
}
|
|
2413
|
+
const operatorsRefusal = operatorPrincipalsRequiredRefusal({ durableApproval: dgDurable, requirePrincipal, operatorPrincipals: dgOperators });
|
|
2414
|
+
if (operatorsRefusal !== undefined) {
|
|
2415
|
+
throw new Error(operatorsRefusal);
|
|
2416
|
+
}
|
|
2417
|
+
const bakeOperatorsRefusal = bakeOperatorPrincipalsRefusal({ imageBakes: orchestration.imageBakes, operatorPrincipals: dgOperators });
|
|
2418
|
+
if (bakeOperatorsRefusal !== undefined) {
|
|
2419
|
+
throw new Error(bakeOperatorsRefusal);
|
|
2420
|
+
}
|
|
2421
|
+
const bakeRunnerRefusal = bakeRunnerCredentialRefusal({ imageBakes: orchestration.imageBakes, authTokens });
|
|
2422
|
+
if (bakeRunnerRefusal !== undefined) {
|
|
2423
|
+
throw new Error(bakeRunnerRefusal);
|
|
2424
|
+
}
|
|
2425
|
+
const leaderStoreRefusal = leaderDurableStoreRefusal({ leaderEnabled: orchestration.leaderEnabled, dbBackend: store.dbBackend });
|
|
2426
|
+
if (leaderStoreRefusal !== undefined) {
|
|
2427
|
+
throw new Error(leaderStoreRefusal);
|
|
2430
2428
|
}
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
if (imageBakesEnabled && dgOperators.length === 0) {
|
|
2435
|
-
throw new Error("IMAGE_BAKES_ENABLED=true but OPERATOR_PRINCIPALS is empty — the bake door runs build.sh on the privileged build host; an empty operator set makes isOperator() true-for-all (world-writable RCE). Set OPERATOR_PRINCIPALS or disable bakes (IMAGE-API-DESIGN.md §P2.4a boot invariant)");
|
|
2436
|
-
}
|
|
2437
|
-
if (imageBakesEnabled && !Object.values(authTokens).includes(bakeRunnerPrincipal)) {
|
|
2438
|
-
throw new Error(`IMAGE_BAKES_ENABLED=true but no SERVICE_AUTH_TOKENS entry maps to the runner principal '${bakeRunnerPrincipal}' — the bake-runner authenticates by its credential (token-derived source), so a '<BAKE_RUNNER_TOKEN>=${bakeRunnerPrincipal}' SERVICE_AUTH_TOKENS entry is required or claim/ingest/heartbeat all 403 (IMAGE-API-DESIGN.md §P2.12 boot invariant)`);
|
|
2439
|
-
}
|
|
2440
|
-
if (orchestration.leaderEnabled && store.dbBackend !== "mysql" && store.dbBackend !== "pg") {
|
|
2441
|
-
throw new Error(`LEADER_ENABLED=true but DB_BACKEND=${store.dbBackend} (no external SQL store) is refused: a leader run is an ` +
|
|
2442
|
-
"N-worker fan-out lasting up to 24h that ends in a REAL git push, and without a durable store its registry is " +
|
|
2443
|
-
"a per-process Map — a cross-replica GET 404s, a restart forgets the run, and a retried POST starts a SECOND " +
|
|
2444
|
-
"push. Point the service at a durable store (DB_BACKEND=mysql|pg), or turn the leader off (unset LEADER_ENABLED) " +
|
|
2445
|
-
"(#193 车4 件2 / staleness ledger P1-9+P1-10 boot invariant)");
|
|
2446
|
-
}
|
|
2447
|
-
if (orchestration.requireExecutionLane && orchestration.remoteExec === undefined) {
|
|
2448
|
-
throw new Error(`REQUIRE_EXECUTION_LANE=true but NO execution lane is named on this deployment — refusing to start. ` +
|
|
2449
|
-
`With no lane the engine falls back to its in-process stub environment, where at least the Bash tool is ` +
|
|
2450
|
-
`not mounted, yet every task still returns status=completed — indistinguishable from "the model chose ` +
|
|
2451
|
-
`not to use its tools". (What the roster actually keeps is a per-run fact: read the wiring_manifest ` +
|
|
2452
|
-
`event and count entries whose mountedBy is "hands".) ` +
|
|
2453
|
-
`Cause: REMOTE_EXEC is unset/empty AND CONFIG_PROVIDER is not "local". ` +
|
|
2454
|
-
`Fix: set REMOTE_EXEC to one of ${REMOTE_EXEC_PROVIDER_WORDS.join(" | ")} (and that lane's required env), ` +
|
|
2455
|
-
`or set CONFIG_PROVIDER=local to take the single-machine \`host\` default, ` +
|
|
2456
|
-
`or unset REQUIRE_EXECUTION_LANE to keep running without hands on purpose.`);
|
|
2429
|
+
const laneRequiredRefusal = executionLaneRequiredRefusal(orchestration);
|
|
2430
|
+
if (laneRequiredRefusal !== undefined) {
|
|
2431
|
+
throw new Error(laneRequiredRefusal);
|
|
2457
2432
|
}
|
|
2458
2433
|
{
|
|
2459
2434
|
const envLaneDispatchable = !isRosterGateArmed(integrations.configCenter);
|
package/dist/env-facts.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ImageCapabilities } from "./plugins/store-contracts.js";
|
|
2
|
+
import type { RemoteExecProvider } from "./execution-lane-caps.js";
|
|
2
3
|
export interface SandboxEnvFacts {
|
|
3
4
|
profile?: string;
|
|
4
5
|
capabilities?: string[];
|
|
@@ -53,7 +54,8 @@ export declare function buildEnvFacts(opts: {
|
|
|
53
54
|
* codex 复审 medium:k8s 必须按**有效快照能力**分叉,不能只看车道名——s3Snapshot 未配=park-only,
|
|
54
55
|
* resume 是全新 pod 且**工作区不恢复**,「文件已从快照恢复」那句会让模型信赖不存在的文件(比缺席
|
|
55
56
|
* 更糟的谎)。 */
|
|
56
|
-
export declare function resumeFactsForLane(provider:
|
|
57
|
+
export declare function resumeFactsForLane(provider: RemoteExecProvider | undefined, // 车道词 = settings-schema 词表正本(经本仓读口取型,不手抄;表加词 ⇒ 下方穷举 switch 编译红)
|
|
58
|
+
caps?: {
|
|
57
59
|
k8sSnapshot?: boolean;
|
|
58
60
|
}): SandboxEnvFacts["resumeFacts"] | undefined;
|
|
59
61
|
/** Path-safe session segment: the session id can be CALLER-SUPPLIED (body.sessionId), so it must not be able to
|
package/dist/env-facts.js
CHANGED
|
@@ -42,8 +42,12 @@ export function resumeFactsForLane(provider, caps) {
|
|
|
42
42
|
case "host":
|
|
43
43
|
case undefined:
|
|
44
44
|
return { scratch: "preserved" };
|
|
45
|
-
|
|
45
|
+
case "local-docker":
|
|
46
46
|
return undefined;
|
|
47
|
+
default: {
|
|
48
|
+
const unreachable = provider;
|
|
49
|
+
return unreachable;
|
|
50
|
+
}
|
|
47
51
|
}
|
|
48
52
|
}
|
|
49
53
|
export function scratchpadSessionSegment(sessionId) {
|
|
@@ -1,16 +1,13 @@
|
|
|
1
|
+
import { type ExecutionLane as DeclarableLane, type ExecutionLaneRow } from "@sema-agent/settings-schema/execution-lanes";
|
|
1
2
|
import type { ServiceConfigFlat } from "./config-types.js";
|
|
2
3
|
/** 一位能力的三态。`single-user-only` = 车道支持,但只在单用户部署(`requirePrincipal !== true`)开 ——
|
|
3
4
|
* 多租户下这一位必须关(host 家族的 cwd/scheduler/backgroundShell/LSP 都是这一档,理由同源:
|
|
4
5
|
* 一个租户的输入不许把 agent 指向 worker 宿主上另一个租户/运维的文件与进程)。 */
|
|
5
6
|
export type LaneCapability = "supported" | "unsupported" | "single-user-only";
|
|
6
|
-
/** `REMOTE_EXEC`
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*
|
|
10
|
-
export type RemoteExecProvider = NonNullable<ServiceConfigFlat["remoteExec"]>["provider"];
|
|
11
|
-
/** 闭集:执行车道全集 = provider 词 + `in-process`(`REMOTE_EXEC` 未设的进程内 stub 形,配置面无 provider
|
|
12
|
-
* 可派生 —— 它是「没有 remoteExec」这个状态的名字)。加词 ⇒ {@link executionLaneCaps} 的穷举 switch 与
|
|
13
|
-
* {@link EXECUTION_LANE_WORDS} 两处都编译红,这正是要的(「加词必须逐位表态」)。 */
|
|
7
|
+
/** `REMOTE_EXEC` 能写下去的车道词全集 = settings-schema 词表正本的 `ExecutionLane`(**派生**,本仓零手抄)。 */
|
|
8
|
+
export type RemoteExecProvider = DeclarableLane;
|
|
9
|
+
/** 闭集:执行车道全集 = 表的可声明车道 + `in-process`(`REMOTE_EXEC` 未设的进程内 stub 形 —— 它是「没有 remoteExec」
|
|
10
|
+
* 这个状态的名字,不在表里)。表加词 ⇒ {@link executionLaneCaps} 的穷举 switch 编译红,这正是要的(「加词必须逐位表态」)。 */
|
|
14
11
|
export type ExecutionLane = "in-process" | RemoteExecProvider;
|
|
15
12
|
/**
|
|
16
13
|
* per-lane 能力位。每一位的注里点名**今天真正做判决的那处代码**——对账测试按这些坐标跑真函数,
|
|
@@ -88,12 +85,13 @@ export interface ExecutionLaneCaps {
|
|
|
88
85
|
* · `single-user-only` = 所有任务落在**同一个共享目标**上(worker 宿主:host;一台常驻 SSH 对端:ssh;一台 ADB 设备:adb)
|
|
89
86
|
* ⇒ 只许单用户部署用。
|
|
90
87
|
* 本位**没有** `unsupported` 这一档(那会读成「谁都不许用」,不是车道事实)—— 型上收窄掉,每条车道必须在两档里表态。
|
|
91
|
-
* 🔴
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
88
|
+
* 🔴 **7.114.0 起本位的值不在本仓写**:七条可声明车道读 settings-schema 词表正本的表行(`executionLaneRow(lane).tenantSeparation`;
|
|
89
|
+
* 中心写闸的 `requirePrincipal ∧ single-user-only` 交叉判与本仓 `multi-tenant-shared-lane` 拒启读的是同一个值,不会分叉);
|
|
90
|
+
* 只有 `in-process` 一格是本仓自己的事实(它不在表里)。
|
|
91
|
+
* 唯一消费点是 `config-invariants.ts` 的 `multi-tenant-shared-lane`(经 {@link laneCapabilityHolds} 落成布尔)。本位值与本仓**运行期事实**
|
|
92
|
+
* 不矛盾的对账 = `test/execution-lanes-schema-conformance.test.ts`(隔离位 / 共享文件平面 ⇒ 租户档的蕴含 + 唯一一条「不隔离却分租户」
|
|
93
|
+
* 的车道 = device 点名);ssh / adb 「两租户同一目标」的行为预言机 = `test/multi-tenant-shared-lane.test.ts`
|
|
94
|
+
* (两个租户的任务铸出的 env 报同一个 `workspaceHandle().sandboxId`)。
|
|
97
95
|
*/
|
|
98
96
|
readonly tenantSeparation: Exclude<LaneCapability, "unsupported">;
|
|
99
97
|
}
|
|
@@ -117,23 +115,49 @@ export interface ExecutionLaneCaps {
|
|
|
117
115
|
* 恰是诚实形(§4.3.4)。
|
|
118
116
|
*/
|
|
119
117
|
export declare function executionLaneCaps(lane: ExecutionLane): ExecutionLaneCaps;
|
|
120
|
-
/**
|
|
121
|
-
|
|
122
|
-
|
|
118
|
+
/**
|
|
119
|
+
* 车道普查全集 = 本仓内部状态名 `in-process` + 词表正本的可声明车道(次序 = 正本声明序;对账测试按它逐 lane 跑真判别式)。
|
|
120
|
+
* 由正本派生、不手维护(手维护的「全表」已被代码反证漏过行)。
|
|
121
|
+
*/
|
|
123
122
|
export declare const ALL_EXECUTION_LANES: readonly ExecutionLane[];
|
|
123
|
+
/** 一个开放字符串是不是闭集里的词(不做前缀 / 大小写宽容)。 */
|
|
124
|
+
export declare function isExecutionLane(word: string): word is ExecutionLane;
|
|
124
125
|
/**
|
|
125
|
-
* `REMOTE_EXEC` **真能写下去的**那几个词
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* `enumValues: ["e2b","k8s",…]` 字面量(字段静态型是 `readonly string[]`,与 {@link RemoteExecProvider}
|
|
130
|
-
* 零类型联系)。病形与 S-382 逮到的 `WEB_SEARCH_PROVIDER` 逐字同族:给
|
|
131
|
-
* `ServiceConfigFlat["remoteExec"]` 加一条 provider 臂(config-types.ts 里写着的下一条就是
|
|
132
|
-
* `remote-docker`),本文件的穷举 switch 与词表**会**编译红,而目录行不会 —— 于是运维在
|
|
133
|
-
* `GET /v1/config/catalog` 上读到的可写词集静默少一个,新车道成了「配了但目录说不认识」。
|
|
134
|
-
* 收成一只派生数组之后,目录行**就用它本人**(身份相等有钉),全仓只剩这一张表。
|
|
126
|
+
* `REMOTE_EXEC` **真能写下去的**那几个词 = settings-schema 词表正本的 `EXECUTION_LANE_WORDS`(冻结副本,次序逐字同)。
|
|
127
|
+
* 读者:配置目录 `REMOTE_EXEC` 行的 `enumValues`(**就用它本人**,身份相等有钉 —— S-426 修过「目录手抄一份词表」的病)、
|
|
128
|
+
* [ref] 拒启句的 `valid:` 列表、`REQUIRE_EXECUTION_LANE` 拒启句的出路、`multi-tenant-shared-lane` 拒启句的出路车道。
|
|
129
|
+
* 7.114.0 起全仓唯一的车道词来源是正本;本常量只是它在本仓的那一个读口。
|
|
135
130
|
*/
|
|
136
131
|
export declare const REMOTE_EXEC_PROVIDER_WORDS: readonly RemoteExecProvider[];
|
|
132
|
+
/** 一条可声明车道在词表正本里的那一行(必需 / 可选 env 四栏 + 租户档)。加载期自检过完备性,所以对任何 {@link RemoteExecProvider} 都有行。 */
|
|
133
|
+
export declare function executionLaneRow(lane: RemoteExecProvider): ExecutionLaneRow;
|
|
134
|
+
/**
|
|
135
|
+
* 一条车道**必需**的 env 键名 = 正本表行的 `requiredEnv ∪ requiredSecretEnv`(次序:非密坐标在前、密钥在后 —— 与 7.113.0 本仓手写表逐字同)。
|
|
136
|
+
* 读者:`config.ts` 的 [ref] 拒启门(点了名却缺件 ⇒ 点名缺哪几键)、部署面门(`test/deploy-lanes-contract.test.ts`:`lanes.json` 每行登记
|
|
137
|
+
* 本车道必需键、根 / review compose 透传全部车道必需键之并)、`scripts/gen-lanes-json.mjs`(从同一张正本生成 `lanes.json` 的必需键列)。
|
|
138
|
+
* device 没有必需 env(设备是运行期连进来的;它的 boot 前提是跨键的,在跨域不变量区拒启)。
|
|
139
|
+
*/
|
|
140
|
+
export declare function laneRequiredEnv(lane: RemoteExecProvider): readonly string[];
|
|
141
|
+
/**
|
|
142
|
+
* S-598 件③ / S-1008 —— **「`REQUIRE_EXECUTION_LANE=true` ∧ 没有任何车道被点名 ⇒ 拒启」的判据与拒因句的唯一属主**(`modelProviderRefusal` /
|
|
143
|
+
* `modelRouteEndpointRefusal` 同形:属主在叶模块,执法点各自调它,登记在 `config-invariants.ts`)。
|
|
144
|
+
*
|
|
145
|
+
* 🔴 病(S-1008,test d327-g6-engine-7112 OBS-4 / OBS-5;2026-10-03 亲核):修前**两份句子**。运行期真拒启的是 `config.ts` 手写 throw
|
|
146
|
+
* (点名 `REQUIRE_EXECUTION_LANE` / `REMOTE_EXEC` + 修法,用户在 stderr 读到的就是它);注册表条目另写了一句短的
|
|
147
|
+
* 「the deployment asked to refuse this state」,而它只在 `config-center/stage-limits.ts` 的全表校验里被调 —— 候选视图只装
|
|
148
|
+
* `BATCH1_LIMIT_KEYS` 的限额键,带不上这两键 ⇒ 对本条恒 `next === live`,**那一句没有任何读者**(test 六个拒启格 stderr 里零命中)。
|
|
149
|
+
* 改一份另一份不跟。现形:boot 的 throw 与注册表条目的 `check` **都**调本函数 —— 一只判据、一句话,没有第二份文本可漂。
|
|
150
|
+
*
|
|
151
|
+
* 判据读**装配结果**(`executionLaneOf` = 全仓唯一的缺席归一读口,缺席 ⇒ `in-process`),不重判 env:`remoteExec` 缺席**就是**
|
|
152
|
+
* 「`REMOTE_EXEC` 未设/空串 ∧ `CONFIG_PROVIDER ≠ local`」这条三元链尾臂的落点;点了名却配不齐 / 认不出归 [ref] 那条既有拒启(两句话各说各的问题)。
|
|
153
|
+
*
|
|
154
|
+
* 句子的量词纪律(S-1008 ②):stub 车道上**手带族**(`wiring_manifest.tools.entries[].mountedBy === "hands"`)整族不挂,名册里其余工具
|
|
155
|
+
* 照常执行(test R1n:`Now` 真执行成功)—— 不写「零工具」;名册**总条数**随 core 版本走,不写进句子(S-1008 ③)。
|
|
156
|
+
*/
|
|
157
|
+
export declare function executionLaneRequiredRefusal(deployment: {
|
|
158
|
+
readonly requireExecutionLane: boolean;
|
|
159
|
+
readonly remoteExec?: ServiceConfigFlat["remoteExec"];
|
|
160
|
+
}): string | undefined;
|
|
137
161
|
/**
|
|
138
162
|
* `config.remoteExec?.provider` → 车道词。**全仓唯一**的缺席归一读口(S-602 门 D:`?? "in-process"` 只许住在这里,
|
|
139
163
|
* `?? "host"` 与 `lane === undefined || lane === "host"` 同义折零容忍)。
|
|
@@ -144,9 +168,9 @@ export declare const REMOTE_EXEC_PROVIDER_WORDS: readonly RemoteExecProvider[];
|
|
|
144
168
|
*
|
|
145
169
|
* 两个重载 = 类型收窄([ref] §2.2 A):喂**已装配的** config 形(`RemoteExecProvider | undefined`)⇒
|
|
146
170
|
* 返回型里没有 `undefined`,调用点不必为一条不可达的臂写兜底(兜底写多了就是下一处折词);喂开放字符串
|
|
147
|
-
* (测试、外部输入)⇒ 返回含 `undefined
|
|
148
|
-
* `RemoteExecProvider
|
|
149
|
-
* `
|
|
171
|
+
* (测试、外部输入)⇒ 返回含 `undefined`,逼调用点表态。第一个重载的成立依据是**构造**的:
|
|
172
|
+
* `RemoteExecProvider` 是正本元组 `EXECUTION_LANE_WORDS` 的元素型,而 {@link isExecutionLane} 的集合正是由那只元组
|
|
173
|
+
* (加 `in-process`)建成 ⇒ 对 `RemoteExecProvider` 的每一个值 `isExecutionLane` 恒真(对账格逐词跑过)。
|
|
150
174
|
*/
|
|
151
175
|
export declare function executionLaneOf(provider: RemoteExecProvider | undefined): ExecutionLane;
|
|
152
176
|
export declare function executionLaneOf(provider: string | undefined): ExecutionLane | undefined;
|