@sema-agent/server 7.105.0 → 7.107.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 +105 -0
- package/README.md +2 -2
- package/USAGE.md +135 -43
- 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 +3 -9
- 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 +35 -5
- package/dist/config-catalog.js +92 -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 +35 -0
- package/dist/config.js +69 -16
- 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/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.js +14 -9
- package/dist/http/approval-sweeps.js +13 -1
- package/dist/http/dispatch.js +1 -1
- 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.js +19 -11
- package/dist/http/route-ctx.d.ts +9 -0
- package/dist/http/routes/approvals-assistant.js +0 -1
- package/dist/http/routes/capabilities.js +11 -12
- package/dist/http/routes/config-catalog.js +2 -1
- package/dist/http/routes/diagnostics.js +2 -0
- package/dist/http/routes/tasks.js +4 -1
- package/dist/http/server.d.ts +20 -4
- package/dist/http/session-inherit-env.d.ts +37 -0
- package/dist/http/session-inherit-env.js +6 -0
- 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 +5 -1
- package/dist/observability/fail-open.js +5 -1
- package/dist/observability/metrics.js +2 -0
- 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 +40 -1
- package/dist/observability/run-terminal.js +2 -5
- 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/session-policy-store-sql.d.ts +38 -7
- package/dist/plugins/session-policy-store-sql.js +64 -22
- package/dist/plugins/sql-driver.d.ts +1 -1
- package/dist/plugins/store-backend.d.ts +12 -3
- package/dist/plugins/store-backend.js +16 -5
- package/dist/plugins/tidb-pool.js +1 -1
- package/dist/read-face-posture.d.ts +4 -4
- 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/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 +5 -3
- 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 +64 -62
- package/dist/task-settings.js +57 -27
- 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 +7 -5
- package/dist/trace/project.js +51 -3
- 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/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;
|
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
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() {
|
|
@@ -822,6 +842,18 @@ function noticeAutoCompactAboveWindow(slot, envName, m) {
|
|
|
822
842
|
},
|
|
823
843
|
});
|
|
824
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
|
+
}
|
|
825
857
|
function parseStoreDomain(ctx) {
|
|
826
858
|
const { requirePrincipal } = ctx;
|
|
827
859
|
const dbBackendSet = !!process.env.DB_BACKEND;
|
|
@@ -829,7 +861,7 @@ function parseStoreDomain(ctx) {
|
|
|
829
861
|
!!(process.env.MYSQL_HOST || process.env.PG_HOST);
|
|
830
862
|
const bareBoot = !dbBackendSet && !sqlSignal;
|
|
831
863
|
const multiTenantBareBoot = bareBoot && requirePrincipal;
|
|
832
|
-
const dbBackend = enumEnv("DB_BACKEND", bareBoot ? (multiTenantBareBoot ? "memory" : "local") : "mysql",
|
|
864
|
+
const dbBackend = enumEnv("DB_BACKEND", bareBoot ? (multiTenantBareBoot ? "memory" : "local") : "mysql", DB_BACKEND_WORDS);
|
|
833
865
|
if (multiTenantBareBoot) {
|
|
834
866
|
CONFIG_NOTICES.push({
|
|
835
867
|
level: "warn",
|
|
@@ -846,6 +878,11 @@ function parseStoreDomain(ctx) {
|
|
|
846
878
|
const dbHostSet = dbBackend === "pg" ? !!process.env.PG_HOST : !!process.env.MYSQL_HOST;
|
|
847
879
|
const needsTidb = sessionBackend === "tidb" || (sessionBackend === "auto" && dbHostSet);
|
|
848
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
|
+
}
|
|
849
886
|
return {
|
|
850
887
|
sessionBackend,
|
|
851
888
|
sessionCacheTtlSec: numEnv("SESSION_CACHE_TTL_SEC", "300"),
|
|
@@ -1290,6 +1327,8 @@ function unattendedApprovalPolicyEnv() {
|
|
|
1290
1327
|
function parseApprovalDomain(ctx) {
|
|
1291
1328
|
const { postureOn, singleUserTurnkey, infraReady } = ctx;
|
|
1292
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 } });
|
|
1293
1332
|
const streamAskWindowMsSource = postureSourceOfNumEnv(process.env.STREAM_ASK_WINDOW_MS, singleUserTurnkey);
|
|
1294
1333
|
const toolApprovalEnabled = postureOn("TOOL_APPROVAL_ENABLED");
|
|
1295
1334
|
let streamAskWindowMsEffectiveSource = streamAskWindowMsSource;
|
|
@@ -1335,7 +1374,7 @@ function parseApprovalDomain(ctx) {
|
|
|
1335
1374
|
...((decision) => (decision === undefined ? {} : { writeProtectedPaths: decision }))(parseWriteProtectedTableEnv(process.env.WRITE_PROTECTED_EXTRA, process.env.WRITE_PROTECTED_TABLE_REPLACE)),
|
|
1336
1375
|
...((raw) => {
|
|
1337
1376
|
const trimmed = (raw ?? "").trim();
|
|
1338
|
-
if (trimmed === "always"
|
|
1377
|
+
if (trimmed === "always")
|
|
1339
1378
|
return { manualModeShellGate: trimmed };
|
|
1340
1379
|
if (trimmed === "" || trimmed === "off") {
|
|
1341
1380
|
if (trimmed === "off") {
|
|
@@ -1346,14 +1385,20 @@ function parseApprovalDomain(ctx) {
|
|
|
1346
1385
|
env: "MANUAL_MODE_SHELL_GATE",
|
|
1347
1386
|
raw: trimmed,
|
|
1348
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; " +
|
|
1349
|
-
"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",
|
|
1350
1389
|
},
|
|
1351
1390
|
});
|
|
1352
1391
|
}
|
|
1353
1392
|
return {};
|
|
1354
1393
|
}
|
|
1355
|
-
|
|
1356
|
-
`
|
|
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". ` +
|
|
1357
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`);
|
|
1358
1403
|
})(process.env.MANUAL_MODE_SHELL_GATE),
|
|
1359
1404
|
permissionsDisableAutoMode: boolEnv("PERMISSIONS_DISABLE_AUTO_MODE", false),
|
|
@@ -1384,6 +1429,7 @@ function parseApprovalDomain(ctx) {
|
|
|
1384
1429
|
streamAskWindowMsSource: streamAskWindowMsEffectiveSource,
|
|
1385
1430
|
durableApprovalSource: postureSourceOfKnob("DURABLE_APPROVAL"),
|
|
1386
1431
|
unattendedApprovalPolicy: unattendedPolicy,
|
|
1432
|
+
unattendedPermissionMode: unattendedMode.value,
|
|
1387
1433
|
approvalTimeoutSec: numEnv("APPROVAL_TIMEOUT_SEC", "0"),
|
|
1388
1434
|
};
|
|
1389
1435
|
}
|
|
@@ -1949,7 +1995,7 @@ function parseOrchestrationDomain(ctx) {
|
|
|
1949
1995
|
}
|
|
1950
1996
|
:
|
|
1951
1997
|
ctx.configProvider === "local"
|
|
1952
|
-
? { provider: "host" }
|
|
1998
|
+
? { provider: "host", singleMachineDefault: true }
|
|
1953
1999
|
: undefined,
|
|
1954
2000
|
worktreeIsolation: boolEnv("WORKTREE_ISOLATION_ENABLED", false) && process.env.WORKTREE_REPO_ROOT
|
|
1955
2001
|
? {
|
|
@@ -2136,11 +2182,18 @@ function parseIntegrationsDomain(ctx) {
|
|
|
2136
2182
|
skillsDir: env("SKILLS_DIR", defaultSkillsDir()),
|
|
2137
2183
|
centerCredentialRenewLeadMs: parseCenterCredentialRenewLeadMs(process.env.CENTER_CREDENTIAL_RENEW_LEAD_MS),
|
|
2138
2184
|
configCenter: process.env.SEMA_REGISTRY_URL ? parseConfigCenter(env("SEMA_REGISTRY_URL"), requirePrincipal) : undefined,
|
|
2185
|
+
fleetLeaseFailClosed: parseFleetLeaseFailClosed(process.env.FLEET_LEASE_FAIL_CLOSED, requirePrincipal),
|
|
2139
2186
|
...(configProvider !== undefined ? { configProvider } : {}),
|
|
2140
2187
|
configLocalDir: process.env.CONFIG_LOCAL_DIR || undefined,
|
|
2141
2188
|
a2aServe: parseA2aServe(),
|
|
2142
2189
|
};
|
|
2143
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
|
+
}
|
|
2144
2197
|
function parseConfigCenter(baseUrl, requirePrincipal) {
|
|
2145
2198
|
const worker = process.env.SEMA_REGISTRY_WORKER || undefined;
|
|
2146
2199
|
const declared = readCenterCredentialDeclaration();
|
|
@@ -2246,7 +2299,7 @@ const MODEL_PLANE_GROUP_KEYS = [
|
|
|
2246
2299
|
const APPROVAL_GROUP_KEYS = [
|
|
2247
2300
|
"approvalRequire", "approvalDeny", "approvalTimeoutSec", "approvalAutoBudget", "approvalNeverAuto",
|
|
2248
2301
|
"approvalHmacKeys", "durableApproval", "durableApprovalSource", "streamAskWindowMsSource", "directApprovalDoor", "directDoorActive", "resourceSuspend",
|
|
2249
|
-
"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",
|
|
2250
2303
|
"sensitiveWritePatterns", "writeProtectedPaths", "manualModeShellGate", "permissionsDisableAutoMode", "autoModeClassifierFallback", "autoModeClassifierTimeoutMs", "crossSessionInbound", "crossSessionDialogExpiry",
|
|
2251
2304
|
];
|
|
2252
2305
|
const MEMORY_GROUP_KEYS = [
|
|
@@ -2280,7 +2333,7 @@ const LIMITS_HTTP_GROUP_KEYS = [
|
|
|
2280
2333
|
const OBSERVABILITY_GROUP_KEYS = ["metricsToken", "traceToken", "toolTrace", "traceThinking", "logLevel", "otel"];
|
|
2281
2334
|
const INTEGRATIONS_GROUP_KEYS = [
|
|
2282
2335
|
"pluginsAllowHosts", "mcpServers", "a2aPeers", "a2aServe", "configBootFetchBudgetMs", "gitApiBaseUrl", "gitApiToken", "gitApiKind", "defaultScenario",
|
|
2283
|
-
"skillsDir", "centerCredentialRenewLeadMs", "configCenter", "configProvider", "configLocalDir",
|
|
2336
|
+
"skillsDir", "centerCredentialRenewLeadMs", "configCenter", "fleetLeaseFailClosed", "configProvider", "configLocalDir",
|
|
2284
2337
|
];
|
|
2285
2338
|
export const CONFIG_GROUP_KEYS = {
|
|
2286
2339
|
store: STORE_GROUP_KEYS,
|
|
@@ -2405,7 +2458,7 @@ export function loadConfig() {
|
|
|
2405
2458
|
throw new Error(refusal);
|
|
2406
2459
|
}
|
|
2407
2460
|
}
|
|
2408
|
-
EXEC_LANE_NOTICE = buildExecLaneNotice(orchestration.remoteExec
|
|
2461
|
+
EXEC_LANE_NOTICE = buildExecLaneNotice(orchestration.remoteExec);
|
|
2409
2462
|
return attachConfigGroups({
|
|
2410
2463
|
...store,
|
|
2411
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":
|
package/dist/fleet-client.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Fleet worker-side wiring(announce/heartbeat + usage 批报;契约 = registry-core 0.1.48+
|
|
3
3
|
* `/fleet`,center 端点 = sema-registry `/api/fleet/*`)。设计要点:
|
|
4
|
-
* - **可选分层(D3)
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* - **可选分层(D3),两条环各自有门(S-866 ④)**:客户端在中心车道配齐时启动(`centerLaneConfigured` —— baseUrl + 声明了
|
|
5
|
+
* 凭证 + worker,与租约管理器、用量累加器**同一只**谓词);**用量环**只要这三件;**公告环**(announce / 心跳 / 注销)另要
|
|
6
|
+
* `FLEET_ADVERTISE_ADDRESS` 显式声明(可达性是运维的话,不自猜:k8s pod IP / docker alias / 127.0.0.1:port 只有部署环境自己
|
|
7
|
+
* 知道)。不配中心的部署零行为变化;配了中心而不公告的引擎只报用量、不注册(7.105.0 及以前它连用量都不报)。
|
|
7
8
|
* - **announce=注册=心跳同形状幂等 upsert**:boot 后(listen 之后)POST 一次 + 每 heartbeatMs 续。
|
|
8
9
|
* `ready` / `draining` 两字段由**同一只**就绪谓词的同一次读铸(S-740:`http/readiness.ts` 的 `readinessOf`,
|
|
9
10
|
* 与 `/health.ready`、`GET /readyz`、计费提交门同源):`ready = verdict.ready`(= roster 已落 ∧ ¬draining),
|
|
@@ -74,8 +75,9 @@ export interface FleetClientOpts {
|
|
|
74
75
|
worker: string;
|
|
75
76
|
};
|
|
76
77
|
instanceId: string;
|
|
77
|
-
/** Reachable base URL of THIS instance — the operator's explicit declaration (`FLEET_ADVERTISE_ADDRESS`).
|
|
78
|
-
|
|
78
|
+
/** Reachable base URL of THIS instance — the operator's explicit declaration (`FLEET_ADVERTISE_ADDRESS`).
|
|
79
|
+
* S-866 ④:**缺席 = 公告环不装**(不注册、不心跳、不注销;`announceNow` 是空操作),用量环照装。 */
|
|
80
|
+
advertiseAddress?: string | undefined;
|
|
79
81
|
version: string;
|
|
80
82
|
/** S-740:就绪判(装配层接 `readinessOf(deployment)` —— HTTP 面读的同一个 deps 组)。每条公告现读**一次**,
|
|
81
83
|
* `ready` 与 `draining` 两字段都由这一次读铸(不会撕裂成 `ready:true` + `draining:true`)。 */
|
|
@@ -104,22 +106,25 @@ export interface FleetClient {
|
|
|
104
106
|
stop: () => Promise<void>;
|
|
105
107
|
}
|
|
106
108
|
/**
|
|
107
|
-
* Start the worker-side fleet loops. Caller has already gated on
|
|
108
|
-
*
|
|
109
|
+
* Start the worker-side fleet loops. Caller has already gated on the center lane (startFleetClientFromEnv below).
|
|
110
|
+
* The announce loop runs iff `advertiseAddress` is present; the usage loop iff `usage` is present (S-866 ④: the two
|
|
111
|
+
* loops no longer share one gate).
|
|
109
112
|
*/
|
|
110
113
|
export declare function startFleetClient(opts: FleetClientOpts): FleetClient;
|
|
111
114
|
/**
|
|
112
|
-
* Env/config gate (D3
|
|
113
|
-
* + worker — S-651: declared, not "currently readable": a token
|
|
114
|
-
* client, every request re-reads it)
|
|
115
|
-
*
|
|
116
|
-
*
|
|
115
|
+
* Env/config gate (D3 可选分层;S-866 ④ 两条环各自有门): start when the center lane is configured —
|
|
116
|
+
* {@link centerLaneConfigured}(baseUrl + a DECLARED credential + worker — S-651: declared, not "currently readable": a token
|
|
117
|
+
* file that is unreadable right now still starts the client, every request re-reads it), the SAME predicate the lease
|
|
118
|
+
* manager and the usage accumulator use. The usage loop needs nothing more; the announce loop additionally needs the
|
|
119
|
+
* operator's explicit reachable address (`FLEET_ADVERTISE_ADDRESS`; a malformed one refuses boot). Lane not configured →
|
|
120
|
+
* undefined (zero behavior change for non-fleet deployments); an address set while only the worker is missing is warned
|
|
121
|
+
* (half-configured announce).
|
|
117
122
|
*/
|
|
118
123
|
export declare function startFleetClientFromEnv(cfg: {
|
|
119
124
|
configCenter?: (CenterEndpoint & {
|
|
120
125
|
worker?: string;
|
|
121
126
|
}) | undefined;
|
|
122
127
|
}, opts: Omit<FleetClientOpts, "center" | "advertiseAddress"> & {
|
|
123
|
-
advertiseAddress?: string;
|
|
128
|
+
advertiseAddress?: string | undefined;
|
|
124
129
|
}): FleetClient | undefined;
|
|
125
130
|
//# sourceMappingURL=fleet-client.d.ts.map
|
package/dist/fleet-client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InstanceAnnouncement, UsageReport, DEFAULT_HEARTBEAT_MS } from "@sema-agent/settings-schema/fleet";
|
|
2
2
|
import { buildDisplaySafeUrl } from "./trace/redact.js";
|
|
3
|
-
import { centerFetch } from "./config-center/center-request.js";
|
|
3
|
+
import { centerFetch, centerLaneConfigured } from "./config-center/center-request.js";
|
|
4
4
|
export const FLEET_USAGE_KEY_CAP = 1024;
|
|
5
5
|
export const FLEET_CLIENT_FETCH_TIMEOUT_MS = 5000;
|
|
6
6
|
export class FleetUsageAccumulator {
|
|
@@ -81,8 +81,9 @@ export function startFleetClient(opts) {
|
|
|
81
81
|
const send = (route, method, json) => centerFetch(opts.center, { kind: route }, { method, json, timeoutMs: FLEET_CLIENT_FETCH_TIMEOUT_MS, fetchImpl: f });
|
|
82
82
|
let stopped = false;
|
|
83
83
|
let failStreak = 0;
|
|
84
|
+
const address = opts.advertiseAddress;
|
|
84
85
|
const announceOnce = async () => {
|
|
85
|
-
if (stopped)
|
|
86
|
+
if (stopped || address === undefined)
|
|
86
87
|
return;
|
|
87
88
|
try {
|
|
88
89
|
const verdict = opts.readiness();
|
|
@@ -91,7 +92,7 @@ export function startFleetClient(opts) {
|
|
|
91
92
|
const body = {
|
|
92
93
|
instanceId: opts.instanceId,
|
|
93
94
|
worker: opts.center.worker,
|
|
94
|
-
address
|
|
95
|
+
address,
|
|
95
96
|
version: opts.version,
|
|
96
97
|
ready: verdict.ready,
|
|
97
98
|
draining: !verdict.ready && verdict.reasons.includes("draining"),
|
|
@@ -153,9 +154,11 @@ export function startFleetClient(opts) {
|
|
|
153
154
|
opts.logger?.warn("fleet_usage_report_failed", { error: err instanceof Error ? err.message : String(err), entries: entries.length });
|
|
154
155
|
}
|
|
155
156
|
};
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
const announcing = address !== undefined;
|
|
158
|
+
if (announcing)
|
|
159
|
+
void announceNow();
|
|
160
|
+
const hb = announcing ? setInterval(() => void announceNow(), heartbeatMs) : undefined;
|
|
161
|
+
hb?.unref?.();
|
|
159
162
|
const uw = opts.usage ? setInterval(() => void flushUsage(), usageWindowMs) : undefined;
|
|
160
163
|
uw?.unref?.();
|
|
161
164
|
let stopping = false;
|
|
@@ -163,13 +166,16 @@ export function startFleetClient(opts) {
|
|
|
163
166
|
if (stopping)
|
|
164
167
|
return;
|
|
165
168
|
stopping = true;
|
|
166
|
-
|
|
169
|
+
if (hb)
|
|
170
|
+
clearInterval(hb);
|
|
167
171
|
if (uw)
|
|
168
172
|
clearInterval(uw);
|
|
169
173
|
while (inFlight !== undefined)
|
|
170
174
|
await inFlight;
|
|
171
175
|
stopped = true;
|
|
172
176
|
await flushUsage();
|
|
177
|
+
if (!announcing)
|
|
178
|
+
return;
|
|
173
179
|
try {
|
|
174
180
|
await send("fleet-instances", "DELETE", { instanceId: opts.instanceId, worker: opts.center.worker });
|
|
175
181
|
}
|
|
@@ -180,23 +186,27 @@ export function startFleetClient(opts) {
|
|
|
180
186
|
}
|
|
181
187
|
export function startFleetClientFromEnv(cfg, opts) {
|
|
182
188
|
const cc = cfg.configCenter;
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
189
|
+
const raw = opts.advertiseAddress ?? process.env.FLEET_ADVERTISE_ADDRESS;
|
|
190
|
+
const address = raw === undefined || raw === "" ? undefined : raw;
|
|
191
|
+
if (address !== undefined && cc?.baseUrl && cc.credential.declared) {
|
|
192
|
+
let parsed;
|
|
193
|
+
try {
|
|
194
|
+
parsed = new URL(address);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
parsed = undefined;
|
|
198
|
+
}
|
|
199
|
+
if (parsed === undefined || (parsed.protocol !== "http:" && parsed.protocol !== "https:")) {
|
|
200
|
+
throw new Error(`FLEET_ADVERTISE_ADDRESS must be a parseable http(s) base URL (e.g. "http://10.0.0.5:8090"); got "${buildDisplaySafeUrl(address)}" — the fleet center rejects any other shape on every announce`);
|
|
201
|
+
}
|
|
195
202
|
}
|
|
196
|
-
if (!
|
|
197
|
-
|
|
203
|
+
if (!centerLaneConfigured(cfg)) {
|
|
204
|
+
if (address !== undefined && cc?.baseUrl && cc.credential.declared) {
|
|
205
|
+
opts.logger?.warn("fleet_client_no_worker_scope", { note: "FLEET_ADVERTISE_ADDRESS set but SEMA_REGISTRY_WORKER missing — announce needs the worker (dispatch key); fleet client not started" });
|
|
206
|
+
}
|
|
198
207
|
return undefined;
|
|
199
208
|
}
|
|
200
|
-
|
|
209
|
+
const lane = cfg.configCenter;
|
|
210
|
+
return startFleetClient({ ...opts, center: { baseUrl: lane.baseUrl, credential: lane.credential, worker: lane.worker }, advertiseAddress: address });
|
|
201
211
|
}
|
|
202
212
|
//# sourceMappingURL=fleet-client.js.map
|