@sema-agent/server 7.122.0 → 7.124.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 +209 -0
- package/README.md +7 -2
- package/README.zh-CN.md +5 -2
- package/USAGE.md +76 -56
- 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.d.ts +1 -1
- package/dist/audit.js +53 -23
- 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 +13 -9
- package/dist/auto-mode-face.js +8 -7
- 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} +39 -20
- 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 +16 -16
- 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 +34 -11
- package/dist/config-lkg.js +76 -48
- package/dist/config-provider.d.ts +26 -21
- package/dist/config-provider.js +25 -27
- package/dist/config-types.d.ts +59 -50
- package/dist/config.d.ts +17 -54
- package/dist/config.js +99 -181
- 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 +61 -30
- package/dist/fleet/engine-signal.js +29 -5
- 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 +15 -13
- package/dist/fleet-client.js +9 -7
- 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 +7 -16
- package/dist/http/durable-run-leg.js +2 -1
- package/dist/http/health-self-report.d.ts +7 -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 +6 -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 +2 -5
- 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.d.ts +4 -3
- package/dist/http/stored-body-replay.js +5 -5
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/key-resolver.d.ts +1 -1
- package/dist/leader/leader.d.ts +7 -6
- package/dist/leader/leader.js +3 -1
- package/dist/leader/run-entry.d.ts +2 -2
- package/dist/leader/run-entry.js +3 -3
- package/dist/leader/wire.d.ts +4 -4
- 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 +20 -11
- package/dist/model-entry-refusal.js +22 -17
- package/dist/model-provider.d.ts +2 -2
- package/dist/model-route-endpoint.d.ts +22 -2
- package/dist/model-route-endpoint.js +9 -3
- package/dist/model-route-gap.d.ts +103 -0
- package/dist/model-route-gap.js +80 -0
- 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/namespaced-rule-name.d.ts +16 -30
- package/dist/namespaced-rule-name.js +2 -20
- package/dist/observability/fail-open.d.ts +0 -4
- package/dist/observability/fail-open.js +0 -4
- 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 +51 -0
- package/dist/plugins/scheduler-support.d.ts +26 -7
- package/dist/plugins/scheduler-support.js +96 -53
- package/dist/plugins/session-policy-store-sql.js +1 -2
- 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/plugins/workflow-run-store-sql.d.ts +4 -2
- 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/rule-entry-list.d.ts +3 -13
- package/dist/rule-entry-list.js +1 -12
- package/dist/run-local.js +10 -7
- package/dist/runtime-caps-resolver.d.ts +4 -4
- package/dist/runtime-caps-resolver.js +2 -2
- package/dist/runtime-governance.d.ts +36 -13
- package/dist/runtime-governance.js +7 -2
- 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-policy-wire.d.ts +47 -49
- package/dist/session-policy-wire.js +41 -36
- package/dist/session-sync.d.ts +5 -1
- package/dist/session-sync.js +2 -1
- 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/spec-fields.d.ts +4 -4
- package/dist/spec-fields.js +1 -4
- package/dist/task-a2a.d.ts +1 -1
- package/dist/task-settings.d.ts +4 -5
- package/dist/task-settings.js +5 -8
- 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 +1 -6
- package/dist/trace/self-minted-wire.d.ts +147 -3
- package/dist/trace/self-minted-wire.js +50 -9
- package/dist/unattended-permission-mode.d.ts +3 -3
- package/dist/unattended-permission-mode.js +2 -2
- 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 +104 -121
- 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 +15 -15
- 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/web-admin-client/read-warnings.d.ts +137 -0
- package/dist/web-admin-client/read-warnings.js +168 -0
- 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 +19 -47
- package/package.json +3 -3
- package/skills/find-skills.md +2 -2
- package/dist/config-center/read-warnings.d.ts +0 -109
- package/dist/config-center/read-warnings.js +0 -110
- 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,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { expandTiers, resolveTaskModel as coreResolveTaskModel } from "@sema-agent/core";
|
|
2
|
+
import { WEB_ADMIN_ENV, narrowAtMention, resolveActiveTiers, resolveCatalogDefaultModel } from "@sema-agent/settings-schema";
|
|
2
3
|
import { modelKeyPoison } from "../key-resolver.js";
|
|
4
|
+
import { readAtMention } from "../model-select.js";
|
|
3
5
|
import { applyAutoCompactWindow, buildModelCostKey, parseAutonomy, unmatchableToolNamesRefusal } from "../config.js";
|
|
4
6
|
import { buildModelCompat, readModelCompat } from "../model-compat.js";
|
|
5
7
|
import { buildModelHeadersKey } from "../model-entry-headers.js";
|
|
@@ -64,41 +66,27 @@ export function mutateInPlace(target, source) {
|
|
|
64
66
|
}
|
|
65
67
|
const appliedGeneration = new WeakMap();
|
|
66
68
|
const envRolesBaseline = new WeakMap();
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
logger?.warn("sema_registry_at_model_allowlist_invalid", {
|
|
73
|
-
type: typeof raw,
|
|
74
|
-
hint: "models.atModelAllowlist must be an array of catalog model names — an unreadable allowlist is treated as ALLOWING NOTHING (fail-closed: a broken restriction must not read as 'no restriction'); tasks still run on the default/role models",
|
|
69
|
+
function stageAtMention(raw, usableNames, logger) {
|
|
70
|
+
const read = readAtMention(raw);
|
|
71
|
+
if (read === undefined) {
|
|
72
|
+
logger?.warn("web_admin_at_mention_invalid", {
|
|
73
|
+
hint: "models.atMention does not read as {mode:\"all\"} | {mode:\"none\"} | {mode:\"only\", models:[…]} — treated as {mode:\"none\"} (fail-closed: a broken restriction must not read as 'no restriction'); tasks still run on the default/role models",
|
|
75
74
|
});
|
|
76
|
-
return
|
|
75
|
+
return { mode: "none" };
|
|
77
76
|
}
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
77
|
+
const narrowed = narrowAtMention(read, usableNames);
|
|
78
|
+
if (read.mode === "only") {
|
|
79
|
+
const kept = new Set(narrowed.mode === "only" ? narrowed.models : []);
|
|
80
|
+
const dangling = read.models.filter((n) => !kept.has(n));
|
|
81
|
+
if (dangling.length > 0) {
|
|
82
|
+
logger?.warn("web_admin_at_mention_dangling", {
|
|
83
|
+
dangling,
|
|
84
|
+
narrowedTo: narrowed.mode,
|
|
85
|
+
hint: "these models.atMention names are not in this generation's usable catalog (not enabled, or unavailable on this host) — they allow nothing; when none is left the reading is {mode:\"none\"} and every user model pick is refused",
|
|
86
|
+
});
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
|
-
|
|
89
|
-
logger?.warn("sema_registry_at_model_allowlist_bad_entries", {
|
|
90
|
-
bad,
|
|
91
|
-
hint: "non-string / empty entries in models.atModelAllowlist allow NOTHING (they are kept as unresolvable placeholders, never dropped — dropping them would silently WIDEN the allowlist, and an all-bad list would collapse to the empty = allow-everything form)",
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
const dangling = names.filter((n) => n !== UNPARSABLE_AT_MODEL_ALLOWLIST_ENTRY && models[n] === undefined);
|
|
95
|
-
if (dangling.length > 0) {
|
|
96
|
-
logger?.warn("sema_registry_at_model_allowlist_dangling", {
|
|
97
|
-
dangling,
|
|
98
|
-
hint: "these @-mention allowlist entries name a model that is not in the enabled catalog — they allow nothing; an allowlist that is ENTIRELY dangling refuses every user model pick",
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
return names;
|
|
89
|
+
return narrowed;
|
|
102
90
|
}
|
|
103
91
|
export function applyEffective(config, eff, logger, opts) {
|
|
104
92
|
const staged = stageEffective(config, eff, logger, opts);
|
|
@@ -106,7 +94,7 @@ export function applyEffective(config, eff, logger, opts) {
|
|
|
106
94
|
const live = appliedGeneration.get(config);
|
|
107
95
|
const ordered = opts.generation !== undefined || staged.version > 0;
|
|
108
96
|
if (ordered && live !== undefined && gen < live) {
|
|
109
|
-
logger?.warn("
|
|
97
|
+
logger?.warn("web_admin_stale_generation_refused", { staged: staged.version, stagedOrdinal: gen, live, note: "an older staged generation must not overwrite a newer committed one — refused whole; next poll replays" });
|
|
110
98
|
return false;
|
|
111
99
|
}
|
|
112
100
|
const mp = staged.modelPlaneGroup.stages.models;
|
|
@@ -115,7 +103,7 @@ export function applyEffective(config, eff, logger, opts) {
|
|
|
115
103
|
opts.swapPlane({ models: mp.models, tiers: mp.activeTiers });
|
|
116
104
|
}
|
|
117
105
|
catch (err) {
|
|
118
|
-
logger?.warn("
|
|
106
|
+
logger?.warn("web_admin_model_plane_swap_refused", {
|
|
119
107
|
version: staged.version,
|
|
120
108
|
error: err instanceof Error ? err.message : String(err),
|
|
121
109
|
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)",
|
|
@@ -143,13 +131,13 @@ export function applyEffective(config, eff, logger, opts) {
|
|
|
143
131
|
opts.report({ ...report, fullyApplied: refusedGroups(report).length === 0, modelRefusals: modelRefusalVerdictOf(staged.modelPlaneGroup) });
|
|
144
132
|
}
|
|
145
133
|
if (staged.limits.changed.length > 0)
|
|
146
|
-
logger?.info("
|
|
134
|
+
logger?.info("web_admin_limits", { version: staged.version, keys: staged.limits.changed });
|
|
147
135
|
if (mp)
|
|
148
|
-
logger?.info("
|
|
136
|
+
logger?.info("web_admin_models", mp.infoLine);
|
|
149
137
|
if (staged.approvalGate.next !== undefined)
|
|
150
|
-
logger?.info("
|
|
138
|
+
logger?.info("web_admin_runtime", { version: staged.version, approvalRequire: staged.approvalGate.next });
|
|
151
139
|
if (Object.keys(staged.hot.infoLine).length > 0)
|
|
152
|
-
logger?.info("
|
|
140
|
+
logger?.info("web_admin_runtime_hot", staged.hot.infoLine);
|
|
153
141
|
for (const n of staged.modelPlaneGroup.stages.collab?.notices ?? []) {
|
|
154
142
|
if (n.level === "warn")
|
|
155
143
|
logger?.warn(n.msg, n.fields);
|
|
@@ -157,7 +145,7 @@ export function applyEffective(config, eff, logger, opts) {
|
|
|
157
145
|
logger?.info(n.msg, n.fields);
|
|
158
146
|
}
|
|
159
147
|
if (Object.keys(staged.projects).length > 0)
|
|
160
|
-
logger?.info("
|
|
148
|
+
logger?.info("web_admin_projects", { projects: Object.keys(staged.projects) });
|
|
161
149
|
return true;
|
|
162
150
|
}
|
|
163
151
|
function commitStaged(config, staged) {
|
|
@@ -166,12 +154,13 @@ function commitStaged(config, staged) {
|
|
|
166
154
|
config.modelApiKeyEnv = mp.modelApiKeyEnv;
|
|
167
155
|
config.modelApiKeys = mp.modelApiKeys;
|
|
168
156
|
config.modelRefusals = mp.modelRefusals;
|
|
157
|
+
config.droppedCatalogEntries = mp.droppedCatalogEntries;
|
|
169
158
|
mutateInPlace(config.modelQuotaWeights, mp.quotaWeights);
|
|
170
159
|
mutateInPlace(config.models, mp.models);
|
|
171
160
|
config.model = config.models.default;
|
|
172
161
|
mutateInPlace(config.roles, mp.roles);
|
|
173
162
|
mutateInPlace(config.tiers, mp.activeTiers);
|
|
174
|
-
config.
|
|
163
|
+
config.atMention = mp.atMention;
|
|
175
164
|
}
|
|
176
165
|
if (staged.approvalGate.next !== undefined)
|
|
177
166
|
config.approvalRequire = staged.approvalGate.next;
|
|
@@ -190,7 +179,7 @@ function commitStaged(config, staged) {
|
|
|
190
179
|
}
|
|
191
180
|
export const MODEL_PLANE_DOMAINS = ["models", "collab"];
|
|
192
181
|
const MODEL_PLANE_SURFACES = {
|
|
193
|
-
models: ["models", "model", "modelRefusals", "modelApiKeyEnv", "modelApiKeys", "modelQuotaWeights", "roles", "tiers", "
|
|
182
|
+
models: ["models", "model", "modelRefusals", "droppedCatalogEntries", "modelApiKeyEnv", "modelApiKeys", "modelQuotaWeights", "roles", "tiers", "atMention"],
|
|
194
183
|
collab: ["collabWorkflows"],
|
|
195
184
|
};
|
|
196
185
|
const MODEL_PLANE_GROUP_KEYS = MODEL_PLANE_DOMAINS.flatMap((d) => MODEL_PLANE_SURFACES[d]);
|
|
@@ -230,15 +219,15 @@ function modelPlaneGroupReport(group) {
|
|
|
230
219
|
const ROLE_BINDING_UNAVAILABLE_HINT = "tasks resolving this role fail: alias/tier expansion cannot mint this name; the engine refuses it loud — bind the role to a usable model, or make the entry usable (see the entry's own warn)";
|
|
231
220
|
function stageModelPlane(config, eff, logger, sealedKeys) {
|
|
232
221
|
const enabled = (eff.models?.models ?? []).filter((m) => m.enabled !== false);
|
|
233
|
-
const verdict = judgeCatalogEntries(enabled, { anthropicRouteMounted: config.anthropic !== undefined }, eff.models?.
|
|
234
|
-
if (enabled.length === 0 &&
|
|
222
|
+
const verdict = judgeCatalogEntries(enabled, { anthropicRouteMounted: config.anthropic !== undefined }, eff.models?.droppedCatalogEntries);
|
|
223
|
+
if (enabled.length === 0 && verdict.malformed.length === 0 && verdict.dropped.every((row) => row.identity?.enabled === false))
|
|
235
224
|
return { stages: { collab: stageCollabDomain(eff) } };
|
|
236
225
|
{
|
|
237
226
|
for (const [name, r] of Object.entries(verdict.refused))
|
|
238
227
|
logger?.warn(refusalEventOf(r.reason), refusalWarnFields(name, r));
|
|
239
228
|
const refuse = (code, reason, detail = {}) => {
|
|
240
229
|
const invariant = PLANE_REFUSAL_INVARIANT_OF[code];
|
|
241
|
-
logger?.warn("
|
|
230
|
+
logger?.warn("web_admin_model_plane_refused", {
|
|
242
231
|
version: typeof eff.version === "number" ? eff.version : 0,
|
|
243
232
|
invariant,
|
|
244
233
|
reason,
|
|
@@ -257,16 +246,16 @@ function stageModelPlane(config, eff, logger, sealedKeys) {
|
|
|
257
246
|
const envDefaults = { maxTokens: envModel.maxTokens, contextWindow: envModel.contextWindow, reasoning: envModel.reasoning, charsPerToken: envModel.charsPerToken, defaultThinking: envModel.defaultThinking, compat: envModel.compat };
|
|
258
247
|
for (const m of usable) {
|
|
259
248
|
if (m.baseUrl)
|
|
260
|
-
logger?.warn("
|
|
249
|
+
logger?.warn("web_admin_model_base_url", { model: m.name, baseUrl: buildDisplaySafeUrl(m.baseUrl), hint: "registry-directed gateway override — this model's key authenticates AGAINST THIS URL" });
|
|
261
250
|
models[m.name] = toModel(m, envDefaults, logger);
|
|
262
251
|
if (m.sealedApiKey) {
|
|
263
252
|
const s = m.sealedApiKey;
|
|
264
253
|
if (!sealedKeys) {
|
|
265
|
-
logger?.warn("
|
|
254
|
+
logger?.warn("web_admin_model_sealed_key_no_store", { model: m.name, publicKeyId: s.publicKeyId, hint: "no sealed-key store on this host (boot custody init failed or lane unwired) — key POISONED: tasks on this model fail loud (no apiKeyEnv fallback, no gateway fallback)" });
|
|
266
255
|
modelApiKeys[m.name] = modelKeyPoison({ source: "sealed", reason: "no_key_store", publicKeyId: s.publicKeyId });
|
|
267
256
|
}
|
|
268
257
|
else if (!sealedKeys.has(s.publicKeyId)) {
|
|
269
|
-
logger?.warn("
|
|
258
|
+
logger?.warn("web_admin_model_sealed_key_no_private_key", { model: m.name, publicKeyId: s.publicKeyId, held: sealedKeys.ids(), hint: "ciphertext targets a private key this host doesn't hold (rotated away / other host) — key POISONED: tasks on this model fail loud until the operator re-pastes the key in the web UI (it seals against the newest registered public key)" });
|
|
270
259
|
modelApiKeys[m.name] = modelKeyPoison({ source: "sealed", reason: "stale_ciphertext", publicKeyId: s.publicKeyId });
|
|
271
260
|
}
|
|
272
261
|
else {
|
|
@@ -274,7 +263,7 @@ function stageModelPlane(config, eff, logger, sealedKeys) {
|
|
|
274
263
|
modelApiKeys[m.name] = sealedKeys.open(s.ciphertext, s.publicKeyId);
|
|
275
264
|
}
|
|
276
265
|
catch (err) {
|
|
277
|
-
logger?.warn("
|
|
266
|
+
logger?.warn("web_admin_model_sealed_key_open_failed", { model: m.name, publicKeyId: s.publicKeyId, err: String(err), hint: "corrupt/foreign ciphertext — key POISONED: tasks on this model fail loud (no apiKeyEnv fallback, no gateway fallback)" });
|
|
278
267
|
modelApiKeys[m.name] = modelKeyPoison({ source: "sealed", reason: "sealed_decrypt_failed", publicKeyId: s.publicKeyId, errKind: err instanceof Error ? err.name : "unknown" });
|
|
279
268
|
}
|
|
280
269
|
}
|
|
@@ -282,7 +271,7 @@ function stageModelPlane(config, eff, logger, sealedKeys) {
|
|
|
282
271
|
else if (m.apiKeyEnv) {
|
|
283
272
|
modelApiKeyEnv[m.name] = m.apiKeyEnv;
|
|
284
273
|
if (!process.env[m.apiKeyEnv])
|
|
285
|
-
logger?.warn("
|
|
274
|
+
logger?.warn("web_admin_model_key_env_missing", { model: m.name, apiKeyEnv: m.apiKeyEnv, hint: `env var ${m.apiKeyEnv} not set (or empty) in this service — key POISONED: tasks on this model fail loud naming ${m.apiKeyEnv} (no gateway fallback) until it is set; or drop apiKeyEnv from the entry to use the gateway key on purpose` });
|
|
286
275
|
}
|
|
287
276
|
}
|
|
288
277
|
const quotaWeights = {};
|
|
@@ -304,16 +293,6 @@ function stageModelPlane(config, eff, logger, sealedKeys) {
|
|
|
304
293
|
centerRoles[role] = { select: tgt.select };
|
|
305
294
|
}
|
|
306
295
|
const roles = { ...envRolesBaseline.get(config), ...centerRoles };
|
|
307
|
-
let picked;
|
|
308
|
-
if (usable.length > 0) {
|
|
309
|
-
picked = resolveDefaultModelName(eff, (n) => models[n] !== undefined, usable[0].name, (source, name) => logger?.warn("sema_registry_default_dangling", {
|
|
310
|
-
source,
|
|
311
|
-
...(Object.hasOwn(verdict.refused, name) ? refusedNameLogField("name", name) : { name }),
|
|
312
|
-
...(Object.hasOwn(verdict.refused, name) ? { unavailable: verdict.refused[name].code } : {}),
|
|
313
|
-
hint: "explicit default names a model that is not in the usable catalog (not enabled, or unavailable on this host — see `unavailable`) — falling to the next source",
|
|
314
|
-
}));
|
|
315
|
-
models.default = models[picked.name];
|
|
316
|
-
}
|
|
317
296
|
const activeTiers = {};
|
|
318
297
|
for (const [tier, target] of Object.entries(resolveActiveTiers(eff.models) ?? {})) {
|
|
319
298
|
if (models[target] !== undefined) {
|
|
@@ -321,7 +300,39 @@ function stageModelPlane(config, eff, logger, sealedKeys) {
|
|
|
321
300
|
continue;
|
|
322
301
|
}
|
|
323
302
|
const refusal = Object.hasOwn(verdict.refused, target) ? verdict.refused[target] : undefined;
|
|
324
|
-
logger?.warn("
|
|
303
|
+
logger?.warn("web_admin_tier_binding_unavailable", { tier, ...(refusal !== undefined ? refusedNameLogField("model", target) : { model: target }), ...(refusal !== undefined ? { code: refusal.code } : {}), hint: "this tier binding names a model that is not in this generation's usable catalog — the binding is dropped (the tier word falls to the next lower bound tier, core's ladder); fix the entry or rebind the tier" });
|
|
304
|
+
}
|
|
305
|
+
let picked;
|
|
306
|
+
if (usable.length > 0) {
|
|
307
|
+
const decided = resolveCatalogDefaultModel(eff.models, (n) => models[n] !== undefined, usable[0].name);
|
|
308
|
+
switch (decided.kind) {
|
|
309
|
+
case "named":
|
|
310
|
+
for (const d of decided.dangling) {
|
|
311
|
+
logger?.warn("web_admin_default_dangling", {
|
|
312
|
+
source: d.source,
|
|
313
|
+
...(Object.hasOwn(verdict.refused, d.name) ? refusedNameLogField("name", d.name) : { name: d.name }),
|
|
314
|
+
...(Object.hasOwn(verdict.refused, d.name) ? { unavailable: verdict.refused[d.name].code } : {}),
|
|
315
|
+
hint: "explicit default names a model that is not in the usable catalog (not enabled, or unavailable on this host — see `unavailable`) — falling to the next source",
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
picked = { name: decided.name, source: decided.source };
|
|
319
|
+
break;
|
|
320
|
+
case "select": {
|
|
321
|
+
const engine = engineDefaultOfSelect(decided.select, models, activeTiers);
|
|
322
|
+
if ("unresolved" in engine) {
|
|
323
|
+
return refuse(DEFAULT_MODEL_MISSING_CODE, `this catalog candidate has no default model [${DEFAULT_MODEL_MISSING_CODE}] — roles.default is a {select} that the engine resolves to no usable catalog entry ` +
|
|
324
|
+
"(no enabled entry this host can serve matches its criteria, and no tier binding catches the default role); widen the criteria, enable a matching entry, or bind roles.default to a model by name", { engine: engine.unresolved });
|
|
325
|
+
}
|
|
326
|
+
picked = { name: engine.name, source: "roles.default.select" };
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
default: {
|
|
330
|
+
const unhandled = decided;
|
|
331
|
+
throw new Error(`catalog default: unknown kind ${JSON.stringify(unhandled)}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (picked !== undefined)
|
|
335
|
+
models.default = models[picked.name];
|
|
325
336
|
}
|
|
326
337
|
const nextView = { ...config, models, tiers: activeTiers, modelRefusals: verdict.refused };
|
|
327
338
|
const noDefault = crossInvariant("default-model-resolvable").check({ live: config, next: nextView });
|
|
@@ -337,21 +348,22 @@ function stageModelPlane(config, eff, logger, sealedKeys) {
|
|
|
337
348
|
const target = typeof spec === "string" ? spec : spec !== null && typeof spec === "object" && "model" in spec && typeof spec.model === "string" ? spec.model : undefined;
|
|
338
349
|
const refusal = target !== undefined && Object.hasOwn(verdict.refused, target) ? verdict.refused[target] : undefined;
|
|
339
350
|
if (refusal !== undefined) {
|
|
340
|
-
logger?.warn("
|
|
351
|
+
logger?.warn("web_admin_role_binding_unavailable", { role, ...refusedNameLogField("model", target), code: refusal.code, hint: ROLE_BINDING_UNAVAILABLE_HINT });
|
|
341
352
|
}
|
|
342
353
|
}
|
|
343
|
-
const
|
|
354
|
+
const atMention = stageAtMention(eff.models?.atMention, new Set(usable.map((m) => m.name)), logger);
|
|
344
355
|
const defaultModel = models.default;
|
|
345
356
|
const plane = {
|
|
346
357
|
models,
|
|
347
358
|
modelApiKeyEnv,
|
|
348
359
|
modelApiKeys,
|
|
349
360
|
modelRefusals: { ...verdict.refused },
|
|
361
|
+
droppedCatalogEntries: [...verdict.dropped],
|
|
350
362
|
quotaWeights,
|
|
351
363
|
roles,
|
|
352
364
|
activeTiers,
|
|
353
|
-
|
|
354
|
-
infoLine: { count: usable.length, ...(Object.keys(verdict.refused).length > 0 ? refusedNamesLogFields("unavailable", Object.keys(verdict.refused)) : {}), default: defaultModel.id, defaultSource: picked.source, roles: Object.keys(roles), tiers: Object.keys(activeTiers),
|
|
365
|
+
atMention,
|
|
366
|
+
infoLine: { count: usable.length, ...(Object.keys(verdict.refused).length > 0 ? refusedNamesLogFields("unavailable", Object.keys(verdict.refused)) : {}), default: defaultModel.id, defaultSource: picked.source, roles: Object.keys(roles), tiers: Object.keys(activeTiers), atMention: atMention.mode, sealedKeys: Object.values(modelApiKeys).filter((v) => typeof v === "string").length, sealedPoisoned: Object.values(modelApiKeys).filter((v) => typeof v !== "string").length },
|
|
355
367
|
};
|
|
356
368
|
return { stages: { models: plane, collab: stageCollabDomain(eff) }, defaultName: picked.name, refused: verdict.refused };
|
|
357
369
|
}
|
|
@@ -360,23 +372,23 @@ function stageCollabDomain(eff) {
|
|
|
360
372
|
const projected = projectCollabToWorkflows(eff.collab?.templates);
|
|
361
373
|
const notices = [];
|
|
362
374
|
if (Object.keys(projected.workflows).length > 0)
|
|
363
|
-
notices.push({ level: "info", msg: "
|
|
375
|
+
notices.push({ level: "info", msg: "web_admin_collab_workflows", fields: { workflows: Object.keys(projected.workflows) } });
|
|
364
376
|
if (projected.skipped.length > 0)
|
|
365
|
-
notices.push({ level: "warn", msg: "
|
|
377
|
+
notices.push({ level: "warn", msg: "web_admin_collab_skipped", fields: { skipped: projected.skipped } });
|
|
366
378
|
if (projected.notes.length > 0)
|
|
367
|
-
notices.push({ level: "info", msg: "
|
|
379
|
+
notices.push({ level: "info", msg: "web_admin_collab_notes", fields: { notes: projected.notes } });
|
|
368
380
|
return { workflows: projected.workflows, notices };
|
|
369
381
|
}
|
|
370
382
|
function stageEffective(config, eff, logger, opts) {
|
|
371
383
|
if (opts.leg === "boot" && !eff.version) {
|
|
372
|
-
logger?.warn("
|
|
384
|
+
logger?.warn("web_admin_unpublished", {
|
|
373
385
|
version: eff.version,
|
|
374
386
|
note: "empty/unpublished effective config — using env + built-in fallback; publish a config or unset CONFIG_PUBLISH_MODE",
|
|
375
387
|
});
|
|
376
388
|
}
|
|
377
389
|
const modelPlaneGroup = stageModelPlane(config, eff, logger, opts.sealedKeys);
|
|
378
390
|
const approvalGate = stageApprovalGate(config, eff.governance, opts, logger);
|
|
379
|
-
const limits = stageLimits(config, { version: typeof eff.version === "number" ? eff.version : 0, ...("limits" in eff ? { limits: eff.limits } : {})
|
|
391
|
+
const limits = stageLimits(config, { version: typeof eff.version === "number" ? eff.version : 0, ...("limits" in eff ? { limits: eff.limits } : {}) }, logger);
|
|
380
392
|
const hot = stageRuntimeHot(config, eff.governance, logger);
|
|
381
393
|
const projDomain = eff.projects;
|
|
382
394
|
const rawProjects = (projDomain && typeof projDomain === "object" && !Array.isArray(projDomain) &&
|
|
@@ -403,7 +415,6 @@ function stageEffective(config, eff, logger, opts) {
|
|
|
403
415
|
projects,
|
|
404
416
|
};
|
|
405
417
|
}
|
|
406
|
-
export const RUNTIME_GATE_KEYS = ["rateLimitPerMin", "maxTaskCostUsd", "maxTaskTokens", "maxPrincipalCostUsd", "costQuotaWindowSec"];
|
|
407
418
|
export function centerManagedConfigKeys(eff) {
|
|
408
419
|
const out = new Set();
|
|
409
420
|
if (!eff)
|
|
@@ -414,12 +425,6 @@ export function centerManagedConfigKeys(eff) {
|
|
|
414
425
|
if (Object.prototype.hasOwnProperty.call(limits, k))
|
|
415
426
|
out.add(k);
|
|
416
427
|
}
|
|
417
|
-
const rt = eff.runtime;
|
|
418
|
-
if (rt) {
|
|
419
|
-
for (const k of RUNTIME_GATE_KEYS)
|
|
420
|
-
if (runtimeGatePresent(rt, k))
|
|
421
|
-
out.add(k);
|
|
422
|
-
}
|
|
423
428
|
if (eff.governance) {
|
|
424
429
|
if (eff.governance.autonomy !== undefined)
|
|
425
430
|
out.add("autonomy");
|
|
@@ -441,16 +446,10 @@ export function centerManagedConfigKeys(eff) {
|
|
|
441
446
|
}
|
|
442
447
|
if ((eff.models?.models ?? []).some((m) => m.enabled !== false))
|
|
443
448
|
out.add("models");
|
|
444
|
-
if (eff.models?.
|
|
445
|
-
out.add("
|
|
449
|
+
if (eff.models?.atMention !== undefined)
|
|
450
|
+
out.add("atMention");
|
|
446
451
|
return out;
|
|
447
452
|
}
|
|
448
|
-
export function runtimeGatePresent(rt, key) {
|
|
449
|
-
const v = rt[key];
|
|
450
|
-
if (key === "costQuotaWindowSec")
|
|
451
|
-
return typeof v === "number" && v > 0;
|
|
452
|
-
return typeof v === "number";
|
|
453
|
-
}
|
|
454
453
|
const KEPT_WOULD_MATCH_NOTHING = "the prior approvalRequire is unchanged — the published list would have silently matched nothing";
|
|
455
454
|
const KEPT_WOULD_REMOVE_GATE = "the prior approvalRequire is unchanged — coercing this malformed value would have silently REMOVED the approval gate";
|
|
456
455
|
const KEPT_WOULD_NEVER_PARK = "the prior approvalRequire is unchanged — this deployment has nowhere to park a gated call, so applying the list would leave it silently unenforced";
|
|
@@ -475,7 +474,7 @@ function stageApprovalRequire(raw) {
|
|
|
475
474
|
return {
|
|
476
475
|
ok: false,
|
|
477
476
|
detail: unmatchable.message,
|
|
478
|
-
reason: `published governance.approvalRequire has ${unmatchable.names.length} entr${unmatchable.names.length === 1 ? "y" : "ies"} that match no live tool (retired name / incomplete protocol name / server segment the engine never mints / CC-form rule) — the offending entries are named in the
|
|
477
|
+
reason: `published governance.approvalRequire has ${unmatchable.names.length} entr${unmatchable.names.length === 1 ? "y" : "ies"} that match no live tool (retired name / incomplete protocol name / server segment the engine never mints / CC-form rule) — the offending entries are named in the web_admin_approval_require_invalid log line`,
|
|
479
478
|
names: unmatchable.names,
|
|
480
479
|
kept: KEPT_WOULD_MATCH_NOTHING,
|
|
481
480
|
};
|
|
@@ -502,7 +501,7 @@ function stageApprovalGate(config, gov, leg, logger) {
|
|
|
502
501
|
if (!approvalEnvBaseline.has(config))
|
|
503
502
|
approvalEnvBaseline.set(config, Object.freeze([...config.approvalRequire]));
|
|
504
503
|
const refuse = (r) => {
|
|
505
|
-
logger?.warn("
|
|
504
|
+
logger?.warn("web_admin_approval_require_invalid", { keys: [...APPROVAL_GROUP_KEYS], detail: r.detail, names: [...r.names], kept: r.kept });
|
|
506
505
|
return { rejected: { keys: [...APPROVAL_GROUP_KEYS], reason: r.reason } };
|
|
507
506
|
};
|
|
508
507
|
const raw = gov?.approvalRequire;
|
|
@@ -522,27 +521,18 @@ function stageApprovalGate(config, gov, leg, logger) {
|
|
|
522
521
|
const same = next.length === config.approvalRequire.length && next.every((n, i) => n === config.approvalRequire[i]);
|
|
523
522
|
return same ? {} : { next };
|
|
524
523
|
}
|
|
525
|
-
function legacyGateValues(rt) {
|
|
526
|
-
const out = {};
|
|
527
|
-
if (!rt)
|
|
528
|
-
return out;
|
|
529
|
-
for (const k of RUNTIME_GATE_KEYS)
|
|
530
|
-
if (runtimeGatePresent(rt, k))
|
|
531
|
-
out[k] = rt[k];
|
|
532
|
-
return out;
|
|
533
|
-
}
|
|
534
524
|
export function applyRuntimeGates(config, eff, leg, logger) {
|
|
535
525
|
const approval = stageApprovalGate(config, eff?.governance, leg, logger);
|
|
536
526
|
if (approval.next !== undefined)
|
|
537
527
|
config.approvalRequire = approval.next;
|
|
538
|
-
const limits = stageLimits(config, { version: 0,
|
|
528
|
+
const limits = stageLimits(config, { version: 0, ...(eff !== undefined && "limits" in eff ? { limits: eff.limits } : {}) }, logger);
|
|
539
529
|
for (const [k, v] of limits.assignments)
|
|
540
530
|
config[k] = v;
|
|
541
531
|
for (const k of limits.deletions)
|
|
542
532
|
delete config[k];
|
|
543
533
|
const info = { ...(approval.next !== undefined ? { approvalRequire: approval.next } : {}), ...Object.fromEntries(limits.assignments) };
|
|
544
534
|
if (Object.keys(info).length > 0)
|
|
545
|
-
logger?.info("
|
|
535
|
+
logger?.info("web_admin_runtime", info);
|
|
546
536
|
}
|
|
547
537
|
function stageRuntimeHot(config, rt, logger) {
|
|
548
538
|
const infoLine = {};
|
|
@@ -556,7 +546,7 @@ function stageRuntimeHot(config, rt, logger) {
|
|
|
556
546
|
if (rt?.commandPolicy !== undefined) {
|
|
557
547
|
const errors = validateCommandRules(rt.commandPolicy);
|
|
558
548
|
if (errors.length > 0) {
|
|
559
|
-
logger?.error("
|
|
549
|
+
logger?.error("web_admin_commandpolicy_invalid", { errors, kept: config.commandPolicy?.length ?? 0 });
|
|
560
550
|
}
|
|
561
551
|
else if (JSON.stringify(config.commandPolicy) !== JSON.stringify(rt.commandPolicy)) {
|
|
562
552
|
setCommandPolicy = true;
|
|
@@ -578,26 +568,19 @@ export function applyRuntimeHot(config, gov, logger) {
|
|
|
578
568
|
if (s.setCommandPolicy)
|
|
579
569
|
config.commandPolicy = s.commandPolicyNext;
|
|
580
570
|
if (Object.keys(s.infoLine).length > 0)
|
|
581
|
-
logger?.info("
|
|
571
|
+
logger?.info("web_admin_runtime_hot", s.infoLine);
|
|
582
572
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
{
|
|
591
|
-
...DEFAULT_ROLE_TIER_LADDER.map((t) => ({ source: `tierGroup.${t}`, name: activeTiers[t] })),
|
|
592
|
-
];
|
|
593
|
-
for (const c of candidates) {
|
|
594
|
-
if (!c.name)
|
|
595
|
-
continue;
|
|
596
|
-
if (has(c.name))
|
|
597
|
-
return { name: c.name, source: c.source };
|
|
598
|
-
onDangling?.(c.source, c.name);
|
|
573
|
+
function engineDefaultOfSelect(select, models, activeTiers) {
|
|
574
|
+
const catalog = expandTiers(models, activeTiers) ?? models;
|
|
575
|
+
let picked;
|
|
576
|
+
try {
|
|
577
|
+
picked = coreResolveTaskModel({ modelRole: "default" }, { models: catalog, roles: { default: { select } } }).model;
|
|
578
|
+
}
|
|
579
|
+
catch (err) {
|
|
580
|
+
return { unresolved: err instanceof Error ? err.message : String(err) };
|
|
599
581
|
}
|
|
600
|
-
|
|
582
|
+
const name = Object.keys(models).find((n) => models[n] === picked);
|
|
583
|
+
return name !== undefined ? { name } : { unresolved: "the engine picked a model object that is not an entry of the usable catalog" };
|
|
601
584
|
}
|
|
602
585
|
export function logEffectiveDiff(config, eff, logger) {
|
|
603
586
|
const enabled = (eff.models?.models ?? []).filter((m) => m.enabled !== false);
|
|
@@ -606,8 +589,8 @@ export function logEffectiveDiff(config, eff, logger) {
|
|
|
606
589
|
const centerModels = enabled.map((m) => ({ name: m.name, id: m.id, provider: m.provider, apiKeyEnv: m.apiKeyEnv ?? null, tier: m.tier ?? null }));
|
|
607
590
|
const envModels = Object.entries(config.models).map(([name, m]) => ({ name, id: m.id, provider: m.provider }));
|
|
608
591
|
const centerRoles = Object.fromEntries(Object.entries(eff.models?.roles ?? {}).map(([r, t]) => [r, "model" in t ? t.model : { select: t.select }]));
|
|
609
|
-
logger?.warn("
|
|
610
|
-
note:
|
|
592
|
+
logger?.warn("web_admin_dry_run", {
|
|
593
|
+
note: `DRY RUN — web-admin config NOT applied; unset ${WEB_ADMIN_ENV.dryRun} to go live`,
|
|
611
594
|
version: eff.version,
|
|
612
595
|
wouldOverrideModels: enabled.length > 0,
|
|
613
596
|
wouldDefaultModel: plane.defaultName ?? null,
|
|
@@ -619,9 +602,9 @@ export function logEffectiveDiff(config, eff, logger) {
|
|
|
619
602
|
collab: { centerWouldRegister: Object.keys(projectCollabToWorkflows(eff.collab?.templates).workflows) },
|
|
620
603
|
skills: { centerWouldLoad: (eff.skills?.skills ?? []).filter((s) => s.enabled !== false).map((s) => s.name) },
|
|
621
604
|
mcp: { centerWouldRegister: (eff.mcp?.servers ?? []).filter((s) => s.enabled !== false).map((s) => s.name) },
|
|
622
|
-
|
|
623
|
-
center: eff.
|
|
624
|
-
current: Object.fromEntries(
|
|
605
|
+
limits: {
|
|
606
|
+
center: eff.limits ?? null,
|
|
607
|
+
current: Object.fromEntries(BATCH1_LIMIT_KEYS.map((k) => [k, config[k]])),
|
|
625
608
|
},
|
|
626
609
|
governance: {
|
|
627
610
|
center: eff.governance ?? null,
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* `CONFIG_PROVIDER=local` 下是 registry-core file-store 的内容哈希(u53,上游契约只承诺
|
|
10
10
|
* 「同 files ⇒ 同 number」,从未承诺序)——把它当序数消费,哈希更小的合法新代会被永久
|
|
11
11
|
* 静默忽略且本账双盲(target/applied 都不动)。修后:**推进判断吃收货处铸的进程内单调
|
|
12
|
-
* ordinal**(
|
|
12
|
+
* ordinal**(web-admin `generationOf`;等版本重放沿用同 ordinal=幂等语义保留),
|
|
13
13
|
* `version` 只做对账身份标——configTargetVersion 语义从「max(见过)」变为「**最后**见过的
|
|
14
14
|
* 源代」(remote 单调腿二者等价;local 腿这才是真相)。
|
|
15
15
|
*
|
|
@@ -36,7 +36,7 @@ export type ConfigApplyGroup = (typeof CONFIG_APPLY_GROUPS)[number];
|
|
|
36
36
|
/**
|
|
37
37
|
* S-810(车QU codex r1 [high] F1,验真后修)—— 一组在**热腿**被整组拒之后,新进程在 **boot 腿**上对同一候选重判会怎样。每组一格的闭集
|
|
38
38
|
* (`satisfies Record<ConfigApplyGroup, …>`:加一组 ⇒ 这里编译红,不许静默落进某一格)。部分应用的拍据此决定能不能发重启信号
|
|
39
|
-
* (`boot/
|
|
39
|
+
* (`boot/web-admin.ts` 的 `signalRestart`):
|
|
40
40
|
* · `"refuses-group"` —— boot 腿同一判据、同样整组拒,其余组照落 ⇒ 重启收敛(新进程的 boot 快照就是这份候选,同一差异不再签)。
|
|
41
41
|
* `limitsHttp`:`stageLimits` 不分腿。
|
|
42
42
|
* · `"may-refuse-boot"` —— boot 腿的结局取决于那一刻的部署事实,可能整个进程起不来 ⇒ 照信号重启不保证收敛(可能把一台还在服务的
|
|
@@ -5,10 +5,10 @@ export declare const CENTER_PULL_TIMEOUT_MS = 8000;
|
|
|
5
5
|
/** 已装 settings-schema 的 major(机器派生)。版本号不成形 ⇒ 模块装载即抛(装坏了的包不该带着一个猜出来的
|
|
6
6
|
* major 去跟中心协商 wire 形)。 */
|
|
7
7
|
export declare const SETTINGS_SCHEMA_MAJOR: string;
|
|
8
|
-
/** 一个中心端点 = 基址 + 凭证来源(`ServiceConfig.
|
|
8
|
+
/** 一个中心端点 = 基址 + 凭证来源(`ServiceConfig.webAdmin` 结构上满足)。 */
|
|
9
9
|
export interface CenterEndpoint {
|
|
10
10
|
readonly baseUrl: string;
|
|
11
|
-
/** 活性组的基址(env `
|
|
11
|
+
/** 活性组的基址(env `SEMA_WEB_ADMIN_LIVE_URL` 的回声;缺席 ⇒ 活性组也打 {@link baseUrl})。「缺席 = 主基址」这条规则只在 {@link centerFetch} 读。 */
|
|
12
12
|
readonly liveBaseUrl?: string | undefined;
|
|
13
13
|
readonly credential: CenterCredentialSource;
|
|
14
14
|
}
|
|
@@ -21,11 +21,11 @@ export interface CenterEndpoint {
|
|
|
21
21
|
* (7.105.0 及以前累加器与用量环都挂在公告地址上,不公告的引擎于是从不报用量,租约的已耗量只剩中心自己的估计)。
|
|
22
22
|
*/
|
|
23
23
|
export declare function centerLaneConfigured<C extends {
|
|
24
|
-
readonly
|
|
24
|
+
readonly webAdmin?: (CenterEndpoint & {
|
|
25
25
|
readonly worker?: string | undefined;
|
|
26
26
|
}) | undefined;
|
|
27
27
|
}>(cfg: C): cfg is C & {
|
|
28
|
-
readonly
|
|
28
|
+
readonly webAdmin: NonNullable<C["webAdmin"]> & {
|
|
29
29
|
readonly worker: string;
|
|
30
30
|
};
|
|
31
31
|
};
|
|
@@ -73,8 +73,8 @@ export interface CenterRequestInit {
|
|
|
73
73
|
readonly fetchImpl?: typeof fetch;
|
|
74
74
|
}
|
|
75
75
|
/**
|
|
76
|
-
* 「后面要拼路径的基址」的拒句 —— 中心基址三席(`
|
|
77
|
-
* 公告地址(`FLEET_ADVERTISE_ADDRESS`,中心往它后面拼 `/v1/...`)同读。判官是
|
|
76
|
+
* 「后面要拼路径的基址」的拒句 —— 中心基址三席(`SEMA_WEB_ADMIN_URL` / `SEMA_WEB_ADMIN_LIVE_URL` / `SEMA_WEB_ADMIN_WEB_URL`)与本实例的
|
|
77
|
+
* 公告地址(`FLEET_ADVERTISE_ADDRESS`,中心往它后面拼 `/v1/...`)同读。判官是 settings-schema 的 `httpUrlFlaws`(层级式 http(s) URL;Node 的 fetch
|
|
78
78
|
* 认 `file://` / `data:`,配错的地址不许变成读本地文件),本函数只声明这一席禁哪几件({@link PATH_BASE_FORBIDS})。起服时 `config.ts` 判
|
|
79
79
|
* (拒启),请求期 {@link centerFetch} 对同一份基址再判一次(自拼 `ServiceConfig` 的嵌入方也吃同一条规则)。合格 ⇒ `undefined`;否则交回
|
|
80
80
|
* `<subject> <规则词>; got "<展示面安全形>"` —— 拒句里没有从值里解析出来的任何记号(scheme / userinfo / 主机名),捕获点照原样落日志即可。
|
|
@@ -93,7 +93,7 @@ export declare function centerTokenRefresh(issuer: string, refreshToken: string,
|
|
|
93
93
|
}): Promise<Response>;
|
|
94
94
|
/**
|
|
95
95
|
* 对中心发一条请求。返回响应与**这次请求所用凭证的身份**(调用方据此给拿回来的候选打分箱标记,
|
|
96
|
-
* 见 `boot/
|
|
96
|
+
* 见 `boot/web-admin.ts` 的 LKG / prompt 状态分箱)。
|
|
97
97
|
*/
|
|
98
98
|
export declare function centerFetch(center: CenterEndpoint, route: CenterRoute, init: CenterRequestInit): Promise<{
|
|
99
99
|
res: Response;
|
|
@@ -2,7 +2,8 @@ import settingsSchemaPackage from "@sema-agent/settings-schema/package.json" wit
|
|
|
2
2
|
import { AUTH_V1_PATHS } from "@sema-agent/settings-schema/api/auth";
|
|
3
3
|
import { FLEET_LIVE_PATHS, FLEET_PATHS } from "@sema-agent/settings-schema/fleet";
|
|
4
4
|
import { CenterCredentialUnavailableError } from "../center-credential.js";
|
|
5
|
-
import {
|
|
5
|
+
import { WEB_ADMIN_ENV, httpUrlFlaws } from "@sema-agent/settings-schema";
|
|
6
|
+
import { buildHttpUrlRefusal } from "../url-refusal.js";
|
|
6
7
|
export const SCHEMA_MAJOR_HEADER = "x-sema-schema-major";
|
|
7
8
|
export const CENTER_PULL_TIMEOUT_MS = 8000;
|
|
8
9
|
export const SETTINGS_SCHEMA_MAJOR = (() => {
|
|
@@ -13,7 +14,7 @@ export const SETTINGS_SCHEMA_MAJOR = (() => {
|
|
|
13
14
|
return m[1];
|
|
14
15
|
})();
|
|
15
16
|
export function centerLaneConfigured(cfg) {
|
|
16
|
-
const cc = cfg.
|
|
17
|
+
const cc = cfg.webAdmin;
|
|
17
18
|
return cc !== undefined && cc.baseUrl !== "" && cc.credential.declared && cc.worker !== undefined && cc.worker !== "";
|
|
18
19
|
}
|
|
19
20
|
export function pullViewQuery(view) {
|
|
@@ -83,7 +84,7 @@ export async function centerTokenRefresh(issuer, refreshToken, init) {
|
|
|
83
84
|
});
|
|
84
85
|
}
|
|
85
86
|
export async function centerFetch(center, route, init) {
|
|
86
|
-
assertPathBaseUrl(center.baseUrl,
|
|
87
|
+
assertPathBaseUrl(center.baseUrl, WEB_ADMIN_ENV.url);
|
|
87
88
|
const cred = center.credential.read();
|
|
88
89
|
if (!cred.ok)
|
|
89
90
|
throw new CenterCredentialUnavailableError(cred.reason);
|
|
@@ -97,7 +98,7 @@ export async function centerFetch(center, route, init) {
|
|
|
97
98
|
const path = routePath(route, cred.kind);
|
|
98
99
|
const liveBase = FLEET_LIVE_PATHS.includes(path) ? center.liveBaseUrl : undefined;
|
|
99
100
|
if (liveBase !== undefined)
|
|
100
|
-
assertPathBaseUrl(liveBase,
|
|
101
|
+
assertPathBaseUrl(liveBase, WEB_ADMIN_ENV.liveUrl);
|
|
101
102
|
const url = `${(liveBase ?? center.baseUrl).replace(/\/+$/, "")}${path}`;
|
|
102
103
|
const res = await (init.fetchImpl ?? fetch)(url, {
|
|
103
104
|
method: init.method ?? "GET",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 🔴 三条纪律:
|
|
5
5
|
* 1. `EFFECTIVE_CONFIG_KEYS` 与 {@link EffectiveConfig} **类型级**绑定(`satisfies Record<keyof …, true>`):接口加键 /
|
|
6
6
|
* 删键而不动这里 = **编译错**——运行时键表永不与类型面漂移(这不是第二份表,是同一份接口的运行时投影;
|
|
7
|
-
*
|
|
7
|
+
* governance 域的键表同法,用于与 schema zod shape 的集合等式墓碑;settings-schema 11 退役了 `runtime` 域,它的键表随之删)。
|
|
8
8
|
* 2. 三张登记表(非域白名单 / 不消费表 / 远端专属表)每行**必须带理由**;行的成立性由对账门**双向**钉——登记了
|
|
9
9
|
* 却不成立(死账)与成立了却没登记(半接线)都红,理由失效之日格子自己翻红(墓碑),禁裸豁免。
|
|
10
10
|
* 3. schema 侧的数据(`DOMAINS` / `PORTABLE_DOMAINS` / zod shape)一律由对账门**读 dist**,绝不抄进这里
|
|
@@ -15,8 +15,6 @@ import type { EffectiveConfig } from "./types.js";
|
|
|
15
15
|
export declare const EFFECTIVE_CONFIG_KEYS: ReadonlyArray<keyof EffectiveConfig>;
|
|
16
16
|
/** 信封键(不是域)。 */
|
|
17
17
|
export declare const EFFECTIVE_ENVELOPE_KEYS: readonly ["version", "updatedAt"];
|
|
18
|
-
/** `runtime` 域(限额残余)的键集——对账门与 schema `RuntimeConfig.shape` 钉集合等式([ref] 件3 退役键墓碑)。 */
|
|
19
|
-
export declare const RUNTIME_DOMAIN_KEYS: readonly string[];
|
|
20
18
|
/** `governance` 域(治理三件)的键集——对账门与 schema `GovernanceConfig.shape` 钉集合等式。 */
|
|
21
19
|
export declare const GOVERNANCE_DOMAIN_KEYS: readonly string[];
|
|
22
20
|
/** EffectiveConfig 上**不是** settings-schema 域的顶层键(键 → 成文理由)。⚠️ 墓碑语义:任一键将来出现在 schema
|
|
@@ -7,7 +7,6 @@ const EFFECTIVE_CONFIG_KEY_TABLE = {
|
|
|
7
7
|
mcp: true,
|
|
8
8
|
a2a: true,
|
|
9
9
|
scenarios: true,
|
|
10
|
-
runtime: true,
|
|
11
10
|
limits: true,
|
|
12
11
|
projects: true,
|
|
13
12
|
governance: true,
|
|
@@ -17,14 +16,6 @@ const EFFECTIVE_CONFIG_KEY_TABLE = {
|
|
|
17
16
|
};
|
|
18
17
|
export const EFFECTIVE_CONFIG_KEYS = Object.keys(EFFECTIVE_CONFIG_KEY_TABLE);
|
|
19
18
|
export const EFFECTIVE_ENVELOPE_KEYS = ["version", "updatedAt"];
|
|
20
|
-
const RUNTIME_DOMAIN_KEY_TABLE = {
|
|
21
|
-
rateLimitPerMin: true,
|
|
22
|
-
maxTaskCostUsd: true,
|
|
23
|
-
maxTaskTokens: true,
|
|
24
|
-
maxPrincipalCostUsd: true,
|
|
25
|
-
costQuotaWindowSec: true,
|
|
26
|
-
};
|
|
27
|
-
export const RUNTIME_DOMAIN_KEYS = Object.keys(RUNTIME_DOMAIN_KEY_TABLE);
|
|
28
19
|
const GOVERNANCE_DOMAIN_KEY_TABLE = {
|
|
29
20
|
autonomy: true,
|
|
30
21
|
commandPolicy: true,
|