@sema-agent/server 7.123.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 +132 -0
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/USAGE.md +53 -51
- package/dist/approval-ask-audit-store.d.ts +27 -32
- package/dist/approval-ask-audit-store.js +10 -9
- package/dist/approval-crash-convergence.d.ts +2 -2
- package/dist/approval-crash-convergence.js +5 -5
- package/dist/approval-deny-reasons.d.ts +163 -51
- package/dist/approval-deny-reasons.js +79 -8
- package/dist/approval-policy-settlement.d.ts +4 -1
- package/dist/approval-policy-settlement.js +3 -2
- package/dist/approval-reconciler.d.ts +3 -3
- package/dist/approval-reconciler.js +10 -10
- package/dist/approval.d.ts +1 -1
- package/dist/approval.js +1 -1
- package/dist/audit.js +1 -1
- package/dist/auth-bridge.d.ts +8 -8
- package/dist/auth-bridge.js +6 -6
- package/dist/auth-keys.d.ts +2 -2
- package/dist/auto-mode-face.d.ts +1 -1
- package/dist/boot/budget-tracing.js +1 -1
- package/dist/boot/execution-env.js +1 -0
- package/dist/boot/leader.d.ts +1 -1
- package/dist/boot/memory-consolidation.d.ts +4 -4
- package/dist/boot/org-memory.d.ts +3 -3
- package/dist/boot/org-memory.js +5 -5
- package/dist/boot/resolve-spec.js +7 -7
- package/dist/boot/runner-deps.d.ts +1 -1
- package/dist/boot/runtime-caps.d.ts +1 -1
- package/dist/boot/runtime-caps.js +10 -10
- package/dist/boot/shutdown.d.ts +2 -2
- package/dist/boot/shutdown.js +2 -2
- package/dist/boot/side-query-lane.d.ts +3 -3
- package/dist/boot/{stage-02-registry-pull.d.ts → stage-02-web-admin-pull.d.ts} +2 -2
- package/dist/boot/{stage-02-registry-pull.js → stage-02-web-admin-pull.js} +4 -4
- package/dist/boot/stage-03-store-backend.d.ts +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.d.ts +1 -1
- package/dist/boot/stage-06-runners.d.ts +2 -2
- package/dist/boot/stage-06-runners.js +6 -6
- package/dist/boot/stage-07-capability-layer.d.ts +2 -2
- package/dist/boot/stage-07-capability-layer.js +6 -5
- package/dist/boot/stage-08-reapers.d.ts +3 -3
- package/dist/boot/stage-08-reapers.js +3 -3
- package/dist/boot/stage-09-leader.d.ts +3 -3
- package/dist/boot/stage-09-leader.js +7 -7
- package/dist/boot/stage-10-http-server.d.ts +3 -3
- package/dist/boot/stage-10-http-server.js +19 -19
- package/dist/boot/unpriced-ceiling-verdict.d.ts +1 -1
- package/dist/boot/{config-center.d.ts → web-admin.d.ts} +11 -11
- package/dist/boot/{config-center.js → web-admin.js} +38 -22
- package/dist/boot/webfetch-summarize-lane.d.ts +2 -2
- package/dist/brain.d.ts +1 -1
- package/dist/brain.js +1 -1
- package/dist/budget.d.ts +3 -3
- package/dist/canonical-json.d.ts +2 -2
- package/dist/capabilities/center-plugins.d.ts +1 -1
- package/dist/capabilities/center-wiring.d.ts +12 -5
- package/dist/capabilities/center-wiring.js +11 -2
- package/dist/capabilities/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/capabilities/scenarios.js +2 -2
- package/dist/capabilities/team.d.ts +1 -1
- package/dist/capabilities/tool-defer.d.ts +1 -1
- package/dist/center-credential-renewer.d.ts +1 -1
- package/dist/center-credential-renewer.js +7 -7
- package/dist/center-credential.d.ts +9 -9
- package/dist/center-credential.js +6 -6
- package/dist/config-catalog.d.ts +7 -10
- package/dist/config-catalog.js +15 -15
- package/dist/config-d-migration.d.ts +32 -0
- package/dist/config-d-migration.js +59 -0
- package/dist/config-invariants.d.ts +14 -4
- package/dist/config-invariants.js +31 -2
- package/dist/config-lkg.d.ts +26 -6
- package/dist/config-lkg.js +43 -14
- package/dist/config-provider.d.ts +23 -19
- package/dist/config-provider.js +24 -26
- package/dist/config-types.d.ts +52 -51
- package/dist/config.d.ts +9 -46
- package/dist/config.js +33 -92
- package/dist/execution-lane-caps.d.ts +1 -1
- package/dist/fleet/drain-entry.d.ts +1 -1
- package/dist/fleet/engine-signal.d.ts +8 -8
- package/dist/fleet/fleet-bus.d.ts +12 -3
- package/dist/fleet/fleet-bus.js +13 -9
- package/dist/fleet/serving-lease.d.ts +3 -3
- package/dist/fleet/serving-lease.js +1 -1
- package/dist/fleet-client.d.ts +7 -7
- package/dist/fleet-client.js +5 -4
- package/dist/fleet-lease.d.ts +3 -3
- package/dist/fleet-lease.js +2 -2
- package/dist/host-lsp-manager.d.ts +1 -1
- package/dist/hosted-posture.d.ts +4 -4
- package/dist/http/admission.js +4 -4
- package/dist/http/checkpoint-ctx.d.ts +20 -3
- package/dist/http/checkpoint-ctx.js +13 -5
- package/dist/http/dispatch.js +2 -2
- package/dist/http/durable-run-leg.js +2 -1
- package/dist/http/health-self-report.d.ts +4 -4
- package/dist/http/health-self-report.js +3 -3
- package/dist/http/idempotency.d.ts +1 -1
- package/dist/http/resume-legs.d.ts +4 -3
- package/dist/http/resume-legs.js +31 -18
- package/dist/http/route-ctx.d.ts +8 -0
- package/dist/http/routes/admin-config-refresh.d.ts +1 -1
- package/dist/http/routes/admin-config-refresh.js +1 -1
- package/dist/http/routes/admin-drain.d.ts +1 -1
- package/dist/http/routes/admin-drain.js +5 -4
- package/dist/http/routes/approvals-assistant.js +4 -4
- package/dist/http/routes/capabilities.js +5 -5
- package/dist/http/routes/config-catalog.js +2 -3
- package/dist/http/routes/diagnostics.js +1 -1
- package/dist/http/routes/runs.js +8 -4
- package/dist/http/routes/sessions-list.js +2 -1
- package/dist/http/routes/sessions.js +1 -1
- package/dist/http/routes/side-query.js +3 -3
- package/dist/http/routes/tasks.js +3 -2
- package/dist/http/run-meta.d.ts +1 -1
- package/dist/http/run-meta.js +2 -2
- package/dist/http/server.d.ts +7 -7
- package/dist/http/server.js +1 -1
- package/dist/http/stored-body-replay.js +3 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/key-resolver.d.ts +1 -1
- package/dist/leader/wire.d.ts +2 -2
- package/dist/leader/wire.js +2 -1
- package/dist/main.js +1 -1
- package/dist/mcp-tool-faces.d.ts +2 -2
- package/dist/model-entry-refusal.d.ts +3 -3
- package/dist/model-entry-refusal.js +4 -4
- package/dist/model-provider.d.ts +2 -2
- package/dist/model-route-endpoint.d.ts +1 -1
- package/dist/model-route-url.d.ts +1 -1
- package/dist/model-route-url.js +2 -1
- package/dist/model-select.d.ts +24 -21
- package/dist/model-select.js +22 -17
- package/dist/observability/metrics.d.ts +1 -1
- package/dist/org-memory-admission.d.ts +1 -1
- package/dist/plugins/approval-ask-store-file.d.ts +4 -2
- package/dist/plugins/approval-ask-store-file.js +3 -3
- package/dist/plugins/approval-ask-store-memory.d.ts +4 -3
- package/dist/plugins/approval-ask-store-memory.js +10 -4
- package/dist/plugins/approval-ask-store-sql.d.ts +16 -2
- package/dist/plugins/approval-ask-store-sql.js +20 -13
- package/dist/plugins/caching-session-store.d.ts +0 -10
- package/dist/plugins/caching-session-store.js +0 -3
- package/dist/plugins/file-run-store.js +9 -3
- package/dist/plugins/leader-run-store-sql.d.ts +3 -2
- package/dist/plugins/leader-run-store-sql.js +3 -2
- package/dist/plugins/local-session-store.d.ts +20 -27
- package/dist/plugins/local-session-store.js +19 -17
- package/dist/plugins/memory-run-store.js +9 -3
- package/dist/plugins/one-time-migrations.js +26 -2
- package/dist/plugins/scheduler-support.d.ts +26 -7
- package/dist/plugins/scheduler-support.js +96 -53
- package/dist/plugins/store-backend.js +1 -1
- package/dist/plugins/store-contracts.d.ts +20 -4
- package/dist/plugins/web-search.d.ts +1 -1
- package/dist/plugins/web-search.js +2 -1
- package/dist/posture-source.d.ts +1 -1
- package/dist/read-face-posture.d.ts +3 -3
- package/dist/read-face-posture.js +1 -1
- package/dist/run-local.js +6 -3
- package/dist/runtime-caps-resolver.d.ts +4 -4
- package/dist/runtime-caps-resolver.js +2 -2
- package/dist/runtime-governance.d.ts +5 -5
- package/dist/sealed-key.d.ts +4 -4
- package/dist/sealed-key.js +3 -2
- package/dist/security.d.ts +3 -3
- package/dist/service-version.d.ts +1 -1
- package/dist/service-version.js +5 -0
- package/dist/session-titler.d.ts +0 -11
- package/dist/session-titler.js +3 -5
- package/dist/shared-memory-scope-authorizer.d.ts +4 -4
- package/dist/task-a2a.d.ts +1 -1
- package/dist/tool-approval.d.ts +43 -4
- package/dist/tool-approval.js +147 -98
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/project.d.ts +3 -3
- package/dist/trace/redact.d.ts +2 -23
- package/dist/trace/redact.js +1 -6
- package/dist/trace/self-minted-wire.d.ts +147 -3
- package/dist/trace/self-minted-wire.js +50 -9
- package/dist/url-refusal.d.ts +35 -0
- package/dist/url-refusal.js +15 -0
- package/dist/user-authored-text.d.ts +31 -0
- package/dist/user-authored-text.js +18 -0
- package/dist/{config-center → web-admin-client}/apply-effective.d.ts +13 -70
- package/dist/{config-center → web-admin-client}/apply-effective.js +100 -119
- package/dist/{config-center → web-admin-client}/apply-ledger.d.ts +2 -2
- package/dist/{config-center → web-admin-client}/center-request.d.ts +7 -7
- package/dist/{config-center → web-admin-client}/center-request.js +5 -4
- package/dist/{config-center → web-admin-client}/effective-keys.d.ts +1 -3
- package/dist/{config-center → web-admin-client}/effective-keys.js +0 -9
- package/dist/{config-center → web-admin-client}/facade.d.ts +6 -6
- package/dist/{config-center → web-admin-client}/facade.js +2 -2
- package/dist/{config-center → web-admin-client}/http-client.d.ts +6 -10
- package/dist/{config-center → web-admin-client}/http-client.js +12 -12
- package/dist/{config-center → web-admin-client}/pull-state.d.ts +1 -1
- package/dist/{config-center → web-admin-client}/read-face.js +4 -4
- package/dist/{config-center → web-admin-client}/read-warnings.d.ts +23 -16
- package/dist/{config-center → web-admin-client}/read-warnings.js +33 -23
- package/dist/{config-center → web-admin-client}/restart-signal.d.ts +5 -3
- package/dist/{config-center → web-admin-client}/restart-signal.js +3 -4
- package/dist/{config-center → web-admin-client}/skills-mcp.d.ts +1 -1
- package/dist/{config-center → web-admin-client}/skills-mcp.js +13 -13
- package/dist/{config-center → web-admin-client}/stage-limits.d.ts +1 -7
- package/dist/{config-center → web-admin-client}/stage-limits.js +6 -7
- package/dist/{config-center → web-admin-client}/types.d.ts +12 -39
- package/package.json +2 -2
- package/skills/find-skills.md +2 -2
- package/dist/url-shape.d.ts +0 -31
- package/dist/url-shape.js +0 -41
- /package/dist/{config-center → web-admin-client}/apply-ledger.js +0 -0
- /package/dist/{config-center → web-admin-client}/hot-keys-registry.d.ts +0 -0
- /package/dist/{config-center → web-admin-client}/hot-keys-registry.js +0 -0
- /package/dist/{config-center → web-admin-client}/mcp-revocation.d.ts +0 -0
- /package/dist/{config-center → web-admin-client}/mcp-revocation.js +0 -0
- /package/dist/{config-center → web-admin-client}/pull-state.js +0 -0
- /package/dist/{config-center → web-admin-client}/read-face.d.ts +0 -0
- /package/dist/{config-center → web-admin-client}/types.js +0 -0
package/dist/config-catalog.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { type RetiredEnvWire } from "@sema-agent/settings-schema";
|
|
1
2
|
import type { ServiceConfig, ServiceConfigGroups } from "./config-types.js";
|
|
2
|
-
import type { ConfigKnobRecord
|
|
3
|
-
import type { RestartSlice } from "./
|
|
3
|
+
import type { ConfigKnobRecord } from "./config.js";
|
|
4
|
+
import type { RestartSlice } from "./web-admin-client/restart-signal.js";
|
|
4
5
|
export declare const CATALOG_VALUE_TYPES: readonly ["boolean", "number", "string", "enum", "csv", "json", "path", "url", "secret", "opaque"];
|
|
5
6
|
export type CatalogValueType = (typeof CATALOG_VALUE_TYPES)[number];
|
|
6
7
|
export declare const CATALOG_TIMINGS: readonly ["hot", "restart", "deploy", "inert"];
|
|
@@ -14,7 +15,7 @@ export declare const CATALOG_RESTART_SLICES: readonly RestartSlice[];
|
|
|
14
15
|
export interface CatalogCenterSeatSpec {
|
|
15
16
|
readonly domain: string;
|
|
16
17
|
readonly path: string;
|
|
17
|
-
/** `centerManagedKeys`(
|
|
18
|
+
/** `centerManagedKeys`(web-admin 在管键集)里查「在管」用的键;models 面统一 "models"。 */
|
|
18
19
|
readonly centerKey: string;
|
|
19
20
|
readonly precedence: "center-wins" | "env-wins";
|
|
20
21
|
readonly timing: "hot" | "restart" | "inert";
|
|
@@ -144,12 +145,8 @@ export interface ConfigCatalogInputs {
|
|
|
144
145
|
* 它值级 import 了 core 与契约包(词表 / 中心凭证键名 / S-866 ⑤a 的 `DOMAIN_SCHEMAS` 座位门);这一格照旧由调用方传,
|
|
145
146
|
* 只因为它是 wire 上的一段列表、由路由决定取哪一份。 */
|
|
146
147
|
readonly centerDomains: readonly string[];
|
|
147
|
-
/**
|
|
148
|
+
/** web-admin 当前在管的 ServiceConfig 键集(缺席 = 没有配置管道)。 */
|
|
148
149
|
readonly centerManagedKeys?: ReadonlySet<string> | undefined;
|
|
149
|
-
/** S-885:退役 env 墓碑表的投影 —— 调用方传 `config.ts` 的 `buildRetiredEnvWire()`(同一张 `RETIRED_ENVS`,不手抄)。**必填**:
|
|
150
|
-
* 本模块不能值引 config.ts(`server-secret-env.ts` 值引本模块 ⇒ config → server-secret-env → config-catalog → config 成环,
|
|
151
|
-
* 干净进程直接 import 本模块即 TDZ 崩;codex r1 F2)⇒ 由调用方注入,缺了就是编译错而不是一张静默空表。 */
|
|
152
|
-
readonly retired: readonly RetiredEnvWire[];
|
|
153
150
|
}
|
|
154
151
|
export interface ConfigCatalogWire {
|
|
155
152
|
readonly catalogVersion: 1;
|
|
@@ -164,8 +161,8 @@ export interface ConfigCatalogWire {
|
|
|
164
161
|
readonly centerManaged: number;
|
|
165
162
|
};
|
|
166
163
|
readonly rows: readonly ConfigCatalogRow[];
|
|
167
|
-
/** S-885(7.106.0)—— 退役 env 墓碑表(
|
|
168
|
-
* rows 是「本进程会读的 env」,这里是「本进程会拒的旧名」—— 一个名字不会同时在两处(墓碑名没有读取点)。 */
|
|
164
|
+
/** S-885(7.106.0)—— 退役 env 墓碑表(settings-schema `RETIRED_ENVS`,本进程起服按它拒启)的投影 `buildRetiredEnvWire()`,**恒在场**
|
|
165
|
+
* (可为空数组)。与 `rows` 分开:rows 是「本进程会读的 env」,这里是「本进程会拒的旧名」—— 一个名字不会同时在两处(墓碑名没有读取点)。 */
|
|
169
166
|
readonly retired: readonly RetiredEnvWire[];
|
|
170
167
|
}
|
|
171
168
|
/**
|
package/dist/config-catalog.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES } from "@sema-agent/core";
|
|
2
2
|
import { GLOBAL_SCOPE } from "@sema-agent/settings-schema/api/scopes";
|
|
3
|
-
import {
|
|
3
|
+
import { WEB_ADMIN_ENV, DOMAIN_SCHEMAS, buildRetiredEnvWire } from "@sema-agent/settings-schema";
|
|
4
4
|
import { CENTER_CREDENTIAL_RENEW_LEAD_DEFAULT_MS, CENTER_CREDENTIAL_RENEW_LEAD_MAX_MS, CENTER_CREDENTIAL_RENEW_LEAD_MIN_MS } from "./center-credential-renewer.js";
|
|
5
5
|
import { THINKING_FORMATS } from "./model-compat.js";
|
|
6
6
|
import { AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS, CENTER_BASE_URL_RULE, CONFIG_PROVIDER_WORDS, DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS, DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS, FIRST_BYTE_TIMEOUT_BOUNDS, FLEET_LEASE_OUTAGE_MAX_DEFAULT_MS, RESILIENCE_DEFAULTS, TASK_WRITE_FACE_ESCALATIONS } from "./config-types.js";
|
|
@@ -72,7 +72,7 @@ export const CONFIG_CATALOG = [
|
|
|
72
72
|
r("ATTACHMENT_ORPHAN_GRACE_MS", "limitsHttp", "number", "附件孤儿对象 GC 宽限(ms;0=关本腿,坏值回默认+warn)", { staticDefault: "3600000", danger: DATA, center: cLimits("attachmentOrphanGraceMs") }),
|
|
73
73
|
r("ATTACHMENT_UNBOUND_TTL_MS", "limitsHttp", "number", "未绑定附件的 TTL(ms)", { staticDefault: String(24 * 3600 * 1000), danger: DATA, configKey: "attachmentUnboundTtlMs", center: cLimits("attachmentUnboundTtlMs") }),
|
|
74
74
|
r("AUTH_BRIDGE_AUDIENCE", "auth", "string", "registry SSO-JWT 验签期望 aud", { danger: SEC }),
|
|
75
|
-
r("AUTH_BRIDGE_ISSUER", "auth", "url", "registry SSO-JWT issuer(auth bridge 的开关之一;S-671 / C-R100 ① 起**不是**托管证据 —— 多租户只认 REQUIRE_PRINCIPAL=true)", { danger: SEC, derivedDefaultNote: "缺席 ⇒ 桥的装配处回退到
|
|
75
|
+
r("AUTH_BRIDGE_ISSUER", "auth", "url", "registry SSO-JWT issuer(auth bridge 的开关之一;S-671 / C-R100 ① 起**不是**托管证据 —— 多租户只认 REQUIRE_PRINCIPAL=true)", { danger: SEC, derivedDefaultNote: "缺席 ⇒ 桥的装配处回退到 webAdmin.baseUrl(配了 center 即默认开桥);显式值与回退值在托管判定上同判(都不算)", configKey: "authBridgeIssuer" }),
|
|
76
76
|
r("AUTH_BRIDGE_JWKS_URL", "auth", "url", "registry SSO-JWT 的 JWKS 地址(缺省从 issuer 派生)", { danger: SEC }),
|
|
77
77
|
r("AUTONOMY", "auth", "enum", "运维 autonomy 模式(read-only ⇒ 只读手 + 写面 roots,每段续跑重折,子代同受、委派照挂;ask ⇒ 恒 shellGate,每段重折;plan ⇒ 提交入口的模式词地板:新提交的任务以 permissionMode:plan 起步,请求词钳到它之下,写与非只读调用在调用时问 / 拒,批准计划之后整条 run 按批准的档跑完);center 腿热且 non-sticky(plan 地板只作用于之后提交的任务)", { danger: SEC, enumValues: AUTONOMY_MODES, configKey: "autonomy", center: cGov("autonomy") }),
|
|
78
78
|
r("AUTO_MODE_CLASSIFIER_FALLBACK", "approval", "enum", "auto 分类器席答不出裁决时的梯子(main=主模型代判 / none=只留分类器那一级,整条拒)", { enumValues: ["main", "none"], danger: SEC, derivedDefaultNote: "未设/空串 ⇒ 不铸键,跟随引擎缺省 main(core 7.18.0 起;空串按未设,与其余词表旋钮同判);none = 7.77.0 及以前的行为逐字;词表外的值启动期拒启。权限轴放宽,升级须知见 DEPLOY-PREREQS", configKey: "autoModeClassifierFallback" }),
|
|
@@ -88,7 +88,7 @@ export const CONFIG_CATALOG = [
|
|
|
88
88
|
r("BG_AGENT_RETENTION_MS", "orchestration", "number", "TERMINAL 后台 agent 行保留期(ms,reaper 按 scope 清)", { staticDefault: String(7 * 24 * 60 * 60 * 1000), danger: DATA, configKey: "backgroundAgentRetentionMs" }),
|
|
89
89
|
r("BG_AGENT_STALE_RUNNING_MS", "orchestration", "number", "running 后台 agent 行写者心跳断更超此值翻 failed(ms;下限 10min 硬钳)", { staticDefault: String(15 * 60_000), configKey: "backgroundAgentStaleRunningMs" }),
|
|
90
90
|
r("BIND_HOST", "auth", "string", "监听绑址(显式值恒生效)", { danger: SEC, derivedDefaultNote: "缺席:无鉴权写面(allowUnauthedWrites ∧ 零凭证)自动收窄 127.0.0.1,其余全接口", resolve: (c) => c.bindHost ?? null }),
|
|
91
|
-
r("CENTER_CREDENTIAL_RENEW_LEAD_MS", "integrations", "number", `center 凭证续签提前量(ms;${
|
|
91
|
+
r("CENTER_CREDENTIAL_RENEW_LEAD_MS", "integrations", "number", `center 凭证续签提前量(ms;${WEB_ADMIN_ENV.tokenFile} 指向人的登录会话信封时,访问令牌离到期 ≤ 本值即由引擎续签并原子改写同一文件;0 = 关续签只读信封;其余须为 [${CENTER_CREDENTIAL_RENEW_LEAD_MIN_MS}, ${CENTER_CREDENTIAL_RENEW_LEAD_MAX_MS}] 内整数,坏值拒启;裸行 / 值形凭证不续签)`, { staticDefault: String(CENTER_CREDENTIAL_RENEW_LEAD_DEFAULT_MS), danger: SEC, configKey: "centerCredentialRenewLeadMs" }),
|
|
92
92
|
r("COMPLIANCE_ADDITIONAL_DENIES", "auth", "csv", "合规档位之上额外拒绝的能力名(只能收紧;词表=core 闭集,拼错拒启)", { danger: SEC }),
|
|
93
93
|
r("COMPLIANCE_PROFILE", "auth", "enum", "本部署合规档位(core compliancePostureResolver 供数;拼错拒启)", { danger: SEC, enumValues: ["hipaa", "zdr"], resolve: (c) => c.compliancePosture?.profile ?? null }),
|
|
94
94
|
r("CONFIG_BOOT_FETCH_BUDGET_MS", "integrations", "number", "boot 首拉配置的抢答窗(ms;过窗以 env/兜底起服,拉取转后台热应用)", { staticDefault: "1500", configKey: "configBootFetchBudgetMs" }),
|
|
@@ -97,7 +97,7 @@ export const CONFIG_CATALOG = [
|
|
|
97
97
|
r("CONFIG_LKG_ENABLED", "integrations", "boolean", "last-known-good 配置盘缓存读写开关(仅远程 provider 有意义)", { staticDefault: "true", configKey: "configLkgEnabled" }),
|
|
98
98
|
r("CONFIG_LKG_PATH", "integrations", "path", "LKG 落盘路径(设了 ⇒ 派生一条读拒行 = 这条绝对路径:根锚定,只守这一个文件,经符号链接的目录时引擎沿链连目标一起守;大小写不敏感;用户仓里同尾的路径读得到;空串拒启)", { derivedDefaultNote: "缺席 ⇒ 配置根(SEMA_CONFIG_DIR,缺省 ~/.sema)下按 worker + 凭证身份分箱派生", configKey: "configLkgPath" }),
|
|
99
99
|
r("CONFIG_LOCAL_DIR", "integrations", "path", "本地配置源根(config.d/<domain>.json);兼作数据根回落链一环", { configKey: "configLocalDir", danger: DATA }),
|
|
100
|
-
r("CONFIG_PROVIDER", "integrations", "enum", "配置源双模选择(闭集,逐字比较;认不出的词 ⇒ 拒启,S-841)", { enumValues: CONFIG_PROVIDER_WORDS, derivedDefaultNote:
|
|
100
|
+
r("CONFIG_PROVIDER", "integrations", "enum", "配置源双模选择(闭集,逐字比较;认不出的词 ⇒ 拒启,S-841)", { enumValues: CONFIG_PROVIDER_WORDS, derivedDefaultNote: `缺席:${WEB_ADMIN_ENV.url} 在场 ⇒ remote,否则纯 env(无管道)`, configKey: "configProvider" }),
|
|
101
101
|
r("CONFIG_REQUIRE_ROSTER", "integrations", "boolean", "远程 registry 非 dryRun 时布防 ready 门:roster 未落地 ⇒ 计费提交 503 + /health ready:false(严格 'true')", { staticDefault: "false" }),
|
|
102
102
|
r("CORS_ORIGIN", "auth", "csv", "CORS 白名单(单值 v1 兼容回显;多值按请求 Origin 匹配 + Vary;未设=无 CORS 面)", { danger: SEC, configKey: "corsOrigins" }),
|
|
103
103
|
r("COST_QUOTA_WINDOW_SEC", "limitsHttp", "number", "per-principal 成本配额窗长(秒);SQL 腿窗长进桶主键 ⇒ center 腿显式 deferred(重启生效)", { staticDefault: "86400", danger: BILL, configKey: "costQuotaWindowSec", center: cLimits("costQuotaWindowSec", { timing: "restart", restartReason: "cost-quota-window" }) }),
|
|
@@ -105,7 +105,7 @@ export const CONFIG_CATALOG = [
|
|
|
105
105
|
r("CROSS_SESSION_INBOUND", "approval", "enum", "入站跨会话消息的部署/组织层治理(CC settings 键名 crossSessionInbound 的 managed 层;未设=本层不表态≠ next-turn;词表外拒启)", { danger: SEC, enumValues: [...CROSS_SESSION_INBOUND_SETTINGS], configKey: "crossSessionInbound", derivedDefaultNote: "缺席 ⇒ managed 层不设,由其余层与 mode-parity 决定", resolve: (c) => c.crossSessionInbound ?? null }),
|
|
106
106
|
r("DB_BACKEND", "store", "enum", "store 后端引擎选择(mysql 兼容/pg/本机文件店/纯内存)", { enumValues: STORE_BACKENDS, danger: DATA, derivedDefaultNote: "裸 boot ⇒ local(多租户裸 boot ⇒ memory + notice);有 SQL 信号 ⇒ mysql", configKey: "dbBackend", resolve: (c) => c.dbBackend }),
|
|
107
107
|
r("DB_QUERY_TIMEOUT_MS", "store", "number", "per-query DB 超时(ms;坏值回默认姿态+warn,绝不 NaN 进 driver)", { derivedDefaultNote: "缺席 ⇒ 池级不设/计数族 30s(按引擎)", configKey: "dbQueryTimeoutMs" }),
|
|
108
|
-
r("DEFAULT_SCENARIO", "integrations", "string", "出厂缺省场景(编码产品出厂态;通用中性 persona 用 default 显式选回);必须是本部署在册的场景名(内建 ∪
|
|
108
|
+
r("DEFAULT_SCENARIO", "integrations", "string", "出厂缺省场景(编码产品出厂态;通用中性 persona 用 default 显式选回);必须是本部署在册的场景名(内建 ∪ web-admin 声明的),不在册 ⇒ 拒启", { staticDefault: "code", configKey: "defaultScenario" }),
|
|
109
109
|
r("DELEGATION_MAX_CONCURRENT", "orchestration", "number", "委派入口并发帽", { derivedDefaultNote: "缺席 ⇒ 不铸键,core 自缺省(CC parity 20)" }),
|
|
110
110
|
r("DELEGATION_MAX_PER_SESSION", "orchestration", "number", "委派入口每会话累计帽", { derivedDefaultNote: "缺席 ⇒ 不铸键,core 自缺省(CC parity 200)" }),
|
|
111
111
|
r("DEVICE_DELIVERY_TIMEOUT_MS", "orchestration", "number", "device 车道等在途槽上限(ms;到期 device.busy)", { derivedDefaultNote: "缺席 ⇒ adapter 默认 10s" }),
|
|
@@ -401,16 +401,16 @@ export const CONFIG_CATALOG = [
|
|
|
401
401
|
r("SEMA_NPM_REGISTRY", "orchestration", "url", "SANDBOX_PKG_SOURCE=custom 的显式 npm registry 地址"),
|
|
402
402
|
r("SEMA_PARENT_PID", "limitsHttp", "number", "引擎 parent 监视(壳 pid;被过继走或该 pid 不存在即把引擎标成孤儿——不直接停机,改用 ENGINE_ORPHAN_LINGER_MS 窗走同一条租约自退;0/负数语义完全不同 ⇒ 拒启)", { derivedDefaultNote: "缺席 ⇒ 看门狗不装配", configKey: "parentPid" }),
|
|
403
403
|
r("SEMA_PIP_INDEX", "orchestration", "url", "SANDBOX_PKG_SOURCE=custom 的显式 pip index 地址"),
|
|
404
|
-
r("SEMA_REGISTRY_ADMIN_TOKEN", "integrations", "secret", "写 center 的管理凭证(仅 boot 上报执行公钥用;刻意不入 ServiceConfig —— 不进 configHash/日志面)", { danger: SEC, scope: "bootstrap-only" }),
|
|
405
|
-
r("SEMA_REGISTRY_DRY_RUN", "integrations", "boolean", "center 腿 dry-run(拉而不应用)", { staticDefault: "false" }),
|
|
406
|
-
r("SEMA_REGISTRY_LIVE_URL", "integrations", "url", `fleet 活性组(心跳 / 注销、服务租约 = settings-schema FLEET_LIVE_PATHS)的中心基址:给管理面在就绪门后、活性面另走一条不看就绪的入口的部署。缺席 = SEMA_REGISTRY_URL。${CENTER_BASE_URL_RULE}`),
|
|
407
|
-
r(CENTER_CREDENTIAL_ENV.token, "integrations", "secret", `center 拉取凭据(值形;与 ${CENTER_CREDENTIAL_ENV.tokenFile} 二选一,都设=拒启)`, { danger: SEC }),
|
|
408
|
-
r(CENTER_CREDENTIAL_ENV.tokenFile, "integrations", "path", `center 拉取凭据文件(每条中心请求都重读 ⇒ 轮换 / 换账号不重启;与 ${CENTER_CREDENTIAL_ENV.token} 二选一,都设=拒启;内容两种类:裸行 = 静态 bearer / JSON 信封 user-session = 人的登录会话(可续签,S-698);文件不可读 / 为空 = 响亮 + 健康态 unreadable,不当作无凭证;内容读不成 / 多租户读到信封 = 拒启)`, { danger: SEC }),
|
|
409
|
-
r("SEMA_REGISTRY_URL", "integrations", "url", `sema-registry 地址(设了=remote 配置源开关)。${CENTER_BASE_URL_RULE}`),
|
|
410
|
-
r("SEMA_REGISTRY_WEB_URL", "integrations", "url", `中心 web 面的固定基址:引擎原样转述到 /health 的 center.webBaseUrl,壳按它拼会话深链(路径归壳)。缺席即缺席(引擎不拼不猜);只在接了中心(SEMA_REGISTRY_URL)时挂。${CENTER_BASE_URL_RULE}`),
|
|
411
|
-
r("SEMA_REGISTRY_WORKER", "integrations", "string", "本 worker 在 center 的名字(per-worker effective)"),
|
|
412
404
|
r("SEMA_UV_INDEX", "orchestration", "url", "SANDBOX_PKG_SOURCE=custom 的显式 uv index 地址"),
|
|
413
405
|
r("SEMA_UV_PY_MIRROR", "orchestration", "url", "SANDBOX_PKG_SOURCE=custom 的显式 uv python 分发镜像地址"),
|
|
406
|
+
r(WEB_ADMIN_ENV.adminToken, "integrations", "secret", "写 web-admin 的管理凭证(仅 boot 上报执行公钥用;刻意不入 ServiceConfig —— 不进 configHash/日志面)", { danger: SEC, scope: "bootstrap-only" }),
|
|
407
|
+
r(WEB_ADMIN_ENV.dryRun, "integrations", "boolean", "web-admin 腿 dry-run(拉而不应用)", { staticDefault: "false" }),
|
|
408
|
+
r(WEB_ADMIN_ENV.liveUrl, "integrations", "url", `fleet 活性组(心跳 / 注销、服务租约 = settings-schema FLEET_LIVE_PATHS)的 web-admin 基址:给管理面在就绪门后、活性面另走一条不看就绪的入口的部署。缺席 = ${WEB_ADMIN_ENV.url}。传输地址,不参与中心身份互证。${CENTER_BASE_URL_RULE}`),
|
|
409
|
+
r(WEB_ADMIN_ENV.token, "integrations", "secret", `center 拉取凭据(值形;与 ${WEB_ADMIN_ENV.tokenFile} 二选一,都设=拒启)`, { danger: SEC }),
|
|
410
|
+
r(WEB_ADMIN_ENV.tokenFile, "integrations", "path", `center 拉取凭据文件(每条中心请求都重读 ⇒ 轮换 / 换账号不重启;与 ${WEB_ADMIN_ENV.token} 二选一,都设=拒启;内容两种类:裸行 = 静态 bearer / JSON 信封 user-session = 人的登录会话(可续签,S-698);文件不可读 / 为空 = 响亮 + 健康态 unreadable,不当作无凭证;内容读不成 / 多租户读到信封 = 拒启)`, { danger: SEC }),
|
|
411
|
+
r(WEB_ADMIN_ENV.url, "integrations", "url", `web-admin 基址(设了=remote 配置源开关;传输地址 —— 编排器注入的可以是集群内地址;AUTH_BRIDGE_ISSUER 缺席时它也是中心身份)。${CENTER_BASE_URL_RULE}`),
|
|
412
|
+
r(WEB_ADMIN_ENV.webUrl, "integrations", "url", `web-admin 人用界面的固定基址:引擎原样转述到 /health 的 center.webBaseUrl,壳按它拼会话深链(路径归壳)。缺席即缺席(引擎不拼不猜);只在接了中心(${WEB_ADMIN_ENV.url})时挂;必须与中心身份(AUTH_BRIDGE_ISSUER,缺席 = ${WEB_ADMIN_ENV.url})同主机同端口(http ↔ https 同族),否则拒启。${CENTER_BASE_URL_RULE}`),
|
|
413
|
+
r(WEB_ADMIN_ENV.worker, "integrations", "string", "本 worker 在 web-admin 上的名字(per-worker effective)"),
|
|
414
414
|
r("SEND_USER_FILE_SANDBOX_PUT_ENDPOINT", "store", "url", "沙箱侧 presigned PUT Host 绑定 endpoint(in-cluster 可用内网面)", { derivedDefaultNote: "缺席 ⇒ publicEndpoint" }),
|
|
415
415
|
r("SEND_USER_FILE_URL_TTL", "store", "number", "公链 TTL(秒;非法值回 0=永久轨,绝不 NaN 进签名)", { staticDefault: "0" }),
|
|
416
416
|
r("SENSITIVE_WRITE_PATTERNS", "approval", "csv", "敏感路径写 deny 集(unset=core 推荐集;显式值整表替换;off/空=关 —— 真放松杆)", { danger: SEC, derivedDefaultNote: "缺席 ⇒ core RECOMMENDED_SENSITIVE_PATTERNS", configKey: "sensitiveWritePatterns" }),
|
|
@@ -609,7 +609,7 @@ function valueClassOf(type) {
|
|
|
609
609
|
return "plain";
|
|
610
610
|
}
|
|
611
611
|
export function buildConfigCatalog(inputs) {
|
|
612
|
-
const { config, env, boot, knobs, centerDomains, centerManagedKeys
|
|
612
|
+
const { config, env, boot, knobs, centerDomains, centerManagedKeys } = inputs;
|
|
613
613
|
const knobByName = new Map(knobs.map((k) => [k.name, k]));
|
|
614
614
|
const rows = [...CONFIG_CATALOG]
|
|
615
615
|
.sort((a, b) => (a.name < b.name ? -1 : 1))
|
|
@@ -686,7 +686,7 @@ export function buildConfigCatalog(inputs) {
|
|
|
686
686
|
centerManaged: rows.filter((x) => x.center?.managedNow === true).length,
|
|
687
687
|
},
|
|
688
688
|
rows,
|
|
689
|
-
retired,
|
|
689
|
+
retired: buildRetiredEnvWire(),
|
|
690
690
|
};
|
|
691
691
|
}
|
|
692
692
|
//# sourceMappingURL=config-catalog.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type ConfigDirectoryMigration } from "@sema-agent/settings-schema/node";
|
|
2
|
+
/** 一次起服迁移的结果:契约的结果闭集,外加本仓入口的两形(目录不在 ⇒ 不调;调用抛 ⇒ 失败)。纯数据。 */
|
|
3
|
+
export type ConfigDirMigration = {
|
|
4
|
+
readonly kind: "no-config-dir";
|
|
5
|
+
readonly dir: string;
|
|
6
|
+
} | {
|
|
7
|
+
readonly kind: "threw";
|
|
8
|
+
readonly dir: string;
|
|
9
|
+
readonly reason: string;
|
|
10
|
+
} | (ConfigDirectoryMigration & {
|
|
11
|
+
readonly dir: string;
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* 对 `<root>/config.d` 的读回面跑一次契约的目录迁移(见文件头)。`root` = 本地配置根(`FileConfigStore` 吃的同一个)。
|
|
15
|
+
* 不抛:每一形都是一个结果,调用方 {@link logConfigDirMigration}。
|
|
16
|
+
*/
|
|
17
|
+
export declare function migrateLocalConfigDirectory(root: string): Promise<ConfigDirMigration>;
|
|
18
|
+
/** 迁移结果的日志口(logger 的子集)。 */
|
|
19
|
+
export interface ConfigDirMigrationLog {
|
|
20
|
+
info(msg: string, meta?: Record<string, unknown>): void;
|
|
21
|
+
warn(msg: string, meta?: Record<string, unknown>): void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 一次起服迁移的**唯一**说法(两个入口同调),对契约结果穷举:
|
|
25
|
+
* · `applied` ⇒ info `config_dir_migrated`(收据:迁移 id / 文档 / 坐标 / 处置,不带值;写回 / 删除的文件名);
|
|
26
|
+
* · `noop` / `no-documents` / 目录不在 ⇒ 不出声(没有东西可迁);
|
|
27
|
+
* · `refused` ⇒ warn `config_dir_migration_refused`(一字未写;逐条点名迁移 id、坐标与契约的拒句 —— 读侧随后对这些旧形照旧响亮拒);
|
|
28
|
+
* · `unreadable` / 调用抛 ⇒ warn `config_dir_migration_failed`(一字未写或写到一半;原因 —— 坏文件的句子与读腿同一只判官)。
|
|
29
|
+
* · `pending` 只在契约的 `check: true` 下出现,本仓从不传 ⇒ 撞上即是接线错,响亮抛。
|
|
30
|
+
*/
|
|
31
|
+
export declare function logConfigDirMigration(log: ConfigDirMigrationLog, m: ConfigDirMigration): void;
|
|
32
|
+
//# sourceMappingURL=config-d-migration.d.ts.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { RETIRED_DOMAINS, formatConfigPath, isRetiredDomainName } from "@sema-agent/settings-schema";
|
|
4
|
+
import { PORTABLE_DOMAINS, migrateConfigDirectory, nodeConfigDirectoryIo } from "@sema-agent/settings-schema/node";
|
|
5
|
+
const READ_BACK_DOCUMENTS = [...PORTABLE_DOMAINS, ...Object.keys(RETIRED_DOMAINS).filter(isRetiredDomainName)];
|
|
6
|
+
const errnoOf = (err) => (err instanceof Error && "code" in err && typeof err.code === "string" ? err.code : undefined);
|
|
7
|
+
export async function migrateLocalConfigDirectory(root) {
|
|
8
|
+
const dir = join(root, "config.d");
|
|
9
|
+
try {
|
|
10
|
+
if (!(await stat(dir)).isDirectory())
|
|
11
|
+
return { kind: "no-config-dir", dir };
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
if (errnoOf(err) === "ENOENT")
|
|
15
|
+
return { kind: "no-config-dir", dir };
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
return { ...(await migrateConfigDirectory(dir, { documents: READ_BACK_DOCUMENTS, io: nodeConfigDirectoryIo })), dir };
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
return { kind: "threw", dir, reason: `${err instanceof Error ? err.message : String(err)}${errnoOf(err) !== undefined ? ` (${errnoOf(err)})` : ""}` };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function logConfigDirMigration(log, m) {
|
|
25
|
+
switch (m.kind) {
|
|
26
|
+
case "no-config-dir":
|
|
27
|
+
case "no-documents":
|
|
28
|
+
case "noop":
|
|
29
|
+
return;
|
|
30
|
+
case "applied":
|
|
31
|
+
log.info("config_dir_migrated", {
|
|
32
|
+
dir: m.dir,
|
|
33
|
+
receipts: m.applied.map((r) => ({ migration: r.migration, document: r.document, path: formatConfigPath(r.path), appliedAs: r.appliedAs })),
|
|
34
|
+
written: m.written,
|
|
35
|
+
removed: m.removed,
|
|
36
|
+
note: "settings-schema one-time migrations rewrote the documents the local reader reads, once (idempotent: the next run changes nothing); files outside the reader's coverage were not touched",
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
case "refused":
|
|
40
|
+
log.warn("config_dir_migration_refused", {
|
|
41
|
+
dir: m.dir,
|
|
42
|
+
refusals: m.refused,
|
|
43
|
+
why: m.why,
|
|
44
|
+
note: "nothing was written — the named coordinates cannot be migrated automatically; the reader refuses those old forms (each refusal names its migration id) until they are fixed by hand",
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
case "unreadable":
|
|
48
|
+
case "threw":
|
|
49
|
+
log.warn("config_dir_migration_failed", { dir: m.dir, reason: m.reason, note: "the one-time migration did not complete — the reader judges the files as they are (old forms are refused loudly)" });
|
|
50
|
+
return;
|
|
51
|
+
case "pending":
|
|
52
|
+
throw new Error(`config.d migration: the contract answered "pending" (check-only) but this engine never asks for a check-only run — wiring error at ${m.dir}`);
|
|
53
|
+
default: {
|
|
54
|
+
const unhandled = m;
|
|
55
|
+
throw new Error(`config.d migration: unknown result ${JSON.stringify(unhandled)}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=config-d-migration.js.map
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
* 打开、bake runner 凭据被删、直连门半开、reaper 追杀活 run……),而进程照跑。逐条在 stage 里手抄一遍
|
|
7
7
|
* 是必错的形(抄漏一条没人会发现),所以本文件是**唯一**的表:
|
|
8
8
|
*
|
|
9
|
-
* · stage 面(
|
|
9
|
+
* · stage 面(web-admin):批1 的任一键触发**全表**校验,过不了 ⇒ 整批拒 + 旧值继续服务 + 铸
|
|
10
10
|
* `restartReason`(运维据此知道「这个变更只能重启生效」,而不是被静默吞掉)。
|
|
11
11
|
* · boot 面:每条 throw 点旁挂 `CROSS-INVARIANT:<id>` 锚,由 test/config-hot/batch1-cross-invariants.test.ts
|
|
12
12
|
* 双向对表 —— 新增一条拒启却不登记 = 门红。boot 的判据文本与拒启行为**逐字不变**(本批不改拒启),
|
|
13
13
|
* 表在这一侧承担的是「不许有表外不变量」的登记职责。
|
|
14
14
|
*
|
|
15
15
|
* 🔴 **报文次序**(车PI 验修轮 F7,如实登记):boot 期一次只报一条。条目按 `bootAnchor` 分住几个区 —— `config.ts` 的 loadConfig 区与
|
|
16
|
-
* `boot/stage-01-config.ts`(第 1 段)、`boot/execution-lane.ts`(第 2 段开头的车道合成步)、`boot/stage-02-
|
|
16
|
+
* `boot/stage-01-config.ts`(第 1 段)、`boot/execution-lane.ts`(第 2 段开头的车道合成步)、`boot/stage-02-web-admin-pull.ts`(第 2 段
|
|
17
17
|
* 目录应用之后)。**跨区次序 = 段序,区内次序 = 表序**:同时撞上两个区的两条,先到的区先报,与表序无关(例:`LEADER_ENABLED` + host
|
|
18
18
|
* + 无 SQL 先报 loadConfig 区的 `leader-durable-store`,改完再在合成步撞 `leader-host-lane`)。锚点按「读什么键、那个键在哪一段定形」定,
|
|
19
19
|
* 不为报文次序挪区 —— 多一次往返,没有安全后果。
|
|
@@ -34,9 +34,9 @@ import type { ServiceConfigFlat } from "./config-types.js";
|
|
|
34
34
|
*/
|
|
35
35
|
export declare const LIVENESS_HEARTBEAT_MS = 30000;
|
|
36
36
|
/** 闭集:新增一条不变量必须在这里加词(拼错 = 编译红,而不是运行期悄悄不判)。 */
|
|
37
|
-
export type CrossInvariantId = "run-stale-heartbeat" | "bind-posture" | "bake-runner-credential" | "direct-door-anchors" | "operator-principals-required" | "bake-operator-principals" | "sensitive-write-patterns" | "leader-host-lane" | "multi-tenant-shared-lane" | "leader-durable-store" | "device-durable-store" | "device-durable-approval" | "model-provider-word" | "gateway-url-userinfo" | "model-route-endpoint" | "default-model-resolvable" | "refused-names-not-minted" | "require-execution-lane";
|
|
37
|
+
export type CrossInvariantId = "run-stale-heartbeat" | "bind-posture" | "bake-runner-credential" | "direct-door-anchors" | "operator-principals-required" | "bake-operator-principals" | "sensitive-write-patterns" | "leader-host-lane" | "multi-tenant-shared-lane" | "leader-durable-store" | "device-durable-store" | "device-durable-approval" | "model-provider-word" | "gateway-url-userinfo" | "model-route-endpoint" | "default-model-resolvable" | "refused-names-not-minted" | "require-execution-lane" | "center-one-identity";
|
|
38
38
|
/** 表读的键集(取自平铺配置型 ⇒ 键名/类型永不与真配置漂)。 */
|
|
39
|
-
export type CrossInvariantView = Readonly<Pick<ServiceConfigFlat, "runStaleSec" | "authToken" | "authTokens" | "allowUnauthedWrites" | "imageBakes" | "directApprovalDoor" | "durableApproval" | "approvalHmacKeys" | "principalJwtPubkeys" | "principalJwtIss" | "principalJwtAud" | "requirePrincipal" | "operatorPrincipals" | "sensitiveWritePatterns" | "leaderEnabled" | "remoteExec" | "requireExecutionLane" | "dbBackend" | "tidb" | "pg" | "envModel" | "gatewayBaseUrl" | "gatewayFallbackUrls" | "anthropic" | "models" | "modelRefusals" | "tiers">>;
|
|
39
|
+
export type CrossInvariantView = Readonly<Pick<ServiceConfigFlat, "runStaleSec" | "authToken" | "authTokens" | "allowUnauthedWrites" | "imageBakes" | "directApprovalDoor" | "durableApproval" | "approvalHmacKeys" | "principalJwtPubkeys" | "principalJwtIss" | "principalJwtAud" | "requirePrincipal" | "operatorPrincipals" | "sensitiveWritePatterns" | "leaderEnabled" | "remoteExec" | "requireExecutionLane" | "dbBackend" | "tidb" | "pg" | "envModel" | "gatewayBaseUrl" | "gatewayFallbackUrls" | "anthropic" | "models" | "modelRefusals" | "tiers" | "authBridgeIssuer" | "webAdmin">>;
|
|
40
40
|
export interface CrossInvariantInput {
|
|
41
41
|
/** 现役配置(boot 期 = 候选自身)。 */
|
|
42
42
|
readonly live: CrossInvariantView;
|
|
@@ -84,6 +84,16 @@ export declare function bakeRunnerCredentialRefusal(v: Pick<CrossInvariantView,
|
|
|
84
84
|
* 「多副本」在 config 层判不了(副本数不是本进程的知识),可判的**代理**就是 durable 前提本身。
|
|
85
85
|
*/
|
|
86
86
|
export declare function leaderDurableStoreRefusal(v: Pick<CrossInvariantView, "leaderEnabled" | "dbBackend">): string | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* `center-one-identity`(7.124.0)—— **中心只有一个身份**。引擎接的 web-admin 由三只旋钮描述:基址(`WEB_ADMIN_ENV.url`,传输地址)、
|
|
89
|
+
* 鉴权桥 issuer(`AUTH_BRIDGE_ISSUER`,缺席 = 基址 —— {@link authBridgeIssuerOf} 一只取值口)、web 基址(`WEB_ADMIN_ENV.webUrl`,上
|
|
90
|
+
* `/health.center.webBaseUrl`)。壳先判 `center.issuer` 是不是自己登录的那个中心,再按 `center.webBaseUrl` 拼会话深链 ⇒ 两者不是同一个中心
|
|
91
|
+
* = 把人带到另一个中心。规则:**web 基址必须落在身份(issuer)的 origin 族**。**origin 族 = `URL.host` 相同**(主机 + 非缺省端口;scheme 不比 ⇒ http ↔ https 同族:TLS 终结在
|
|
92
|
+
* 前面的部署两种写法都见)。判不出主机(issuer 不是 http(s) 的层级式 URL)= 证不了同一个中心 ⇒ 同样拒(fail-closed)。
|
|
93
|
+
* 基址 / 活性基址是**传输地址**,不参与:编排器给 worker 注入的基址是集群内地址,issuer 是对外域名 —— 两者天然不同主机,拿基址互证会拒掉
|
|
94
|
+
* 标准部署形。没有 web 基址 ⇒ 无可互证,过。
|
|
95
|
+
*/
|
|
96
|
+
export declare function centerIdentityRefusal(v: Pick<CrossInvariantView, "authBridgeIssuer" | "webAdmin">): string | undefined;
|
|
87
97
|
export declare const CONFIG_CROSS_INVARIANTS: readonly CrossInvariant[];
|
|
88
98
|
/**
|
|
89
99
|
* 按 id 取**一条**(S-157②:boot 面的单源化出口)。
|
|
@@ -3,6 +3,9 @@ import { modelProviderRefusal } from "./model-provider.js";
|
|
|
3
3
|
import { modelRouteUrlRefusal } from "./model-route-url.js";
|
|
4
4
|
import { modelRouteEndpointRefusal } from "./model-route-endpoint.js";
|
|
5
5
|
import { defaultModelRefusal, refusedNameMintedRefusal } from "./model-entry-refusal.js";
|
|
6
|
+
import { WEB_ADMIN_ENV, httpUrlFlaws } from "@sema-agent/settings-schema";
|
|
7
|
+
import { authBridgeIssuerOf } from "./auth-bridge.js";
|
|
8
|
+
import { buildDisplaySafeUrl } from "./trace/redact.js";
|
|
6
9
|
import { executionLaneCaps, executionLaneOf, executionLaneRequiredRefusal, laneCapabilityHolds, REMOTE_EXEC_PROVIDER_WORDS } from "./execution-lane-caps.js";
|
|
7
10
|
export const LIVENESS_HEARTBEAT_MS = 30_000;
|
|
8
11
|
const anyServiceAuth = (v) => Boolean(v.authToken) || Object.keys(v.authTokens ?? {}).length > 0;
|
|
@@ -61,6 +64,25 @@ export function leaderDurableStoreRefusal(v) {
|
|
|
61
64
|
"(#193 车4 件2 / staleness ledger P1-9+P1-10 boot invariant)"
|
|
62
65
|
: undefined;
|
|
63
66
|
}
|
|
67
|
+
export function centerIdentityRefusal(v) {
|
|
68
|
+
const webBaseUrl = v.webAdmin?.webBaseUrl;
|
|
69
|
+
if (webBaseUrl === undefined)
|
|
70
|
+
return undefined;
|
|
71
|
+
const identity = authBridgeIssuerOf(v);
|
|
72
|
+
if (identity === undefined)
|
|
73
|
+
return undefined;
|
|
74
|
+
const hostOf = (raw) => (httpUrlFlaws(raw, []).length === 0 ? new URL(raw).host : undefined);
|
|
75
|
+
const issuerHost = hostOf(identity.issuer);
|
|
76
|
+
const webHost = hostOf(webBaseUrl);
|
|
77
|
+
if (issuerHost !== undefined && issuerHost === webHost)
|
|
78
|
+
return undefined;
|
|
79
|
+
const issuerSeat = identity.source === "explicit" ? "AUTH_BRIDGE_ISSUER" : `AUTH_BRIDGE_ISSUER (unset, so ${WEB_ADMIN_ENV.url})`;
|
|
80
|
+
const issuerShown = issuerHost === undefined ? `${buildDisplaySafeUrl(identity.issuer)}, which is not an http(s) URL with a host` : `${buildDisplaySafeUrl(identity.issuer)} (host ${issuerHost})`;
|
|
81
|
+
return (`${WEB_ADMIN_ENV.webUrl}=${buildDisplaySafeUrl(webBaseUrl)} (host ${webHost ?? "?"}) is not on this engine's web-admin identity — ${issuerSeat} = ${issuerShown}. ` +
|
|
82
|
+
`A client checks the reported issuer against the web-admin it signed in to and then links sessions under the reported web console base, so both must name ` +
|
|
83
|
+
`the same web-admin: same host and port (http and https on one host count as one). Set ${WEB_ADMIN_ENV.webUrl} to that web-admin's console, set ` +
|
|
84
|
+
`AUTH_BRIDGE_ISSUER to the web-admin's public origin, or unset ${WEB_ADMIN_ENV.webUrl}. Refusing to boot.`);
|
|
85
|
+
}
|
|
64
86
|
export const CONFIG_CROSS_INVARIANTS = [
|
|
65
87
|
{
|
|
66
88
|
id: "run-stale-heartbeat",
|
|
@@ -217,16 +239,23 @@ export const CONFIG_CROSS_INVARIANTS = [
|
|
|
217
239
|
id: "default-model-resolvable",
|
|
218
240
|
keys: ["models", "modelRefusals"],
|
|
219
241
|
restartReason: "default-model",
|
|
220
|
-
bootAnchor: { file: "src/boot/stage-02-
|
|
242
|
+
bootAnchor: { file: "src/boot/stage-02-web-admin-pull.ts", marker: "CROSS-INVARIANT:default-model-resolvable" },
|
|
221
243
|
check: ({ live, next }) => (next.models === live.models ? undefined : defaultModelRefusal(next, "catalog")),
|
|
222
244
|
},
|
|
223
245
|
{
|
|
224
246
|
id: "refused-names-not-minted",
|
|
225
247
|
keys: ["models", "tiers", "modelRefusals"],
|
|
226
248
|
restartReason: "refused-model-name",
|
|
227
|
-
bootAnchor: { file: "src/boot/stage-02-
|
|
249
|
+
bootAnchor: { file: "src/boot/stage-02-web-admin-pull.ts", marker: "CROSS-INVARIANT:refused-names-not-minted" },
|
|
228
250
|
check: ({ live, next }) => (next.models === live.models ? undefined : refusedNameMintedRefusal(next)),
|
|
229
251
|
},
|
|
252
|
+
{
|
|
253
|
+
id: "center-one-identity",
|
|
254
|
+
keys: ["authBridgeIssuer", "webAdmin"],
|
|
255
|
+
restartReason: "center-identity",
|
|
256
|
+
bootAnchor: { file: "src/config.ts", marker: "CROSS-INVARIANT:center-one-identity" },
|
|
257
|
+
check: ({ next }) => centerIdentityRefusal(next),
|
|
258
|
+
},
|
|
230
259
|
];
|
|
231
260
|
export function crossInvariant(id) {
|
|
232
261
|
const inv = CONFIG_CROSS_INVARIANTS.find((i) => i.id === id);
|
package/dist/config-lkg.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import { type MigrationReceipt } from "@sema-agent/settings-schema/one-time-migrations";
|
|
2
|
+
/** 落盘信封。`effective`/`etag` 逐字=当时的 fetchEffective 返回(JSON verbatim 面)。`formatVersion` 的语义见头注(4 = 载荷是 settings-schema 11 的形)。 */
|
|
2
3
|
export interface PersistedLkg {
|
|
3
|
-
formatVersion:
|
|
4
|
+
formatVersion: 4;
|
|
4
5
|
savedAt: string;
|
|
5
6
|
worker: string;
|
|
6
7
|
/** 拉到这份候选时所用凭证的身份(`center-credential.ts`;摘要形,不含凭证原文)。 */
|
|
@@ -46,19 +47,38 @@ export declare function lkgReadDenyRows(configLkgPath: string | undefined): {
|
|
|
46
47
|
/** 原子落盘一个干净候选(tmp+rename,0600)。BEST-EFFORT:失败=一条 warn 的素材(调用方发),绝不
|
|
47
48
|
* 影响在飞的应用链——LKG 缓存丢失的代价只是下次冷启动回到 env 兜底。 */
|
|
48
49
|
export declare function saveLkg(path: string, worker: string | undefined, identity: string, effective: Record<string, unknown>, etag: string | undefined): Promise<void>;
|
|
50
|
+
/** 一次读回做过的一次性迁移(调用方响亮打一行 `config_lkg_migrated`,并按 4 写回原位):`fromFormat` = 盘上的版本号;`note` = 信封层的说明
|
|
51
|
+
* (2 → 3 那一步,{@link migrateV2Lkg});`receipts` = 载荷层 settings-schema 迁移的收据(只点名坐标与处置,不带值)。 */
|
|
52
|
+
export interface LkgReadMigration {
|
|
53
|
+
readonly fromFormat: 2 | 3 | 4;
|
|
54
|
+
readonly note?: string;
|
|
55
|
+
readonly receipts: readonly MigrationReceipt[];
|
|
56
|
+
}
|
|
49
57
|
/**
|
|
50
58
|
* 读盘上 LKG。返回 undefined 的所有情形都「安静地回 env 兜底」(调用方 warn 点名 reason):
|
|
51
59
|
* 文件缺席/JSON 坏/信封形不对/worker 或凭证身份不匹配(防同名文件跨 worker / 跨账号串包;`CONFIG_LKG_PATH`
|
|
52
|
-
* 单一路径时这是唯一的一道)/prompts 面复判失败。
|
|
53
|
-
* `migrated`:读回时做过一次性迁移(
|
|
60
|
+
* 单一路径时这是唯一的一道)/载荷有迁不动的旧形({@link migrateLkgPayload})/prompts 面复判失败。
|
|
61
|
+
* `migrated`:读回时做过一次性迁移(信封 2 → 3 或载荷的 settings-schema 迁移)—— 调用方响亮打一行,并把交回的 4 写回原位。
|
|
54
62
|
*/
|
|
55
63
|
export declare function loadLkg(path: string, worker: string | undefined, identity: string): Promise<{
|
|
56
64
|
lkg: PersistedLkg;
|
|
57
|
-
migrated?:
|
|
65
|
+
migrated?: LkgReadMigration;
|
|
58
66
|
} | {
|
|
59
67
|
lkg?: undefined;
|
|
60
68
|
reason: string;
|
|
61
69
|
}>;
|
|
70
|
+
/**
|
|
71
|
+
* LKG 载荷(一份冻结的 effective)→ settings-schema 11 的形:调契约的 `migrateEffectiveSnapshot`(本仓不写任何一条改写规则;只认形、幂等,
|
|
72
|
+
* `version` / `updatedAt` 与其余信封键原样)。有收据 `refused`(迁不动 —— 例:同一份文档里 `atModelAllowlist` 与 `atMention` 并存、
|
|
73
|
+
* `runtime` 文档读不进)⇒ 交回拒因(**点名迁移 id 与契约的拒句**,不带值),调用方不读回这份 LKG:本仓应用 LKG 不经契约读腿,迁不动的旧形
|
|
74
|
+
* 留在载荷里就会被下游当作缺席静默略过(例:`atModelAllowlist` 不再有读者 = 限制被静默放宽)。
|
|
75
|
+
*/
|
|
76
|
+
export declare function migrateLkgPayload(effective: Record<string, unknown>): {
|
|
77
|
+
effective: Record<string, unknown>;
|
|
78
|
+
receipts: readonly MigrationReceipt[];
|
|
79
|
+
} | {
|
|
80
|
+
refused: string;
|
|
81
|
+
};
|
|
62
82
|
/** 7.98.0 及以前的默认 LKG 路径(按当时的写法逐字保留 —— 这是盘上已有文件的名字,不是现行分箱规则)。 */
|
|
63
83
|
export declare function legacyV1LkgPath(config: {
|
|
64
84
|
readonly agentConfigDir?: string;
|
|
@@ -84,7 +104,7 @@ export type LegacyLkgMigration = {
|
|
|
84
104
|
* 2. 旧文件没有(ENOENT / ENOTDIR)或不是 formatVersion 1 信封 ⇒ `none`。旧默认路径上却读不了 / 不是 JSON ⇒
|
|
85
105
|
* `not-migrated`(响亮,不当它不存在);`CONFIG_LKG_PATH` 同路径时这两形由 loadLkg 自己报(不重复打一行)。
|
|
86
106
|
* 3. v1 信封只记 worker:唯一推得出身份的形是拉取令牌(身份 = worker,`pullTokenIdentity`)。此刻身份正是它、worker
|
|
87
|
-
* 对得上、载荷过 {@link lkgPayloadProblem} ⇒ **先写后退役**(修复验证 N9):先按新格式把它写进 `binPath`(原子写,
|
|
107
|
+
* 对得上、载荷过 {@link migrateLkgPayload}(迁成 11 的形;迁不动 ⇒ 响亮拒、不写箱)与 {@link lkgPayloadProblem} ⇒ **先写后退役**(修复验证 N9):先按新格式把它写进 `binPath`(原子写,
|
|
88
108
|
* 0600),写成了才把旧文件改名 `<legacyPath>.migrated-v1`。写失败 ⇒ `not-migrated`,旧文件原样留在原位,下一次
|
|
89
109
|
* 照常可迁;写成之后改名失败 ⇒ 仍是 `migrated`(`retireError` 响一行),箱已在 ⇒ 下一次按判序 1 不再看旧文件。
|
|
90
110
|
* 同路径(`CONFIG_LKG_PATH`)时新箱的写入会原子替换旧文件本身,所以旧字节在写之前留一份 `.migrated-v1` 副本
|
package/dist/config-lkg.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { promises as fs } from "node:fs";
|
|
2
|
+
import { migrateEffectiveSnapshot } from "@sema-agent/settings-schema/one-time-migrations";
|
|
3
|
+
import { formatConfigPath } from "@sema-agent/settings-schema";
|
|
2
4
|
import { join, dirname, resolve } from "node:path";
|
|
3
5
|
import { randomBytes } from "node:crypto";
|
|
4
6
|
import { validatePromptsDomain } from "./prompts-domain-validate.js";
|
|
@@ -25,7 +27,7 @@ export function lkgReadDenyRows(configLkgPath) {
|
|
|
25
27
|
return [{ pattern: configLkgPath, caseSensitive: false }];
|
|
26
28
|
}
|
|
27
29
|
export async function saveLkg(path, worker, identity, effective, etag) {
|
|
28
|
-
const body = { formatVersion:
|
|
30
|
+
const body = { formatVersion: 4, savedAt: new Date().toISOString(), worker: worker ?? "global", identity, ...(etag !== undefined ? { etag } : {}), effective };
|
|
29
31
|
const tmp = `${path}.${randomBytes(6).toString("hex")}.tmp`;
|
|
30
32
|
await fs.mkdir(dirname(path), { recursive: true });
|
|
31
33
|
await fs.writeFile(tmp, JSON.stringify(body), { mode: 0o600 });
|
|
@@ -52,16 +54,44 @@ export async function loadLkg(path, worker, identity) {
|
|
|
52
54
|
const formatVersion = p.formatVersion;
|
|
53
55
|
if (formatVersion === 1)
|
|
54
56
|
return { reason: "pre-identity LKG (formatVersion 1) — not read back: it cannot prove whose credential fetched it (S-651); the next clean candidate rewrites it" };
|
|
55
|
-
if (formatVersion !== 2 && formatVersion !== 3)
|
|
57
|
+
if (formatVersion !== 2 && formatVersion !== 3 && formatVersion !== 4)
|
|
56
58
|
return { reason: `unknown formatVersion ${String(formatVersion)}` };
|
|
57
59
|
if (typeof p.worker !== "string" || p.worker !== (worker ?? "global"))
|
|
58
60
|
return { reason: `worker mismatch (file=${String(p.worker)})` };
|
|
59
61
|
if (typeof p.identity !== "string" || p.identity !== identity)
|
|
60
62
|
return { reason: "credential identity mismatch (the LKG was fetched under another credential identity — not read back)" };
|
|
61
|
-
|
|
63
|
+
if (p.effective === null || typeof p.effective !== "object" || Array.isArray(p.effective))
|
|
64
|
+
return { reason: "bad effective" };
|
|
65
|
+
let envelope = p;
|
|
66
|
+
let note;
|
|
67
|
+
if (formatVersion === 2) {
|
|
68
|
+
const v2 = migrateV2Lkg(envelope);
|
|
69
|
+
if (v2.lkg === undefined)
|
|
70
|
+
return v2;
|
|
71
|
+
envelope = v2.lkg;
|
|
72
|
+
note = v2.migrated;
|
|
73
|
+
}
|
|
74
|
+
const payload = migrateLkgPayload(envelope.effective);
|
|
75
|
+
if ("refused" in payload)
|
|
76
|
+
return { reason: payload.refused };
|
|
77
|
+
const problem = lkgPayloadProblem(payload.effective, envelope.etag);
|
|
62
78
|
if (problem !== undefined)
|
|
63
79
|
return { reason: problem };
|
|
64
|
-
|
|
80
|
+
const lkg = { ...envelope, formatVersion: 4, effective: payload.effective };
|
|
81
|
+
const migrated = formatVersion !== 4 || payload.receipts.length > 0;
|
|
82
|
+
return migrated ? { lkg, migrated: { fromFormat: formatVersion, ...(note !== undefined ? { note } : {}), receipts: payload.receipts } } : { lkg };
|
|
83
|
+
}
|
|
84
|
+
export function migrateLkgPayload(effective) {
|
|
85
|
+
const m = migrateEffectiveSnapshot(effective);
|
|
86
|
+
const refused = m.receipts.filter((r) => r.appliedAs === "refused");
|
|
87
|
+
if (refused.length > 0) {
|
|
88
|
+
return {
|
|
89
|
+
refused: "settings-schema one-time migration refused part of this LKG's payload (" +
|
|
90
|
+
refused.map((r) => `${r.migration} ${r.document}:${formatConfigPath(r.path)} — ${r.reason}`).join("; ") +
|
|
91
|
+
") — not read back: the engine applies an LKG without the contract's reader, so a form the migration cannot rewrite would be silently skipped; the next clean candidate rewrites it",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return { effective: m.effective, receipts: m.receipts };
|
|
65
95
|
}
|
|
66
96
|
const RETIRED_CATALOG_COORDINATE = /^models\.(0|[1-9][0-9]*)$/;
|
|
67
97
|
function migrateDroppedIdentityRow(row) {
|
|
@@ -87,7 +117,7 @@ function migrateV2Lkg(lkg) {
|
|
|
87
117
|
effective = { ...lkg.effective, models: { ...rest, droppedCatalogEntries: Array.isArray(retired) ? retired.map(migrateDroppedIdentityRow) : retired } };
|
|
88
118
|
}
|
|
89
119
|
return {
|
|
90
|
-
lkg: { ...envelope,
|
|
120
|
+
lkg: { ...envelope, effective },
|
|
91
121
|
migrated: "formatVersion 2 → 3 (one-time): this LKG was written before every dropped catalog entry was recorded, so its etag is not reused — the next pull re-reads the whole candidate and re-mints the full record" +
|
|
92
122
|
(hasRetired ? "; the retired carrier models.droppedIdentities was moved to models.droppedCatalogEntries" : ""),
|
|
93
123
|
};
|
|
@@ -100,7 +130,7 @@ function lkgPayloadProblem(effective, etag) {
|
|
|
100
130
|
const eff = effective;
|
|
101
131
|
if (typeof eff.version !== "number")
|
|
102
132
|
return "bad effective.version";
|
|
103
|
-
for (const [domain, arrayKey] of [["models", "models"], ["skills", "skills"], ["mcp", "servers"], ["scenarios", "scenarios"], ["
|
|
133
|
+
for (const [domain, arrayKey] of [["models", "models"], ["skills", "skills"], ["mcp", "servers"], ["scenarios", "scenarios"], ["limits", null]]) {
|
|
104
134
|
const v = eff[domain];
|
|
105
135
|
if (v === undefined)
|
|
106
136
|
continue;
|
|
@@ -112,12 +142,6 @@ function lkgPayloadProblem(effective, etag) {
|
|
|
112
142
|
return `bad effective.${domain}.${arrayKey}`;
|
|
113
143
|
}
|
|
114
144
|
}
|
|
115
|
-
{
|
|
116
|
-
const rt = eff.runtime;
|
|
117
|
-
if (rt && (rt.autonomy !== undefined || rt.commandPolicy !== undefined || rt.approvalRequire !== undefined)) {
|
|
118
|
-
return "legacy runtime governance keys (pre-0.7.1 mirror shape; no contract producer since LKG shipped) — refusing rather than silently dropping governance";
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
145
|
const promptsRaw = eff.prompts;
|
|
122
146
|
if (promptsRaw !== undefined && !validatePromptsDomain(promptsRaw).ok)
|
|
123
147
|
return "prompts face fails re-validation";
|
|
@@ -168,7 +192,12 @@ export async function migrateV1Lkg(o) {
|
|
|
168
192
|
if (o.identity !== pullTokenIdentity(o.worker)) {
|
|
169
193
|
return refuse("its envelope records only the worker, and only a pull-token credential's identity (= the worker) can be derived from that — the current credential is not this worker's pull token, so the file cannot prove whose credential fetched it");
|
|
170
194
|
}
|
|
171
|
-
|
|
195
|
+
if (v1.effective === null || typeof v1.effective !== "object" || Array.isArray(v1.effective))
|
|
196
|
+
return refuse("its payload is unusable (bad effective)");
|
|
197
|
+
const payload = migrateLkgPayload(v1.effective);
|
|
198
|
+
if ("refused" in payload)
|
|
199
|
+
return refuse(`its payload cannot be migrated to the current settings form (${payload.refused})`);
|
|
200
|
+
const problem = lkgPayloadProblem(payload.effective, v1.etag);
|
|
172
201
|
if (problem !== undefined)
|
|
173
202
|
return refuse(`its payload is unusable (${problem})`);
|
|
174
203
|
const retiredTo = `${o.legacyPath}.migrated-v1`;
|
|
@@ -182,7 +211,7 @@ export async function migrateV1Lkg(o) {
|
|
|
182
211
|
}
|
|
183
212
|
}
|
|
184
213
|
try {
|
|
185
|
-
await saveLkg(o.binPath, o.worker, o.identity,
|
|
214
|
+
await saveLkg(o.binPath, o.worker, o.identity, payload.effective, undefined);
|
|
186
215
|
}
|
|
187
216
|
catch (err) {
|
|
188
217
|
return refuse(`writing it into this credential identity's bin failed (${String(err)}) — the old file is left in place and the migration retries on the next LKG read`);
|