@sema-agent/server 7.99.0 → 7.101.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 +35 -1
- package/README.md +12 -1
- package/README.zh-CN.md +9 -1
- package/USAGE.md +150 -56
- package/dist/approval-ask-machine.d.ts +10 -0
- package/dist/approval-ask-machine.js +3 -0
- package/dist/approval-card.d.ts +86 -1
- package/dist/approval-card.js +42 -8
- package/dist/approval-policy-settlement.d.ts +31 -0
- package/dist/approval-policy-settlement.js +16 -0
- package/dist/approval-reconciler.d.ts +8 -1
- package/dist/approval-reconciler.js +5 -4
- package/dist/approval.d.ts +44 -20
- package/dist/approval.js +14 -6
- package/dist/boot/config-center.d.ts +16 -8
- package/dist/boot/config-center.js +114 -90
- package/dist/boot/execution-env.d.ts +1 -2
- package/dist/boot/execution-env.js +21 -3
- package/dist/boot/leader.js +12 -9
- package/dist/boot/memory-consolidation.d.ts +38 -0
- package/dist/boot/memory-consolidation.js +35 -0
- package/dist/boot/parked-revive-gate.js +1 -0
- package/dist/boot/reapers.js +1 -0
- package/dist/boot/resolve-spec.d.ts +0 -1
- package/dist/boot/resolve-spec.js +28 -10
- package/dist/boot/runner-deps.d.ts +2 -2
- package/dist/boot/runner-deps.js +9 -3
- package/dist/boot/stage-02-registry-pull.js +9 -0
- 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 +3 -3
- package/dist/boot/stage-07-capability-layer.d.ts +2 -3
- package/dist/boot/stage-07-capability-layer.js +6 -6
- package/dist/boot/stage-08-reapers.d.ts +3 -4
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +2 -3
- package/dist/boot/stage-10-http-server.d.ts +2 -3
- package/dist/boot/stage-10-http-server.js +2 -2
- package/dist/boot/stores.js +4 -0
- package/dist/capabilities/center-plugins.d.ts +14 -2
- package/dist/capabilities/center-plugins.js +13 -5
- package/dist/capabilities/hands-lane.js +2 -1
- package/dist/capabilities/scenarios.js +18 -5
- package/dist/capabilities/tool-defer.d.ts +2 -2
- package/dist/config-catalog.d.ts +2 -2
- package/dist/config-catalog.js +16 -15
- package/dist/config-center/apply-effective.d.ts +59 -39
- package/dist/config-center/apply-effective.js +231 -120
- package/dist/config-center/apply-ledger.d.ts +45 -16
- package/dist/config-center/apply-ledger.js +28 -18
- package/dist/config-center/facade.d.ts +12 -6
- package/dist/config-center/facade.js +2 -2
- package/dist/config-center/hot-keys-registry.d.ts +11 -4
- package/dist/config-center/hot-keys-registry.js +11 -7
- package/dist/config-center/http-client.d.ts +4 -1
- package/dist/config-center/http-client.js +15 -10
- package/dist/config-center/read-warnings.d.ts +115 -6
- package/dist/config-center/read-warnings.js +115 -5
- package/dist/config-center/restart-signal.d.ts +21 -14
- package/dist/config-center/restart-signal.js +16 -22
- package/dist/config-center/types.d.ts +9 -3
- package/dist/config-invariants.d.ts +2 -2
- package/dist/config-invariants.js +15 -0
- package/dist/config-provider.d.ts +4 -0
- package/dist/config-provider.js +64 -9
- package/dist/config-types.d.ts +57 -13
- package/dist/config.js +87 -22
- package/dist/cross-session-settings.js +1 -1
- package/dist/deployment-governance.d.ts +45 -12
- package/dist/deployment-governance.js +37 -9
- package/dist/env-name-allowlist-knobs.d.ts +37 -0
- package/dist/env-name-allowlist-knobs.js +24 -0
- package/dist/fleet-client.d.ts +2 -1
- package/dist/hooks/hook-llm.js +13 -4
- package/dist/host-lsp-manager.d.ts +41 -0
- package/dist/host-lsp-manager.js +15 -0
- package/dist/http/active-run-conflict.d.ts +2 -12
- package/dist/http/active-run-conflict.js +1 -6
- package/dist/http/admission.js +30 -7
- package/dist/http/memory-store-refusal-reply.d.ts +24 -0
- package/dist/http/memory-store-refusal-reply.js +10 -0
- package/dist/http/resume-legs.d.ts +1 -1
- package/dist/http/resume-legs.js +14 -10
- package/dist/http/route-ctx.d.ts +2 -2
- package/dist/http/routes/approvals-assistant.d.ts +1 -1
- package/dist/http/routes/approvals-assistant.js +3 -2
- package/dist/http/routes/capabilities.js +3 -3
- package/dist/http/routes/memory-bundle.js +3 -0
- package/dist/http/routes/memory-compliance.js +3 -0
- package/dist/http/routes/memory-origin.js +3 -0
- package/dist/http/routes/sessions.js +7 -0
- package/dist/http/routes/side-query.js +10 -4
- package/dist/http/routes/tasks.js +7 -6
- package/dist/http/server.d.ts +6 -5
- package/dist/http/wire-types.d.ts +6 -3
- package/dist/instruction-source-notice.d.ts +97 -0
- package/dist/instruction-source-notice.js +78 -0
- package/dist/leader/wire.d.ts +20 -12
- package/dist/leader/wire.js +7 -6
- package/dist/memory-layer-legacy-lock.d.ts +14 -0
- package/dist/memory-layer-legacy-lock.js +74 -0
- package/dist/memory-store-refusal.d.ts +37 -0
- package/dist/memory-store-refusal.js +14 -0
- package/dist/model-entry-refusal.d.ts +130 -0
- package/dist/model-entry-refusal.js +157 -0
- package/dist/model-provider.d.ts +9 -3
- package/dist/model-route-endpoint.d.ts +1 -1
- package/dist/model-select.d.ts +49 -4
- package/dist/model-select.js +48 -17
- package/dist/observability/corrupt-read-seat.d.ts +21 -4
- package/dist/observability/corrupt-read-seat.js +31 -0
- package/dist/observability/fail-open.d.ts +16 -4
- package/dist/observability/fail-open.js +16 -4
- package/dist/observability/metrics.js +1 -0
- package/dist/observability/run-terminal.js +1 -0
- package/dist/observability/secret-env-scrub.d.ts +27 -3
- package/dist/observability/secret-env-scrub.js +22 -6
- package/dist/orchestration/workflow-completion-inbox.d.ts +31 -23
- package/dist/orchestration/workflow-completion-inbox.js +8 -3
- package/dist/plugins/approval-ask-store-file.d.ts +20 -2
- package/dist/plugins/approval-ask-store-file.js +21 -3
- package/dist/plugins/approval-ask-store-memory.d.ts +2 -0
- package/dist/plugins/approval-ask-store-memory.js +39 -13
- package/dist/plugins/approval-ask-store-sql.d.ts +24 -5
- package/dist/plugins/approval-ask-store-sql.js +27 -3
- package/dist/plugins/caching-session-store.d.ts +6 -0
- package/dist/plugins/caching-session-store.js +6 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +9 -3
- package/dist/plugins/checkpoint-store-sql.js +19 -2
- package/dist/plugins/local-checkpoint-store.js +2 -0
- package/dist/plugins/local-session-store.d.ts +20 -25
- package/dist/plugins/local-session-store.js +111 -64
- package/dist/plugins/mailbox-store-sql.d.ts +7 -5
- package/dist/plugins/mailbox-store-sql.js +6 -4
- package/dist/plugins/permission-rule-store-file.js +3 -2
- package/dist/plugins/pg-session-storage.d.ts +7 -0
- package/dist/plugins/pg-session-storage.js +19 -0
- package/dist/plugins/remote-env-host.d.ts +3 -1
- package/dist/plugins/remote-env-host.js +3 -6
- package/dist/plugins/sql-json-column.d.ts +16 -3
- package/dist/plugins/sql-json-column.js +15 -4
- package/dist/plugins/tidb-pool.js +5 -0
- package/dist/plugins/tidb-session-store.d.ts +13 -0
- package/dist/plugins/tidb-session-store.js +14 -0
- package/dist/plugins/web-search.d.ts +44 -23
- package/dist/plugins/web-search.js +161 -50
- package/dist/project-memory.d.ts +10 -0
- package/dist/project-memory.js +28 -14
- package/dist/run-local.d.ts +17 -15
- package/dist/run-local.js +119 -18
- package/dist/runs.js +6 -5
- package/dist/runtime-governance.d.ts +50 -3
- package/dist/runtime-governance.js +5 -0
- package/dist/sealed-key.d.ts +5 -2
- package/dist/server-secret-env.d.ts +26 -2
- package/dist/server-secret-env.js +9 -0
- package/dist/task-settings.d.ts +1 -1
- package/dist/tool-approval.d.ts +150 -28
- package/dist/tool-approval.js +150 -54
- package/dist/trace/core-keyset-guard.d.ts +30 -8
- package/dist/trace/engine-notice-wire.d.ts +27 -1
- package/dist/trace/engine-notice-wire.js +8 -0
- package/dist/trace/ledger-sink.js +2 -2
- package/dist/trace/project.d.ts +13 -0
- package/dist/trace/project.js +22 -5
- package/dist/trace/projection-drop.d.ts +2 -0
- package/dist/trace/projection-drop.js +6 -0
- package/dist/trace/sema-provenance.d.ts +4 -1
- package/dist/trace/sema-provenance.js +1 -1
- package/dist/trace/task-notification-facets.d.ts +65 -0
- package/dist/trace/task-notification-facets.js +31 -0
- package/dist/trace/wire-projection-faces.d.ts +3 -3
- package/dist/trace/wire-projection-faces.js +3 -3
- package/package.json +3 -3
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import { resolveActiveTiers
|
|
1
|
+
import { resolveActiveTiers } from "@sema-agent/settings-schema";
|
|
2
2
|
import { sealedKeyPoison } from "../sealed-key.js";
|
|
3
3
|
import { applyAutoCompactWindow, buildModelCostKey, findUnmatchableToolNames, formatUnmatchableToolNames, parseAutonomy } from "../config.js";
|
|
4
4
|
import { buildModelCompat, readModelCompat } from "../model-compat.js";
|
|
5
5
|
import { validateCommandRules } from "../runtime-governance.js";
|
|
6
6
|
import { projectCollabToWorkflows, registerCollabWorkflows } from "../capabilities/collab-workflows.js";
|
|
7
7
|
import { stageLimits } from "./stage-limits.js";
|
|
8
|
-
import { BATCH1_LIMIT_KEYS } from "./hot-keys-registry.js";
|
|
8
|
+
import { APPROVAL_GROUP_KEYS, BATCH1_LIMIT_KEYS } from "./hot-keys-registry.js";
|
|
9
9
|
import { buildDisplaySafeUrl } from "../trace/redact.js";
|
|
10
10
|
import { crossInvariant } from "../config-invariants.js";
|
|
11
|
+
import { judgeCatalogEntries, mintedRefusedNames, refusalEventOf, refusalWarnFields, REFUSED_NAME_MINTED_CODE, unreadableDroppedRefusal } from "../model-entry-refusal.js";
|
|
12
|
+
import { gateIntentUnserviceableReason } from "../approval.js";
|
|
13
|
+
import { refusedGroups } from "./apply-ledger.js";
|
|
11
14
|
const charsPerTokenOf = (v) => (typeof v === "number" && Number.isFinite(v) && v > 0 ? v : undefined);
|
|
12
15
|
function envDerivedModel(config) {
|
|
13
16
|
const m = config.envModel;
|
|
@@ -93,7 +96,7 @@ function stageAtModelAllowlist(raw, models, logger) {
|
|
|
93
96
|
}
|
|
94
97
|
return names;
|
|
95
98
|
}
|
|
96
|
-
export function applyEffective(config, eff, logger, opts
|
|
99
|
+
export function applyEffective(config, eff, logger, opts) {
|
|
97
100
|
const staged = stageEffective(config, eff, logger, opts);
|
|
98
101
|
const gen = opts.generation ?? staged.version;
|
|
99
102
|
const live = appliedGeneration.get(config);
|
|
@@ -102,9 +105,10 @@ export function applyEffective(config, eff, logger, opts = {}) {
|
|
|
102
105
|
logger?.warn("sema_registry_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" });
|
|
103
106
|
return false;
|
|
104
107
|
}
|
|
105
|
-
|
|
108
|
+
const mp = staged.modelPlaneGroup.stages.models;
|
|
109
|
+
if (mp !== undefined && opts.swapPlane !== undefined) {
|
|
106
110
|
try {
|
|
107
|
-
opts.swapPlane({ models:
|
|
111
|
+
opts.swapPlane({ models: mp.models, tiers: mp.activeTiers });
|
|
108
112
|
}
|
|
109
113
|
catch (err) {
|
|
110
114
|
logger?.warn("sema_registry_model_plane_swap_refused", {
|
|
@@ -118,25 +122,31 @@ export function applyEffective(config, eff, logger, opts = {}) {
|
|
|
118
122
|
commitStaged(config, staged);
|
|
119
123
|
if (ordered)
|
|
120
124
|
appliedGeneration.set(config, gen);
|
|
121
|
-
opts.report
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
125
|
+
if (opts.report) {
|
|
126
|
+
const report = {
|
|
127
|
+
version: staged.version,
|
|
128
|
+
...(opts.generation !== undefined ? { ordinal: opts.generation } : {}),
|
|
129
|
+
groups: {
|
|
130
|
+
limitsHttp: {
|
|
131
|
+
applied: staged.limits.changed,
|
|
132
|
+
...(staged.limits.deferred.length > 0 ? { deferred: staged.limits.deferred } : {}),
|
|
133
|
+
...(staged.limits.rejected ? { rejected: staged.limits.rejected } : {}),
|
|
134
|
+
},
|
|
135
|
+
approvalGate: groupReportOf(staged.approvalGate),
|
|
136
|
+
modelPlane: modelPlaneGroupReport(staged.modelPlaneGroup),
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
opts.report({ ...report, fullyApplied: refusedGroups(report).length === 0 });
|
|
140
|
+
}
|
|
131
141
|
if (staged.limits.changed.length > 0)
|
|
132
142
|
logger?.info("sema_registry_limits", { version: staged.version, keys: staged.limits.changed });
|
|
133
|
-
if (
|
|
134
|
-
logger?.info("sema_registry_models",
|
|
135
|
-
if (staged.
|
|
136
|
-
logger?.info("sema_registry_runtime", staged.
|
|
143
|
+
if (mp)
|
|
144
|
+
logger?.info("sema_registry_models", mp.infoLine);
|
|
145
|
+
if (staged.approvalGate.next !== undefined)
|
|
146
|
+
logger?.info("sema_registry_runtime", { version: staged.version, approvalRequire: staged.approvalGate.next });
|
|
137
147
|
if (Object.keys(staged.hot.infoLine).length > 0)
|
|
138
148
|
logger?.info("sema_registry_runtime_hot", staged.hot.infoLine);
|
|
139
|
-
for (const n of staged.
|
|
149
|
+
for (const n of staged.modelPlaneGroup.stages.collab?.notices ?? []) {
|
|
140
150
|
if (n.level === "warn")
|
|
141
151
|
logger?.warn(n.msg, n.fields);
|
|
142
152
|
else
|
|
@@ -147,10 +157,11 @@ export function applyEffective(config, eff, logger, opts = {}) {
|
|
|
147
157
|
return true;
|
|
148
158
|
}
|
|
149
159
|
function commitStaged(config, staged) {
|
|
150
|
-
const mp = staged.
|
|
160
|
+
const mp = staged.modelPlaneGroup.stages.models;
|
|
151
161
|
if (mp) {
|
|
152
162
|
config.modelApiKeyEnv = mp.modelApiKeyEnv;
|
|
153
163
|
config.modelApiKeys = mp.modelApiKeys;
|
|
164
|
+
config.modelRefusals = mp.modelRefusals;
|
|
154
165
|
mutateInPlace(config.modelQuotaWeights, mp.quotaWeights);
|
|
155
166
|
mutateInPlace(config.models, mp.models);
|
|
156
167
|
config.model = config.models.default;
|
|
@@ -158,10 +169,8 @@ function commitStaged(config, staged) {
|
|
|
158
169
|
mutateInPlace(config.tiers, mp.activeTiers);
|
|
159
170
|
config.atModelAllowlist = mp.atModelAllowlist;
|
|
160
171
|
}
|
|
161
|
-
if (staged.
|
|
162
|
-
|
|
163
|
-
config[k] = v;
|
|
164
|
-
}
|
|
172
|
+
if (staged.approvalGate.next !== undefined)
|
|
173
|
+
config.approvalRequire = staged.approvalGate.next;
|
|
165
174
|
for (const [k, v] of staged.limits.assignments)
|
|
166
175
|
config[k] = v;
|
|
167
176
|
for (const k of staged.limits.deletions)
|
|
@@ -170,62 +179,90 @@ function commitStaged(config, staged) {
|
|
|
170
179
|
config.autonomy = staged.hot.autonomyNext;
|
|
171
180
|
if (staged.hot.setCommandPolicy)
|
|
172
181
|
config.commandPolicy = staged.hot.commandPolicyNext;
|
|
173
|
-
|
|
174
|
-
|
|
182
|
+
const collab = staged.modelPlaneGroup.stages.collab;
|
|
183
|
+
if (collab !== undefined)
|
|
184
|
+
registerCollabWorkflows(collab.workflows);
|
|
175
185
|
mutateInPlace(config.projects, staged.projects);
|
|
176
186
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
187
|
+
export const MODEL_PLANE_DOMAINS = ["models", "collab"];
|
|
188
|
+
const MODEL_PLANE_SURFACES = {
|
|
189
|
+
models: ["models", "model", "modelRefusals", "modelApiKeyEnv", "modelApiKeys", "modelQuotaWeights", "roles", "tiers", "atModelAllowlist"],
|
|
190
|
+
collab: ["collabWorkflows"],
|
|
191
|
+
};
|
|
192
|
+
const MODEL_PLANE_GROUP_KEYS = MODEL_PLANE_DOMAINS.flatMap((d) => MODEL_PLANE_SURFACES[d]);
|
|
193
|
+
export function withModelPlaneOf(target, source) {
|
|
194
|
+
const out = { ...target };
|
|
195
|
+
for (const d of MODEL_PLANE_DOMAINS)
|
|
196
|
+
takeModelPlaneDomain(out, source, d);
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
function takeModelPlaneDomain(out, source, d) {
|
|
200
|
+
const v = source[d];
|
|
201
|
+
if (v === undefined)
|
|
202
|
+
delete out[d];
|
|
203
|
+
else
|
|
204
|
+
out[d] = v;
|
|
205
|
+
}
|
|
206
|
+
export function buildEnvOnlyModelPlane() {
|
|
207
|
+
return { models: { models: [], roles: {} } };
|
|
208
|
+
}
|
|
209
|
+
function modelPlaneGroupReport(group) {
|
|
210
|
+
return {
|
|
211
|
+
applied: MODEL_PLANE_DOMAINS.flatMap((d) => (group.stages[d] !== undefined ? MODEL_PLANE_SURFACES[d] : [])),
|
|
212
|
+
...(group.rejected !== undefined ? { rejected: { keys: group.rejected.keys, reason: group.rejected.reason } } : {}),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
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)";
|
|
216
|
+
function stageModelPlane(config, eff, logger, sealedKeys) {
|
|
184
217
|
const enabled = (eff.models?.models ?? []).filter((m) => m.enabled !== false);
|
|
185
|
-
|
|
186
|
-
if (enabled.length
|
|
218
|
+
const droppedEnabled = eff.models?.droppedIdentities;
|
|
219
|
+
if (enabled.length === 0 && !(Array.isArray(droppedEnabled) && droppedEnabled.length > 0))
|
|
220
|
+
return { stages: { collab: stageCollabDomain(eff) } };
|
|
221
|
+
{
|
|
222
|
+
const verdict = judgeCatalogEntries(enabled, { anthropicRouteMounted: config.anthropic !== undefined }, eff.models.droppedIdentities);
|
|
223
|
+
for (const [name, r] of Object.entries(verdict.refused))
|
|
224
|
+
logger?.warn(refusalEventOf(r.reason), refusalWarnFields(name, r));
|
|
225
|
+
const refuse = (invariant, reason, detail = {}) => {
|
|
226
|
+
logger?.warn("sema_registry_model_plane_refused", {
|
|
227
|
+
version: typeof eff.version === "number" ? eff.version : 0,
|
|
228
|
+
invariant,
|
|
229
|
+
reason,
|
|
230
|
+
...detail,
|
|
231
|
+
kept: "the model plane of this candidate is refused as a group — the current model plane (catalog / roles / tiers / refusals / allowlist, and the collab workflows that reference it) keeps serving and the other groups of this candidate still apply; the candidate is not fully applied (LKG / ETag stay on the last fully applied generation) and is re-judged next pull",
|
|
232
|
+
});
|
|
233
|
+
return { stages: {}, rejected: { keys: MODEL_PLANE_GROUP_KEYS, reason, invariant }, refused: verdict.refused };
|
|
234
|
+
};
|
|
235
|
+
if (verdict.unreadable.length > 0) {
|
|
236
|
+
logger?.warn("sema_registry_model_entry_dropped_unproven", {
|
|
237
|
+
entries: [...verdict.unreadable],
|
|
238
|
+
code: REFUSED_NAME_MINTED_CODE,
|
|
239
|
+
hint: "these catalog entries were dropped by the settings-schema read and the raw document could not be matched to the snapshot that was parsed (e.g. the local file changed between two reads) — their names cannot be proven, so no tombstone can be minted and the candidate's model plane is refused (a name nobody can prove cannot be shown not to be re-minted by the tier/alias expansion); the next read re-judges",
|
|
240
|
+
});
|
|
241
|
+
return refuse("refused-names-not-minted", unreadableDroppedRefusal(verdict.unreadable));
|
|
242
|
+
}
|
|
243
|
+
const usable = verdict.usable;
|
|
187
244
|
const models = {};
|
|
188
245
|
const modelApiKeyEnv = {};
|
|
189
246
|
const modelApiKeys = {};
|
|
190
247
|
const envModel = envDerivedModel(config);
|
|
191
248
|
const envDefaults = { maxTokens: envModel.maxTokens, contextWindow: envModel.contextWindow, reasoning: envModel.reasoning, charsPerToken: envModel.charsPerToken, defaultThinking: envModel.defaultThinking, compat: envModel.compat };
|
|
192
|
-
for (const m of
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
let ok = false;
|
|
197
|
-
try {
|
|
198
|
-
const u = new URL(rawBaseUrl);
|
|
199
|
-
ok = u.protocol === "http:" || u.protocol === "https:";
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
ok = false;
|
|
203
|
-
}
|
|
204
|
-
if (ok)
|
|
205
|
-
logger?.warn("sema_registry_model_base_url", { model: m.name, baseUrl: buildDisplaySafeUrl(rawBaseUrl), hint: "registry-directed gateway override — this model's key authenticates AGAINST THIS URL" });
|
|
206
|
-
else {
|
|
207
|
-
logger?.warn("sema_registry_model_base_url_rejected", { model: m.name, baseUrl: buildDisplaySafeUrl(String(rawBaseUrl)), hint: "only http(s) URLs are honored — falling back to the deployment gateway (MODEL_GATEWAY_BASEURL); with none declared the whole candidate is refused (model-route-endpoint)" });
|
|
208
|
-
safeUrl = undefined;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
models[m.name] = toModel(safeUrl === rawBaseUrl ? m : { ...m, baseUrl: safeUrl }, envDefaults, logger);
|
|
249
|
+
for (const m of usable) {
|
|
250
|
+
if (m.baseUrl)
|
|
251
|
+
logger?.warn("sema_registry_model_base_url", { model: m.name, baseUrl: buildDisplaySafeUrl(m.baseUrl), hint: "registry-directed gateway override — this model's key authenticates AGAINST THIS URL" });
|
|
252
|
+
models[m.name] = toModel(m, envDefaults, logger);
|
|
212
253
|
if (m.sealedApiKey) {
|
|
213
254
|
const s = m.sealedApiKey;
|
|
214
|
-
if (
|
|
215
|
-
logger?.warn("sema_registry_model_sealed_key_bad_alg", { model: m.name, alg: String(s.alg), expected: SEALED_BOX_ALG, hint: "unsupported sealed-box alg — key POISONED: tasks on this model fail loud (no apiKeyEnv fallback, no gateway fallback; contract v1 pins the alg)" });
|
|
216
|
-
modelApiKeys[m.name] = sealedKeyPoison(s.publicKeyId, "unsupported_alg");
|
|
217
|
-
}
|
|
218
|
-
else if (!opts.sealedKeys) {
|
|
255
|
+
if (!sealedKeys) {
|
|
219
256
|
logger?.warn("sema_registry_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)" });
|
|
220
257
|
modelApiKeys[m.name] = sealedKeyPoison(s.publicKeyId, "no_key_store");
|
|
221
258
|
}
|
|
222
|
-
else if (!
|
|
223
|
-
logger?.warn("sema_registry_model_sealed_key_no_private_key", { model: m.name, publicKeyId: s.publicKeyId, held:
|
|
259
|
+
else if (!sealedKeys.has(s.publicKeyId)) {
|
|
260
|
+
logger?.warn("sema_registry_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)" });
|
|
224
261
|
modelApiKeys[m.name] = sealedKeyPoison(s.publicKeyId, "stale_ciphertext");
|
|
225
262
|
}
|
|
226
263
|
else {
|
|
227
264
|
try {
|
|
228
|
-
modelApiKeys[m.name] =
|
|
265
|
+
modelApiKeys[m.name] = sealedKeys.open(s.ciphertext, s.publicKeyId);
|
|
229
266
|
}
|
|
230
267
|
catch (err) {
|
|
231
268
|
logger?.warn("sema_registry_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)" });
|
|
@@ -239,12 +276,9 @@ function stageEffective(config, eff, logger, opts) {
|
|
|
239
276
|
else
|
|
240
277
|
logger?.warn("sema_registry_model_key_env_missing", { model: m.name, apiKeyEnv: m.apiKeyEnv, hint: "env var not set in this service — model falls back to the gateway key" });
|
|
241
278
|
}
|
|
242
|
-
if (m.provider === "anthropic" && !config.anthropic) {
|
|
243
|
-
logger?.warn("sema_registry_model_no_brain", { model: m.name, provider: m.provider, hint: "set ANTHROPIC_API_KEY in this service's env, else it mis-routes to the gateway brain" });
|
|
244
|
-
}
|
|
245
279
|
}
|
|
246
280
|
const quotaWeights = {};
|
|
247
|
-
for (const m of
|
|
281
|
+
for (const m of usable) {
|
|
248
282
|
const qw = typeof m.quotaWeight === "number" && Number.isFinite(m.quotaWeight) && m.quotaWeight > 0 ? m.quotaWeight : undefined;
|
|
249
283
|
if (qw !== undefined) {
|
|
250
284
|
quotaWeights[m.name] = qw;
|
|
@@ -262,41 +296,80 @@ function stageEffective(config, eff, logger, opts) {
|
|
|
262
296
|
centerRoles[role] = { select: tgt.select };
|
|
263
297
|
}
|
|
264
298
|
const roles = { ...envRolesBaseline.get(config), ...centerRoles };
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
"
|
|
299
|
+
let picked;
|
|
300
|
+
if (usable.length > 0) {
|
|
301
|
+
picked = resolveDefaultModelName(eff, (n) => models[n] !== undefined, usable[0].name, (source, name) => logger?.warn("sema_registry_default_dangling", {
|
|
302
|
+
source,
|
|
303
|
+
name,
|
|
304
|
+
...(Object.hasOwn(verdict.refused, name) ? { unavailable: verdict.refused[name].code } : {}),
|
|
305
|
+
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",
|
|
306
|
+
}));
|
|
307
|
+
models.default = models[picked.name];
|
|
308
|
+
}
|
|
309
|
+
const activeTiers = {};
|
|
310
|
+
for (const [tier, target] of Object.entries(resolveActiveTiers(eff.models) ?? {})) {
|
|
311
|
+
if (models[target] !== undefined) {
|
|
312
|
+
activeTiers[tier] = target;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
const refusal = Object.hasOwn(verdict.refused, target) ? verdict.refused[target] : undefined;
|
|
316
|
+
logger?.warn("sema_registry_tier_binding_unavailable", { tier, 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" });
|
|
317
|
+
}
|
|
318
|
+
const nextView = { ...config, models, tiers: activeTiers, modelRefusals: verdict.refused };
|
|
319
|
+
const noDefault = crossInvariant("default-model-resolvable").check({ live: config, next: nextView });
|
|
320
|
+
if (noDefault !== undefined)
|
|
321
|
+
return refuse("default-model-resolvable", noDefault);
|
|
322
|
+
const endpointRefusal = crossInvariant("model-route-endpoint").check({ live: config, next: nextView });
|
|
323
|
+
if (endpointRefusal !== undefined)
|
|
324
|
+
return refuse("model-route-endpoint", endpointRefusal);
|
|
325
|
+
const minted = crossInvariant("refused-names-not-minted").check({ live: config, next: nextView });
|
|
326
|
+
if (minted !== undefined)
|
|
327
|
+
return refuse("refused-names-not-minted", minted, { minted: mintedRefusedNames(nextView) });
|
|
328
|
+
for (const [role, spec] of Object.entries(roles)) {
|
|
329
|
+
const target = typeof spec === "string" ? spec : spec !== null && typeof spec === "object" && "model" in spec && typeof spec.model === "string" ? spec.model : undefined;
|
|
330
|
+
const refusal = target !== undefined && Object.hasOwn(verdict.refused, target) ? verdict.refused[target] : undefined;
|
|
331
|
+
if (refusal !== undefined) {
|
|
332
|
+
logger?.warn("sema_registry_role_binding_unavailable", { role, model: target, code: refusal.code, hint: ROLE_BINDING_UNAVAILABLE_HINT });
|
|
333
|
+
}
|
|
272
334
|
}
|
|
273
335
|
const atModelAllowlist = stageAtModelAllowlist(eff.models?.atModelAllowlist, models, logger);
|
|
274
|
-
|
|
336
|
+
const defaultModel = models.default;
|
|
337
|
+
const plane = {
|
|
275
338
|
models,
|
|
276
339
|
modelApiKeyEnv,
|
|
277
340
|
modelApiKeys,
|
|
341
|
+
modelRefusals: { ...verdict.refused },
|
|
278
342
|
quotaWeights,
|
|
279
343
|
roles,
|
|
280
344
|
activeTiers,
|
|
281
345
|
atModelAllowlist,
|
|
282
|
-
infoLine: { count:
|
|
346
|
+
infoLine: { count: usable.length, ...(Object.keys(verdict.refused).length > 0 ? { unavailable: Object.keys(verdict.refused) } : {}), default: defaultModel.id, defaultSource: picked.source, roles: Object.keys(roles), tiers: Object.keys(activeTiers), atModelAllowlist: atModelAllowlist.length, sealedKeys: Object.values(modelApiKeys).filter((v) => typeof v === "string").length, sealedPoisoned: Object.values(modelApiKeys).filter((v) => typeof v !== "string").length },
|
|
283
347
|
};
|
|
348
|
+
return { stages: { models: plane, collab: stageCollabDomain(eff) }, defaultName: picked.name, refused: verdict.refused };
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
function stageCollabDomain(eff) {
|
|
352
|
+
const projected = projectCollabToWorkflows(eff.collab?.templates);
|
|
353
|
+
const notices = [];
|
|
354
|
+
if (Object.keys(projected.workflows).length > 0)
|
|
355
|
+
notices.push({ level: "info", msg: "sema_registry_collab_workflows", fields: { workflows: Object.keys(projected.workflows) } });
|
|
356
|
+
if (projected.skipped.length > 0)
|
|
357
|
+
notices.push({ level: "warn", msg: "sema_registry_collab_skipped", fields: { skipped: projected.skipped } });
|
|
358
|
+
if (projected.notes.length > 0)
|
|
359
|
+
notices.push({ level: "info", msg: "sema_registry_collab_notes", fields: { notes: projected.notes } });
|
|
360
|
+
return { workflows: projected.workflows, notices };
|
|
361
|
+
}
|
|
362
|
+
function stageEffective(config, eff, logger, opts) {
|
|
363
|
+
if (opts.leg === "boot" && !eff.version) {
|
|
364
|
+
logger?.warn("sema_registry_unpublished", {
|
|
365
|
+
version: eff.version,
|
|
366
|
+
note: "empty/unpublished effective config — using env + built-in fallback; publish a config or unset CONFIG_PUBLISH_MODE",
|
|
367
|
+
});
|
|
284
368
|
}
|
|
285
|
-
const
|
|
369
|
+
const modelPlaneGroup = stageModelPlane(config, eff, logger, opts.sealedKeys);
|
|
370
|
+
const approvalGate = stageApprovalGate(config, eff.governance, opts, logger);
|
|
286
371
|
const limits = stageLimits(config, { version: typeof eff.version === "number" ? eff.version : 0, ...("limits" in eff ? { limits: eff.limits } : {}), legacyGates: legacyGateValues(eff.runtime) }, logger);
|
|
287
372
|
const hot = stageRuntimeHot(config, eff.governance, logger);
|
|
288
|
-
let collabPlane;
|
|
289
|
-
if (opts.deferModelPlane !== true) {
|
|
290
|
-
const projected = projectCollabToWorkflows(eff.collab?.templates);
|
|
291
|
-
const notices = [];
|
|
292
|
-
if (Object.keys(projected.workflows).length > 0)
|
|
293
|
-
notices.push({ level: "info", msg: "sema_registry_collab_workflows", fields: { workflows: Object.keys(projected.workflows) } });
|
|
294
|
-
if (projected.skipped.length > 0)
|
|
295
|
-
notices.push({ level: "warn", msg: "sema_registry_collab_skipped", fields: { skipped: projected.skipped } });
|
|
296
|
-
if (projected.notes.length > 0)
|
|
297
|
-
notices.push({ level: "info", msg: "sema_registry_collab_notes", fields: { notes: projected.notes } });
|
|
298
|
-
collabPlane = { workflows: projected.workflows, notices };
|
|
299
|
-
}
|
|
300
373
|
const projDomain = eff.projects;
|
|
301
374
|
const rawProjects = (projDomain && typeof projDomain === "object" && !Array.isArray(projDomain) &&
|
|
302
375
|
"projects" in projDomain && typeof projDomain.projects === "object"
|
|
@@ -315,16 +388,14 @@ function stageEffective(config, eff, logger, opts) {
|
|
|
315
388
|
}
|
|
316
389
|
return {
|
|
317
390
|
version: typeof eff.version === "number" && Number.isFinite(eff.version) ? eff.version : 0,
|
|
318
|
-
|
|
319
|
-
|
|
391
|
+
modelPlaneGroup,
|
|
392
|
+
approvalGate,
|
|
320
393
|
limits,
|
|
321
394
|
hot,
|
|
322
|
-
...(collabPlane !== undefined ? { collabPlane } : {}),
|
|
323
395
|
projects,
|
|
324
396
|
};
|
|
325
397
|
}
|
|
326
398
|
export const RUNTIME_GATE_KEYS = ["rateLimitPerMin", "maxTaskCostUsd", "maxTaskTokens", "maxPrincipalCostUsd", "costQuotaWindowSec"];
|
|
327
|
-
export const BOOT_GATE_KEYS = ["approvalRequire"];
|
|
328
399
|
export function centerManagedConfigKeys(eff) {
|
|
329
400
|
const out = new Set();
|
|
330
401
|
if (!eff)
|
|
@@ -372,40 +443,76 @@ export function runtimeGatePresent(rt, key) {
|
|
|
372
443
|
return typeof v === "number" && v > 0;
|
|
373
444
|
return typeof v === "number";
|
|
374
445
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
};
|
|
380
|
-
const KEPT_WOULD_MATCH_NOTHING = "the prior approvalRequire is unchanged — the published list would have silently matched nothing";
|
|
381
|
-
const KEPT_WOULD_REMOVE_GATE = "the prior approvalRequire is unchanged — coercing this malformed value would have silently REMOVED the approval gate";
|
|
446
|
+
const KEPT_WOULD_MATCH_NOTHING = "the prior approvalRequire is unchanged — the published list would have silently matched nothing";
|
|
447
|
+
const KEPT_WOULD_REMOVE_GATE = "the prior approvalRequire is unchanged — coercing this malformed value would have silently REMOVED the approval gate";
|
|
448
|
+
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";
|
|
449
|
+
function stageApprovalRequire(raw) {
|
|
382
450
|
if (!Array.isArray(raw)) {
|
|
383
|
-
|
|
384
|
-
`Refusing the
|
|
451
|
+
const detail = `published governance.approvalRequire must be an ARRAY of whole tool names — got ${raw === null ? "null" : typeof raw}. ` +
|
|
452
|
+
`Refusing the group instead of coercing it: an empty list means "no approval gate", so coercing a malformed value would SILENTLY remove the gate.`;
|
|
453
|
+
return { ok: false, detail, reason: detail, names: [], kept: KEPT_WOULD_REMOVE_GATE };
|
|
385
454
|
}
|
|
386
455
|
const nonString = raw
|
|
387
456
|
.map((n, i) => (typeof n === "string" ? undefined : { i, t: n === null ? "null" : typeof n }))
|
|
388
457
|
.filter((e) => e !== undefined);
|
|
389
458
|
if (nonString.length > 0) {
|
|
390
|
-
|
|
459
|
+
const detail = `published governance.approvalRequire contains ${nonString.length} non-string entr${nonString.length === 1 ? "y" : "ies"} ` +
|
|
391
460
|
`(${nonString.map((e) => `[${e.i}]:${e.t}`).join(", ")}) — every entry must be a whole tool name string. ` +
|
|
392
|
-
`Refusing the
|
|
461
|
+
`Refusing the group instead of dropping them: dropping every entry would SILENTLY remove the approval gate.`;
|
|
462
|
+
return { ok: false, detail, reason: detail, names: nonString.map((e) => `[${e.i}]`), kept: KEPT_WOULD_REMOVE_GATE };
|
|
393
463
|
}
|
|
394
464
|
const names = raw.filter((n) => typeof n === "string").map((n) => n.trim()).filter((n) => n.length > 0);
|
|
395
465
|
const bad = findUnmatchableToolNames(names, { consumer: "tool-name-table", behavior: "deny" });
|
|
396
466
|
if (bad.length > 0) {
|
|
397
|
-
return
|
|
467
|
+
return {
|
|
468
|
+
ok: false,
|
|
469
|
+
detail: formatUnmatchableToolNames("published governance.approvalRequire", bad),
|
|
470
|
+
reason: `published governance.approvalRequire has ${bad.length} entr${bad.length === 1 ? "y" : "ies"} that match no live tool (retired name / incomplete MCP name / CC-form rule) — the offending entries are named in the sema_registry_approval_require_invalid log line`,
|
|
471
|
+
names: bad.map((b) => b.name),
|
|
472
|
+
kept: KEPT_WOULD_MATCH_NOTHING,
|
|
473
|
+
};
|
|
398
474
|
}
|
|
399
|
-
return { value: names };
|
|
475
|
+
return { ok: true, value: names };
|
|
400
476
|
}
|
|
401
|
-
function
|
|
477
|
+
function unserviceableOnLeg(leg, candidate) {
|
|
478
|
+
switch (leg.leg) {
|
|
479
|
+
case "boot":
|
|
480
|
+
return undefined;
|
|
481
|
+
case "hot":
|
|
482
|
+
return gateIntentUnserviceableReason(candidate, leg.checkpointStorePresent);
|
|
483
|
+
default: {
|
|
484
|
+
const unknownLeg = leg;
|
|
485
|
+
throw new Error(`applyEffective: unknown apply leg ${JSON.stringify(unknownLeg)}`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function groupReportOf(g) {
|
|
490
|
+
return { applied: g.next !== undefined ? [...APPROVAL_GROUP_KEYS] : [], ...(g.rejected ? { rejected: g.rejected } : {}) };
|
|
491
|
+
}
|
|
492
|
+
const approvalEnvBaseline = new WeakMap();
|
|
493
|
+
function stageApprovalGate(config, gov, leg, logger) {
|
|
494
|
+
if (!approvalEnvBaseline.has(config))
|
|
495
|
+
approvalEnvBaseline.set(config, Object.freeze([...config.approvalRequire]));
|
|
496
|
+
const refuse = (r) => {
|
|
497
|
+
logger?.warn("sema_registry_approval_require_invalid", { keys: [...APPROVAL_GROUP_KEYS], detail: r.detail, names: [...r.names], kept: r.kept });
|
|
498
|
+
return { rejected: { keys: [...APPROVAL_GROUP_KEYS], reason: r.reason } };
|
|
499
|
+
};
|
|
402
500
|
const raw = gov?.approvalRequire;
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
501
|
+
let next;
|
|
502
|
+
if (raw === undefined) {
|
|
503
|
+
next = [...approvalEnvBaseline.get(config)];
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
const verdict = stageApprovalRequire(raw);
|
|
507
|
+
if (!verdict.ok)
|
|
508
|
+
return refuse(verdict);
|
|
509
|
+
next = verdict.value;
|
|
510
|
+
}
|
|
511
|
+
const unserviceable = unserviceableOnLeg(leg, { ...config, approvalRequire: next });
|
|
512
|
+
if (unserviceable !== undefined)
|
|
513
|
+
return refuse({ detail: unserviceable, reason: unserviceable, names: next, kept: KEPT_WOULD_NEVER_PARK });
|
|
514
|
+
const same = next.length === config.approvalRequire.length && next.every((n, i) => n === config.approvalRequire[i]);
|
|
515
|
+
return same ? {} : { next };
|
|
409
516
|
}
|
|
410
517
|
function legacyGateValues(rt) {
|
|
411
518
|
const out = {};
|
|
@@ -416,16 +523,16 @@ function legacyGateValues(rt) {
|
|
|
416
523
|
out[k] = rt[k];
|
|
417
524
|
return out;
|
|
418
525
|
}
|
|
419
|
-
export function applyRuntimeGates(config, eff, logger) {
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
config
|
|
526
|
+
export function applyRuntimeGates(config, eff, leg, logger) {
|
|
527
|
+
const approval = stageApprovalGate(config, eff?.governance, leg, logger);
|
|
528
|
+
if (approval.next !== undefined)
|
|
529
|
+
config.approvalRequire = approval.next;
|
|
423
530
|
const limits = stageLimits(config, { version: 0, legacyGates: legacyGateValues(eff?.runtime) }, logger);
|
|
424
531
|
for (const [k, v] of limits.assignments)
|
|
425
532
|
config[k] = v;
|
|
426
533
|
for (const k of limits.deletions)
|
|
427
534
|
delete config[k];
|
|
428
|
-
const info = { ...
|
|
535
|
+
const info = { ...(approval.next !== undefined ? { approvalRequire: approval.next } : {}), ...Object.fromEntries(limits.assignments) };
|
|
429
536
|
if (Object.keys(info).length > 0)
|
|
430
537
|
logger?.info("sema_registry_runtime", info);
|
|
431
538
|
}
|
|
@@ -486,6 +593,8 @@ export function resolveDefaultModelName(eff, has, fallback, onDangling) {
|
|
|
486
593
|
}
|
|
487
594
|
export function logEffectiveDiff(config, eff, logger) {
|
|
488
595
|
const enabled = (eff.models?.models ?? []).filter((m) => m.enabled !== false);
|
|
596
|
+
const plane = stageModelPlane(config, eff, undefined, undefined);
|
|
597
|
+
const refused = plane.refused ?? {};
|
|
489
598
|
const centerModels = enabled.map((m) => ({ name: m.name, id: m.id, provider: m.provider, apiKeyEnv: m.apiKeyEnv ?? null, tier: m.tier ?? null }));
|
|
490
599
|
const envModels = Object.entries(config.models).map(([name, m]) => ({ name, id: m.id, provider: m.provider }));
|
|
491
600
|
const centerRoles = Object.fromEntries(Object.entries(eff.models?.roles ?? {}).map(([r, t]) => [r, "model" in t ? t.model : { select: t.select }]));
|
|
@@ -493,7 +602,9 @@ export function logEffectiveDiff(config, eff, logger) {
|
|
|
493
602
|
note: "DRY RUN — registry config NOT applied; unset SEMA_REGISTRY_DRY_RUN to go live",
|
|
494
603
|
version: eff.version,
|
|
495
604
|
wouldOverrideModels: enabled.length > 0,
|
|
496
|
-
wouldDefaultModel:
|
|
605
|
+
wouldDefaultModel: plane.defaultName ?? null,
|
|
606
|
+
...(Object.keys(refused).length > 0 ? { wouldRefuse: Object.fromEntries(Object.entries(refused).map(([n, r]) => [n, r.code])) } : {}),
|
|
607
|
+
...(plane.rejected !== undefined ? { wouldRefuseModelPlane: { invariant: plane.rejected.invariant, reason: plane.rejected.reason } } : {}),
|
|
497
608
|
currentDefaultModel: config.model.id,
|
|
498
609
|
models: { center: centerModels, envDerived: envModels },
|
|
499
610
|
roles: { center: centerRoles, current: config.roles },
|
|
@@ -20,7 +20,20 @@
|
|
|
20
20
|
* 🔴 原子承诺的**域**是单进程(§2):本账记的是「本副本的收敛状态」,不是 fleet 的。fleet 级判据 = 各副本
|
|
21
21
|
* /health 的 applied == target,拉齐工具归运维面。
|
|
22
22
|
*/
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* 世代账按组记账的**组闭集**(读面 `groups` 的键集;加组 = 这里加词,报告形 `Record<组, …>` 随之编译红)。
|
|
25
|
+
* · `limitsHttp` —— [ref] 批1 限额组(属主 `stage-limits.ts`);
|
|
26
|
+
* · `approvalGate` —— S-668 波 2 审批组(`governance.approvalRequire`;属主 `apply-effective.ts` 的
|
|
27
|
+
* `stageApprovalGate`:坏形 / 工具名匹配不到 / ④ 门意图不可服务 ⇒ 整组拒)。
|
|
28
|
+
* · `modelPlane` —— 模型面组(组内有哪些 effective 域的**唯一定义** = `apply-effective.ts` 的 `MODEL_PLANE_DOMAINS`:models 域 =
|
|
29
|
+
* 目录 / roles / tiers / 拒因表 / 名单 / 钥匙表,collab 域 = 引用它的命名 workflow 注册表;属主 `stageModelPlane`:
|
|
30
|
+
* 模型面三条跨键不变量 `default-model-resolvable` / `model-route-endpoint` / `refused-names-not-minted` 任一不成立 ⇒ 整组拒,
|
|
31
|
+
* 组内各域都停在上一代。opus 腿 O3(7.101.0 合并复审):此前这三条是 stage 抛 = 整份候选中止,同候选其余组陪葬)。
|
|
32
|
+
* 各组同姿势、各用各的判据表(审批组**不**复用限额组的跨键不变量表 —— 那张表的候选视图只叠限额键)。
|
|
33
|
+
*/
|
|
34
|
+
export declare const CONFIG_APPLY_GROUPS: readonly ["limitsHttp", "approvalGate", "modelPlane"];
|
|
35
|
+
export type ConfigApplyGroup = (typeof CONFIG_APPLY_GROUPS)[number];
|
|
36
|
+
/** 一组的收敛状态(读面形对所有组相同)。 */
|
|
24
37
|
export interface ConfigApplyGroupState {
|
|
25
38
|
/** 本组最后一次**真的落地**的源版本(身份标)。 */
|
|
26
39
|
readonly appliedVersion: number;
|
|
@@ -48,27 +61,43 @@ export interface ConfigApplyLedgerView {
|
|
|
48
61
|
readonly configAppliedOrdinal: number;
|
|
49
62
|
/** target≠applied 的**持续**时长(ms);收敛时键缺席(健康面不制造噪音)。 */
|
|
50
63
|
readonly configApplyStaleMs?: number;
|
|
51
|
-
readonly groups: Readonly<Record<
|
|
64
|
+
readonly groups: Readonly<Partial<Record<ConfigApplyGroup, ConfigApplyGroupState>>>;
|
|
65
|
+
}
|
|
66
|
+
/** 一组在一次 apply 里的报告(所有组同形)。 */
|
|
67
|
+
export interface ConfigApplyGroupReport {
|
|
68
|
+
/** 本世代真正换了值的键。 */
|
|
69
|
+
readonly applied: readonly string[];
|
|
70
|
+
/** 值合法但**不热**的键(逐键 defer:变更只能重启生效)。 */
|
|
71
|
+
readonly deferred?: ReadonlyArray<{
|
|
72
|
+
readonly key: string;
|
|
73
|
+
readonly reason: string;
|
|
74
|
+
}>;
|
|
75
|
+
/** 整组被拒时的成因(消毒:键名 + 判据文案,绝不带值)。 */
|
|
76
|
+
readonly rejected?: {
|
|
77
|
+
readonly keys: readonly string[];
|
|
78
|
+
readonly reason: string;
|
|
79
|
+
readonly restartReason?: string;
|
|
80
|
+
};
|
|
52
81
|
}
|
|
53
|
-
/** applyEffective 的 post-commit 报告(见 apply-effective.ts `ApplyReport`)
|
|
82
|
+
/** applyEffective 的 post-commit 报告(见 apply-effective.ts `ApplyReport`)—— **每一组都报**。 */
|
|
54
83
|
export interface ConfigApplyReport {
|
|
55
84
|
readonly version: number;
|
|
56
85
|
/** [ref]:本候选的收货序数(applyEffective opts.generation 原样带回;缺席=version 兼容臂)。 */
|
|
57
86
|
readonly ordinal?: number;
|
|
58
|
-
readonly
|
|
59
|
-
readonly applied: readonly string[];
|
|
60
|
-
readonly deferred?: ReadonlyArray<{
|
|
61
|
-
readonly key: string;
|
|
62
|
-
readonly reason: string;
|
|
63
|
-
}>;
|
|
64
|
-
readonly rejected?: {
|
|
65
|
-
readonly keys: readonly string[];
|
|
66
|
-
readonly reason: string;
|
|
67
|
-
readonly restartReason?: string;
|
|
68
|
-
};
|
|
69
|
-
};
|
|
70
|
-
readonly fullyApplied?: boolean;
|
|
87
|
+
readonly groups: Readonly<Record<ConfigApplyGroup, ConfigApplyGroupReport>>;
|
|
71
88
|
}
|
|
89
|
+
/** 本报告里被整组拒的那些组(按组闭集序)。空 ⇒ 候选完整应用。两条 commit 腿的「部分应用」warn 与世代账共用。 */
|
|
90
|
+
export declare function refusedGroups(report: ConfigApplyReport): Array<{
|
|
91
|
+
readonly group: ConfigApplyGroup;
|
|
92
|
+
readonly keys: readonly string[];
|
|
93
|
+
readonly reason: string;
|
|
94
|
+
}>;
|
|
95
|
+
/**
|
|
96
|
+
* O3:这一次 apply 的**模型面组**(组内各域 = `MODEL_PLANE_DOMAINS`,目录与引用它的 collab 模板同进退)落地了没有(整组被拒 ⇒
|
|
97
|
+
* false;报告缺席 = 没走到 commit ⇒ false,如实)。调用方据此决定「Runner 现在跑的是哪一代模型面」(`appliedPlaneEff`,swap 判据的
|
|
98
|
+
* 基准)、名册门开不开、以及 boot 要不要按组回落 LKG —— 都只许从真落地的模型面推进。
|
|
99
|
+
*/
|
|
100
|
+
export declare function modelPlaneCommitted(report: ConfigApplyReport | undefined): boolean;
|
|
72
101
|
export interface ConfigApplyLedger {
|
|
73
102
|
/** 拉到一个候选(无论后续是否应用)。ordinal=收货序数,version=身份标。 */
|
|
74
103
|
seenTarget(ordinal: number, version: number): void;
|