@sema-agent/server 7.127.2 → 7.129.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 +130 -0
- package/USAGE.md +137 -20
- package/dist/approval-card.d.ts +53 -26
- package/dist/approval-card.js +40 -11
- package/dist/approval-crash-convergence.js +4 -4
- package/dist/audit.js +4 -1
- package/dist/bake-runner/main.js +2 -1
- package/dist/boot/execution-lane.d.ts +11 -0
- package/dist/boot/execution-lane.js +6 -0
- package/dist/boot/memory-consolidation.d.ts +46 -16
- package/dist/boot/memory-consolidation.js +15 -8
- package/dist/boot/resolve-spec.js +3 -1
- package/dist/boot/side-query-lane.d.ts +7 -20
- package/dist/boot/side-query-lane.js +0 -24
- package/dist/boot/stage-01-config.js +3 -0
- package/dist/boot/stage-06-runners.js +1 -1
- package/dist/boot/stores.js +2 -2
- package/dist/boot/web-admin.js +31 -20
- package/dist/boot/webfetch-summarize-lane.d.ts +10 -7
- package/dist/boot/webfetch-summarize-lane.js +2 -3
- package/dist/brain.js +6 -9
- package/dist/capabilities/builtin-tools.d.ts +2 -2
- package/dist/capabilities/engine-capabilities.d.ts +14 -0
- package/dist/capabilities/engine-capabilities.js +12 -0
- package/dist/capabilities/mcp-probe.js +11 -5
- package/dist/capability-facts.d.ts +3 -1
- package/dist/capability-facts.js +1 -0
- package/dist/config-catalog.js +1 -1
- package/dist/config-provider.d.ts +17 -4
- package/dist/config-provider.js +8 -6
- package/dist/config-types.d.ts +6 -5
- package/dist/egress-redirect.d.ts +12 -0
- package/dist/egress-redirect.js +7 -0
- package/dist/entry-mode-clamp-notice.d.ts +30 -0
- package/dist/entry-mode-clamp-notice.js +13 -0
- package/dist/fleet/fleet-bus.d.ts +7 -0
- package/dist/fleet/fleet-bus.js +1 -0
- package/dist/hooks/hook-llm.js +6 -2
- package/dist/hooks/hook-runner.d.ts +1 -0
- package/dist/hooks/hook-runner.js +10 -4
- package/dist/host-lsp-manager.d.ts +3 -3
- package/dist/http/admission.js +5 -2
- package/dist/http/resume-legs.js +46 -27
- package/dist/http/route-ctx.d.ts +1 -1
- package/dist/http/routes/approvals-assistant.js +6 -4
- package/dist/http/routes/capabilities.js +1 -0
- package/dist/http/routes/session-sync.js +3 -4
- package/dist/http/routes/sessions.js +4 -17
- package/dist/http/send.d.ts +1 -1
- package/dist/http/send.js +4 -3
- package/dist/http/wire-types.d.ts +14 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/main.js +2 -0
- package/dist/mcp-assertion-header.d.ts +89 -0
- package/dist/mcp-assertion-header.js +49 -0
- package/dist/observability/fail-open.d.ts +7 -3
- package/dist/observability/fail-open.js +7 -3
- package/dist/observability/secret-env-scrub.d.ts +37 -3
- package/dist/observability/secret-env-scrub.js +2 -2
- package/dist/parked-decide.js +5 -2
- package/dist/plugins/host-platform.d.ts +43 -13
- package/dist/plugins/host-platform.js +67 -18
- package/dist/plugins/memory-embedder.js +5 -0
- package/dist/plugins/one-time-migrations.d.ts +180 -9
- package/dist/plugins/one-time-migrations.js +315 -3
- package/dist/plugins/pg-pool.js +3 -1
- package/dist/plugins/remote-env-host.d.ts +17 -20
- package/dist/plugins/remote-env-host.js +16 -28
- package/dist/plugins/session-policy-store-sql.d.ts +16 -4
- package/dist/plugins/session-policy-store-sql.js +17 -20
- package/dist/plugins/session-rules-column.d.ts +16 -0
- package/dist/plugins/session-rules-column.js +6 -0
- package/dist/plugins/tidb-pool.js +4 -1
- package/dist/read-face-posture.d.ts +2 -1
- package/dist/request-value-closure.js +1 -1
- package/dist/retired-card-words.d.ts +17 -0
- package/dist/retired-card-words.js +49 -0
- package/dist/rule-entry-list.js +2 -0
- package/dist/run-local.js +15 -6
- package/dist/server-secret-env.d.ts +77 -85
- package/dist/server-secret-env.js +40 -26
- package/dist/session-policy-wire.d.ts +61 -124
- package/dist/session-policy-wire.js +59 -121
- package/dist/session-sync.d.ts +8 -6
- package/dist/session-sync.js +6 -5
- package/dist/spec-fields.d.ts +3 -2
- package/dist/standing-answer-grant.d.ts +17 -9
- package/dist/standing-answer-grant.js +11 -8
- package/dist/task-mcp.d.ts +31 -3
- package/dist/task-mcp.js +42 -4
- package/dist/task-settings.d.ts +6 -1
- package/dist/task-settings.js +1 -1
- package/dist/tool-approval.d.ts +14 -7
- package/dist/tool-approval.js +23 -18
- package/dist/trace/core-keyset-guard.d.ts +3 -3
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +1 -0
- package/dist/trace/project.d.ts +7 -4
- package/dist/trace/project.js +7 -76
- package/dist/trace/tool-output-media.d.ts +10 -4
- package/dist/trace/tool-output-media.js +7 -29
- package/dist/web-admin-client/apply-effective.js +28 -11
- package/dist/web-admin-client/facade.d.ts +2 -2
- package/dist/web-admin-client/facade.js +2 -2
- package/dist/web-admin-client/http-client.d.ts +2 -2
- package/dist/web-admin-client/read-face.d.ts +73 -20
- package/dist/web-admin-client/read-face.js +76 -36
- package/dist/web-admin-client/skills-mcp.d.ts +20 -17
- package/dist/web-admin-client/skills-mcp.js +27 -8
- package/dist/web-admin-client/types.d.ts +18 -0
- package/package.json +3 -3
package/dist/trace/project.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { admitProviderEcho, redactErrorMessage, redactModelText } from "../observability/run-terminal.js";
|
|
2
|
-
import {
|
|
2
|
+
import { MCP_DELIVERY_VERDICTS, MCP_LIVENESS_STATES, screenGateOutcome, TEAM_ROLES, ToolRoster, ToolRosterDelta, WRITE_PROTECTION_TARGET_VIEWS } from "@sema-agent/core";
|
|
3
3
|
import { Value } from "typebox/value";
|
|
4
4
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
5
5
|
import { redactSecrets, redactDeep } from "./redact.js";
|
|
@@ -111,77 +111,16 @@ export function screenedGate(v) {
|
|
|
111
111
|
return undefined;
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
-
function modeLedgerDefect(d) {
|
|
115
|
-
const classesOk = (v) => Array.isArray(v) && v.every((c) => isAskClass(c));
|
|
116
|
-
if (d.kind === "denied") {
|
|
117
|
-
if (d.mode !== undefined && !isPermissionMode(d.mode))
|
|
118
|
-
return "disposition.mode is outside the closed set";
|
|
119
|
-
if (d.askClass !== undefined && !isAskClass(d.askClass))
|
|
120
|
-
return "disposition.askClass is outside the closed set";
|
|
121
|
-
if (d.classes !== undefined && !classesOk(d.classes))
|
|
122
|
-
return "disposition.classes is not a list of ask classes";
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
if (d.released === undefined)
|
|
126
|
-
return false;
|
|
127
|
-
const r = d.released;
|
|
128
|
-
if (r === null || typeof r !== "object")
|
|
129
|
-
return "disposition.released is not an object";
|
|
130
|
-
const o = r;
|
|
131
|
-
if (!isPermissionMode(o.mode) || !isAskClass(o.askClass) || !classesOk(o.classes))
|
|
132
|
-
return "disposition.released.mode / askClass / classes is outside its closed set";
|
|
133
|
-
if (o.origin !== undefined && !isAskOrigin(o.origin))
|
|
134
|
-
return "disposition.released.origin is outside the closed set";
|
|
135
|
-
if (o.mandate !== undefined && !isPersistedRuleMandate(o.mandate))
|
|
136
|
-
return "disposition.released.mandate is outside the closed set";
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
function withReleaseReasonTheEngineReads(g) {
|
|
140
|
-
const d = g.disposition;
|
|
141
|
-
if (d.kind !== "allowed" || d.released === undefined)
|
|
142
|
-
return g;
|
|
143
|
-
const r = d.released;
|
|
144
|
-
if (r === null || typeof r !== "object")
|
|
145
|
-
return g;
|
|
146
|
-
const reason = d.released.reason;
|
|
147
|
-
if (typeof reason !== "string" || isModeReleaseReason(reason))
|
|
148
|
-
return g;
|
|
149
|
-
const { reason: _unread, ...kept } = d.released;
|
|
150
|
-
return { ...g, disposition: { ...d, released: kept } };
|
|
151
|
-
}
|
|
152
114
|
function screenedGateOrThrow(v) {
|
|
153
|
-
if (v ===
|
|
154
|
-
if (v !== undefined)
|
|
155
|
-
recordFailOpen("server.trace.gate-outcome-defective", "gate is not an object");
|
|
156
|
-
return undefined;
|
|
157
|
-
}
|
|
158
|
-
const stored = v;
|
|
159
|
-
if (stored.disposition === null || typeof stored.disposition !== "object" || (stored.disposition.kind !== "allowed" && stored.disposition.kind !== "denied")) {
|
|
160
|
-
recordFailOpen("server.trace.gate-outcome-defective", "disposition is not one of the two arms");
|
|
161
|
-
return undefined;
|
|
162
|
-
}
|
|
163
|
-
const g = withReleaseReasonTheEngineReads(stored);
|
|
164
|
-
const d = g.disposition;
|
|
165
|
-
const classifierFallbackRaw = d.classifier !== null && typeof d.classifier === "object" ? d.classifier.fallback : undefined;
|
|
166
|
-
const shapeDefect = (d.kind === "denied" && typeof d.deniedBy !== "string" && "disposition.deniedBy is not a string") ||
|
|
167
|
-
(d.kind === "denied" && d.cause !== undefined && typeof d.cause !== "string" && "disposition.cause is not a string") ||
|
|
168
|
-
(d.classifier !== undefined && (d.classifier === null || typeof d.classifier !== "object") && "disposition.classifier is not an object") ||
|
|
169
|
-
(classifierFallbackRaw !== undefined && (classifierFallbackRaw === null || typeof classifierFallbackRaw !== "object") && "disposition.classifier.fallback is not an object") ||
|
|
170
|
-
(classifierFallbackRaw !== undefined && classifierFallbackRaw !== null && typeof classifierFallbackRaw === "object" && typeof classifierFallbackRaw.cause !== "string" && "disposition.classifier.fallback.cause is not a string") ||
|
|
171
|
-
modeLedgerDefect(d) ||
|
|
172
|
-
(g.settlement !== undefined && (g.settlement === null || typeof g.settlement !== "object") && "settlement is not an object") ||
|
|
173
|
-
(g.settlement !== undefined && g.settlement !== null && typeof g.settlement.kind !== "string" && "settlement.kind is not a string") ||
|
|
174
|
-
(g.origin !== undefined && typeof g.origin !== "string" && "origin is not a string") ||
|
|
175
|
-
undefined;
|
|
176
|
-
if (shapeDefect !== undefined) {
|
|
177
|
-
recordFailOpen("server.trace.gate-outcome-defective", shapeDefect);
|
|
115
|
+
if (v === undefined)
|
|
178
116
|
return undefined;
|
|
179
|
-
|
|
180
|
-
const defects = screenGateOutcome(g);
|
|
117
|
+
const defects = screenGateOutcome(v);
|
|
181
118
|
if (defects.length > 0) {
|
|
182
119
|
recordFailOpen("server.trace.gate-outcome-defective", defects.join("; "));
|
|
183
120
|
return undefined;
|
|
184
121
|
}
|
|
122
|
+
const g = v;
|
|
123
|
+
const d = g.disposition;
|
|
185
124
|
const attribution = d.classifier;
|
|
186
125
|
const classifier = attribution === undefined
|
|
187
126
|
? undefined
|
|
@@ -193,10 +132,6 @@ function screenedGateOrThrow(v) {
|
|
|
193
132
|
: { fallback: { from: attribution.fallback.from, to: attribution.fallback.to, cause: attribution.fallback.cause } }),
|
|
194
133
|
};
|
|
195
134
|
const settlement = g.settlement === undefined ? undefined : projectSettlement(g.settlement);
|
|
196
|
-
if (g.settlement !== undefined && settlement === undefined) {
|
|
197
|
-
recordFailOpen("server.trace.gate-outcome-defective", "settlement.who is not an object with a party in the closed set");
|
|
198
|
-
return undefined;
|
|
199
|
-
}
|
|
200
135
|
const rel = d.kind === "allowed" ? d.released : undefined;
|
|
201
136
|
const released = rel === undefined
|
|
202
137
|
? undefined
|
|
@@ -242,11 +177,7 @@ export function screenedStoredEventData(type, data) {
|
|
|
242
177
|
return { ...d, gate };
|
|
243
178
|
}
|
|
244
179
|
function projectSettlement(st) {
|
|
245
|
-
const
|
|
246
|
-
const partyV = whoV === null || typeof whoV !== "object" ? undefined : whoV.party;
|
|
247
|
-
if (!isSettlementParty(partyV))
|
|
248
|
-
return undefined;
|
|
249
|
-
const who = whoV;
|
|
180
|
+
const who = st.who;
|
|
250
181
|
return {
|
|
251
182
|
kind: st.kind,
|
|
252
183
|
when: st.when,
|
|
@@ -289,7 +220,7 @@ export function toolEndEventData(ev) {
|
|
|
289
220
|
...(ev.output !== undefined ? { output: projectToolOutput(ev.output) } : {}),
|
|
290
221
|
...(ev.truncated ? { truncated: true } : {}),
|
|
291
222
|
...(Number.isFinite(ev.totalChars) ? { totalChars: ev.totalChars } : {}),
|
|
292
|
-
...(ev.structured !== undefined ? { structured: projectToolStructured(ev.structured) } : {}),
|
|
223
|
+
...(ev.structured !== undefined ? { structured: projectToolStructured(ev.structured, redactDeep) } : {}),
|
|
293
224
|
...(typeof ev.errorCode === "string" && ev.errorCode.length <= 128 ? { errorCode: ev.errorCode } : {}),
|
|
294
225
|
...(typeof ev.delivered === "string" && MCP_DELIVERY_VERDICTS.includes(ev.delivered) ? { delivered: ev.delivered } : {}),
|
|
295
226
|
...(gate !== undefined ? { gate } : {}),
|
|
@@ -4,15 +4,21 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export declare function projectToolOutput(output: unknown): unknown;
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
* 其它一切整只过 `
|
|
7
|
+
* 媒体卡座位的**唯一一只遍历**(与 {@link projectToolOutput} 同律):媒体卡逐成员过 `redact`,`file` 里 `base64` 逐字、其余成员照脱;
|
|
8
|
+
* 其它一切整只过 `redact`。保键序(逐键映射),不改入参。
|
|
9
|
+
*
|
|
10
|
+
* 形判与遍历只此一只,**脱敏器由调用面给**(「什么算该脱」是各面自己的事,「字节座位不过它」是这里的事):
|
|
11
|
+
* · `tool_end` 活面 / 持久面(`toolEndEventData` 经 `trace/project.ts`):凭据脱敏 `redactDeep`;
|
|
12
|
+
* · 会话存档读面(`audit.ts` 的 `toolResult.details` 叶):模型文本段脱敏的深扫(`redactDeepWith(v, redactModelText)`)。
|
|
13
|
+
* `redact` 必须是整值→整值的纯函数(环 / 深度守卫在它里面,遍历不重做);非媒体卡原样交给它,所以非媒体卡的读数就是调用面自己的深扫。
|
|
9
14
|
*/
|
|
10
|
-
export declare function projectToolStructured(structured: unknown): unknown;
|
|
15
|
+
export declare function projectToolStructured(structured: unknown, redact: (value: unknown) => unknown): unknown;
|
|
11
16
|
/**
|
|
12
17
|
* `tool_end` 载荷的媒体降级(持久面恒用;活面只在连接积压时用),**一次调用降两个座位**:
|
|
13
18
|
* · `output`:带 `data` 的媒体块换成描述符,有任何一块被换 ⇒ 帧级 `truncated: true` 且帧级 `totalChars` 在场 —— core 不变量
|
|
14
19
|
* 「Present iff truncated」:被界之前那个值的真实大小。帧上已有 `totalChars`(core 已截断:文本被切而媒体留着)⇒ **原样保留**
|
|
15
|
-
* (它说的是更早的原值,本仓手里的已是切过的值,重算只会低报);没有 ⇒
|
|
20
|
+
* (它说的是更早的原值,本仓手里的已是切过的值,重算只会低报);没有 ⇒ 按引擎的逐块计数器 `toolOutputBlockChars` 求和(core 7.51.1 导出,`tool_end.totalChars` 加总的就是它;本仓 7.128.x 及以前的拷贝
|
|
21
|
+
* `trueSizeOf` 随之删除)(core 没截,`output` 就是原值 ——
|
|
16
22
|
* 文本块是本服务脱敏后的字节,即本服务交付的内容)。已是描述符的块(无 `data`)原样 ⇒ 幂等。
|
|
17
23
|
* · `structured`:媒体卡去掉 `file.base64`(core 卡形自己的「不带字节」形),其余成员逐字。帧级 `truncated` / `totalChars` 只说 `output`,
|
|
18
24
|
* 单独剥卡**不动**它们(`output` 已是 core 描述符而卡仍带 ≤48K 字节的帧,和卡超 48K 时 core 自己略去 `base64` 的帧,在这两个键上读数一致)。
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
+
import { isMediaBlock, mediaBlockDescriptor, toolOutputBlockChars } from "@sema-agent/core";
|
|
1
2
|
import { redactDeep } from "./redact.js";
|
|
2
3
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
3
|
-
function isMediaBlock(b) {
|
|
4
|
-
if (b === null || typeof b !== "object" || Array.isArray(b))
|
|
5
|
-
return false;
|
|
6
|
-
const block = b;
|
|
7
|
-
return (block.type === "image" || block.type === "document") && typeof block.data === "string";
|
|
8
|
-
}
|
|
9
|
-
const MIME_ECHO_MAX = 255;
|
|
10
4
|
export function projectToolOutput(output) {
|
|
11
5
|
if (!Array.isArray(output))
|
|
12
6
|
return redactDeep(output);
|
|
@@ -21,20 +15,11 @@ function isMediaCard(s) {
|
|
|
21
15
|
const file = card.file;
|
|
22
16
|
return file !== null && typeof file === "object" && !Array.isArray(file) && typeof file.base64 === "string";
|
|
23
17
|
}
|
|
24
|
-
export function projectToolStructured(structured) {
|
|
18
|
+
export function projectToolStructured(structured, redact) {
|
|
25
19
|
if (!isMediaCard(structured))
|
|
26
|
-
return
|
|
27
|
-
const file = Object.fromEntries(Object.entries(structured.file).map(([k, v]) => [k, k === "base64" ? v :
|
|
28
|
-
return Object.fromEntries(Object.entries(structured).map(([k, v]) => [k, k === "file" ? file :
|
|
29
|
-
}
|
|
30
|
-
function trueSizeOf(b) {
|
|
31
|
-
if (isMediaBlock(b))
|
|
32
|
-
return b.data.length;
|
|
33
|
-
if (b !== null && typeof b === "object" && !Array.isArray(b) && b.type === "text" && typeof b.text === "string") {
|
|
34
|
-
const { type: _type, text, ...rest } = b;
|
|
35
|
-
return text.length + Math.max(0, (JSON.stringify(rest)?.length ?? 2) - 2);
|
|
36
|
-
}
|
|
37
|
-
return JSON.stringify(b)?.length ?? 0;
|
|
20
|
+
return redact(structured);
|
|
21
|
+
const file = Object.fromEntries(Object.entries(structured.file).map(([k, v]) => [k, k === "base64" ? v : redact(v)]));
|
|
22
|
+
return Object.fromEntries(Object.entries(structured).map(([k, v]) => [k, k === "file" ? file : redact(v)]));
|
|
38
23
|
}
|
|
39
24
|
export function toolEndMediaAsDescriptors(data) {
|
|
40
25
|
const output = data.output;
|
|
@@ -45,15 +30,8 @@ export function toolEndMediaAsDescriptors(data) {
|
|
|
45
30
|
const next = { ...data };
|
|
46
31
|
if (outputHasMedia) {
|
|
47
32
|
const blocks = output;
|
|
48
|
-
const totalChars = typeof data.totalChars === "number" && Number.isFinite(data.totalChars) ? data.totalChars : blocks.reduce((n, b) => n +
|
|
49
|
-
next.output = blocks.map((b) => isMediaBlock(b)
|
|
50
|
-
? {
|
|
51
|
-
type: b.type,
|
|
52
|
-
...(typeof b.mimeType === "string" && b.mimeType.length <= MIME_ECHO_MAX ? { mimeType: b.mimeType } : {}),
|
|
53
|
-
bytes: Math.floor((b.data.length * 3) / 4),
|
|
54
|
-
truncated: true,
|
|
55
|
-
}
|
|
56
|
-
: b);
|
|
33
|
+
const totalChars = typeof data.totalChars === "number" && Number.isFinite(data.totalChars) ? data.totalChars : blocks.reduce((n, b) => n + toolOutputBlockChars(b), 0);
|
|
34
|
+
next.output = blocks.map((b) => (isMediaBlock(b) ? mediaBlockDescriptor(b) : b));
|
|
57
35
|
next.truncated = true;
|
|
58
36
|
next.totalChars = totalChars;
|
|
59
37
|
}
|
|
@@ -15,6 +15,7 @@ import { judgeModelRouteEndpoint } from "../model-route-endpoint.js";
|
|
|
15
15
|
import { gateIntentUnserviceableReason } from "../approval.js";
|
|
16
16
|
import { refusedGroups } from "./apply-ledger.js";
|
|
17
17
|
import { judgeConfigCandidate } from "./candidate-gate.js";
|
|
18
|
+
import { declareServerWithheldEnv, withdrawServerWithheldEnv } from "../server-secret-env.js";
|
|
18
19
|
const charsPerTokenOf = (v) => (typeof v === "number" && Number.isFinite(v) && v > 0 ? v : undefined);
|
|
19
20
|
function envDerivedModel(config) {
|
|
20
21
|
const m = config.envModel;
|
|
@@ -97,17 +98,21 @@ export function applyEffective(config, eff, logger, opts) {
|
|
|
97
98
|
return false;
|
|
98
99
|
}
|
|
99
100
|
const mp = staged.modelPlaneGroup.stages.models;
|
|
100
|
-
if (mp !== undefined
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
101
|
+
if (mp !== undefined) {
|
|
102
|
+
declareCandidateWithheldEnv(mp.modelApiKeyEnv, staged.version);
|
|
103
|
+
if (opts.swapPlane !== undefined) {
|
|
104
|
+
try {
|
|
105
|
+
opts.swapPlane({ models: mp.models, tiers: mp.activeTiers });
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
withdrawServerWithheldEnv();
|
|
109
|
+
logger?.warn("web_admin_model_plane_swap_refused", {
|
|
110
|
+
version: staged.version,
|
|
111
|
+
error: err instanceof Error ? err.message : String(err),
|
|
112
|
+
note: "the Runner refused the candidate model plane (swapDeps screens the whole candidate atomically — the current generation stays in force untouched); candidate refused whole, re-judged next poll (/health shows target≠applied while it persists)",
|
|
113
|
+
});
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
111
116
|
}
|
|
112
117
|
}
|
|
113
118
|
commitStaged(config, staged);
|
|
@@ -141,6 +146,18 @@ export function applyEffective(config, eff, logger, opts) {
|
|
|
141
146
|
logger?.info("web_admin_projects", { projects: Object.keys(staged.projects) });
|
|
142
147
|
return true;
|
|
143
148
|
}
|
|
149
|
+
function declareCandidateWithheldEnv(modelApiKeyEnv, version) {
|
|
150
|
+
try {
|
|
151
|
+
declareServerWithheldEnv(modelApiKeyEnv);
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
const entries = Object.entries(modelApiKeyEnv)
|
|
155
|
+
.map(([model, envName]) => `${model}→${envName}`)
|
|
156
|
+
.join(", ");
|
|
157
|
+
throw new Error(`model catalog (effective v${version}): a model entry's apiKeyEnv names a variable the engine cannot withhold from model-driven shells — ` +
|
|
158
|
+
`the candidate is refused whole and the current generation keeps serving; fix that entry's apiKeyEnv (entries: ${entries}). Engine: ${err instanceof Error ? err.message : String(err)}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
144
161
|
function commitStaged(config, staged) {
|
|
145
162
|
const mp = staged.modelPlaneGroup.stages.models;
|
|
146
163
|
if (mp) {
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, WebAdminHttpError, fetchSkillContent, fetchPromptArtifact, fetchPromptBlob, } from "./http-client.js";
|
|
51
51
|
export { mutateInPlace, applyEffective, type ApplyReport, applyRuntimeGates, applyRuntimeHot, logEffectiveDiff, MODEL_PLANE_DOMAINS, type ModelPlaneDomain, type ModelPlaneOf, withModelPlaneOf, fullyLandedForLkg, } from "./apply-effective.js";
|
|
52
52
|
export { restartReasons, modelPlaneChanged, type RestartSlice, type RestartSliceCtx, type EngineRestartSignal, } from "./restart-signal.js";
|
|
53
|
-
export {
|
|
54
|
-
export { applyCenterReadFace, CENTER_READ_FACE_LIVE_KEYS, type CenterReadFaceApply, type CenterReadFaceReport, type ReadFaceBaseline } from "./read-face.js";
|
|
53
|
+
export { applySkillsManifest, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./skills-mcp.js";
|
|
54
|
+
export { applyCenterReadFace, buildReadFaceBaseline, buildReadFaceSwapCandidate, restoreReadFaceBaseline, CENTER_READ_FACE_LIVE_KEYS, type CenterReadFaceApply, type CenterReadFaceReport, type ReadFaceBaseline, type ReadFaceSeat } from "./read-face.js";
|
|
55
55
|
export type { CenterSkillManifest, CenterMcpServer, CenterA2aPeer, CenterModel, CenterReadFace, EffectiveConfig, ExecutionRuling, SessionMirrorRuling, } from "./types.js";
|
|
56
56
|
//# sourceMappingURL=facade.d.ts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, WebAdminHttpError, fetchSkillContent, fetchPromptArtifact, fetchPromptBlob, } from "./http-client.js";
|
|
2
2
|
export { mutateInPlace, applyEffective, applyRuntimeGates, applyRuntimeHot, logEffectiveDiff, MODEL_PLANE_DOMAINS, withModelPlaneOf, fullyLandedForLkg, } from "./apply-effective.js";
|
|
3
3
|
export { restartReasons, modelPlaneChanged, } from "./restart-signal.js";
|
|
4
|
-
export {
|
|
5
|
-
export { applyCenterReadFace, CENTER_READ_FACE_LIVE_KEYS } from "./read-face.js";
|
|
4
|
+
export { applySkillsManifest, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./skills-mcp.js";
|
|
5
|
+
export { applyCenterReadFace, buildReadFaceBaseline, buildReadFaceSwapCandidate, restoreReadFaceBaseline, CENTER_READ_FACE_LIVE_KEYS } from "./read-face.js";
|
|
6
6
|
//# sourceMappingURL=facade.js.map
|
|
@@ -88,8 +88,8 @@ export declare class WebAdminHttpError extends Error {
|
|
|
88
88
|
* sha256 matches the manifest hash (integrity + rewrite check). The content is immutable per hash, so the
|
|
89
89
|
* caller caches by hash and never re-fetches an unchanged skill. Throws a {@link WebAdminHttpError}
|
|
90
90
|
* (carrying `.status`) on an HTTP error — INCLUDING 404 — or a plain Error on hash mismatch. Callers that
|
|
91
|
-
* want "unknown hash → undefined" parity with the local backend inspect `.status === 404` and swallow it
|
|
92
|
-
*
|
|
91
|
+
* want "unknown hash → undefined" parity with the local backend inspect `.status === 404` and swallow it —
|
|
92
|
+
* `RemoteConfigProvider.fetchSkillContent` does exactly that, and it is the only caller on the skills read port.
|
|
93
93
|
*/
|
|
94
94
|
export declare function fetchSkillContent(center: CenterEndpoint, contentHash: string, fetchImpl?: typeof fetch): Promise<string>;
|
|
95
95
|
/** [ref]b / [ref]① — §9.4 by-digest 补拉二函(fetchSkillContent 同姿势:HTTP+Bearer+8s 超时+
|
|
@@ -1,22 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #A4([ref] 窗三件批 件1;[ref]/[ref] 承诺件)—— READ 容纳面四键的 **web-admin 消费腿**。
|
|
3
|
+
*
|
|
4
|
+
* 部署 env 的四键(`READ_FACE` / `READ_DENY_PATTERNS` / `READ_DENY_BUILTIN_TIERS` / `READ_DENY_BUILTIN_EXCLUDE`,见 config.ts)
|
|
5
|
+
* 此前是这条轴上的唯一入口:一个用 web-admin 管理整片 worker 的组织,想统一收紧 READ 面只能逐机改 env。本模块把
|
|
6
|
+
* `EffectiveConfig.readFace`(见 types.ts {@link CenterReadFace})投影到同样的四个 `ServiceConfig` 键上。
|
|
7
|
+
*
|
|
8
|
+
* ── 生效时机:**逐席由引擎答**(S-174 起 face;S-1560 / core 7.51.0 起四席全热)──────────────────────────
|
|
9
|
+
* 哪些席能热换,不是本仓的意见:`KEY_MAP` 每行的 `cfgKey` 就是 core 的席名,能不能热换由 core 的 `isSwappableDepSeat` 亲答
|
|
10
|
+
* ({@link CENTER_READ_FACE_LIVE_KEYS})。本模块按这**一个**判据分相({@link CenterReadFaceApply}):
|
|
11
|
+
* · `boot` 相 —— 四键全落(Runner 还没建,每一席都还会 bake);
|
|
12
|
+
* · `live` 相(refresh 拍)—— **只落引擎有活体席的键**,回执 {@link CenterReadFaceReport.changedSeats} 报这一拍换了值的席,
|
|
13
|
+
* 调用方据它向全部 boot Runner `swapDeps` 一次。引擎**没有**活体席的键 live 相**刻意不落**:写进 `config` 会让
|
|
14
|
+
* `/health` / wiring 读面宣称一个引擎并没有在执行的 READ 姿态(而 `read-face` restart 片仍在为它要一次重启)—— 那正是这条
|
|
15
|
+
* 轴上最危险的谎(读面说收紧了、执行面没有)。
|
|
16
|
+
* 落配、`applied` 日志、换席候选、重启片的指纹读的是**同一个**判据 —— core 把某席移进 / 移出闭集,四处自动一起走
|
|
17
|
+
* (core 7.51.0 把读拒三席移了进去,而 live 相的落配当时是一句手写的「deny 三键只在 boot 相落」,与另三处的派生读法分家:
|
|
18
|
+
* 重启片说「热了」、落配说「不落」,组织收紧读拒规则 ⇒ 既不生效也不要求重启。S-1560 收成一个判据)。
|
|
19
|
+
*
|
|
20
|
+
* ── 三条规则,每条都有失败方向的论证 ──────────────────────────────────────────────────────────
|
|
21
|
+
*
|
|
22
|
+
* ① **env 显式恒赢,逐键判**(operator-knob 律)。env 是**这台机器**的部署主权表达,center 是组织下发;
|
|
23
|
+
* 冲突时保留 env 并留一条点名 warn。为什么不照 6 闸的「center 覆盖 env」:那六闸是**限额/治理**面
|
|
24
|
+
* (组织比单机更知道该收多紧),而 READ 容纳面描述的是**这台机器的磁盘上有什么** —— 运维在这台机器上
|
|
25
|
+
* 写下 `READ_FACE=roots` 时知道的事情,组织的下发面不知道。静默盖掉它是本仓「部署级旋钮」纪律的反面。
|
|
26
|
+
* 逐键而非整域:env 只写了 face 时,center 的三个 deny 键照常落(否则一个 env 键会把整片组织策略挡掉)。
|
|
27
|
+
* 判据 = **键在场**(`!== undefined`):`readDenyBuiltinTiers: []` 是 env `none` 的显式空姿态,
|
|
28
|
+
* 不是「没设过」—— 用真值判会把「一档内建都不要」这个显式表态判成缺席。
|
|
29
|
+
* 🔴 判在**基线**上(本机 env / posture,center 之前的值),不是判「键现在在不在场」:center 自己上一拍写下的值
|
|
30
|
+
* 也让键在场,拿它当 env 就会把组织自己的第二次发布挡在门外。
|
|
31
|
+
*
|
|
32
|
+
* ② **坏值整域拒 + 响亮**([ref] 安全轴)。任何一个成员过不了引擎自己的校验器 ⇒ 整个 readFace 域不落,
|
|
33
|
+
* 保留 env 前值 + warn 点名。两个方向都要:半应用一份 READ 姿态(收紧了一半)会让运维以为策略生效;
|
|
34
|
+
* 而 throw 会让一次坏发布把常驻进程炸掉(env 腿 throw 是对的 —— 那是启动期、由本机运维自己写的值)。
|
|
35
|
+
* 与 `stageApprovalRequire` 的域级拒绝先例同形。
|
|
36
|
+
*
|
|
37
|
+
* ③ **校验一律交给引擎**(`compileReadDeny` / `resolveReadDenyBuiltins`)。宿主不自造第二份判定语义,
|
|
38
|
+
* 与 env 腿逐字同款;且校验跑在**合成后的最终姿态**上(env 键 + center 键混合形),否则
|
|
39
|
+
* 「env 选了档位、center 删了一个不在该档里的行名」这类跨腿坏组合会漏过。
|
|
40
|
+
*/
|
|
41
|
+
import { type SwappableDeps } from "@sema-agent/core";
|
|
1
42
|
import type { ServiceConfig } from "../config-types.js";
|
|
2
43
|
import type { ReadFaceSource } from "../read-face-posture.js";
|
|
3
44
|
import type { Logger } from "../observability/logger.js";
|
|
4
45
|
import type { CenterReadFace, EffectiveConfig } from "./types.js";
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* `
|
|
14
|
-
|
|
15
|
-
export interface ReadFaceBaseline {
|
|
16
|
-
readonly readFace?: "open" | "roots";
|
|
46
|
+
/** 四个 center 成员落到的 `ServiceConfig` 键 —— 同时就是 core 的席名(`SWAPPABLE_DEP_SEATS` 的成员名)。 */
|
|
47
|
+
export type ReadFaceSeat = "readFace" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude";
|
|
48
|
+
type ReadFaceSeatValues = {
|
|
49
|
+
readonly [K in ReadFaceSeat]?: ServiceConfig[K];
|
|
50
|
+
};
|
|
51
|
+
/** 本进程的**前 center** READ 姿态(env / posture / 引擎默认)—— 四键的值 + `readFace` 的来源。三处读它:
|
|
52
|
+
* ① env-wins 判据(基线上谁被 env 占住);② `live` 相的撤回臂(一次发布**撤回**某键时,活席要还原到它,而不是把上一份发布的
|
|
53
|
+
* 值无限期 latch 住 —— 那是这条轴上的 fail-OPEN 方向:组织把 `open` / 一条 deny 规则撤回之后,worker 还在旧姿态上);
|
|
54
|
+
* ③ 换席抛时的整体回滚(由 {@link buildReadFaceBaseline} 在调用前现取一份)。boot 相不需要传:那一刻 `config` 上的值就是基线。 */
|
|
55
|
+
export type ReadFaceBaseline = ReadFaceSeatValues & {
|
|
17
56
|
readonly readFaceSource: ReadFaceSource;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
57
|
+
};
|
|
58
|
+
/** 取 `config` 此刻的 READ 姿态快照(纯数据;数组按引用 —— 本模块只整值替换、从不原地改)。 */
|
|
59
|
+
export declare function buildReadFaceBaseline(config: ServiceConfig): ReadFaceBaseline;
|
|
60
|
+
/** 把 `config` 的 READ 姿态整体写回一份快照(换席抛时的回滚;快照里缺席的键写回 `undefined`)。 */
|
|
61
|
+
export declare function restoreReadFaceBaseline(config: ServiceConfig, snap: ReadFaceBaseline): void;
|
|
62
|
+
export declare const CENTER_READ_FACE_LIVE_KEYS: ReadonlySet<keyof CenterReadFace>;
|
|
63
|
+
/** 本次调用落在哪一相(见文件头注「逐席由引擎答」)。判别联合而不是可选字段:`baseline` 只在
|
|
20
64
|
* `live` 相有意义,写成可选就等于允许一次「没有基线的 live 调用」——那正是撤回臂会静默失效的形。 */
|
|
21
65
|
export type CenterReadFaceApply = {
|
|
22
66
|
readonly phase: "boot";
|
|
@@ -24,20 +68,29 @@ export type CenterReadFaceApply = {
|
|
|
24
68
|
readonly phase: "live";
|
|
25
69
|
readonly baseline: ReadFaceBaseline;
|
|
26
70
|
};
|
|
27
|
-
/** 调用回执。`envHeld` 同旧返回值(喂 `RestartSliceCtx.readFaceEnvHeld`);`
|
|
28
|
-
*
|
|
71
|
+
/** 调用回执。`envHeld` 同旧返回值(喂 `RestartSliceCtx.readFaceEnvHeld`);`changedSeats` 是 live 相的换席信号 ——
|
|
72
|
+
* 这一拍**值真的变了**的活体席(按 {@link ReadFaceSeat} 席名)。非空 ⇒ 调用方必须把这些席的值 `swapDeps` 给全部 boot
|
|
73
|
+
* Runner 一次,否则 config 与 Runner 分家。boot 相无人消费(Runner 还没建)。 */
|
|
29
74
|
export interface CenterReadFaceReport {
|
|
30
75
|
readonly envHeld: readonly (keyof CenterReadFace)[];
|
|
31
|
-
readonly
|
|
76
|
+
readonly changedSeats: readonly ReadFaceSeat[];
|
|
32
77
|
}
|
|
33
78
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
79
|
+
* 换席候选:`changed`(回执的 {@link CenterReadFaceReport.changedSeats})里每一席取 `seats` 里的值。
|
|
80
|
+
* `seats` 由调用方从部署姿态座席族的唯一属主取(`boot/runner-deps.ts` `buildDeploymentPostureSeats`)—— 席的**值**是座席族
|
|
81
|
+
* 铸的(例如 `readDenyPatterns` 并入 LKG 派生行),本模块只决定**换哪几席**。键在场即指令:值为 `undefined` 的席也进候选
|
|
82
|
+
* (core 据此把那一席清回引擎缺省 —— 撤回臂靠它兑现)。
|
|
83
|
+
*/
|
|
84
|
+
export declare function buildReadFaceSwapCandidate(seats: Pick<SwappableDeps, ReadFaceSeat>, changed: readonly ReadFaceSeat[]): SwappableDeps;
|
|
85
|
+
/**
|
|
86
|
+
* 应用 center 的 readFace 域(boot 相:调用点在 boot 拉取之后、`createRunnerDeps` 之前;live 相:refresh 拍两条 commit 腿)。
|
|
87
|
+
* 缺席 ⇒ 零触碰(live 相:撤回臂把活体席还原到基线)。规则见文件头注 ①②③。
|
|
36
88
|
*
|
|
37
|
-
* 返回值 = **本机 env 显式占住的 center 键名**(与本次发布内容无关的部署事实)。调用方把它喂给
|
|
89
|
+
* 返回值 `envHeld` = **本机 env 显式占住的 center 键名**(与本次发布内容无关的部署事实)。调用方把它喂给
|
|
38
90
|
* `RestartSliceCtx.readFaceEnvHeld`,`read-face` restart 片于是只指纹「真会 boot-bake 的那部分」——
|
|
39
91
|
* 否则 center 改一个本机永远用不上的键,整片 fleet 会为一次对自己毫无影响的发布滚动重启一遍
|
|
40
92
|
* (codex 交叉复审 finding 2,验真后采纳的那一半)。
|
|
41
93
|
*/
|
|
42
94
|
export declare function applyCenterReadFace(config: ServiceConfig, eff: EffectiveConfig, logger?: Logger, apply?: CenterReadFaceApply): CenterReadFaceReport;
|
|
95
|
+
export {};
|
|
43
96
|
//# sourceMappingURL=read-face.d.ts.map
|
|
@@ -1,11 +1,43 @@
|
|
|
1
1
|
import { compileReadDeny, isSwappableDepSeat, resolveReadDenyBuiltins } from "@sema-agent/core";
|
|
2
|
+
export function buildReadFaceBaseline(config) {
|
|
3
|
+
return {
|
|
4
|
+
...(config.readFace !== undefined ? { readFace: config.readFace } : {}),
|
|
5
|
+
readFaceSource: config.readFaceSource,
|
|
6
|
+
...(config.readDenyPatterns !== undefined ? { readDenyPatterns: config.readDenyPatterns } : {}),
|
|
7
|
+
...(config.readDenyBuiltinTiers !== undefined ? { readDenyBuiltinTiers: config.readDenyBuiltinTiers } : {}),
|
|
8
|
+
...(config.readDenyBuiltinExclude !== undefined ? { readDenyBuiltinExclude: config.readDenyBuiltinExclude } : {}),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function restoreReadFaceBaseline(config, snap) {
|
|
12
|
+
config.readFace = snap.readFace;
|
|
13
|
+
config.readFaceSource = snap.readFaceSource;
|
|
14
|
+
config.readDenyPatterns = snap.readDenyPatterns;
|
|
15
|
+
config.readDenyBuiltinTiers = snap.readDenyBuiltinTiers;
|
|
16
|
+
config.readDenyBuiltinExclude = snap.readDenyBuiltinExclude;
|
|
17
|
+
}
|
|
2
18
|
const KEY_MAP = [
|
|
3
|
-
{ centerKey: "face", cfgKey: "readFace", envHolds: (
|
|
4
|
-
{ centerKey: "denyPatterns", cfgKey: "readDenyPatterns", envHolds: (
|
|
5
|
-
{ centerKey: "denyBuiltinTiers", cfgKey: "readDenyBuiltinTiers", envHolds: (
|
|
6
|
-
{ centerKey: "denyBuiltinExclude", cfgKey: "readDenyBuiltinExclude", envHolds: (
|
|
19
|
+
{ centerKey: "face", cfgKey: "readFace", envHolds: (b) => b.readFaceSource === "env" },
|
|
20
|
+
{ centerKey: "denyPatterns", cfgKey: "readDenyPatterns", envHolds: (b) => b.readDenyPatterns !== undefined },
|
|
21
|
+
{ centerKey: "denyBuiltinTiers", cfgKey: "readDenyBuiltinTiers", envHolds: (b) => b.readDenyBuiltinTiers !== undefined },
|
|
22
|
+
{ centerKey: "denyBuiltinExclude", cfgKey: "readDenyBuiltinExclude", envHolds: (b) => b.readDenyBuiltinExclude !== undefined },
|
|
7
23
|
];
|
|
8
|
-
|
|
24
|
+
const LIVE_SEATS = new Set(KEY_MAP.filter((r) => isSwappableDepSeat(r.cfgKey)).map((r) => r.cfgKey));
|
|
25
|
+
export const CENTER_READ_FACE_LIVE_KEYS = new Set(KEY_MAP.filter((r) => LIVE_SEATS.has(r.cfgKey)).map((r) => r.centerKey));
|
|
26
|
+
function putCandidateSeat(candidate, key, value) {
|
|
27
|
+
candidate[key] = value;
|
|
28
|
+
}
|
|
29
|
+
export function buildReadFaceSwapCandidate(seats, changed) {
|
|
30
|
+
const candidate = {};
|
|
31
|
+
for (const seat of changed)
|
|
32
|
+
putCandidateSeat(candidate, seat, seats[seat]);
|
|
33
|
+
return candidate;
|
|
34
|
+
}
|
|
35
|
+
function putSeat(config, key, value) {
|
|
36
|
+
config[key] = value;
|
|
37
|
+
}
|
|
38
|
+
function sameSeatValue(a, b) {
|
|
39
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
40
|
+
}
|
|
9
41
|
class ReadFaceDomainError extends Error {
|
|
10
42
|
}
|
|
11
43
|
function normalizeDenyPatterns(raw) {
|
|
@@ -44,18 +76,25 @@ function normalizeNameList(field, raw) {
|
|
|
44
76
|
});
|
|
45
77
|
}
|
|
46
78
|
export function applyCenterReadFace(config, eff, logger, apply = { phase: "boot" }) {
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
|
|
79
|
+
const base = apply.phase === "live" ? apply.baseline : buildReadFaceBaseline(config);
|
|
80
|
+
const envHeldKeys = KEY_MAP.filter((r) => r.envHolds(base)).map((r) => r.centerKey);
|
|
81
|
+
const lands = (seat) => apply.phase === "boot" || LIVE_SEATS.has(seat);
|
|
82
|
+
const before = buildReadFaceBaseline(config);
|
|
83
|
+
const report = () => ({
|
|
84
|
+
envHeld: envHeldKeys,
|
|
85
|
+
changedSeats: KEY_MAP.filter((r) => LIVE_SEATS.has(r.cfgKey) && !sameSeatValue(before[r.cfgKey], config[r.cfgKey])).map((r) => r.cfgKey),
|
|
86
|
+
});
|
|
87
|
+
const revertToBaseline = (r) => {
|
|
88
|
+
if (apply.phase !== "live" || !lands(r.cfgKey))
|
|
52
89
|
return;
|
|
53
|
-
config.
|
|
54
|
-
|
|
90
|
+
putSeat(config, r.cfgKey, base[r.cfgKey]);
|
|
91
|
+
if (r.cfgKey === "readFace")
|
|
92
|
+
config.readFaceSource = base.readFaceSource;
|
|
55
93
|
};
|
|
56
94
|
const domain = eff.readFace;
|
|
57
95
|
if (domain === undefined || domain === null) {
|
|
58
|
-
|
|
96
|
+
for (const r of KEY_MAP)
|
|
97
|
+
revertToBaseline(r);
|
|
59
98
|
return report();
|
|
60
99
|
}
|
|
61
100
|
if (typeof domain !== "object" || Array.isArray(domain)) {
|
|
@@ -66,7 +105,7 @@ export function applyCenterReadFace(config, eff, logger, apply = { phase: "boot"
|
|
|
66
105
|
return report();
|
|
67
106
|
}
|
|
68
107
|
const d = domain;
|
|
69
|
-
const envHeld = KEY_MAP.filter((r) => d[r.centerKey] !== undefined && r.envHolds(
|
|
108
|
+
const envHeld = KEY_MAP.filter((r) => d[r.centerKey] !== undefined && r.envHolds(base)).map((r) => `${r.cfgKey} (readFace.${r.centerKey})`);
|
|
70
109
|
let staged;
|
|
71
110
|
try {
|
|
72
111
|
const normalized = {
|
|
@@ -80,10 +119,12 @@ export function applyCenterReadFace(config, eff, logger, apply = { phase: "boot"
|
|
|
80
119
|
if (normalized.readDenyBuiltinTiers !== undefined || normalized.readDenyBuiltinExclude !== undefined) {
|
|
81
120
|
resolveReadDenyBuiltins(builtinsArg(normalized.readDenyBuiltinTiers, normalized.readDenyBuiltinExclude));
|
|
82
121
|
}
|
|
83
|
-
staged = Object.fromEntries(KEY_MAP.filter((r) => !r.envHolds(
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
122
|
+
staged = Object.fromEntries(KEY_MAP.filter((r) => !r.envHolds(base)).map((r) => [r.cfgKey, normalized[r.cfgKey]]).filter(([, v]) => v !== undefined));
|
|
123
|
+
const baseSeats = base;
|
|
124
|
+
const finalOf = (k) => staged[k] ?? (lands(k) ? baseSeats[k] : config[k]);
|
|
125
|
+
const finalPatterns = finalOf("readDenyPatterns");
|
|
126
|
+
const finalTiers = finalOf("readDenyBuiltinTiers");
|
|
127
|
+
const finalExclude = finalOf("readDenyBuiltinExclude");
|
|
87
128
|
if (finalPatterns !== undefined)
|
|
88
129
|
compileReadDeny(finalPatterns);
|
|
89
130
|
if (finalTiers !== undefined || finalExclude !== undefined)
|
|
@@ -96,20 +137,19 @@ export function applyCenterReadFace(config, eff, logger, apply = { phase: "boot"
|
|
|
96
137
|
});
|
|
97
138
|
return report();
|
|
98
139
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
config.
|
|
111
|
-
|
|
112
|
-
config.readDenyBuiltinExclude = staged.readDenyBuiltinExclude;
|
|
140
|
+
const landed = [];
|
|
141
|
+
for (const r of KEY_MAP) {
|
|
142
|
+
if (!lands(r.cfgKey))
|
|
143
|
+
continue;
|
|
144
|
+
const v = staged[r.cfgKey];
|
|
145
|
+
if (v === undefined) {
|
|
146
|
+
revertToBaseline(r);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
putSeat(config, r.cfgKey, v);
|
|
150
|
+
if (r.cfgKey === "readFace")
|
|
151
|
+
config.readFaceSource = "center";
|
|
152
|
+
landed.push(r.cfgKey);
|
|
113
153
|
}
|
|
114
154
|
if (envHeld.length > 0) {
|
|
115
155
|
logger?.warn("web_admin_read_face_env_wins", {
|
|
@@ -117,13 +157,13 @@ export function applyCenterReadFace(config, eff, logger, apply = { phase: "boot"
|
|
|
117
157
|
note: "this machine's explicit env READ-face keys WIN over the published readFace domain (deployment sovereignty) — the published values for these keys were NOT applied; unset the env key if the org publication should own it",
|
|
118
158
|
});
|
|
119
159
|
}
|
|
120
|
-
if (
|
|
160
|
+
if (landed.length > 0) {
|
|
121
161
|
logger?.info("web_admin_read_face", {
|
|
122
|
-
applied:
|
|
123
|
-
...(staged.readFace !== undefined ? { face: staged.readFace } : {}),
|
|
162
|
+
applied: landed,
|
|
163
|
+
...(staged.readFace !== undefined && landed.includes("readFace") ? { face: staged.readFace } : {}),
|
|
124
164
|
note: apply.phase === "boot"
|
|
125
165
|
? "boot phase — every seat still bakes into the runner deps that are about to be assembled"
|
|
126
|
-
: "live phase — the
|
|
166
|
+
: "live phase — the landed seats are hot-swapped into every boot Runner (core swapDeps seats); a seat the engine cannot hot-swap is not written here and keeps asking for the `read-face` restart slice",
|
|
127
167
|
});
|
|
128
168
|
}
|
|
129
169
|
return report();
|