@sema-agent/server 7.123.0 → 7.124.1
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 +138 -0
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/USAGE.md +53 -51
- package/dist/approval-ask-audit-store.d.ts +27 -32
- package/dist/approval-ask-audit-store.js +10 -9
- package/dist/approval-crash-convergence.d.ts +2 -2
- package/dist/approval-crash-convergence.js +5 -5
- package/dist/approval-deny-reasons.d.ts +163 -51
- package/dist/approval-deny-reasons.js +79 -8
- package/dist/approval-policy-settlement.d.ts +4 -1
- package/dist/approval-policy-settlement.js +3 -2
- package/dist/approval-reconciler.d.ts +3 -3
- package/dist/approval-reconciler.js +10 -10
- package/dist/approval.d.ts +1 -1
- package/dist/approval.js +1 -1
- package/dist/audit.js +1 -1
- package/dist/auth-bridge.d.ts +8 -8
- package/dist/auth-bridge.js +6 -6
- package/dist/auth-keys.d.ts +2 -2
- package/dist/auto-mode-face.d.ts +1 -1
- package/dist/boot/budget-tracing.js +1 -1
- package/dist/boot/execution-env.js +1 -0
- package/dist/boot/leader.d.ts +1 -1
- package/dist/boot/memory-consolidation.d.ts +4 -4
- package/dist/boot/org-memory.d.ts +3 -3
- package/dist/boot/org-memory.js +5 -5
- package/dist/boot/resolve-spec.js +7 -7
- package/dist/boot/runner-deps.d.ts +1 -1
- package/dist/boot/runtime-caps.d.ts +1 -1
- package/dist/boot/runtime-caps.js +10 -10
- package/dist/boot/shutdown.d.ts +2 -2
- package/dist/boot/shutdown.js +2 -2
- package/dist/boot/side-query-lane.d.ts +3 -3
- package/dist/boot/{stage-02-registry-pull.d.ts → stage-02-web-admin-pull.d.ts} +2 -2
- package/dist/boot/{stage-02-registry-pull.js → stage-02-web-admin-pull.js} +4 -4
- package/dist/boot/stage-03-store-backend.d.ts +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.d.ts +1 -1
- package/dist/boot/stage-06-runners.d.ts +2 -2
- package/dist/boot/stage-06-runners.js +6 -6
- package/dist/boot/stage-07-capability-layer.d.ts +2 -2
- package/dist/boot/stage-07-capability-layer.js +6 -5
- package/dist/boot/stage-08-reapers.d.ts +3 -3
- package/dist/boot/stage-08-reapers.js +3 -3
- package/dist/boot/stage-09-leader.d.ts +3 -3
- package/dist/boot/stage-09-leader.js +7 -7
- package/dist/boot/stage-10-http-server.d.ts +3 -3
- package/dist/boot/stage-10-http-server.js +19 -19
- package/dist/boot/unpriced-ceiling-verdict.d.ts +1 -1
- package/dist/boot/{config-center.d.ts → web-admin.d.ts} +11 -11
- package/dist/boot/{config-center.js → web-admin.js} +38 -22
- package/dist/boot/webfetch-summarize-lane.d.ts +2 -2
- package/dist/brain.d.ts +1 -1
- package/dist/brain.js +1 -1
- package/dist/budget.d.ts +3 -3
- package/dist/canonical-json.d.ts +2 -2
- package/dist/capabilities/center-plugins.d.ts +1 -1
- package/dist/capabilities/center-wiring.d.ts +12 -5
- package/dist/capabilities/center-wiring.js +11 -2
- package/dist/capabilities/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/capabilities/scenarios.js +2 -2
- package/dist/capabilities/team.d.ts +1 -1
- package/dist/capabilities/tool-defer.d.ts +1 -1
- package/dist/center-credential-renewer.d.ts +1 -1
- package/dist/center-credential-renewer.js +7 -7
- package/dist/center-credential.d.ts +9 -9
- package/dist/center-credential.js +6 -6
- package/dist/config-catalog.d.ts +7 -10
- package/dist/config-catalog.js +15 -15
- package/dist/config-d-migration.d.ts +32 -0
- package/dist/config-d-migration.js +59 -0
- package/dist/config-invariants.d.ts +14 -4
- package/dist/config-invariants.js +31 -2
- package/dist/config-lkg.d.ts +26 -6
- package/dist/config-lkg.js +43 -14
- package/dist/config-provider.d.ts +23 -19
- package/dist/config-provider.js +24 -26
- package/dist/config-types.d.ts +52 -51
- package/dist/config.d.ts +9 -46
- package/dist/config.js +33 -92
- package/dist/execution-lane-caps.d.ts +1 -1
- package/dist/fleet/drain-entry.d.ts +1 -1
- package/dist/fleet/engine-signal.d.ts +8 -8
- package/dist/fleet/fleet-bus.d.ts +12 -3
- package/dist/fleet/fleet-bus.js +13 -9
- package/dist/fleet/serving-lease.d.ts +3 -3
- package/dist/fleet/serving-lease.js +1 -1
- package/dist/fleet-client.d.ts +7 -7
- package/dist/fleet-client.js +5 -4
- package/dist/fleet-lease.d.ts +3 -3
- package/dist/fleet-lease.js +2 -2
- package/dist/host-lsp-manager.d.ts +1 -1
- package/dist/hosted-posture.d.ts +4 -4
- package/dist/http/admission.js +4 -4
- package/dist/http/checkpoint-ctx.d.ts +20 -3
- package/dist/http/checkpoint-ctx.js +13 -5
- package/dist/http/dispatch.js +2 -2
- package/dist/http/durable-run-leg.js +2 -1
- package/dist/http/health-self-report.d.ts +4 -4
- package/dist/http/health-self-report.js +3 -3
- package/dist/http/idempotency.d.ts +1 -1
- package/dist/http/resume-legs.d.ts +4 -3
- package/dist/http/resume-legs.js +31 -18
- package/dist/http/route-ctx.d.ts +8 -0
- package/dist/http/routes/admin-config-refresh.d.ts +1 -1
- package/dist/http/routes/admin-config-refresh.js +1 -1
- package/dist/http/routes/admin-drain.d.ts +1 -1
- package/dist/http/routes/admin-drain.js +5 -4
- package/dist/http/routes/approvals-assistant.js +4 -4
- package/dist/http/routes/capabilities.js +5 -5
- package/dist/http/routes/config-catalog.js +2 -3
- package/dist/http/routes/diagnostics.js +1 -1
- package/dist/http/routes/runs.js +8 -4
- package/dist/http/routes/sessions-list.js +2 -1
- package/dist/http/routes/sessions.js +1 -1
- package/dist/http/routes/side-query.js +3 -3
- package/dist/http/routes/tasks.js +3 -2
- package/dist/http/run-meta.d.ts +1 -1
- package/dist/http/run-meta.js +2 -2
- package/dist/http/server.d.ts +7 -7
- package/dist/http/server.js +1 -1
- package/dist/http/stored-body-replay.js +3 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/key-resolver.d.ts +1 -1
- package/dist/leader/wire.d.ts +2 -2
- package/dist/leader/wire.js +2 -1
- package/dist/main.js +1 -1
- package/dist/mcp-tool-faces.d.ts +2 -2
- package/dist/model-entry-refusal.d.ts +3 -3
- package/dist/model-entry-refusal.js +4 -4
- package/dist/model-provider.d.ts +2 -2
- package/dist/model-route-endpoint.d.ts +1 -1
- package/dist/model-route-url.d.ts +1 -1
- package/dist/model-route-url.js +2 -1
- package/dist/model-select.d.ts +24 -21
- package/dist/model-select.js +22 -17
- package/dist/observability/metrics.d.ts +1 -1
- package/dist/org-memory-admission.d.ts +1 -1
- package/dist/plugins/approval-ask-store-file.d.ts +4 -2
- package/dist/plugins/approval-ask-store-file.js +3 -3
- package/dist/plugins/approval-ask-store-memory.d.ts +4 -3
- package/dist/plugins/approval-ask-store-memory.js +10 -4
- package/dist/plugins/approval-ask-store-sql.d.ts +16 -2
- package/dist/plugins/approval-ask-store-sql.js +20 -13
- package/dist/plugins/caching-session-store.d.ts +0 -10
- package/dist/plugins/caching-session-store.js +0 -3
- package/dist/plugins/file-run-store.js +9 -3
- package/dist/plugins/leader-run-store-sql.d.ts +3 -2
- package/dist/plugins/leader-run-store-sql.js +3 -2
- package/dist/plugins/local-session-store.d.ts +20 -27
- package/dist/plugins/local-session-store.js +19 -17
- package/dist/plugins/memory-run-store.js +9 -3
- package/dist/plugins/one-time-migrations.js +26 -2
- package/dist/plugins/scheduler-support.d.ts +26 -7
- package/dist/plugins/scheduler-support.js +96 -53
- package/dist/plugins/store-backend.js +1 -1
- package/dist/plugins/store-contracts.d.ts +20 -4
- package/dist/plugins/web-search.d.ts +1 -1
- package/dist/plugins/web-search.js +2 -1
- package/dist/posture-source.d.ts +1 -1
- package/dist/read-face-posture.d.ts +3 -3
- package/dist/read-face-posture.js +1 -1
- package/dist/run-local.js +6 -3
- package/dist/runtime-caps-resolver.d.ts +4 -4
- package/dist/runtime-caps-resolver.js +2 -2
- package/dist/runtime-governance.d.ts +5 -5
- package/dist/sealed-key.d.ts +4 -4
- package/dist/sealed-key.js +3 -2
- package/dist/security.d.ts +3 -3
- package/dist/service-version.d.ts +1 -1
- package/dist/service-version.js +5 -0
- package/dist/session-titler.d.ts +0 -11
- package/dist/session-titler.js +3 -5
- package/dist/shared-memory-scope-authorizer.d.ts +4 -4
- package/dist/task-a2a.d.ts +1 -1
- package/dist/tool-approval.d.ts +43 -4
- package/dist/tool-approval.js +147 -98
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/project.d.ts +3 -3
- package/dist/trace/redact.d.ts +2 -23
- package/dist/trace/redact.js +17 -15
- package/dist/trace/self-minted-wire.d.ts +134 -3
- package/dist/trace/self-minted-wire.js +46 -9
- package/dist/url-refusal.d.ts +35 -0
- package/dist/url-refusal.js +15 -0
- package/dist/user-authored-text.d.ts +31 -0
- package/dist/user-authored-text.js +18 -0
- package/dist/{config-center → web-admin-client}/apply-effective.d.ts +13 -70
- package/dist/{config-center → web-admin-client}/apply-effective.js +100 -119
- package/dist/{config-center → web-admin-client}/apply-ledger.d.ts +2 -2
- package/dist/{config-center → web-admin-client}/center-request.d.ts +7 -7
- package/dist/{config-center → web-admin-client}/center-request.js +5 -4
- package/dist/{config-center → web-admin-client}/effective-keys.d.ts +1 -3
- package/dist/{config-center → web-admin-client}/effective-keys.js +0 -9
- package/dist/{config-center → web-admin-client}/facade.d.ts +6 -6
- package/dist/{config-center → web-admin-client}/facade.js +2 -2
- package/dist/{config-center → web-admin-client}/http-client.d.ts +6 -10
- package/dist/{config-center → web-admin-client}/http-client.js +12 -12
- package/dist/{config-center → web-admin-client}/pull-state.d.ts +1 -1
- package/dist/{config-center → web-admin-client}/read-face.js +4 -4
- package/dist/{config-center → web-admin-client}/read-warnings.d.ts +23 -16
- package/dist/{config-center → web-admin-client}/read-warnings.js +33 -23
- package/dist/{config-center → web-admin-client}/restart-signal.d.ts +5 -3
- package/dist/{config-center → web-admin-client}/restart-signal.js +3 -4
- package/dist/{config-center → web-admin-client}/skills-mcp.d.ts +1 -1
- package/dist/{config-center → web-admin-client}/skills-mcp.js +13 -13
- package/dist/{config-center → web-admin-client}/stage-limits.d.ts +1 -7
- package/dist/{config-center → web-admin-client}/stage-limits.js +6 -7
- package/dist/{config-center → web-admin-client}/types.d.ts +12 -39
- package/package.json +2 -2
- package/skills/find-skills.md +2 -2
- package/dist/url-shape.d.ts +0 -31
- package/dist/url-shape.js +0 -41
- /package/dist/{config-center → web-admin-client}/apply-ledger.js +0 -0
- /package/dist/{config-center → web-admin-client}/hot-keys-registry.d.ts +0 -0
- /package/dist/{config-center → web-admin-client}/hot-keys-registry.js +0 -0
- /package/dist/{config-center → web-admin-client}/mcp-revocation.d.ts +0 -0
- /package/dist/{config-center → web-admin-client}/mcp-revocation.js +0 -0
- /package/dist/{config-center → web-admin-client}/pull-state.js +0 -0
- /package/dist/{config-center → web-admin-client}/read-face.d.ts +0 -0
- /package/dist/{config-center → web-admin-client}/types.js +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory,
|
|
2
|
-
export { mutateInPlace, applyEffective, applyRuntimeGates, applyRuntimeHot,
|
|
1
|
+
export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, WebAdminHttpError, fetchSkillContent, fetchPromptArtifact, fetchPromptBlob, } from "./http-client.js";
|
|
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
4
|
export { applyCenterSkills, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./skills-mcp.js";
|
|
5
5
|
export { applyCenterReadFace, CENTER_READ_FACE_LIVE_KEYS } from "./read-face.js";
|
|
@@ -9,11 +9,7 @@ import { type CenterEndpoint } from "./center-request.js";
|
|
|
9
9
|
* boot/refresh 的候选门直接消费。`droppedEntries`(S-704 ②):schema 逐条丢掉的条目(坐标 + 字段诊断)——
|
|
10
10
|
* **不**进候选门,调用方响亮点名、其余照用(分面单点 `splitFoldedReadWarnings`,与本地腿同一只)。
|
|
11
11
|
*
|
|
12
|
-
* `readWarnings`:契约 `onWarning` 流的**原值**(未折叠)
|
|
13
|
-
* `hosts-grandfathered` / `legacy-governance-lifted` 两员按律折成 `null`(值已被收编接受,见
|
|
14
|
-
* `read-warnings.ts` 头注),于是只看 `domainErrors` 的人**永远**看不见兼容臂有没有开着;而契约把
|
|
15
|
-
* `legacy-governance-lifted` 铸出来,理由逐字写在 `legacyGovernanceFromRuntime` 的 SUNSET 注里:
|
|
16
|
-
* 「关窗的前提是有人知道它还开着」。两条读腿各返一份原流,那句话在本仓才成立。
|
|
12
|
+
* `readWarnings`:契约 `onWarning` 流的**原值**(未折叠),与本地腿同键同义(读者:一致性门的读腿适配器,见本地腿同名键注)。
|
|
17
13
|
* 🔐 原流的 `error` 字段**未经** `redactConfigError`(那道脱敏挂在真正进日志流的 `domainErrors` 产出边界上)
|
|
18
14
|
* —— 任何把本键路由进结构化日志/审计流的消费者必须自己先脱敏。
|
|
19
15
|
*
|
|
@@ -36,12 +32,12 @@ export declare function fetchEffective(center: CenterEndpoint, etag: string | un
|
|
|
36
32
|
* Center pre-resolves the tighten-only intersection (fleet defaults ∩ tier ⊕ roles + overrides) and returns a
|
|
37
33
|
* `runtimeCaps` block — the service passes it through verbatim (no further tightening, per center's ruling).
|
|
38
34
|
*
|
|
39
|
-
* 🔐 TOKEN/WORKER (center ruling,
|
|
35
|
+
* 🔐 TOKEN/WORKER (center ruling, web-admin `aeb4979`): two valid postures —
|
|
40
36
|
* • FULL `SERVICE_PULL_TOKEN` + `?principal=` → full per-user body (caps + per-user config).
|
|
41
37
|
* • worker-scoped `wpt_<self>` + `?principal=&worker=<self>` → **caps-only** body (`{principal, configured,
|
|
42
38
|
* runtimeCaps, budget}`; never other tenants' config) — a worker MAY resolve its OWN worker's principals.
|
|
43
39
|
* A `wpt_` with NO `?worker=` (or a mismatched worker) still 403s. So we ALWAYS pass `?worker=` when the
|
|
44
|
-
* deployment has one (`
|
|
40
|
+
* deployment has one (`SEMA_WEB_ADMIN_WORKER`): it authorizes the wpt_ path AND is harmless on the full token
|
|
45
41
|
* (the caps value is worker-independent — `resolveEntitlement` resolves from the principal alone; worker only
|
|
46
42
|
* scopes the config view we ignore). This re-adds the `worker` arg dropped in 1.14.0 (L2-3) — center's ruling
|
|
47
43
|
* makes it load-bearing for AUTHORIZATION, not for the caps value.
|
|
@@ -77,9 +73,9 @@ export declare function fetchPrincipalCaps(center: CenterEndpoint, principal: st
|
|
|
77
73
|
* 因此 304 在传输层就是 fail-loud。
|
|
78
74
|
*/
|
|
79
75
|
export declare function fetchPrincipalOrgMemory(center: CenterEndpoint, principal: string, fetchImpl?: typeof fetch, worker?: string): Promise<unknown>;
|
|
80
|
-
/** An HTTP error from a
|
|
76
|
+
/** An HTTP error from a web-admin fetch that carries the response status so callers can branch on it
|
|
81
77
|
* (e.g. translate a 404 to "unknown hash → undefined") WITHOUT parsing the message string. */
|
|
82
|
-
export declare class
|
|
78
|
+
export declare class WebAdminHttpError extends Error {
|
|
83
79
|
readonly status: number;
|
|
84
80
|
constructor(message: string, status: number);
|
|
85
81
|
}
|
|
@@ -87,7 +83,7 @@ export declare class ConfigCenterHttpError extends Error {
|
|
|
87
83
|
* Fetch one skill's body by content hash (`GET <content root>/skills/content/<hash>`, Bearer; the root is
|
|
88
84
|
* `/api/v1/me` for a user-JWT credential and `/api/config` otherwise — center-request.ts) and VERIFY its
|
|
89
85
|
* sha256 matches the manifest hash (integrity + rewrite check). The content is immutable per hash, so the
|
|
90
|
-
* caller caches by hash and never re-fetches an unchanged skill. Throws a {@link
|
|
86
|
+
* caller caches by hash and never re-fetches an unchanged skill. Throws a {@link WebAdminHttpError}
|
|
91
87
|
* (carrying `.status`) on an HTTP error — INCLUDING 404 — or a plain Error on hash mismatch. Callers that
|
|
92
88
|
* want "unknown hash → undefined" parity with the local backend inspect `.status === 404` and swallow it;
|
|
93
89
|
* the legacy `applyCenterSkills` path keeps treating every throw as "skip this skill, keep baseline".
|
|
@@ -9,7 +9,7 @@ export async function fetchEffective(center, etag, fetchImpl = fetch, worker) {
|
|
|
9
9
|
if (res.status === 304)
|
|
10
10
|
return null;
|
|
11
11
|
if (!res.ok)
|
|
12
|
-
throw new Error(`
|
|
12
|
+
throw new Error(`web-admin HTTP ${res.status}`);
|
|
13
13
|
const warnings = [];
|
|
14
14
|
const body = await res.json();
|
|
15
15
|
const wire = readEffectiveWire(body, (w) => warnings.push(w));
|
|
@@ -35,7 +35,7 @@ export async function fetchPrincipalCaps(center, principal, etag, fetchImpl = fe
|
|
|
35
35
|
return null;
|
|
36
36
|
const parsedBody = PrincipalCapsWire.safeParse(raw.json);
|
|
37
37
|
if (!parsedBody.success) {
|
|
38
|
-
throw new Error(`
|
|
38
|
+
throw new Error(`web-admin principal-caps body failed schema validation: ${parsedBody.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ").slice(0, 600)}`);
|
|
39
39
|
}
|
|
40
40
|
const body = parsedBody.data;
|
|
41
41
|
assertPrincipalEcho(body.principal, principal);
|
|
@@ -79,7 +79,7 @@ export async function fetchPrincipalCaps(center, principal, etag, fetchImpl = fe
|
|
|
79
79
|
}
|
|
80
80
|
function assertPrincipalEcho(echoed, principal) {
|
|
81
81
|
if (echoed !== undefined && echoed !== principal) {
|
|
82
|
-
throw new Error(`
|
|
82
|
+
throw new Error(`web-admin principal-caps response echoes principal ${JSON.stringify(echoed)} but ${JSON.stringify(principal)} was requested — discarding the whole response`);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
async function requestPrincipalView(center, principal, etag, fetchImpl, worker) {
|
|
@@ -91,12 +91,12 @@ async function requestPrincipalView(center, principal, etag, fetchImpl, worker)
|
|
|
91
91
|
};
|
|
92
92
|
if (res.status === 304) {
|
|
93
93
|
if (!etag)
|
|
94
|
-
throw new Error("
|
|
94
|
+
throw new Error("web-admin returned 304 to a non-conditional principal-caps request");
|
|
95
95
|
stillCurrent();
|
|
96
96
|
return null;
|
|
97
97
|
}
|
|
98
98
|
if (!res.ok)
|
|
99
|
-
throw new Error(`
|
|
99
|
+
throw new Error(`web-admin principal-caps HTTP ${res.status}`);
|
|
100
100
|
const responseEtag = res.headers.get("etag");
|
|
101
101
|
const json = await res.json();
|
|
102
102
|
stillCurrent();
|
|
@@ -106,26 +106,26 @@ const PrincipalOrgMemoryEnvelope = PrincipalCapsWire.pick({ principal: true, org
|
|
|
106
106
|
export async function fetchPrincipalOrgMemory(center, principal, fetchImpl = fetch, worker) {
|
|
107
107
|
const raw = await requestPrincipalView(center, principal, undefined, fetchImpl, worker);
|
|
108
108
|
if (raw === null)
|
|
109
|
-
throw new Error("
|
|
109
|
+
throw new Error("web-admin returned 304 to a non-conditional org-directory fetch");
|
|
110
110
|
const parsed = PrincipalOrgMemoryEnvelope.safeParse(raw.json);
|
|
111
111
|
if (!parsed.success) {
|
|
112
|
-
throw new Error(`
|
|
112
|
+
throw new Error(`web-admin org-memory section failed schema validation: ${parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ").slice(0, 600)}`);
|
|
113
113
|
}
|
|
114
114
|
assertPrincipalEcho(parsed.data.principal, principal);
|
|
115
115
|
return parsed.data.orgMemory;
|
|
116
116
|
}
|
|
117
|
-
export class
|
|
117
|
+
export class WebAdminHttpError extends Error {
|
|
118
118
|
status;
|
|
119
119
|
constructor(message, status) {
|
|
120
120
|
super(message);
|
|
121
121
|
this.status = status;
|
|
122
|
-
this.name = "
|
|
122
|
+
this.name = "WebAdminHttpError";
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
export async function fetchSkillContent(center, contentHash, fetchImpl = fetch) {
|
|
126
126
|
const { res } = await centerFetch(center, { kind: "skill-content", contentHash }, { timeoutMs: CENTER_PULL_TIMEOUT_MS, fetchImpl });
|
|
127
127
|
if (!res.ok)
|
|
128
|
-
throw new
|
|
128
|
+
throw new WebAdminHttpError(`skill content HTTP ${res.status} for ${contentHash}`, res.status);
|
|
129
129
|
const content = await res.text();
|
|
130
130
|
const want = contentHash.replace(/^sha256:/, "").toLowerCase();
|
|
131
131
|
const got = createHash("sha256").update(content, "utf8").digest("hex");
|
|
@@ -136,13 +136,13 @@ export async function fetchSkillContent(center, contentHash, fetchImpl = fetch)
|
|
|
136
136
|
export async function fetchPromptArtifact(center, artifactDigest, fetchImpl = fetch) {
|
|
137
137
|
const { res } = await centerFetch(center, { kind: "prompt-artifact", digest: artifactDigest }, { timeoutMs: CENTER_PULL_TIMEOUT_MS, fetchImpl });
|
|
138
138
|
if (!res.ok)
|
|
139
|
-
throw new
|
|
139
|
+
throw new WebAdminHttpError(`prompt artifact HTTP ${res.status} for ${artifactDigest}`, res.status);
|
|
140
140
|
return await res.json();
|
|
141
141
|
}
|
|
142
142
|
export async function fetchPromptBlob(center, contentDigest, fetchImpl = fetch) {
|
|
143
143
|
const { res } = await centerFetch(center, { kind: "prompt-blob", digest: contentDigest }, { timeoutMs: CENTER_PULL_TIMEOUT_MS, fetchImpl });
|
|
144
144
|
if (!res.ok)
|
|
145
|
-
throw new
|
|
145
|
+
throw new WebAdminHttpError(`prompt blob HTTP ${res.status} for ${contentDigest}`, res.status);
|
|
146
146
|
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
147
147
|
const want = contentDigest.replace(/^sha256:/, "").toLowerCase();
|
|
148
148
|
const got = createHash("sha256").update(bytes).digest("hex");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* S-866 ②(设计稿 v1.1 §2②)—— 「这台引擎**此刻**是不是真的接着中心」的两件读数:最近一次实拉的结局(`lastPull`)与生效修订号
|
|
3
|
-
* (`effectiveRevision`)。属主 =
|
|
3
|
+
* (`effectiveRevision`)。属主 = web-admin 运行时(`boot/web-admin.ts` 的 `centerPullState()`);读者 = caps `centerWiring`。
|
|
4
4
|
*
|
|
5
5
|
* 纯叶(零值 import):词表与读面形住这里,运行时与读面两侧都从这里取型,不各写一份。
|
|
6
6
|
*/
|
|
@@ -59,7 +59,7 @@ export function applyCenterReadFace(config, eff, logger, apply = { phase: "boot"
|
|
|
59
59
|
return report();
|
|
60
60
|
}
|
|
61
61
|
if (typeof domain !== "object" || Array.isArray(domain)) {
|
|
62
|
-
logger?.warn("
|
|
62
|
+
logger?.warn("web_admin_read_face_invalid", {
|
|
63
63
|
detail: `published readFace domain must be an object with optional {face,denyPatterns,denyBuiltinTiers,denyBuiltinExclude} — got ${Array.isArray(domain) ? "array" : typeof domain}`,
|
|
64
64
|
kept: "the deployment's env READ-face posture is unchanged (refusing a malformed domain WHOLE — a half-applied read-containment posture is worse than none)",
|
|
65
65
|
});
|
|
@@ -90,7 +90,7 @@ export function applyCenterReadFace(config, eff, logger, apply = { phase: "boot"
|
|
|
90
90
|
resolveReadDenyBuiltins(builtinsArg(finalTiers, finalExclude));
|
|
91
91
|
}
|
|
92
92
|
catch (e) {
|
|
93
|
-
logger?.warn("
|
|
93
|
+
logger?.warn("web_admin_read_face_invalid", {
|
|
94
94
|
detail: e instanceof Error ? e.message : String(e),
|
|
95
95
|
kept: "the deployment's env READ-face posture is unchanged (refusing the readFace domain WHOLE — applying half of a read-containment posture would read as 'policy landed' while part of it silently did not)",
|
|
96
96
|
});
|
|
@@ -112,13 +112,13 @@ export function applyCenterReadFace(config, eff, logger, apply = { phase: "boot"
|
|
|
112
112
|
config.readDenyBuiltinExclude = staged.readDenyBuiltinExclude;
|
|
113
113
|
}
|
|
114
114
|
if (envHeld.length > 0) {
|
|
115
|
-
logger?.warn("
|
|
115
|
+
logger?.warn("web_admin_read_face_env_wins", {
|
|
116
116
|
keys: envHeld,
|
|
117
117
|
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
118
|
});
|
|
119
119
|
}
|
|
120
120
|
if (Object.keys(staged).length > 0) {
|
|
121
|
-
logger?.info("
|
|
121
|
+
logger?.info("web_admin_read_face", {
|
|
122
122
|
applied: Object.keys(staged).filter((k) => apply.phase === "boot" || KEY_MAP.some((r) => r.cfgKey === k && CENTER_READ_FACE_LIVE_KEYS.has(r.centerKey))),
|
|
123
123
|
...(staged.readFace !== undefined ? { face: staged.readFace } : {}),
|
|
124
124
|
note: apply.phase === "boot"
|
|
@@ -10,20 +10,17 @@
|
|
|
10
10
|
* 截断:schema 的键列表有界(64),超界带 `truncatedFrom`(真实总数)——文案如实带上,消费方永远不把有界列表当穷举。
|
|
11
11
|
* 词表是闭集:`switch` 穷举 `kind`,schema 加新 kind 这里编译红,不会静默漏臂。
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* **归族判据**(下游两个消费者:候选门 = 非空 ⇒ 这棵树整拒、保 LKG;逐条丢弃面 = 响亮点名、其余照用):每一种告警都是
|
|
14
|
+
* 「写了没生效」,按**失效的颗粒**分:丢的是**一条条目**(`entry-dropped`)⇒ `scope:"entry"` —— 这条条目单独不在,同域其余条目照用,
|
|
15
|
+
* 候选**不**因它整拒;丢的是一个域 / 一组键 / 一个文件 ⇒ `scope:"domain"` —— 候选门错误。(settings-schema 11 起读路径上零抬升臂:
|
|
16
|
+
* 此前「值已被收编接受」的两种告警 `hosts-grandfathered` / `legacy-governance-lifted` 随抬升臂一起退役,旧形由一次性迁移改写,
|
|
17
|
+
* 读侧遇旧形一律拒 —— 没有「不入诊断面」的那一族了。)
|
|
17
18
|
*
|
|
18
19
|
* 🔴 S-704 ②(2026-09-24,凭据轴):此前 `entry-dropped` 归「候选门错误」(settings-schema 2.0.0 提货时的归族),于是
|
|
19
20
|
* schema 的**逐条容错**在本仓的 refresh / 迟到 boot 两条腿上被**还原成整份拒** —— 目录里一条 `contextWindow:"abc"`
|
|
20
21
|
* 就让整份目录停在上一代;而上一代若还是 env(boot 拉取失败 / 超窗且无 LKG),env 模型就一直服务、env 钥匙一直被送到
|
|
21
22
|
* 网关缺省端点(红先实测:`config_candidate_rejected` + env 模型继续服务)。粒度对了规则反而更少:颗粒是条目的失效
|
|
22
23
|
* 留在条目上,只有颗粒更粗的失效才动候选。
|
|
23
|
-
* `legacy-governance-lifted`(0.5 期 runtime 文档里的治理三闸被抬进 governance 域)归①——那几个键**生效了**
|
|
24
|
-
* (`buildEffective` 用抬升结果解析 governance),把它折成错误会让一份合法的 0.5 期存量配置每一次 refresh 都被
|
|
25
|
-
* 候选门整拒,而它在 1.10.0 上零告警零 domainErrors(亲验读数)。兼容臂的关窗观测归属主(settings-schema 的
|
|
26
|
-
* `onWarning` 流),不借候选门这条带副作用的通道传信息。
|
|
27
24
|
*/
|
|
28
25
|
import { z } from "zod";
|
|
29
26
|
import { type DroppedEntryIdentity, type DroppedEntryReasonCode, type EffectiveReadWarning } from "@sema-agent/settings-schema";
|
|
@@ -49,19 +46,28 @@ export type DroppedConfigEntry = {
|
|
|
49
46
|
readonly reasonCode: DroppedEntryReasonCode;
|
|
50
47
|
};
|
|
51
48
|
/**
|
|
52
|
-
* zod 形的错误 → 一行可读的字段诊断(`contextWindow: expected number, received string;
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* 读不出 issues 形 ⇒ `undefined`(调用方退回既有文案)。模型面 stage
|
|
49
|
+
* zod 形的错误 → 一行可读的字段诊断(`contextWindow: Invalid input: expected number, received string; apiKeyEnv: <契约句>`)。
|
|
50
|
+
* 每条 issue 的句子只有一只读法 {@link issueSentenceOf}、坐标只有一只写法 {@link issueCoordinateOf}(整域面 {@link describeDomainIssues} 读同两只);
|
|
51
|
+
* 坐标缺席(issue 指整条)写 `(entry)`。
|
|
52
|
+
* 读不出 issues 形 ⇒ `undefined`(调用方退回既有文案)。模型面 stage 的逐条判据、目录载体的 `issues` 与本折叠点共用这一只(一个刻度)。
|
|
56
53
|
*/
|
|
57
54
|
export declare function describeSchemaIssues(error: unknown): string | undefined;
|
|
58
55
|
/**
|
|
59
|
-
* zod 形错误 → 坏字段的**键路径**(去重保序;`(entry)` = 整条)。与 {@link describeSchemaIssues}
|
|
56
|
+
* zod 形错误 → 坏字段的**键路径**(去重保序;`(entry)` = 整条)。与 {@link describeSchemaIssues} 同一只坐标写法({@link issueCoordinateOf}),
|
|
60
57
|
* 不含值。模型面 stage 的逐条判据(`judgeCatalogEntry`)与本折叠点的被丢条目身份共用这一只 —— 一个刻度。
|
|
61
58
|
*/
|
|
62
59
|
export declare function describeSchemaIssueFields(error: unknown): string[];
|
|
63
|
-
/**
|
|
64
|
-
export declare
|
|
60
|
+
/** 契约句子里**值**的位置在本仓诊断面上的写法(一个洞,不回显值;句子的固定词 —— 迁移指路、词表出路、字段名 —— 原样)。 */
|
|
61
|
+
export declare const SCHEMA_OPERAND_HOLE = "\u00ABvalue\u00BB";
|
|
62
|
+
/**
|
|
63
|
+
* 整域失效(`domain-defaulted`)的诊断文案:逐条 issue `坐标: 句子`,句子读 {@link issueSentenceOf}(与逐条丢弃面同一只):带结构化操作数的契约句重铸、
|
|
64
|
+
* 操作数换成 {@link SCHEMA_OPERAND_HOLE};不带的(zod 内建句、退役键墓碑句、迁移指路)原样。读不出 issues 形 ⇒ 退回 {@link errorText}。
|
|
65
|
+
* 为什么不直接用 `error.message`:zod 4 的 message 是整份 issues 的 JSON —— 本地腿出口的引号段指纹(`redactConfigError`)会把它从第一个
|
|
66
|
+
* 引号到最后一个引号整段吞掉,契约拒句里点名的迁移 id(「旧形读写两面响亮拒、拒句点名迁移 id」)就到不了运维眼前。
|
|
67
|
+
*/
|
|
68
|
+
export declare function describeDomainIssues(error: unknown): string;
|
|
69
|
+
/** 一条读告警 → 它的诊断面(每一种都进:见文件头「归族判据」)。 */
|
|
70
|
+
export declare function foldEffectiveReadWarning(w: EffectiveReadWarning): FoldedReadWarning;
|
|
65
71
|
/**
|
|
66
72
|
* 🔴 S-712 / S-1295 —— **被丢目录条目的载体行**(载体 = `EffectiveConfig.models.droppedCatalogEntries`,读腿铸,随 effective 走 —— 盘上 LKG
|
|
67
73
|
* 从同一个对象落盘,重启从 LKG 起服时它还在)。形的**唯一定义**(型由它推出;判据按它判盘上来的行)。
|
|
@@ -97,7 +103,8 @@ export type DroppedCatalogEntry = z.infer<typeof DroppedCatalogEntryRow>;
|
|
|
97
103
|
* 两条读腿(远端 `http-client.ts` / 本地 `config-provider.ts`)共用的**分面单点**:折叠产物按 `scope` 分进候选门
|
|
98
104
|
* (`domainErrors`)与逐条丢弃面(`droppedEntries`);逐条丢弃里的**目录条目**同一拍铸进目录载体(`droppedCatalogEntries`,
|
|
99
105
|
* 一次遍历、一行折叠、一个词 —— 日志行与载体行不各算一次)。`switch` 穷举 —— 加一种 scope 不补臂 = 编译红,两腿不会各分各的。
|
|
100
|
-
*
|
|
106
|
+
* 文案**未脱敏**:进日志流之前各腿在自己的产出边界上脱敏 —— `domainErrors` 过 `redactConfigError`(引号段读法);`droppedEntries` 的文案是结构化句子
|
|
107
|
+
* (键名坐标 + 类型词 + 契约整句,值位是洞)、不含配置里的值,只过 `redactSecrets`(契约句的固定词里有引号 / 撇号,引号段读法会把迁移指路整段吞掉)。
|
|
101
108
|
* 坏行本身照样走 `droppedEntries`(坐标 + 字段 + 词,不带值)。
|
|
102
109
|
*/
|
|
103
110
|
export declare function splitFoldedReadWarnings(warnings: readonly EffectiveReadWarning[]): {
|
|
@@ -1,37 +1,52 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { DROPPED_ENTRY_REASON_CODES, formatConfigPath, isDisclosableIdentity, } from "@sema-agent/settings-schema";
|
|
2
|
+
import { DROPPED_ENTRY_REASON_CODES, formatConfigPath, isDisclosableIdentity, renderQuotedSentence, } from "@sema-agent/settings-schema";
|
|
3
3
|
export function describeSchemaIssues(error) {
|
|
4
4
|
const issues = schemaIssuesOf(error);
|
|
5
5
|
if (issues === undefined)
|
|
6
6
|
return undefined;
|
|
7
7
|
return issues
|
|
8
|
-
.map((raw) => {
|
|
9
|
-
const i = (typeof raw === "object" && raw !== null ? raw : {});
|
|
10
|
-
const field = Array.isArray(i.path) && i.path.length > 0 ? i.path.map(String).join(".") : "(entry)";
|
|
11
|
-
const what = i.code === "invalid_type" && typeof i.expected === "string" && typeof i.received === "string"
|
|
12
|
-
? `expected ${i.expected}, received ${i.received}`
|
|
13
|
-
: typeof i.code === "string"
|
|
14
|
-
? i.code
|
|
15
|
-
: "invalid";
|
|
16
|
-
return `${field}: ${what}`;
|
|
17
|
-
})
|
|
8
|
+
.map((raw) => `${issueCoordinateOf(raw) ?? "(entry)"}: ${issueSentenceOf(raw)}`)
|
|
18
9
|
.join("; ");
|
|
19
10
|
}
|
|
11
|
+
function issueCoordinateOf(raw) {
|
|
12
|
+
const p = typeof raw === "object" && raw !== null ? raw.path : undefined;
|
|
13
|
+
return Array.isArray(p) && p.length > 0 ? formatConfigPath(p) : undefined;
|
|
14
|
+
}
|
|
15
|
+
function issueSentenceOf(raw) {
|
|
16
|
+
const message = typeof raw === "object" && raw !== null ? raw.message : undefined;
|
|
17
|
+
return contractSentenceOf(raw) ?? (typeof message === "string" ? message : "(unreadable issue)");
|
|
18
|
+
}
|
|
19
|
+
function contractSentenceOf(raw) {
|
|
20
|
+
const params = (typeof raw === "object" && raw !== null ? raw.params : undefined);
|
|
21
|
+
return params !== undefined && Array.isArray(params.template) && Array.isArray(params.operands)
|
|
22
|
+
? renderQuotedSentence(params, () => SCHEMA_OPERAND_HOLE)
|
|
23
|
+
: undefined;
|
|
24
|
+
}
|
|
20
25
|
function schemaIssuesOf(error) {
|
|
21
26
|
const issues = typeof error === "object" && error !== null ? error.issues : undefined;
|
|
22
27
|
return Array.isArray(issues) && issues.length > 0 ? issues : undefined;
|
|
23
28
|
}
|
|
24
29
|
export function describeSchemaIssueFields(error) {
|
|
25
30
|
const issues = schemaIssuesOf(error) ?? [];
|
|
26
|
-
|
|
27
|
-
const p = typeof raw === "object" && raw !== null ? raw.path : undefined;
|
|
28
|
-
return Array.isArray(p) && p.length > 0 ? p.map(String).join(".") : "(entry)";
|
|
29
|
-
});
|
|
30
|
-
return [...new Set(fields)];
|
|
31
|
+
return [...new Set(issues.map((raw) => issueCoordinateOf(raw) ?? "(entry)"))];
|
|
31
32
|
}
|
|
32
33
|
const MAX_ERROR_CHARS = 600;
|
|
33
|
-
const
|
|
34
|
+
const TRUNCATED_MARK = "…(truncated)";
|
|
35
|
+
const cap = (s) => (s.length <= MAX_ERROR_CHARS ? s : `${s.slice(0, MAX_ERROR_CHARS - TRUNCATED_MARK.length)}${TRUNCATED_MARK}`);
|
|
34
36
|
const errorText = (e) => (e instanceof Error ? e.message : String(e));
|
|
37
|
+
export const SCHEMA_OPERAND_HOLE = "«value»";
|
|
38
|
+
export function describeDomainIssues(error) {
|
|
39
|
+
const issues = schemaIssuesOf(error);
|
|
40
|
+
if (issues === undefined)
|
|
41
|
+
return errorText(error);
|
|
42
|
+
return issues
|
|
43
|
+
.map((raw) => {
|
|
44
|
+
const at = issueCoordinateOf(raw);
|
|
45
|
+
const sentence = issueSentenceOf(raw);
|
|
46
|
+
return at !== undefined ? `${at}: ${sentence}` : sentence;
|
|
47
|
+
})
|
|
48
|
+
.join("; ");
|
|
49
|
+
}
|
|
35
50
|
const keyList = (prefix, keys, truncatedFrom) => {
|
|
36
51
|
const total = truncatedFrom ?? keys.length;
|
|
37
52
|
const budget = MAX_ERROR_CHARS - prefix.length;
|
|
@@ -50,11 +65,8 @@ const keyList = (prefix, keys, truncatedFrom) => {
|
|
|
50
65
|
};
|
|
51
66
|
export function foldEffectiveReadWarning(w) {
|
|
52
67
|
switch (w.kind) {
|
|
53
|
-
case "hosts-grandfathered":
|
|
54
|
-
case "legacy-governance-lifted":
|
|
55
|
-
return null;
|
|
56
68
|
case "domain-defaulted":
|
|
57
|
-
return { scope: "domain", domain: w.domain, error: cap(
|
|
69
|
+
return { scope: "domain", domain: w.domain, error: cap(describeDomainIssues(w.error)) };
|
|
58
70
|
case "entry-dropped":
|
|
59
71
|
return { scope: "entry", domain: w.domain, entry: formatConfigPath(w.path), error: cap(`entry dropped by domain schema: ${describeSchemaIssues(w.error) ?? errorText(w.error)}`), reasonCode: droppedReasonCodeOf(w) };
|
|
60
72
|
case "unknown-keys-dropped":
|
|
@@ -99,8 +111,6 @@ export function splitFoldedReadWarnings(warnings) {
|
|
|
99
111
|
const droppedCatalogEntries = [];
|
|
100
112
|
for (const w of warnings) {
|
|
101
113
|
const f = foldEffectiveReadWarning(w);
|
|
102
|
-
if (f === null)
|
|
103
|
-
continue;
|
|
104
114
|
switch (f.scope) {
|
|
105
115
|
case "domain":
|
|
106
116
|
domainErrors.push({ domain: f.domain, error: f.error });
|
|
@@ -39,8 +39,8 @@ export interface RestartSliceCtx {
|
|
|
39
39
|
}
|
|
40
40
|
/** S-741(车OG):本引擎发布的重启信号。形的唯一定义是 settings-schema 6.1.0 `RestartSignal`(`{ reasons, version, since }`,在场即信号,
|
|
41
41
|
* 不夹 `restartRequired` 布尔;本地手写的那份接口已删),这里只把 `reasons` 收窄到本文件的闭集 {@link RestartSlice} —— 写口
|
|
42
|
-
* (`boot/
|
|
43
|
-
* `string[]`,闭集的编译期判据丢了)。`/health.restart` 与 fleet 心跳 `restart` 两个出口交
|
|
42
|
+
* (`boot/web-admin.ts` `publishRestart`)按它收,拼错 / 已退役的词编译红(opus 对抗腿 #2:直接用上游型时 `reasons` 是
|
|
43
|
+
* `string[]`,闭集的编译期判据丢了)。`/health.restart` 与 fleet 心跳 `restart` 两个出口交 web-admin 同一只 `restartState()`
|
|
44
44
|
* 的同一个对象,按契约形出门。 */
|
|
45
45
|
export type EngineRestartSignal = RestartSignal & {
|
|
46
46
|
reasons: RestartSlice[];
|
|
@@ -89,6 +89,8 @@ export declare function restartReasons(boot: EffectiveConfig | undefined, curren
|
|
|
89
89
|
* undefined` (env-boot plane, no EffectiveConfig to compare) is conservatively "changed": the first center plane
|
|
90
90
|
* always swaps. Not gated on tier activity: a Runner's expanded catalog goes stale on ANY plane change,
|
|
91
91
|
* including a tiers-off flip, and the default entry rides the fingerprint because the Runner's copy carries its
|
|
92
|
-
* own `default` (codex R8; SAME resolver as applyEffective —
|
|
92
|
+
* own `default` (codex R8; SAME resolver as applyEffective — settings-schema `resolveCatalogDefaultModel`, single source: a
|
|
93
|
+
* `named` default fingerprints by its name, a `{select}` default by its criteria — the engine resolves the same criteria over
|
|
94
|
+
* the same fingerprinted catalog and tier table, so equal criteria on an unchanged plane give the same entry). */
|
|
93
95
|
export declare function modelPlaneChanged(prev: EffectiveConfig | undefined, next: EffectiveConfig): boolean;
|
|
94
96
|
//# sourceMappingURL=restart-signal.d.ts.map
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { resolveActiveTiers } from "@sema-agent/settings-schema";
|
|
1
|
+
import { resolveActiveTiers, resolveCatalogDefaultModel } from "@sema-agent/settings-schema";
|
|
2
2
|
import { stableStringify } from "@sema-agent/settings-schema/hash";
|
|
3
3
|
import { DEFAULT_PLUGIN_ALLOW_HOSTS, materializablePlugins } from "../capabilities/center-plugins.js";
|
|
4
|
-
import { resolveDefaultModelName } from "./apply-effective.js";
|
|
5
4
|
import { CENTER_READ_FACE_LIVE_KEYS } from "./read-face.js";
|
|
6
5
|
const RESTART_SLICES = ["skills", "mcp", "a2a", "scenarios", "degrade-route", "read-face", "memory-consolidation"];
|
|
7
6
|
function enabledOnly(rows) {
|
|
@@ -83,8 +82,8 @@ export function modelPlaneChanged(prev, next) {
|
|
|
83
82
|
const enabled = enabledOnly(e.models?.models);
|
|
84
83
|
const active = e.models ? (resolveActiveTiers(e.models) ?? null) : null;
|
|
85
84
|
const names = new Set((enabled ?? []).map((m) => m.name));
|
|
86
|
-
const def =
|
|
87
|
-
return stableStringify({ models: planeEntriesFingerprint(e.models?.models), tiers: active, default: def.name });
|
|
85
|
+
const def = e.models ? resolveCatalogDefaultModel(e.models, (n) => names.has(n), enabled?.[0]?.name ?? "") : undefined;
|
|
86
|
+
return stableStringify({ models: planeEntriesFingerprint(e.models?.models), tiers: active, default: def === undefined ? null : def.kind === "named" ? def.name : { select: def.select } });
|
|
88
87
|
};
|
|
89
88
|
return fp(prev) !== fp(next);
|
|
90
89
|
}
|
|
@@ -5,7 +5,7 @@ import type { LoadedSkill } from "../capabilities/skills.js";
|
|
|
5
5
|
import type { CenterEndpoint } from "./center-request.js";
|
|
6
6
|
import type { CenterA2aPeer, CenterMcpServer, CenterSkillManifest } from "./types.js";
|
|
7
7
|
/**
|
|
8
|
-
* Merge center skills OVER the image baseline ([ref]-sibling B1; `
|
|
8
|
+
* Merge center skills OVER the image baseline ([ref]-sibling B1; `web-admin docs/MCP-SKILLS.md`). Load order =
|
|
9
9
|
* `loadSkills(SKILLS_DIR)` baseline → center skills lazily fetched by hash and overlaid BY NAME (center
|
|
10
10
|
* wins on a clash → can override a built-in; guarded center-side by publisher RBAC + the publish gate).
|
|
11
11
|
* `enabled:false` is skipped. Content is verified + cached by hash (same hash fetched once). A skill whose
|
|
@@ -18,7 +18,7 @@ export async function applyCenterSkills(baseline, manifest, center, logger, fetc
|
|
|
18
18
|
const text = await fsp.readFile(joinPath(diskCacheDir, hex), "utf8");
|
|
19
19
|
if (skillContentHash(text) === hash)
|
|
20
20
|
return text;
|
|
21
|
-
logger?.warn("
|
|
21
|
+
logger?.warn("web_admin_skill_cache_corrupt", { hash, note: "on-disk body fails its own hash — ignored, refetching from center" });
|
|
22
22
|
}
|
|
23
23
|
catch {
|
|
24
24
|
}
|
|
@@ -34,7 +34,7 @@ export async function applyCenterSkills(baseline, manifest, center, logger, fetc
|
|
|
34
34
|
const tmp = joinPath(diskCacheDir, `${hex}.${Math.random().toString(36).slice(2, 8)}.tmp`);
|
|
35
35
|
await fsp.writeFile(tmp, content, { mode: 0o600 });
|
|
36
36
|
await fsp.rename(tmp, joinPath(diskCacheDir, hex));
|
|
37
|
-
})().catch((err) => logger?.warn("
|
|
37
|
+
})().catch((err) => logger?.warn("web_admin_skill_cache_write_failed", { hash, err: String(err) })));
|
|
38
38
|
};
|
|
39
39
|
const byName = new Map(baseline.map((s) => [s.spec.name, s]));
|
|
40
40
|
const contentCache = new Map();
|
|
@@ -43,7 +43,7 @@ export async function applyCenterSkills(baseline, manifest, center, logger, fetc
|
|
|
43
43
|
if (m.enabled === false)
|
|
44
44
|
continue;
|
|
45
45
|
if (seenNames.has(m.name))
|
|
46
|
-
logger?.warn("
|
|
46
|
+
logger?.warn("web_admin_skill_duplicate_name", { name: m.name, note: "duplicate name in center manifest — last entry wins" });
|
|
47
47
|
seenNames.add(m.name);
|
|
48
48
|
try {
|
|
49
49
|
let content = contentCache.get(m.contentHash);
|
|
@@ -56,16 +56,16 @@ export async function applyCenterSkills(baseline, manifest, center, logger, fetc
|
|
|
56
56
|
contentCache.set(m.contentHash, content);
|
|
57
57
|
const overrodeBuiltin = byName.has(m.name);
|
|
58
58
|
byName.set(m.name, { spec: { name: m.name, description: m.description, content }, scenarios: m.scenarios ?? [] });
|
|
59
|
-
logger?.info("
|
|
59
|
+
logger?.info("web_admin_skill", { name: m.name, hash: m.contentHash, scenarios: m.scenarios, overrodeBuiltin });
|
|
60
60
|
}
|
|
61
61
|
catch (err) {
|
|
62
|
-
logger?.warn("
|
|
62
|
+
logger?.warn("web_admin_skill_failed", { name: m.name, hash: m.contentHash, err: String(err), note: "keeping baseline if any; skipping center version" });
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
await Promise.all(pendingWrites);
|
|
66
66
|
return [...byName.values()];
|
|
67
67
|
}
|
|
68
|
-
function resolveRefs(refs, server, kind, logger, event = "
|
|
68
|
+
function resolveRefs(refs, server, kind, logger, event = "web_admin_mcp_env_missing", skipNote = "skipping this MCP server — referenced env var is unset in this service") {
|
|
69
69
|
if (!refs)
|
|
70
70
|
return {};
|
|
71
71
|
const out = {};
|
|
@@ -86,7 +86,7 @@ export function resolveMcpServers(mcp, logger) {
|
|
|
86
86
|
if (s.enabled === false)
|
|
87
87
|
continue;
|
|
88
88
|
if (!isUsableWireName(s.name)) {
|
|
89
|
-
logger?.warn("
|
|
89
|
+
logger?.warn("web_admin_mcp_name_unusable", {
|
|
90
90
|
server: describeBadWireName(s.name),
|
|
91
91
|
note: "server SKIPPED — an MCP server name is its identity (it becomes the tool namespace prefix) and core REFUSES an out-of-bound name at prepare, which would fail every task in this scenario. Shorten the name and republish; sibling servers are unaffected.",
|
|
92
92
|
});
|
|
@@ -99,7 +99,7 @@ export function resolveMcpServers(mcp, logger) {
|
|
|
99
99
|
if (isContentOriginWord(s.contentOrigin))
|
|
100
100
|
contentOrigin = { contentOrigin: s.contentOrigin };
|
|
101
101
|
else
|
|
102
|
-
logger?.warn("
|
|
102
|
+
logger?.warn("web_admin_mcp_content_class_invalid", {
|
|
103
103
|
server: s.name,
|
|
104
104
|
value: describeBadContentOrigin(s.contentOrigin),
|
|
105
105
|
allowed: CONTENT_ORIGIN_WORDS,
|
|
@@ -109,7 +109,7 @@ export function resolveMcpServers(mcp, logger) {
|
|
|
109
109
|
const source = readMcpSourceLabel(s.name, s.source);
|
|
110
110
|
const facesRead = readMcpToolFaces(s.toolFaces);
|
|
111
111
|
if (!facesRead.ok) {
|
|
112
|
-
logger?.warn("
|
|
112
|
+
logger?.warn("web_admin_mcp_tool_faces_invalid", {
|
|
113
113
|
server: s.name,
|
|
114
114
|
problem: facesRead.problem,
|
|
115
115
|
note: "server SKIPPED — a toolFaces overlay is a NARROWING declaration (its pathTarget is what the sensitive-path / write fences read); mounting the server without it would silently drop the protection you declared. Fix the entry and republish.",
|
|
@@ -137,7 +137,7 @@ export function resolveMcpServers(mcp, logger) {
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
if (out.length > 0)
|
|
140
|
-
logger?.info("
|
|
140
|
+
logger?.info("web_admin_mcp", { servers: out.map((s) => s.spec.name) });
|
|
141
141
|
return { servers: out, reservedNames };
|
|
142
142
|
}
|
|
143
143
|
export function mcpForScenario(center, scenario) {
|
|
@@ -153,13 +153,13 @@ export function resolveA2aPeers(a2a, logger) {
|
|
|
153
153
|
if (p.enabled === false)
|
|
154
154
|
continue;
|
|
155
155
|
if (!isUsableWireName(p.name)) {
|
|
156
|
-
logger?.warn("
|
|
156
|
+
logger?.warn("web_admin_a2a_name_unusable", {
|
|
157
157
|
peer: describeBadWireName(p.name),
|
|
158
158
|
note: "peer SKIPPED — an A2A peer name is its identity (it lands as the roster's `origin.peer`) and core REFUSES an out-of-bound name at prepare, which would fail every task in this scenario. Shorten the name and republish; sibling peers are unaffected.",
|
|
159
159
|
});
|
|
160
160
|
continue;
|
|
161
161
|
}
|
|
162
|
-
const headers = resolveRefs(p.headerRefs, p.name, "header", logger, "
|
|
162
|
+
const headers = resolveRefs(p.headerRefs, p.name, "header", logger, "web_admin_a2a_env_missing", "skipping this A2A peer — referenced env var is unset in this service");
|
|
163
163
|
if (headers === null)
|
|
164
164
|
continue;
|
|
165
165
|
out.push({
|
|
@@ -175,7 +175,7 @@ export function resolveA2aPeers(a2a, logger) {
|
|
|
175
175
|
});
|
|
176
176
|
}
|
|
177
177
|
if (out.length > 0)
|
|
178
|
-
logger?.info("
|
|
178
|
+
logger?.info("web_admin_a2a", { peers: out.map((p) => p.spec.name) });
|
|
179
179
|
return out;
|
|
180
180
|
}
|
|
181
181
|
export function a2aForScenario(peers, scenario) {
|
|
@@ -17,17 +17,11 @@ import type { Logger } from "../observability/logger.js";
|
|
|
17
17
|
import { type Batch1LimitKey } from "./hot-keys-registry.js";
|
|
18
18
|
import type { EffectiveLimits } from "./types.js";
|
|
19
19
|
/**
|
|
20
|
-
* stage
|
|
21
|
-
*
|
|
22
|
-
* 🔴 `legacyGates` 是**已过旧位 presence 判据**的闸值(判据属主 = `runtimeGatePresent`,调用方过一遍再传)。
|
|
23
|
-
* 两位刻意判据不同,不是疏忽:旧位(`runtime`)的容忍语义(例如 `costQuotaWindowSec: 0` 判「缺席」保 env)
|
|
24
|
-
* 是既有 center 的在营契约,收紧它会把一个存量发布变成整批拒;新位(`limits`)从第一天起就是严判
|
|
25
|
-
* (坏值 = 整批响亮拒)。判据属主各只有一个,谁都没有被复刻第二份。
|
|
20
|
+
* stage 的入参视图:`limits` 域是这些键的**唯一**发布位(settings-schema 11 退役了 `runtime` 域,它的旧位读法随之删)。
|
|
26
21
|
*/
|
|
27
22
|
export interface LimitsSource {
|
|
28
23
|
readonly version: number;
|
|
29
24
|
readonly limits?: EffectiveLimits;
|
|
30
|
-
readonly legacyGates?: Readonly<Record<string, unknown>>;
|
|
31
25
|
}
|
|
32
26
|
/** 逐键 defer 的铸因(codex R2-F2):值合法但这一键**不热**,变更只能重启生效。 */
|
|
33
27
|
export interface DeferredLimit {
|
|
@@ -69,7 +69,6 @@ function candidateView(config, assignments) {
|
|
|
69
69
|
}
|
|
70
70
|
export function stageLimits(config, eff, logger) {
|
|
71
71
|
const baseline = baselineOf(config);
|
|
72
|
-
const legacy = eff.legacyGates ?? {};
|
|
73
72
|
const assignments = [];
|
|
74
73
|
const deletions = [];
|
|
75
74
|
const changed = [];
|
|
@@ -79,7 +78,7 @@ export function stageLimits(config, eff, logger) {
|
|
|
79
78
|
const rawLimits = eff.limits;
|
|
80
79
|
if (rawLimits !== undefined && (rawLimits === null || typeof rawLimits !== "object" || Array.isArray(rawLimits))) {
|
|
81
80
|
const rejected = { keys: [...BATCH1_LIMIT_KEYS], reason: `the published limits domain must be a JSON object — got ${rawLimits === null ? "null" : Array.isArray(rawLimits) ? "array" : typeof rawLimits}` };
|
|
82
|
-
logger?.warn("
|
|
81
|
+
logger?.warn("web_admin_limits_invalid", {
|
|
83
82
|
...rejected,
|
|
84
83
|
version: eff.version,
|
|
85
84
|
kept: "the whole limits batch is refused — a malformed container must never read as 'the center manages none of these keys' (that would silently revert every published ceiling to the env baseline)",
|
|
@@ -91,7 +90,7 @@ export function stageLimits(config, eff, logger) {
|
|
|
91
90
|
const known = new Set([...BATCH1_LIMIT_KEYS, ...DEFERRED_LIMIT_KEYS.map((d) => d.key)]);
|
|
92
91
|
const unconsumed = Object.keys(limits).filter((k) => !known.has(k));
|
|
93
92
|
if (unconsumed.length > 0) {
|
|
94
|
-
logger?.warn("
|
|
93
|
+
logger?.warn("web_admin_limits_unconsumed_keys", {
|
|
95
94
|
keys: unconsumed,
|
|
96
95
|
version: eff.version,
|
|
97
96
|
note: "published in the limits domain but not consumed by this server build (misspelled, or a later-batch key) — the value is inert until a batch that owns it ships",
|
|
@@ -99,7 +98,7 @@ export function stageLimits(config, eff, logger) {
|
|
|
99
98
|
}
|
|
100
99
|
}
|
|
101
100
|
for (const key of BATCH1_LIMIT_KEYS) {
|
|
102
|
-
const published = limits !== undefined && Object.prototype.hasOwnProperty.call(limits, key) ? limits[key] :
|
|
101
|
+
const published = limits !== undefined && Object.prototype.hasOwnProperty.call(limits, key) ? limits[key] : undefined;
|
|
103
102
|
let next;
|
|
104
103
|
if (published === undefined) {
|
|
105
104
|
next = baseline[key];
|
|
@@ -129,7 +128,7 @@ export function stageLimits(config, eff, logger) {
|
|
|
129
128
|
}
|
|
130
129
|
if (badKeys.length > 0) {
|
|
131
130
|
const rejected = { keys: badKeys, reason: reason };
|
|
132
|
-
logger?.warn("
|
|
131
|
+
logger?.warn("web_admin_limits_invalid", {
|
|
133
132
|
...rejected,
|
|
134
133
|
version: eff.version,
|
|
135
134
|
kept: "the whole limits batch is refused — the prior values keep serving (a half-applied limit face means the published numbers and the live gate are different generations)",
|
|
@@ -140,7 +139,7 @@ export function stageLimits(config, eff, logger) {
|
|
|
140
139
|
if (!verdict.ok) {
|
|
141
140
|
const involved = changed.length > 0 ? changed : BATCH1_LIMIT_KEYS.slice();
|
|
142
141
|
const rejected = { keys: [...involved], reason: verdict.reason, restartReason: verdict.restartReason };
|
|
143
|
-
logger?.warn("
|
|
142
|
+
logger?.warn("web_admin_limits_invalid", {
|
|
144
143
|
...rejected,
|
|
145
144
|
invariant: verdict.id,
|
|
146
145
|
version: eff.version,
|
|
@@ -149,7 +148,7 @@ export function stageLimits(config, eff, logger) {
|
|
|
149
148
|
return { assignments: [], deletions: [], changed: [], deferred: [], rejected };
|
|
150
149
|
}
|
|
151
150
|
if (deferred.length > 0) {
|
|
152
|
-
logger?.warn("
|
|
151
|
+
logger?.warn("web_admin_limits_deferred", {
|
|
153
152
|
keys: deferred.map((d) => d.key),
|
|
154
153
|
version: eff.version,
|
|
155
154
|
reasons: [...new Set(DEFERRED_LIMIT_KEYS.filter((d) => deferred.some((x) => x.key === d.key)).map((d) => d.why))],
|