@sema-agent/server 7.104.0 → 7.106.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 +114 -0
- package/USAGE.md +193 -52
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +2 -0
- package/deploy/sema-up/cluster-up.sh +15 -2
- package/deploy/sema-up/kube-up.sh +8 -3
- package/deploy/sema-up/lib/flagshape.sh +29 -0
- package/deploy/sema-up/lib/parse-machines.py +4 -1
- package/deploy/sema-up/sema-up.sh +11 -9
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +3 -1
- package/dist/approval-card.d.ts +1 -1
- package/dist/approval.d.ts +6 -1
- package/dist/approval.js +4 -4
- package/dist/auth-bridge.d.ts +14 -0
- package/dist/auth-bridge.js +7 -0
- package/dist/auto-mode-face.d.ts +49 -40
- package/dist/auto-mode-face.js +6 -68
- package/dist/bench/s1/live-deps.d.ts +9 -2
- package/dist/bench/s1/live-deps.js +13 -8
- package/dist/boot/budget-tracing.js +3 -2
- package/dist/boot/config-center.d.ts +4 -0
- package/dist/boot/config-center.js +95 -37
- package/dist/boot/coordinators.d.ts +1 -0
- package/dist/boot/coordinators.js +3 -1
- package/dist/boot/device-lane.d.ts +2 -1
- package/dist/boot/execution-env.d.ts +11 -0
- package/dist/boot/execution-env.js +30 -6
- package/dist/boot/execution-lane.js +2 -3
- package/dist/boot/leader.d.ts +2 -1
- package/dist/boot/leader.js +2 -0
- package/dist/boot/parked-revive-gate.d.ts +10 -7
- package/dist/boot/parked-revive-gate.js +4 -11
- package/dist/boot/resolve-spec.d.ts +1 -4
- package/dist/boot/resolve-spec.js +44 -21
- package/dist/boot/runner-deps.d.ts +5 -8
- package/dist/boot/runner-deps.js +5 -10
- package/dist/boot/side-query-lane.d.ts +2 -2
- package/dist/boot/stage-03-store-backend.d.ts +1 -1
- package/dist/boot/stage-03-store-backend.js +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.d.ts +2 -1
- package/dist/boot/stage-05-execution-env.js +2 -2
- package/dist/boot/stage-06-runners.d.ts +3 -2
- package/dist/boot/stage-06-runners.js +2 -5
- package/dist/boot/stage-07-capability-layer.d.ts +2 -2
- package/dist/boot/stage-07-capability-layer.js +1 -2
- package/dist/boot/stage-08-reapers.d.ts +2 -2
- package/dist/boot/stage-09-leader.d.ts +2 -2
- package/dist/boot/stage-09-leader.js +2 -2
- package/dist/boot/stage-10-http-server.d.ts +2 -2
- package/dist/boot/stage-10-http-server.js +11 -7
- package/dist/boot/stores.d.ts +1 -1
- package/dist/boot/stores.js +6 -4
- package/dist/brain.d.ts +24 -2
- package/dist/brain.js +29 -10
- package/dist/budget.js +3 -0
- package/dist/capabilities/center-wiring.d.ts +64 -0
- package/dist/capabilities/center-wiring.js +24 -0
- package/dist/capabilities/composite-members.d.ts +89 -0
- package/dist/capabilities/composite-members.js +20 -0
- package/dist/capabilities/execution-lane.d.ts +3 -0
- package/dist/capabilities/execution-lane.js +5 -2
- package/dist/config-catalog.d.ts +30 -5
- package/dist/config-catalog.js +75 -26
- package/dist/config-center/apply-effective.d.ts +1 -1
- package/dist/config-center/apply-effective.js +7 -8
- package/dist/config-center/apply-ledger.d.ts +17 -0
- package/dist/config-center/apply-ledger.js +5 -0
- package/dist/config-center/center-request.d.ts +17 -0
- package/dist/config-center/center-request.js +4 -0
- package/dist/config-center/pull-state.d.ts +29 -0
- package/dist/config-center/pull-state.js +2 -0
- package/dist/config-types.d.ts +41 -14
- package/dist/config-types.js +1 -0
- package/dist/config.d.ts +50 -2
- package/dist/config.js +111 -23
- package/dist/cross-session-settings.d.ts +1 -1
- package/dist/deployment-governance.d.ts +12 -1
- package/dist/deployment-governance.js +2 -0
- package/dist/execution-lane-caps.d.ts +30 -2
- package/dist/execution-lane-caps.js +12 -0
- package/dist/file-history-disclosure.d.ts +7 -31
- package/dist/file-history-disclosure.js +12 -0
- package/dist/fleet/fleet-bus.d.ts +5 -1
- package/dist/fleet-client.d.ts +18 -13
- package/dist/fleet-client.js +32 -22
- package/dist/fleet-lease.d.ts +72 -47
- package/dist/fleet-lease.js +66 -14
- package/dist/governance-http-status.d.ts +0 -1
- package/dist/governance-http-status.js +0 -1
- package/dist/hosted-posture.d.ts +6 -17
- package/dist/hosted-posture.js +11 -6
- package/dist/http/admission.d.ts +1 -1
- package/dist/http/admission.js +14 -9
- package/dist/http/approval-sweeps.js +13 -1
- package/dist/http/dispatch.d.ts +18 -0
- package/dist/http/dispatch.js +18 -5
- package/dist/http/durable-run-leg.js +2 -0
- package/dist/http/request-guards.d.ts +25 -0
- package/dist/http/request-guards.js +25 -5
- package/dist/http/resume-legs.d.ts +1 -1
- package/dist/http/resume-legs.js +19 -11
- package/dist/http/route-ctx.d.ts +9 -0
- package/dist/http/route-table.d.ts +15 -4
- package/dist/http/route-table.js +6 -5
- package/dist/http/routes/approvals-assistant.js +0 -1
- package/dist/http/routes/capabilities.js +12 -11
- package/dist/http/routes/config-catalog.js +2 -1
- package/dist/http/routes/diagnostics.js +2 -0
- package/dist/http/routes/fleet.d.ts +3 -2
- package/dist/http/routes/images.js +0 -1
- package/dist/http/routes/runs.js +2 -8
- package/dist/http/routes/session-sync.js +6 -1
- package/dist/http/routes/sessions.js +5 -2
- package/dist/http/routes/side-query.js +8 -1
- package/dist/http/routes/tasks.js +4 -1
- package/dist/http/routes/trace-usage.d.ts +2 -0
- package/dist/http/routes/trace-usage.js +4 -9
- package/dist/http/server.d.ts +29 -8
- package/dist/http/server.js +4 -1
- package/dist/http/session-inherit-env.d.ts +37 -0
- package/dist/http/session-inherit-env.js +6 -0
- package/dist/http/sse-lifecycle.d.ts +1 -1
- package/dist/http/sse-log.d.ts +12 -9
- package/dist/http/sse-log.js +1 -8
- package/dist/http/wire-types.d.ts +19 -28
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/key-resolver.d.ts +74 -19
- package/dist/key-resolver.js +65 -7
- package/dist/leader/leader.d.ts +6 -0
- package/dist/leader/leader.js +2 -1
- package/dist/leader/repair-wire.js +2 -1
- package/dist/leader/wire.d.ts +9 -1
- package/dist/leader/wire.js +10 -6
- package/dist/observability/fail-open.d.ts +9 -1
- package/dist/observability/fail-open.js +9 -1
- package/dist/observability/metrics.js +3 -1
- package/dist/observability/permission-rule-events.d.ts +1 -1
- package/dist/observability/permission-rule-events.js +1 -1
- package/dist/observability/run-terminal.d.ts +82 -9
- package/dist/observability/run-terminal.js +112 -9
- package/dist/observability/secret-env-scrub.js +1 -1
- package/dist/observability/tool-trace.d.ts +2 -0
- package/dist/operator-ask.d.ts +29 -1
- package/dist/operator-ask.js +6 -0
- package/dist/parked-decide.d.ts +14 -1
- package/dist/parked-decide.js +8 -1
- package/dist/plugins/image-bake-store-sql.d.ts +0 -2
- package/dist/plugins/image-bake-store-sql.js +0 -5
- package/dist/plugins/retention-store-sql.js +1 -0
- package/dist/plugins/run-store-sql.d.ts +3 -2
- package/dist/plugins/schema-indexes.js +1 -0
- package/dist/plugins/session-policy-store-sql.d.ts +38 -7
- package/dist/plugins/session-policy-store-sql.js +73 -18
- package/dist/plugins/sql-driver.d.ts +1 -1
- package/dist/plugins/store-backend.d.ts +17 -6
- package/dist/plugins/store-backend.js +16 -5
- package/dist/plugins/tidb-pool.js +5 -4
- package/dist/read-face-posture.d.ts +25 -4
- package/dist/read-face-posture.js +9 -0
- package/dist/request-key-closure.js +2 -2
- package/dist/request-value-closure.d.ts +17 -5
- package/dist/request-value-closure.js +12 -0
- package/dist/router/route-orchestration.d.ts +11 -0
- package/dist/router/route-orchestration.js +7 -6
- package/dist/rules-consent.js +0 -11
- package/dist/run-local.js +4 -2
- package/dist/runs.d.ts +2 -1
- package/dist/runs.js +4 -4
- package/dist/runtime-caps-resolver.d.ts +4 -10
- package/dist/runtime-governance.d.ts +8 -11
- package/dist/runtime-governance.js +10 -41
- package/dist/sealed-key.d.ts +0 -40
- package/dist/sealed-key.js +0 -20
- package/dist/server-secret-env.d.ts +11 -12
- package/dist/session-policy-wire.d.ts +100 -12
- package/dist/session-policy-wire.js +71 -8
- package/dist/session-titler.d.ts +11 -0
- package/dist/session-titler.js +6 -2
- package/dist/sql-engine-posture.d.ts +2 -2
- package/dist/store-posture.d.ts +53 -0
- package/dist/store-posture.js +32 -0
- package/dist/task-settings.d.ts +34 -45
- package/dist/task-settings.js +46 -25
- package/dist/tool-approval.d.ts +51 -22
- package/dist/tool-approval.js +44 -8
- package/dist/tool-declaration-http-status.d.ts +12 -0
- package/dist/tool-declaration-http-status.js +12 -0
- package/dist/trace/core-keyset-guard.d.ts +7 -7
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +5 -0
- package/dist/trace/project.d.ts +13 -7
- package/dist/trace/project.js +54 -6
- package/dist/trace/redact.d.ts +7 -0
- package/dist/trace/redact.js +153 -61
- package/dist/trace/wire-projection-faces.d.ts +1 -1
- package/dist/trace/wire-projection-faces.js +1 -1
- package/dist/unattended-permission-mode.d.ts +110 -0
- package/dist/unattended-permission-mode.js +65 -0
- package/package.json +3 -3
- package/dist/boot/session-shell-gate-registry.d.ts +0 -39
- package/dist/boot/session-shell-gate-registry.js +0 -21
- package/dist/plugins/memory-session-policy-store.d.ts +0 -46
- package/dist/plugins/memory-session-policy-store.js +0 -52
- package/dist/roster-bound-http-status.d.ts +0 -6
- package/dist/roster-bound-http-status.js +0 -6
package/dist/config.d.ts
CHANGED
|
@@ -32,8 +32,43 @@ export declare function parseUsageWindows(raw: string | undefined): readonly {
|
|
|
32
32
|
maxCostUsd?: number;
|
|
33
33
|
anchor: "first-use" | "rolling";
|
|
34
34
|
}[] | undefined;
|
|
35
|
+
/** [ref] 兼容面全清:退役 env 名墓碑——老名**在场即拒启**带新名指引。静默忽略=部署带着失效配置无感跑;双读=兼容层吸收
|
|
36
|
+
* 下游冒烟。都不要。
|
|
37
|
+
*
|
|
38
|
+
* 🪦 **墓碑是永久的响亮拒,不设到期**(S-588)。一行墓碑就是那句拒启句本身,成本一行;删掉它 = 旧名重新被悄悄忽略。
|
|
39
|
+
* ⇒ 本表只增不减,**唯一的删除条件是这个名字要被赋予新含义**:那一次提交同时删掉墓碑行,并在 MIGRATION 写明旧名改作何用。
|
|
40
|
+
* (与一次性迁移不是一回事:迁移码每次起服都跑、碰真数据,所以有窗、窗关即删码 —— 见 `plugins/one-time-migrations.ts`
|
|
41
|
+
* 的登记表;墓碑不碰数据,只拒启。)
|
|
42
|
+
* A tombstone is a permanent loud refusal with no expiry: deleting a row lets the retired name be silently ignored again,
|
|
43
|
+
* so a row goes only when the name is given a new meaning — in that same commit, with a MIGRATION note.
|
|
44
|
+
*
|
|
45
|
+
* **旧名在场即拒,替代名在不在都一样**:判据只问旧名在不在场,不看替代键设没设、设成什么 ——「新旧两个名字都留着」正是
|
|
46
|
+
* 迁移到一半的部署最常见的形,旧名在那一形里若被静默忽略,运维会以为它还在生效。server 3.0.0 的旧负名(`X_DISABLED` →
|
|
47
|
+
* `X_ENABLED`)同在本表(`replaced` 族:极性相反,照抄旧值过去恰好是反义),与其余墓碑走同一只 {@link rejectRetiredEnvs};
|
|
48
|
+
* 那几个旋钮的读点只读正名(`boolEnv`),不再有第二套「正名缺席才看旧名」的判据(S-588 之前的 `boolEnvWithLegacyNegated`)。
|
|
49
|
+
*
|
|
50
|
+
* 墓碑分三族(A4 两族 + S-753 第三族),每族各说各的尾句 —— 族是**闭集**,渲染是穷举表(加一族不写尾句 = 编译红):
|
|
51
|
+
* · `renamed` = 同一能力换了个键名,值语义逐字不变 ⇒ 「照新名改写即可」;
|
|
52
|
+
* · `replaced` = 有替代键,但**值语义变了**(缺省 / `0` 义 / 计量段 / 极性不同)⇒ 点名新键 + 说清变了什么,**不许**说「语义不变」
|
|
53
|
+
* —— 照抄旧值过去就是另一个意思(S-753:`MODEL_CONNECT_TIMEOUT_MS` → `MODEL_FIRST_BYTE_TIMEOUT_MS`);
|
|
54
|
+
* · `removed` = 能力本身没了,`why` 是「为什么没了」,**没有可 rename 的目标**。
|
|
55
|
+
* 拒句带码 `[config.env_key_retired]`(拒启码,拼法随 `config.*` 家族;与中心凭证两码同形,出现在拒句方括号里)。
|
|
56
|
+
* 空串 = 未设(与 `env()` 同一条约定:compose `${X:-}` 透传的空值不是一次表态),不拒。 */
|
|
57
|
+
export type RetiredEnv = readonly [old: string, to: string, kind: "renamed"] | readonly [old: string, to: string, kind: "replaced", why: string] | readonly [old: string, why: string, kind: "removed"];
|
|
58
|
+
export type RetiredEnvKind = RetiredEnv[2];
|
|
35
59
|
/** 墓碑拒启码(机读;拒句方括号里)。 */
|
|
36
60
|
export declare const RETIRED_ENV_REFUSAL_CODE = "config.env_key_retired";
|
|
61
|
+
export declare const RETIRED_ENVS: readonly RetiredEnv[];
|
|
62
|
+
/** S-885 —— 墓碑表的 wire 投影一行(`GET /v1/config/catalog` 的 `retired[]`):按族定形 —— renamed 只带 `replacement`;replaced 带
|
|
63
|
+
* `replacement` + `reason`(值语义怎么变了);removed 只带 `reason`(能力为什么没了)。 */
|
|
64
|
+
export interface RetiredEnvWire {
|
|
65
|
+
readonly name: string;
|
|
66
|
+
readonly kind: RetiredEnvKind;
|
|
67
|
+
readonly replacement?: string;
|
|
68
|
+
readonly reason?: string;
|
|
69
|
+
}
|
|
70
|
+
/** 墓碑表 → wire 投影(纯数据 ⇒ `build*`)。**同一张表、同序**;族是闭集,穷举 switch(加一族不写这里 ⇒ 编译红)。 */
|
|
71
|
+
export declare function buildRetiredEnvWire(): RetiredEnvWire[];
|
|
37
72
|
/** Numeric env with validation: a non-numeric value FAILS at startup instead of silently becoming `NaN`
|
|
38
73
|
* (which e.g. slips past the `runStaleSec` liveness guard and silently disables the reaper). */
|
|
39
74
|
export declare function numEnv(name: string, fallback: string): number;
|
|
@@ -91,10 +126,23 @@ interface ConfigWarning {
|
|
|
91
126
|
detail?: string;
|
|
92
127
|
}
|
|
93
128
|
export declare function drainConfigWarnings(): ConfigWarning[];
|
|
94
|
-
|
|
129
|
+
/** Boot NOTICES ([ref]④): non-error decisions loadConfig makes that the operator should see once (e.g. the
|
|
130
|
+
* MODEL_PROVIDER inference below). Same pre-logger collection pattern as CONFIG_WARNINGS, but each entry
|
|
131
|
+
* carries its own event name + fields (the warnings drain has one fixed "invalid, using default" message
|
|
132
|
+
* which would misdescribe an inference). main.ts drains + logs right after createLogger.
|
|
133
|
+
*
|
|
134
|
+
* S-176 —— 每条带一个**级别**(闭集两词,排空处穷举 switch)。此前这一格固定按 warn 排空,于是「每次 boot 都成立的
|
|
135
|
+
* 常态事实」(posture 派生的档位)在这里没有位置:进 warn 会让一台一个字节都没配错的单机每次启动报警,另开一个
|
|
136
|
+
* 收集器又是第五条日志族。级别写在**铸点**(不是按事件名在排空处猜):
|
|
137
|
+
* · `warn` —— 运维要看一眼的决定(推断出的 provider、被丢掉的缺省行、no-op 旋钮……):既有的每一条;
|
|
138
|
+
* · `info` —— 常态事实,每次这样配就每次成立,不是异常(与执行车道落点那一行同级)。 */
|
|
139
|
+
type ConfigNoticeLevel = "warn" | "info";
|
|
140
|
+
export interface ConfigNotice {
|
|
141
|
+
level: ConfigNoticeLevel;
|
|
95
142
|
event: string;
|
|
96
143
|
fields: Record<string, string>;
|
|
97
|
-
}
|
|
144
|
+
}
|
|
145
|
+
export declare function drainConfigNotices(): ConfigNotice[];
|
|
98
146
|
/** F10 (codex audit): drain + log BOTH pre-logger diagnostic collectors (S20 soft-knob warnings + [ref]④ boot
|
|
99
147
|
* notices) in one place. EVERY entrypoint that calls loadConfig() then builds a logger must call this right
|
|
100
148
|
* after (main.ts, run-local.ts) — a new entry that forgets it would silently drop operator-facing diagnostics
|
package/dist/config.js
CHANGED
|
@@ -10,14 +10,14 @@ import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
|
|
|
10
10
|
import { isV2ScopeKey, memoryReplicationCoverage } 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, 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";
|
|
13
|
+
import { CONFIG_PROVIDER_WORDS, DB_BACKEND_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
14
|
import { buildWriteProtectionPosture } from "./write-protection.js";
|
|
15
15
|
import { normalizeSshFingerprint } from "./ssh-host-key.js";
|
|
16
|
-
import { readFaceForPosture } from "./read-face-posture.js";
|
|
16
|
+
import { buildReadFacePostureNotice, readFaceForPosture } from "./read-face-posture.js";
|
|
17
17
|
import { THINKING_LEVELS, THINKING_FORMATS, buildModelCompat, isThinkingFormat, isThinkingTier } from "./model-compat.js";
|
|
18
18
|
import {} from "./posture-source.js";
|
|
19
19
|
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
20
|
-
import { REMOTE_EXEC_PROVIDER_WORDS, executionLaneOf } from "./execution-lane-caps.js";
|
|
20
|
+
import { REMOTE_EXEC_PROVIDER_WORDS, executionLaneOf, isSingleMachineDefaultLane } from "./execution-lane-caps.js";
|
|
21
21
|
import { AUTONOMY_MODES } from "./runtime-governance.js";
|
|
22
22
|
import { createCenterCredentialSource, readCenterCredentialDeclaration } from "./center-credential.js";
|
|
23
23
|
import { parseCenterCredentialRenewLeadMs } from "./center-credential-renewer.js";
|
|
@@ -27,6 +27,7 @@ import { modelRouteEndpointRefusal } from "./model-route-endpoint.js";
|
|
|
27
27
|
import { buildDisplaySafeUrl, redactSecrets } from "./trace/redact.js";
|
|
28
28
|
import { allowlistedEnv } from "./server-secret-env.js";
|
|
29
29
|
import { ENV_NAME_ALLOWLIST_KNOBS, ENV_NAME_ALLOWLIST_KNOB_NAMES, describeAllowlistPostures } from "./env-name-allowlist-knobs.js";
|
|
30
|
+
import { parseUnattendedPermissionMode } from "./unattended-permission-mode.js";
|
|
30
31
|
export { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC } from "./config-types.js";
|
|
31
32
|
function csv(name) {
|
|
32
33
|
return (process.env[name] ?? "")
|
|
@@ -105,7 +106,7 @@ function defaultSkillsDir() {
|
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
108
|
export const RETIRED_ENV_REFUSAL_CODE = "config.env_key_retired";
|
|
108
|
-
const RETIRED_ENVS = [
|
|
109
|
+
export const RETIRED_ENVS = [
|
|
109
110
|
["TIDB_HOST", "MYSQL_HOST", "renamed"], ["TIDB_PORT", "MYSQL_PORT", "renamed"], ["TIDB_USER", "MYSQL_USER", "renamed"],
|
|
110
111
|
["TIDB_PASSWORD", "MYSQL_PASSWORD", "renamed"], ["TIDB_DATABASE", "MYSQL_DATABASE", "renamed"], ["TIDB_POOL_SIZE", "MYSQL_POOL_SIZE", "renamed"],
|
|
111
112
|
["CONFIG_CENTER_URL", "SEMA_REGISTRY_URL", "renamed"], ["CONFIG_CENTER_TOKEN", CENTER_CREDENTIAL_ENV.token, "renamed"],
|
|
@@ -135,6 +136,25 @@ const RETIRED_ENVS = [
|
|
|
135
136
|
["HOST_BG_DISABLED", "HOST_BG_ENABLED", "replaced", "negative-name alias retired in server 3.0.0 — unset it; the polarity is inverted: HOST_BG_DISABLED=true is HOST_BG_ENABLED=false (the positive name defaults on)"],
|
|
136
137
|
["HOST_EXEC_SPOOL_DISABLED", "HOST_EXEC_SPOOL_ENABLED", "replaced", "negative-name alias retired in server 3.0.0 — unset it; the polarity is inverted: HOST_EXEC_SPOOL_DISABLED=true is HOST_EXEC_SPOOL_ENABLED=false (the positive name defaults on)"],
|
|
137
138
|
];
|
|
139
|
+
for (const row of RETIRED_ENVS)
|
|
140
|
+
Object.freeze(row);
|
|
141
|
+
Object.freeze(RETIRED_ENVS);
|
|
142
|
+
export function buildRetiredEnvWire() {
|
|
143
|
+
return RETIRED_ENVS.map((row) => {
|
|
144
|
+
switch (row[2]) {
|
|
145
|
+
case "renamed":
|
|
146
|
+
return { name: row[0], kind: row[2], replacement: row[1] };
|
|
147
|
+
case "replaced":
|
|
148
|
+
return { name: row[0], kind: row[2], replacement: row[1], reason: row[3] };
|
|
149
|
+
case "removed":
|
|
150
|
+
return { name: row[0], kind: row[2], reason: row[1] };
|
|
151
|
+
default: {
|
|
152
|
+
const unhandled = row;
|
|
153
|
+
throw new Error(`unhandled retired-env kind: ${String(unhandled[2])}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
138
158
|
const RETIRED_ENV_SENTENCE = {
|
|
139
159
|
renamed: (hits) => `${hits.map(([old, to]) => `${old} → use ${to}`).join("; ")} — rename the env key(s); the value semantics are unchanged`,
|
|
140
160
|
replaced: (hits) => `${hits.map(([old, to, , why]) => `${old} → ${to} (${why})`).join("; ")} — set the replacement deliberately: ` +
|
|
@@ -205,11 +225,11 @@ export function drainConfigNotices() {
|
|
|
205
225
|
return CONFIG_NOTICES.splice(0, CONFIG_NOTICES.length);
|
|
206
226
|
}
|
|
207
227
|
let EXEC_LANE_NOTICE;
|
|
208
|
-
function buildExecLaneNotice(remoteExec
|
|
209
|
-
const
|
|
228
|
+
function buildExecLaneNotice(remoteExec) {
|
|
229
|
+
const lane = executionLaneOf(remoteExec?.provider);
|
|
210
230
|
return {
|
|
211
|
-
lane
|
|
212
|
-
source:
|
|
231
|
+
lane,
|
|
232
|
+
source: lane === "in-process" ? "default-unset" : isSingleMachineDefaultLane(remoteExec) ? "default-local-provider" : "explicit-env",
|
|
213
233
|
};
|
|
214
234
|
}
|
|
215
235
|
function parseConfigProvider() {
|
|
@@ -251,8 +271,20 @@ export function logConfigDiagnostics(logger) {
|
|
|
251
271
|
}
|
|
252
272
|
}
|
|
253
273
|
}
|
|
254
|
-
for (const n of drainConfigNotices())
|
|
255
|
-
|
|
274
|
+
for (const n of drainConfigNotices()) {
|
|
275
|
+
switch (n.level) {
|
|
276
|
+
case "warn":
|
|
277
|
+
logger.warn(n.event, n.fields);
|
|
278
|
+
break;
|
|
279
|
+
case "info":
|
|
280
|
+
logger.info?.(n.event, n.fields);
|
|
281
|
+
break;
|
|
282
|
+
default: {
|
|
283
|
+
const unreachable = n.level;
|
|
284
|
+
throw new Error(`unhandled config notice level: ${String(unreachable)}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
256
288
|
const laneNotice = EXEC_LANE_NOTICE;
|
|
257
289
|
EXEC_LANE_NOTICE = undefined;
|
|
258
290
|
if (laneNotice && logger.info)
|
|
@@ -531,6 +563,7 @@ export function parseEnvNameAllowlist(knob, singleUserTurnkey) {
|
|
|
531
563
|
return [];
|
|
532
564
|
case "honored":
|
|
533
565
|
CONFIG_NOTICES.push({
|
|
566
|
+
level: "warn",
|
|
534
567
|
event: "config_env_forwarded_multi_tenant",
|
|
535
568
|
fields: {
|
|
536
569
|
env: knob,
|
|
@@ -634,6 +667,7 @@ function parseWriteProtectedTableEnv(extraRaw, replaceRaw) {
|
|
|
634
667
|
const dropped = posture.droppedDefaultRows ?? [];
|
|
635
668
|
CONFIG_NOTICES.push(posture.source === "off"
|
|
636
669
|
? {
|
|
670
|
+
level: "warn",
|
|
637
671
|
event: "write_protection_table_disabled",
|
|
638
672
|
fields: {
|
|
639
673
|
env: "WRITE_PROTECTED_TABLE_REPLACE",
|
|
@@ -644,6 +678,7 @@ function parseWriteProtectedTableEnv(extraRaw, replaceRaw) {
|
|
|
644
678
|
},
|
|
645
679
|
}
|
|
646
680
|
: {
|
|
681
|
+
level: "warn",
|
|
647
682
|
event: "write_protection_table_replaced",
|
|
648
683
|
fields: {
|
|
649
684
|
env: "WRITE_PROTECTED_TABLE_REPLACE",
|
|
@@ -795,6 +830,7 @@ function noticeAutoCompactAboveWindow(slot, envName, m) {
|
|
|
795
830
|
if (act === undefined || window <= 0 || act <= window)
|
|
796
831
|
return;
|
|
797
832
|
CONFIG_NOTICES.push({
|
|
833
|
+
level: "warn",
|
|
798
834
|
event: "auto_compact_tokens_above_context_window",
|
|
799
835
|
fields: {
|
|
800
836
|
slot,
|
|
@@ -806,6 +842,18 @@ function noticeAutoCompactAboveWindow(slot, envName, m) {
|
|
|
806
842
|
},
|
|
807
843
|
});
|
|
808
844
|
}
|
|
845
|
+
function buildUnusedCoordinatesRefusal(keys, auto) {
|
|
846
|
+
const engine = keys.length === 1 ? (keys[0] === "PG_HOST" ? "pg" : "mysql") : "mysql|pg";
|
|
847
|
+
const probedHostKey = auto?.autoProbes === "pg" ? "PG_HOST" : "MYSQL_HOST";
|
|
848
|
+
const why = auto !== undefined
|
|
849
|
+
? `SESSION_BACKEND=auto probes the ${auto.autoProbes} engine (DB_BACKEND ${process.env.DB_BACKEND ? "names it" : "unset ⇒ mysql"}), whose host key ${probedHostKey} is not set`
|
|
850
|
+
: "no durable session store is selected (neither DB_BACKEND=mysql|pg nor SESSION_BACKEND=mysql|auto), so the store would run IN MEMORY";
|
|
851
|
+
return (`${keys.join(" and ")} ${keys.length === 1 ? "is" : "are"} set but the current configuration never uses ${keys.length === 1 ? "it" : "them"}: ${why}. ` +
|
|
852
|
+
`Refusing to boot rather than running in memory silently. ` +
|
|
853
|
+
`To persist: DB_BACKEND=${engine} (or SESSION_BACKEND=mysql for the MySQL-protocol engine); ` +
|
|
854
|
+
`to run in memory: remove the coordinates, or set DB_BACKEND=memory; ` +
|
|
855
|
+
`for best effort (persist while the store is reachable, memory otherwise): SESSION_BACKEND=auto with the engine pointed right (DB_BACKEND=pg with PG_HOST, DB_BACKEND=mysql with MYSQL_HOST).`);
|
|
856
|
+
}
|
|
809
857
|
function parseStoreDomain(ctx) {
|
|
810
858
|
const { requirePrincipal } = ctx;
|
|
811
859
|
const dbBackendSet = !!process.env.DB_BACKEND;
|
|
@@ -813,9 +861,10 @@ function parseStoreDomain(ctx) {
|
|
|
813
861
|
!!(process.env.MYSQL_HOST || process.env.PG_HOST);
|
|
814
862
|
const bareBoot = !dbBackendSet && !sqlSignal;
|
|
815
863
|
const multiTenantBareBoot = bareBoot && requirePrincipal;
|
|
816
|
-
const dbBackend = enumEnv("DB_BACKEND", bareBoot ? (multiTenantBareBoot ? "memory" : "local") : "mysql",
|
|
864
|
+
const dbBackend = enumEnv("DB_BACKEND", bareBoot ? (multiTenantBareBoot ? "memory" : "local") : "mysql", DB_BACKEND_WORDS);
|
|
817
865
|
if (multiTenantBareBoot) {
|
|
818
866
|
CONFIG_NOTICES.push({
|
|
867
|
+
level: "warn",
|
|
819
868
|
event: "db_backend_default_memory_multi_tenant",
|
|
820
869
|
fields: {
|
|
821
870
|
note: "REQUIRE_PRINCIPAL=true with no DB_BACKEND — the single-user default (local file store) is incompatible with multi-tenant (no tenant isolation in the file owner map), so this deployment runs IN-MEMORY (runs/sessions lost on restart, durable runs 501). Configure DB_BACKEND=mysql|pg for a durable multi-tenant deployment.",
|
|
@@ -829,6 +878,11 @@ function parseStoreDomain(ctx) {
|
|
|
829
878
|
const dbHostSet = dbBackend === "pg" ? !!process.env.PG_HOST : !!process.env.MYSQL_HOST;
|
|
830
879
|
const needsTidb = sessionBackend === "tidb" || (sessionBackend === "auto" && dbHostSet);
|
|
831
880
|
const needsDb = needsTidb;
|
|
881
|
+
const sqlCoordinateKeys = [...(process.env.MYSQL_HOST ? ["MYSQL_HOST"] : []), ...(process.env.PG_HOST ? ["PG_HOST"] : [])];
|
|
882
|
+
const coordinatesUnusedByDeclaration = process.env.DB_BACKEND === "memory" || process.env.DB_BACKEND === "local" || process.env.SESSION_BACKEND === "memory";
|
|
883
|
+
if (sqlCoordinateKeys.length > 0 && !needsDb && !coordinatesUnusedByDeclaration) {
|
|
884
|
+
throw new Error(buildUnusedCoordinatesRefusal(sqlCoordinateKeys, sessionBackend === "auto" ? { autoProbes: dbBackend } : undefined));
|
|
885
|
+
}
|
|
832
886
|
return {
|
|
833
887
|
sessionBackend,
|
|
834
888
|
sessionCacheTtlSec: numEnv("SESSION_CACHE_TTL_SEC", "300"),
|
|
@@ -922,6 +976,7 @@ function parseModelDomain() {
|
|
|
922
976
|
return explicitProvider;
|
|
923
977
|
if (inferredAnthropicBaseUrl && inferredAnthropicCred) {
|
|
924
978
|
CONFIG_NOTICES.push({
|
|
979
|
+
level: "warn",
|
|
925
980
|
event: "model_provider_inferred",
|
|
926
981
|
fields: {
|
|
927
982
|
provider: "anthropic",
|
|
@@ -967,6 +1022,7 @@ function parseModelDomain() {
|
|
|
967
1022
|
const cheapId = process.env.MODEL_CHEAP_ID;
|
|
968
1023
|
if (cheapId && modelId === "") {
|
|
969
1024
|
CONFIG_NOTICES.push({
|
|
1025
|
+
level: "warn",
|
|
970
1026
|
event: "model_cheap_slot_without_main",
|
|
971
1027
|
fields: { hint: "MODEL_CHEAP_ID is set but MODEL_ID is not — the env cheap slot derives from the env main model, so it is not minted; the catalog (config.d/models.json or the config center) serves the roles. Set MODEL_ID to use the env slots, or unset MODEL_CHEAP_ID to silence this line." },
|
|
972
1028
|
});
|
|
@@ -974,6 +1030,7 @@ function parseModelDomain() {
|
|
|
974
1030
|
const cheapIsMain = cheapId !== undefined && cheapId !== "" && modelId !== "" && cheapId === modelId;
|
|
975
1031
|
if (cheapIsMain) {
|
|
976
1032
|
CONFIG_NOTICES.push({
|
|
1033
|
+
level: "warn",
|
|
977
1034
|
event: "config_env_entry_cheap_same_as_main",
|
|
978
1035
|
fields: {
|
|
979
1036
|
env: "MODEL_CHEAP_ID",
|
|
@@ -1007,6 +1064,7 @@ function parseModelDomain() {
|
|
|
1007
1064
|
const mainAct = optFinitePositiveEnv("MODEL_AUTO_COMPACT_TOKENS");
|
|
1008
1065
|
if (applyAutoCompactWindow(model, mainAct) === "removed-1m" && modelId !== "") {
|
|
1009
1066
|
CONFIG_NOTICES.push({
|
|
1067
|
+
level: "warn",
|
|
1010
1068
|
event: "auto_compact_window_not_derived_1m",
|
|
1011
1069
|
fields: {
|
|
1012
1070
|
slot: "main",
|
|
@@ -1022,6 +1080,7 @@ function parseModelDomain() {
|
|
|
1022
1080
|
if (applyAutoCompactWindow(cheapModel, cheapAct) === "removed-1m") {
|
|
1023
1081
|
const inherited = process.env.MODEL_CHEAP_CONTEXT_WINDOW === undefined || process.env.MODEL_CHEAP_CONTEXT_WINDOW === "";
|
|
1024
1082
|
CONFIG_NOTICES.push({
|
|
1083
|
+
level: "warn",
|
|
1025
1084
|
event: "auto_compact_window_not_derived_1m",
|
|
1026
1085
|
fields: {
|
|
1027
1086
|
slot: "cheap",
|
|
@@ -1038,6 +1097,7 @@ function parseModelDomain() {
|
|
|
1038
1097
|
const gatewayApiKey = process.env.MODEL_API_KEY;
|
|
1039
1098
|
if (gatewayBaseUrl === undefined && gatewayApiKey !== undefined && gatewayApiKey !== "") {
|
|
1040
1099
|
CONFIG_NOTICES.push({
|
|
1100
|
+
level: "warn",
|
|
1041
1101
|
event: "model_gateway_key_unpaired",
|
|
1042
1102
|
fields: {
|
|
1043
1103
|
hint: "MODEL_API_KEY is set but MODEL_GATEWAY_BASEURL is not — MODEL_API_KEY is the gateway's credential and pairs only with the gateway's own root, so with no gateway it is NOT loaded and is sent to no route (a model with its own baseUrl authenticates with its own per-model key). Set MODEL_GATEWAY_BASEURL to use it, or unset MODEL_API_KEY to silence this line.",
|
|
@@ -1267,6 +1327,8 @@ function unattendedApprovalPolicyEnv() {
|
|
|
1267
1327
|
function parseApprovalDomain(ctx) {
|
|
1268
1328
|
const { postureOn, singleUserTurnkey, infraReady } = ctx;
|
|
1269
1329
|
const unattendedPolicy = unattendedApprovalPolicyEnv();
|
|
1330
|
+
const unattendedMode = parseUnattendedPermissionMode(process.env.UNATTENDED_PERMISSION_MODE);
|
|
1331
|
+
CONFIG_NOTICES.push({ level: "info", event: "unattended_permission_mode", fields: { value: unattendedMode.value, source: unattendedMode.source } });
|
|
1270
1332
|
const streamAskWindowMsSource = postureSourceOfNumEnv(process.env.STREAM_ASK_WINDOW_MS, singleUserTurnkey);
|
|
1271
1333
|
const toolApprovalEnabled = postureOn("TOOL_APPROVAL_ENABLED");
|
|
1272
1334
|
let streamAskWindowMsEffectiveSource = streamAskWindowMsSource;
|
|
@@ -1312,24 +1374,31 @@ function parseApprovalDomain(ctx) {
|
|
|
1312
1374
|
...((decision) => (decision === undefined ? {} : { writeProtectedPaths: decision }))(parseWriteProtectedTableEnv(process.env.WRITE_PROTECTED_EXTRA, process.env.WRITE_PROTECTED_TABLE_REPLACE)),
|
|
1313
1375
|
...((raw) => {
|
|
1314
1376
|
const trimmed = (raw ?? "").trim();
|
|
1315
|
-
if (trimmed === "always"
|
|
1377
|
+
if (trimmed === "always")
|
|
1316
1378
|
return { manualModeShellGate: trimmed };
|
|
1317
1379
|
if (trimmed === "" || trimmed === "off") {
|
|
1318
1380
|
if (trimmed === "off") {
|
|
1319
1381
|
CONFIG_NOTICES.push({
|
|
1382
|
+
level: "warn",
|
|
1320
1383
|
event: "manual_mode_shell_gate_off_is_noop",
|
|
1321
1384
|
fields: {
|
|
1322
1385
|
env: "MANUAL_MODE_SHELL_GATE",
|
|
1323
1386
|
raw: trimmed,
|
|
1324
1387
|
effect: "accepted as a no-op (normalized to the knob being absent) — this knob has a TIGHTEN half only, so `off` cannot relax anything; " +
|
|
1325
|
-
"the effective shell gate still comes from AUTONOMY and the
|
|
1388
|
+
"the effective shell gate still comes from AUTONOMY and the engine's permission-mode arbitration. NOT symmetric with SENSITIVE_WRITE_PATTERNS=off, which really does clear a server-owned default set",
|
|
1326
1389
|
},
|
|
1327
1390
|
});
|
|
1328
1391
|
}
|
|
1329
1392
|
return {};
|
|
1330
1393
|
}
|
|
1331
|
-
|
|
1332
|
-
`
|
|
1394
|
+
if (trimmed === "classify") {
|
|
1395
|
+
throw new Error(`env MANUAL_MODE_SHELL_GATE="classify" is retired (server 7.106.0 / @sema-agent/core 7.34.0): the engine now puts every full-shell command it cannot prove read-only to its question under EVERY permission mode — ` +
|
|
1396
|
+
`that tier is the default and needs no knob. What "classify" added on top was a question under a bypassPermissions request, which the engine now releases by mode. ` +
|
|
1397
|
+
`Use "always" to keep EVERY shell call behind an approval under every mode (bypassPermissions included), or unset it / "off" to rely on the engine's mode arbitration. ` +
|
|
1398
|
+
`Refusing to boot rather than accepting it as a no-op: that would silently drop the half the operator configured it for`);
|
|
1399
|
+
}
|
|
1400
|
+
throw new Error(`env MANUAL_MODE_SHELL_GATE="${trimmed.slice(0, 120)}" is not a recognized value — use "always" (every shell call is gated) or "off"/unset (this knob contributes nothing). ` +
|
|
1401
|
+
`The words are matched EXACTLY (lowercase, surrounding whitespace trimmed): "OFF"/"Always" land here too, deliberately — a closed word table with no case-folding is what keeps "it looked accepted" from meaning "it did nothing". ` +
|
|
1333
1402
|
`Refusing to boot rather than treating a misspelled value as "unset": that silently ran the deployment with NO shell gate while the operator believed one was configured`);
|
|
1334
1403
|
})(process.env.MANUAL_MODE_SHELL_GATE),
|
|
1335
1404
|
permissionsDisableAutoMode: boolEnv("PERMISSIONS_DISABLE_AUTO_MODE", false),
|
|
@@ -1360,6 +1429,7 @@ function parseApprovalDomain(ctx) {
|
|
|
1360
1429
|
streamAskWindowMsSource: streamAskWindowMsEffectiveSource,
|
|
1361
1430
|
durableApprovalSource: postureSourceOfKnob("DURABLE_APPROVAL"),
|
|
1362
1431
|
unattendedApprovalPolicy: unattendedPolicy,
|
|
1432
|
+
unattendedPermissionMode: unattendedMode.value,
|
|
1363
1433
|
approvalTimeoutSec: numEnv("APPROVAL_TIMEOUT_SEC", "0"),
|
|
1364
1434
|
};
|
|
1365
1435
|
}
|
|
@@ -1614,6 +1684,7 @@ function parseMemoryDomain(ctx) {
|
|
|
1614
1684
|
const replication = memoryReplicationCoverage(memorySync !== undefined ? { memorySync } : {});
|
|
1615
1685
|
if (replication.kind === "scopes" && memoryEngineEnabled && !requirePrincipal && memoryScope !== undefined) {
|
|
1616
1686
|
CONFIG_NOTICES.push({
|
|
1687
|
+
level: "warn",
|
|
1617
1688
|
event: "memory_project_scope_yields_to_sync",
|
|
1618
1689
|
fields: {
|
|
1619
1690
|
replicatedScopes: replication.scopes.join(","),
|
|
@@ -1796,11 +1867,15 @@ function parseOrchestrationDomain(ctx) {
|
|
|
1796
1867
|
}
|
|
1797
1868
|
}
|
|
1798
1869
|
}
|
|
1870
|
+
const readFaceFacts = process.env.READ_FACE !== undefined
|
|
1871
|
+
? { readFace: enumEnv("READ_FACE", "roots", ["open", "roots"]), readFaceSource: "env" }
|
|
1872
|
+
: readFaceForPosture(singleUserTurnkey);
|
|
1873
|
+
const readFaceNotice = buildReadFacePostureNotice(readFaceFacts);
|
|
1874
|
+
if (readFaceNotice !== undefined)
|
|
1875
|
+
CONFIG_NOTICES.push({ level: "info", ...readFaceNotice });
|
|
1799
1876
|
return {
|
|
1800
1877
|
requireExecutionLane: boolEnv("REQUIRE_EXECUTION_LANE", false),
|
|
1801
|
-
...
|
|
1802
|
-
? { readFace: enumEnv("READ_FACE", "roots", ["open", "roots"]), readFaceSource: "env" }
|
|
1803
|
-
: readFaceForPosture(singleUserTurnkey)),
|
|
1878
|
+
...readFaceFacts,
|
|
1804
1879
|
...optionalEnumKey("taskWriteFaceEscalation", "TASK_WRITE_FACE_ESCALATION", process.env.TASK_WRITE_FACE_ESCALATION, TASK_WRITE_FACE_ESCALATIONS),
|
|
1805
1880
|
...(process.env.READ_DENY_PATTERNS !== undefined ? { readDenyPatterns: parseReadDenyPatternsEnv(process.env.READ_DENY_PATTERNS) } : {}),
|
|
1806
1881
|
...(() => {
|
|
@@ -1920,7 +1995,7 @@ function parseOrchestrationDomain(ctx) {
|
|
|
1920
1995
|
}
|
|
1921
1996
|
:
|
|
1922
1997
|
ctx.configProvider === "local"
|
|
1923
|
-
? { provider: "host" }
|
|
1998
|
+
? { provider: "host", singleMachineDefault: true }
|
|
1924
1999
|
: undefined,
|
|
1925
2000
|
worktreeIsolation: boolEnv("WORKTREE_ISOLATION_ENABLED", false) && process.env.WORKTREE_REPO_ROOT
|
|
1926
2001
|
? {
|
|
@@ -2037,7 +2112,13 @@ function parseLimitsHttpDomain() {
|
|
|
2037
2112
|
costQuotaWindowSec: numEnv("COST_QUOTA_WINDOW_SEC", "86400"),
|
|
2038
2113
|
reapIntervalSec: numEnv("REAP_INTERVAL_SEC", "60"),
|
|
2039
2114
|
runStaleSec: numEnv("REAP_RUN_STALE_SEC", "120"),
|
|
2040
|
-
toolResultTtlSec:
|
|
2115
|
+
toolResultTtlSec: (() => {
|
|
2116
|
+
const ttl = numEnv("TOOL_RESULT_TTL_SEC", "86400");
|
|
2117
|
+
if (ttl <= 0) {
|
|
2118
|
+
throw new Error(`env TOOL_RESULT_TTL_SEC=${ttl} must be > 0 — it is the age (seconds) after which an offloaded tool result is reaped, so 0 or less puts the cutoff at or after now and every reaper tick would delete ALL of them; there is no "off": set a larger value to keep results longer (the config center refuses the same value)`);
|
|
2119
|
+
}
|
|
2120
|
+
return ttl;
|
|
2121
|
+
})(),
|
|
2041
2122
|
};
|
|
2042
2123
|
}
|
|
2043
2124
|
function parseObservabilityDomain() {
|
|
@@ -2101,11 +2182,18 @@ function parseIntegrationsDomain(ctx) {
|
|
|
2101
2182
|
skillsDir: env("SKILLS_DIR", defaultSkillsDir()),
|
|
2102
2183
|
centerCredentialRenewLeadMs: parseCenterCredentialRenewLeadMs(process.env.CENTER_CREDENTIAL_RENEW_LEAD_MS),
|
|
2103
2184
|
configCenter: process.env.SEMA_REGISTRY_URL ? parseConfigCenter(env("SEMA_REGISTRY_URL"), requirePrincipal) : undefined,
|
|
2185
|
+
fleetLeaseFailClosed: parseFleetLeaseFailClosed(process.env.FLEET_LEASE_FAIL_CLOSED, requirePrincipal),
|
|
2104
2186
|
...(configProvider !== undefined ? { configProvider } : {}),
|
|
2105
2187
|
configLocalDir: process.env.CONFIG_LOCAL_DIR || undefined,
|
|
2106
2188
|
a2aServe: parseA2aServe(),
|
|
2107
2189
|
};
|
|
2108
2190
|
}
|
|
2191
|
+
function parseFleetLeaseFailClosed(raw, requirePrincipal) {
|
|
2192
|
+
const explicit = parseBoolWord("FLEET_LEASE_FAIL_CLOSED", raw);
|
|
2193
|
+
const value = explicit ?? requirePrincipal;
|
|
2194
|
+
registerKnob({ name: "FLEET_LEASE_FAIL_CLOSED", polarity: "posture", value, source: explicit !== undefined ? "env" : requirePrincipal ? "posture" : "default" });
|
|
2195
|
+
return value;
|
|
2196
|
+
}
|
|
2109
2197
|
function parseConfigCenter(baseUrl, requirePrincipal) {
|
|
2110
2198
|
const worker = process.env.SEMA_REGISTRY_WORKER || undefined;
|
|
2111
2199
|
const declared = readCenterCredentialDeclaration();
|
|
@@ -2211,7 +2299,7 @@ const MODEL_PLANE_GROUP_KEYS = [
|
|
|
2211
2299
|
const APPROVAL_GROUP_KEYS = [
|
|
2212
2300
|
"approvalRequire", "approvalDeny", "approvalTimeoutSec", "approvalAutoBudget", "approvalNeverAuto",
|
|
2213
2301
|
"approvalHmacKeys", "durableApproval", "durableApprovalSource", "streamAskWindowMsSource", "directApprovalDoor", "directDoorActive", "resourceSuspend",
|
|
2214
|
-
"resourceSuspendTtlSec", "resourceSuspendTotalTokens", "resourceSuspendTotalBudgetUsd", "resourceSuspendMaxSlices", "askQuestionEnabled", "questionThrottle", "toolApprovalEnabled", "permissionRulesEnabled", "permissionRulesEnabledExplicit", "streamAskWindowMarginMs", "streamApproval", "unattendedApprovalPolicy", "mcpElicitation",
|
|
2302
|
+
"resourceSuspendTtlSec", "resourceSuspendTotalTokens", "resourceSuspendTotalBudgetUsd", "resourceSuspendMaxSlices", "askQuestionEnabled", "questionThrottle", "toolApprovalEnabled", "permissionRulesEnabled", "permissionRulesEnabledExplicit", "streamAskWindowMarginMs", "streamApproval", "unattendedApprovalPolicy", "unattendedPermissionMode", "mcpElicitation",
|
|
2215
2303
|
"sensitiveWritePatterns", "writeProtectedPaths", "manualModeShellGate", "permissionsDisableAutoMode", "autoModeClassifierFallback", "autoModeClassifierTimeoutMs", "crossSessionInbound", "crossSessionDialogExpiry",
|
|
2216
2304
|
];
|
|
2217
2305
|
const MEMORY_GROUP_KEYS = [
|
|
@@ -2245,7 +2333,7 @@ const LIMITS_HTTP_GROUP_KEYS = [
|
|
|
2245
2333
|
const OBSERVABILITY_GROUP_KEYS = ["metricsToken", "traceToken", "toolTrace", "traceThinking", "logLevel", "otel"];
|
|
2246
2334
|
const INTEGRATIONS_GROUP_KEYS = [
|
|
2247
2335
|
"pluginsAllowHosts", "mcpServers", "a2aPeers", "a2aServe", "configBootFetchBudgetMs", "gitApiBaseUrl", "gitApiToken", "gitApiKind", "defaultScenario",
|
|
2248
|
-
"skillsDir", "centerCredentialRenewLeadMs", "configCenter", "configProvider", "configLocalDir",
|
|
2336
|
+
"skillsDir", "centerCredentialRenewLeadMs", "configCenter", "fleetLeaseFailClosed", "configProvider", "configLocalDir",
|
|
2249
2337
|
];
|
|
2250
2338
|
export const CONFIG_GROUP_KEYS = {
|
|
2251
2339
|
store: STORE_GROUP_KEYS,
|
|
@@ -2370,7 +2458,7 @@ export function loadConfig() {
|
|
|
2370
2458
|
throw new Error(refusal);
|
|
2371
2459
|
}
|
|
2372
2460
|
}
|
|
2373
|
-
EXEC_LANE_NOTICE = buildExecLaneNotice(orchestration.remoteExec
|
|
2461
|
+
EXEC_LANE_NOTICE = buildExecLaneNotice(orchestration.remoteExec);
|
|
2374
2462
|
return attachConfigGroups({
|
|
2375
2463
|
...store,
|
|
2376
2464
|
...modelPlane,
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* 而 `crossSessionInbound` 在 CC 里是**每会话**的 settings 值。本服务是「一个 Runner × 多会话」的宿主:
|
|
20
20
|
* 把某一次提交的 `body.settings.crossSessionInbound` 塞进部署级座位,等于让 A 会话的设置去治理 B 会话的
|
|
21
21
|
* 排水——比不接线更糟,且是静默的。可选的补法只有两条,都不在本件射程内:
|
|
22
|
-
* (a) 引擎开一个**每任务**座(
|
|
22
|
+
* (a) 引擎开一个**每任务**座(形如 `TaskSpec.permissionMode` 那样的每任务座)——上游诉求,已留档;
|
|
23
23
|
* (b) 本仓用 per-run 异步上下文托住层,并保证**每一条** run 腿都建立该上下文——漏一条腿 =
|
|
24
24
|
* 用户设的 `refuse` 静默失效 = 安全轴 fail-open,不许半接。
|
|
25
25
|
* 在 (a)/(b) 落地之前,提交面对这两个键**明拒**(`TASK_SETTINGS_UNSUPPORTED_KEYS`,400 体 `unsupportedKeys`
|
|
@@ -179,7 +179,18 @@ export declare function createDurableQuestionGate(live: LiveQuestionFace | undef
|
|
|
179
179
|
* 无 root/cwd 声明 ⇒ 家族 undefined ⇒ 只认 `/`-rooted,与引擎同一律)。
|
|
180
180
|
*/
|
|
181
181
|
type WriteTargetForm = "unstamped-relative" | "absolute-or-stamped";
|
|
182
|
-
/**
|
|
182
|
+
/**
|
|
183
|
+
* 只在 `form` 那一类写目标上说话,其余显式 allow(让位);非写调用与提不出目标的调用原样交给 policy(它自己判 allow)。
|
|
184
|
+
*
|
|
185
|
+
* 🔴 **S-877(core 7.35.0 #1200):本包装不是 `wrapToolPolicy`,它自报成员**。core 的 wrap 契约是「包装至少拒内层所拒」,
|
|
186
|
+
* 而本包装**放行**内层会拒的另一形写目标 ⇒ 按契约它是一只自己的策略,内层的成员不能照搬(core wire 单点名这一只):
|
|
187
|
+
* · `writeTargetGuard`(守卫集策略自带,shell 写站问「这个绝对目标守不守」)—— 只在本层的形**覆盖守卫的入参形**时带上
|
|
188
|
+
* ({@link WRITE_TARGET_GUARD_DOMAIN}):真身层带;词法层不带(它的 env 对绝对形报错 ⇒ core 的「判不了就守」会把每一次
|
|
189
|
+
* shell 写都判成受守,正与两层射程互补的原设计相反)。这不是给某一形开的特例,是同一条「成员描述它守得住的那部分」的读法;
|
|
190
|
+
* · `projection` / `pathDenyRules` / `nameSets` —— 守卫集策略不带,本包装也不报(报了就是替它声称一张它没有的拒表)。
|
|
191
|
+
* 修前(core 7.33.x)这里是一只 `{ check }` 字面量,7.34.0 起那只字面量把真身层的守卫剥掉了 —— 沙箱 lane 的 shell 写站
|
|
192
|
+
* 因此看不见部署守卫集(`cp x .env` 在 acceptEdits 下被引擎 place 并答掉)。
|
|
193
|
+
*/
|
|
183
194
|
export declare function scopedToWriteTargetForm(policy: ToolPolicy, form: WriteTargetForm): ToolPolicy;
|
|
184
195
|
export declare class RelativeTargetLexicalEnv extends StubExecutionEnv {
|
|
185
196
|
/** 相对形 → `/<词法归一>`;绝对形 / 空串 / 含 NUL 一律报错(fail-closed 守底:`relativeTargetsOnly` 已把这些形挡在外面,
|
|
@@ -26,6 +26,7 @@ function writeTargetFormOf(req) {
|
|
|
26
26
|
return "absolute-or-stamped";
|
|
27
27
|
return "unstamped-relative";
|
|
28
28
|
}
|
|
29
|
+
const WRITE_TARGET_GUARD_DOMAIN = "absolute-or-stamped";
|
|
29
30
|
export function scopedToWriteTargetForm(policy, form) {
|
|
30
31
|
return {
|
|
31
32
|
async check(req, signal) {
|
|
@@ -34,6 +35,7 @@ export function scopedToWriteTargetForm(policy, form) {
|
|
|
34
35
|
return { action: "allow" };
|
|
35
36
|
return policy.check(req, signal);
|
|
36
37
|
},
|
|
38
|
+
...(form === WRITE_TARGET_GUARD_DOMAIN && policy.writeTargetGuard !== undefined ? { writeTargetGuard: policy.writeTargetGuard } : {}),
|
|
37
39
|
};
|
|
38
40
|
}
|
|
39
41
|
export class RelativeTargetLexicalEnv extends StubExecutionEnv {
|
|
@@ -31,8 +31,19 @@ export interface ExecutionLaneCaps {
|
|
|
31
31
|
/** SendUserFile 的**沙箱直传腿**(host/in-process 的本机 fs 源是另一条腿,不是本位)。
|
|
32
32
|
* 判别式:`capabilities/sandbox-file-send.ts` 的 `sandboxSendLaneEnabled`。 */
|
|
33
33
|
readonly sandboxSendUserFile: LaneCapability;
|
|
34
|
-
/**
|
|
35
|
-
|
|
34
|
+
/**
|
|
35
|
+
* OS 级隔离边界(手跑在容器 / microVM / Kata pod 里,而不是一台真机上)。
|
|
36
|
+
* 🔴 S-281 起**本位即判别式**:全仓读它的唯一布尔口是 {@link laneIsolated}(boot `remote_exec_enabled.isolated` 与
|
|
37
|
+
* `remote_exec_real_system` 那一行、`config.d/remote-exec.json` 抢占披露的「隔离降级」判、wire `executionLane.isolated`
|
|
38
|
+
* + 诊断面同位、那一行 note 列的沙箱出路 —— 改前 boot 另手写一份 `e2b || k8s || local-docker`,与本表两处各算)。
|
|
39
|
+
* 本位的**独立作者** = 各 adapter 对 core 的 `capabilities.isolation` 声明:对账测试拿 core 自己的严格读器 `isIsolated`
|
|
40
|
+
* 问每条车道的**真 adapter 实例**,与本表、与 boot 行三方对拍。
|
|
41
|
+
* 型上**没有** `single-user-only` 这一档(隔离是车道的结构事实,不随租户面变)⇒ {@link laneIsolated} 的布尔化是穷举、无兜底。
|
|
42
|
+
* ⚠️ 口径 = **OS 级边界**(容器即算:共享宿主内核的 `local-docker`、runc 上的 k8s pod 都答 supported),**不是**「独立内核」强度。
|
|
43
|
+
* 另一根轴 `router/route-orchestration.ts` 的 `isIsolatedExecEnv`(A 值路由,只认 e2b 与 kata / gvisor 运行时,runc / local-docker
|
|
44
|
+
* fail-closed 判否)问的是 VM 级强度 —— 两个不同的问题,不是本位的第二份;收成一只会让其中一边答错(S-281 收车档登记)。
|
|
45
|
+
*/
|
|
46
|
+
readonly isolation: Exclude<LaneCapability, "single-user-only">;
|
|
36
47
|
/** 可挂起(k8s 以配了 `s3Snapshot` 为前提 —— 「结构上能不能」的口径)。判别式:同上的 `suspendable`。 */
|
|
37
48
|
readonly suspendable: LaneCapability;
|
|
38
49
|
/** `/tmp/scratchpad/<sessionId>` 远端约定。判别式:`plugins/remote-scratchpad.ts` 的 `isRemoteScratchpadLane`。 */
|
|
@@ -155,6 +166,23 @@ export declare function executionLaneOf(provider: string | undefined): Execution
|
|
|
155
166
|
* ③ `approval.ts` 的 `buildNoResponderBootWarns` —— 写门的 ask 只在「有手写本机盘」时才会发生。
|
|
156
167
|
*/
|
|
157
168
|
export declare function handsOnWorkerFilePlane(lane: ExecutionLane): boolean;
|
|
169
|
+
/**
|
|
170
|
+
* S-281 —— 「这条车道的手在不在 OS 级隔离边界里」。**全仓唯一**的布尔判别式(读表 `isolation` 位,见该位头注的读者清单)。
|
|
171
|
+
* 穷举 switch、无 default 臂:`isolation` 的型只有两档,加档 = 编译红。
|
|
172
|
+
*
|
|
173
|
+
* ⚠️ 本函数对 `in-process` 也答(表上那一行是 unsupported ⇒ false)—— 那一格是「表」的完整性,不是 wire 的说法:
|
|
174
|
+
* 无手部署**没有执行边界可言**,wire 上 `executionLane.isolated` 按 present-iff 缺席(`capabilities/execution-lane.ts`),
|
|
175
|
+
* 不是 false。boot 行只在装出了执行环境工厂时才打,够不着那一格。
|
|
176
|
+
*/
|
|
177
|
+
export declare function laneIsolated(lane: ExecutionLane): boolean;
|
|
178
|
+
/**
|
|
179
|
+
* S-281 —— 这条车道是不是**单机形缺省**落点(`REMOTE_EXEC` 未设 / 空串 ∧ `CONFIG_PROVIDER=local`,没人点名)。
|
|
180
|
+
* 判据 = 装配出的车道值自己带的记号 `singleMachineDefault`(唯一写者 = `config.ts` 三元链的隐式 host 尾臂;显式 `REMOTE_EXEC=host`
|
|
181
|
+
* 与 `config.d/remote-exec.json` 覆写都铸不带它的新值)。**唯一读口**,读者两处:`config.ts` 车道落点一行的 `source`
|
|
182
|
+
* (`exec_lane_selected`,文件覆写之前播)与 `boot/execution-env.ts` 的 `remote_exec_real_system.note`(覆写之后打)——
|
|
183
|
+
* 同一个记号、两个时刻,不另按 env 原文重判。
|
|
184
|
+
*/
|
|
185
|
+
export declare function isSingleMachineDefaultLane(remoteExec: ServiceConfigFlat["remoteExec"]): boolean;
|
|
158
186
|
/** 把一位能力在**具体部署**上落成布尔:租户面是唯一入参(旋钮由消费点自己再合取)。
|
|
159
187
|
* 穷举 switch,无 default 臂 —— {@link LaneCapability} 加词而此处不加臂 = 编译红。 */
|
|
160
188
|
export declare function laneCapabilityHolds(cap: LaneCapability, deployment: {
|
|
@@ -174,6 +174,18 @@ export function handsOnWorkerFilePlane(lane) {
|
|
|
174
174
|
const caps = executionLaneCaps(lane);
|
|
175
175
|
return caps.hostFilePlane === "supported" && caps.coreRemoteExecutionEnv === "supported";
|
|
176
176
|
}
|
|
177
|
+
export function laneIsolated(lane) {
|
|
178
|
+
const isolation = executionLaneCaps(lane).isolation;
|
|
179
|
+
switch (isolation) {
|
|
180
|
+
case "supported":
|
|
181
|
+
return true;
|
|
182
|
+
case "unsupported":
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
export function isSingleMachineDefaultLane(remoteExec) {
|
|
187
|
+
return remoteExec?.provider === "host" && remoteExec.singleMachineDefault === true;
|
|
188
|
+
}
|
|
177
189
|
export function laneCapabilityHolds(cap, deployment) {
|
|
178
190
|
switch (cap) {
|
|
179
191
|
case "supported":
|
|
@@ -1,34 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* S-157④ —— SQL file-history 店的 **typed 永久失败**披露座:散文一个铸点 + 机器面一只 sink。
|
|
3
|
-
*
|
|
4
|
-
* ## 病(基线 v7.79.0 逐行亲核 + 亲跑)
|
|
5
|
-
* `plugins/blob-backend.ts:55` 的 `BlobTooLargeError` 带 typed 判别码 `blob_too_large_for_sql`,头注
|
|
6
|
-
* 原话「绝不能与 transient 写失败混在一类(那类是可重试的 502;这类重试永远不会成功)」。可它一旦被
|
|
7
|
-
* `plugins/file-history-store-sql.ts` 的任何一条 `catch` 折进 `FileHistoryResult.error.message`,判别码
|
|
8
|
-
* 就只剩散文,**机器一位都读不到**。两条腿各自的后果:
|
|
9
|
-
*
|
|
10
|
-
* · **snapshot(边界捕获)腿**:引擎把失败包成一句散文交给 `RunnerDeps.onError({phase:"rewind"})`
|
|
11
|
-
* (core 7.20.0 `dist/core/runner/prepare-file-history.js` 的 `wrapFileHistoryEntry`),服务那条臂从文本里抠**第一个**
|
|
12
|
-
* 括号词当 metric label —— 而第一个括号词恒是引擎自己的闭集码。实测:「这个文件永远存不进这口店」
|
|
13
|
-
* 与「连接被重置」在 `rewind_snapshot_failed_total` 上**同形** `code="snapshot_failed"`。
|
|
14
|
-
* · **trackEdit(首触备份)腿**:本服务用引擎默认的 `onTrackFailure:"refuse"`(`boot/runner-deps.ts` 亲读),
|
|
15
|
-
* 那条路 **完全不走 onError** —— 引擎把 `(track_failed: <本店 message>)` 念给模型,末句是
|
|
16
|
-
* 「Retry when the file-history store is healthy.」(`prepare-file-history.js`)。对这枚码那是永远
|
|
17
|
-
* 不会到来的一天,而**运维面一个计数都没有**:这条腿今天在遥测上完全不可见。
|
|
18
|
-
*
|
|
19
|
-
* ## 修法(源头留痕,不走文本通道)
|
|
20
|
-
* · **散文面**({@link discloseCaughtFailure} 的返回值):把 caught error 的 typed `code` 写成一个括号词
|
|
21
|
-
* 前置到它自己的 message 前 —— **不 `instanceof` 单挑任何一个类**(身上有字符串 `code` 就折),该店
|
|
22
|
-
* 11 处折点全部改走这一只函数。人与模型因此看得到「这是哪一类失败」。
|
|
23
|
-
* · **机器面**(同一只函数的副作用):码取自 typed 错**对象**、对闭集词表判定,**绝不从散文里抠**。
|
|
24
|
-
* 🔴 这一条是 codex R1-[high] 验真后的定形:文本通道**天然可伪造** —— 一个名叫
|
|
25
|
-
* `(blob_too_large_for_sql).txt` 的文件读不出来时,它的路径就在 `snapshot_failed` 的散文里,任何
|
|
26
|
-
* 「从散文里取在册括号词」的读法都会把它当成一枚永久码(继而抑制掉真故障的告警)。闭集只约束基数,
|
|
27
|
-
* 不能证明来源。所以机读判定搬回**唯一知道真相的那一层**=铸错的地方,`rewind` 臂的读法一字不动。
|
|
28
|
-
* · 留痕三件套与 `observability/fail-open.ts` / `observability/secret-env-scrub.ts` 同律:**计数逐次**
|
|
29
|
-
* (`file_history_permanent_failure_total{code}`)、**每进程每码一条 warn**(第二条起零新增内容,而
|
|
30
|
-
* 20G 树的用户每 turn 撞一次)、装配前累计的次数在 install 时补进 metrics。
|
|
31
|
-
*/
|
|
32
1
|
import type { Logger } from "./observability/logger.js";
|
|
33
2
|
import type { Metrics } from "./observability/metrics.js";
|
|
34
3
|
interface Sink {
|
|
@@ -49,5 +18,12 @@ export declare function installFileHistoryFailureRecorder(deps: Sink): void;
|
|
|
49
18
|
export declare function fileHistoryPermanentFailureCounts(): ReadonlyMap<string, number>;
|
|
50
19
|
/** 测试用重置(进程级状态在同一个 vitest worker 里跨用例存活)。 */
|
|
51
20
|
export declare function resetFileHistoryFailureRecorderForTest(deps?: Sink): void;
|
|
21
|
+
/** core `FileHistoryError.code` 闭集的值投影(两道差集钉在下面;`satisfies` 让它进 core 读表的 `literals` 段 ——
|
|
22
|
+
* core 退役其中一词时,提货前的求交门就能点名这张表)。 */
|
|
23
|
+
declare const FILE_HISTORY_ERROR_CODE_WORDS: readonly ["track_failed", "snapshot_failed", "restore_failed", "adopt_failed", "not_found", "aborted", "unsupported_format", "conflict"];
|
|
24
|
+
/** rewind 粗码臂的码词 = core 的闭集。 */
|
|
25
|
+
export type FileHistoryErrorCode = (typeof FILE_HISTORY_ERROR_CODE_WORDS)[number];
|
|
26
|
+
/** rewind 臂一次失败的粗码:文本里**第一个**在册的 `(码)` / `(码:`;没有 ⇒ `unknown`。纯函数、不抛。 */
|
|
27
|
+
export declare function rewindFailureCode(err: unknown): FileHistoryErrorCode | "unknown";
|
|
52
28
|
export {};
|
|
53
29
|
//# sourceMappingURL=file-history-disclosure.d.ts.map
|
|
@@ -86,4 +86,16 @@ export function resetFileHistoryFailureRecorderForTest(deps = {}) {
|
|
|
86
86
|
sink = { ...deps };
|
|
87
87
|
deps.metrics?.counter(PERMANENT_FAILURE_COUNTER, PERMANENT_FAILURE_HELP);
|
|
88
88
|
}
|
|
89
|
+
const FILE_HISTORY_ERROR_CODE_WORDS = ["track_failed", "snapshot_failed", "restore_failed", "adopt_failed", "not_found", "aborted", "unsupported_format", "conflict"];
|
|
90
|
+
const FILE_HISTORY_ERROR_CODE_SET = new Set(FILE_HISTORY_ERROR_CODE_WORDS);
|
|
91
|
+
const isFileHistoryErrorCode = (w) => FILE_HISTORY_ERROR_CODE_SET.has(w);
|
|
92
|
+
export function rewindFailureCode(err) {
|
|
93
|
+
const text = err instanceof Error ? err.message : String(err);
|
|
94
|
+
for (const m of text.matchAll(/\(([a-z_]+)[):]/g)) {
|
|
95
|
+
const word = m[1];
|
|
96
|
+
if (isFileHistoryErrorCode(word))
|
|
97
|
+
return word;
|
|
98
|
+
}
|
|
99
|
+
return "unknown";
|
|
100
|
+
}
|
|
89
101
|
//# sourceMappingURL=file-history-disclosure.js.map
|