@sema-agent/server 7.98.0 → 7.99.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.
Files changed (50) hide show
  1. package/MIGRATION.md +6 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +12 -9
  5. package/deploy/sema-up/lib/envf.sh +110 -42
  6. package/deploy/sema-up/sema-up.sh +76 -34
  7. package/dist/boot/config-center.js +167 -22
  8. package/dist/boot/org-memory.d.ts +2 -1
  9. package/dist/boot/org-memory.js +9 -5
  10. package/dist/boot/runtime-caps.js +4 -4
  11. package/dist/capabilities/center-prompts.d.ts +3 -0
  12. package/dist/capabilities/center-prompts.js +1 -1
  13. package/dist/center-credential.d.ts +112 -0
  14. package/dist/center-credential.js +131 -0
  15. package/dist/config-catalog.js +3 -2
  16. package/dist/config-center/center-request.d.ts +60 -0
  17. package/dist/config-center/center-request.js +71 -0
  18. package/dist/config-center/http-client.d.ts +17 -9
  19. package/dist/config-center/http-client.js +27 -36
  20. package/dist/config-center/skills-mcp.d.ts +4 -1
  21. package/dist/config-center/skills-mcp.js +2 -2
  22. package/dist/config-invariants.d.ts +2 -2
  23. package/dist/config-invariants.js +0 -8
  24. package/dist/config-lkg.d.ts +47 -8
  25. package/dist/config-lkg.js +107 -20
  26. package/dist/config-provider.d.ts +8 -7
  27. package/dist/config-provider.js +5 -5
  28. package/dist/config-types.d.ts +8 -4
  29. package/dist/config.js +11 -13
  30. package/dist/fleet-client.d.ts +8 -8
  31. package/dist/fleet-client.js +7 -7
  32. package/dist/fleet-lease.d.ts +11 -11
  33. package/dist/fleet-lease.js +17 -13
  34. package/dist/hosted-posture.d.ts +49 -93
  35. package/dist/hosted-posture.js +1 -25
  36. package/dist/observability/secret-env-scrub.d.ts +2 -26
  37. package/dist/observability/secret-env-scrub.js +2 -2
  38. package/dist/plugins/remote-env-host.d.ts +3 -1
  39. package/dist/plugins/remote-env-host.js +3 -2
  40. package/dist/project-memory.d.ts +28 -1
  41. package/dist/project-memory.js +22 -22
  42. package/dist/run-local.js +0 -2
  43. package/dist/runtime-caps-resolver.d.ts +8 -8
  44. package/dist/runtime-caps-resolver.js +16 -14
  45. package/dist/sealed-key.js +3 -7
  46. package/dist/server-secret-env.d.ts +41 -0
  47. package/dist/server-secret-env.js +18 -0
  48. package/dist/tool-approval.d.ts +48 -1
  49. package/dist/tool-approval.js +10 -3
  50. package/package.json +4 -3
@@ -0,0 +1,131 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { createHash } from "node:crypto";
3
+ import { resolve as resolvePath } from "node:path";
4
+ import { looksLikeJwt } from "@sema-agent/settings-schema/api/auth-bridge";
5
+ export class CenterCredentialUnavailableError extends Error {
6
+ reason;
7
+ constructor(reason) {
8
+ super(`center credential unavailable: ${reason}`);
9
+ this.reason = reason;
10
+ this.name = "CenterCredentialUnavailableError";
11
+ }
12
+ }
13
+ export const CENTER_TOKEN_ENV = "SEMA_REGISTRY_TOKEN";
14
+ export const CENTER_TOKEN_FILE_ENV = "SEMA_REGISTRY_TOKEN_FILE";
15
+ export function readCenterCredentialDeclaration(env = process.env) {
16
+ const token = env.SEMA_REGISTRY_TOKEN ?? "";
17
+ const file = env.SEMA_REGISTRY_TOKEN_FILE ?? "";
18
+ if (token !== "" && file !== "") {
19
+ throw new Error(`${CENTER_TOKEN_ENV} and ${CENTER_TOKEN_FILE_ENV} are both set — pick exactly one: ${CENTER_TOKEN_ENV} carries the credential value, ` +
20
+ `${CENTER_TOKEN_FILE_ENV} names a file re-read on every request (rotation without restart). Two writers on one credential is a guess, not a config.`);
21
+ }
22
+ if (file !== "")
23
+ return { from: "file", path: resolvePath(file) };
24
+ if (token !== "")
25
+ return { from: "env", token };
26
+ return { from: "absent" };
27
+ }
28
+ const digest16 = (s) => createHash("sha256").update(s, "utf8").digest("hex").slice(0, 16);
29
+ function jwtSubject(bearer) {
30
+ let parsed;
31
+ try {
32
+ parsed = JSON.parse(Buffer.from(bearer.split(".")[1] ?? "", "base64url").toString("utf8"));
33
+ }
34
+ catch (err) {
35
+ return { missing: `payload is not JSON (${err instanceof Error ? err.name : "parse error"})` };
36
+ }
37
+ if (parsed === null || typeof parsed !== "object" || !("sub" in parsed))
38
+ return { missing: "no sub claim" };
39
+ return typeof parsed.sub === "string" && parsed.sub !== "" ? { sub: parsed.sub } : { missing: "sub is not a non-empty string" };
40
+ }
41
+ function classify(bearer, worker) {
42
+ if (looksLikeJwt(bearer)) {
43
+ const s = jwtSubject(bearer);
44
+ return { ok: true, kind: "user-jwt", bearer, identity: "sub" in s ? `user-jwt:${digest16(s.sub)}` : `user-jwt-opaque:${digest16(bearer)}` };
45
+ }
46
+ return { ok: true, kind: "pull-token", bearer, identity: pullTokenIdentity(worker), workerScoped: bearer.startsWith("wpt_") };
47
+ }
48
+ export function pullTokenIdentity(worker) {
49
+ return `pull-token:${worker ?? "global"}`;
50
+ }
51
+ const NONE = { ok: true, kind: "none", identity: "none" };
52
+ export function createCenterCredentialSource(declaration, worker, deps = {}) {
53
+ switch (declaration.from) {
54
+ case "absent":
55
+ return { origin: declaration, declared: false, read: () => NONE, health: () => ({ state: "ok" }) };
56
+ case "env": {
57
+ const fixed = classify(declaration.token, worker);
58
+ return { origin: { from: "env" }, declared: true, read: () => fixed, health: () => ({ state: "ok" }) };
59
+ }
60
+ case "file": {
61
+ const readFile = deps.readFile ?? ((p) => readFileSync(p, "utf8"));
62
+ const now = deps.now ?? Date.now;
63
+ let health = { state: "ok" };
64
+ const fail = (reason) => {
65
+ health = health.state === "unreadable" ? { ...health, reason, failures: health.failures + 1 } : { state: "unreadable", reason, since: now(), failures: 1 };
66
+ return { ok: false, reason };
67
+ };
68
+ return {
69
+ origin: declaration,
70
+ declared: true,
71
+ read() {
72
+ let text;
73
+ try {
74
+ text = readFile(declaration.path);
75
+ }
76
+ catch (err) {
77
+ const code = err instanceof Error && "code" in err && typeof err.code === "string" ? err.code : "read error";
78
+ return fail(`${CENTER_TOKEN_FILE_ENV} ${declaration.path} is unreadable (${code})`);
79
+ }
80
+ const bearer = text.trim();
81
+ if (bearer === "")
82
+ return fail(`${CENTER_TOKEN_FILE_ENV} ${declaration.path} is empty`);
83
+ health = { state: "ok" };
84
+ return classify(bearer, worker);
85
+ },
86
+ health: () => health,
87
+ };
88
+ }
89
+ }
90
+ }
91
+ export function scopedTokenNeedsWorker(read, worker) {
92
+ return read.ok && read.kind === "pull-token" && read.workerScoped && !worker;
93
+ }
94
+ export function centerStateBin(worker, identity) {
95
+ return `${worker === undefined ? "global" : `worker-${digest16(worker)}`}.id-${digest16(identity)}`;
96
+ }
97
+ export function credentialIdentityNow(source) {
98
+ const r = source.read();
99
+ return r.ok ? r.identity : undefined;
100
+ }
101
+ export function answerStillCurrent(source, usedIdentity) {
102
+ return usedIdentity !== undefined && credentialIdentityNow(source) === usedIdentity;
103
+ }
104
+ export function createIdentityBins(source, make) {
105
+ const bins = new Map();
106
+ return () => {
107
+ const identity = credentialIdentityNow(source);
108
+ if (bins.has(identity))
109
+ return bins.get(identity);
110
+ const made = make(pinCredential(source, identity));
111
+ bins.set(identity, made);
112
+ return made;
113
+ };
114
+ }
115
+ function pinCredential(source, identity) {
116
+ return {
117
+ origin: source.origin,
118
+ declared: source.declared,
119
+ read() {
120
+ const r = source.read();
121
+ if (!r.ok)
122
+ return r;
123
+ if (identity === undefined) {
124
+ return { ok: false, reason: "this state bin was opened while the center credential was unreadable (identity unknown) — it never talks to the center; the next call runs in the current identity's bin" };
125
+ }
126
+ return r.identity === identity ? r : { ok: false, reason: "center credential identity changed — this request belongs to the previous identity's state bin and is not sent" };
127
+ },
128
+ health: () => source.health(),
129
+ };
130
+ }
131
+ //# sourceMappingURL=center-credential.js.map
@@ -74,7 +74,7 @@ export const CONFIG_CATALOG = [
74
74
  r("ATTACHMENT_ORPHAN_GRACE_MS", "limitsHttp", "number", "附件孤儿对象 GC 宽限(ms;0=关本腿,坏值回默认+warn)", { staticDefault: "3600000", danger: DATA, center: cLimits("attachmentOrphanGraceMs") }),
75
75
  r("ATTACHMENT_UNBOUND_TTL_MS", "limitsHttp", "number", "未绑定附件的 TTL(ms)", { staticDefault: String(24 * 3600 * 1000), danger: DATA, configKey: "attachmentUnboundTtlMs", center: cLimits("attachmentUnboundTtlMs") }),
76
76
  r("AUTH_BRIDGE_AUDIENCE", "auth", "string", "registry SSO-JWT 验签期望 aud", { danger: SEC }),
77
- r("AUTH_BRIDGE_ISSUER", "auth", "url", "registry SSO-JWT issuer(auth bridge 的开关之一;S-591 起也是托管形判据的一条证据 —— 只认显式值,不认 center 回退)", { danger: SEC, derivedDefaultNote: "缺席 ⇒ 桥的装配处回退到 configCenter.baseUrl(配了 center 即默认开桥);但**托管判据不读那条回退**(fleet 管理轴 ≠ 租户轴)", configKey: "authBridgeIssuer" }),
77
+ r("AUTH_BRIDGE_ISSUER", "auth", "url", "registry SSO-JWT issuer(auth bridge 的开关之一;S-671 / C-R100 ① 起**不是**托管证据 —— 多租户只认 REQUIRE_PRINCIPAL=true)", { danger: SEC, derivedDefaultNote: "缺席 ⇒ 桥的装配处回退到 configCenter.baseUrl(配了 center 即默认开桥);显式值与回退值在托管判定上同判(都不算)", configKey: "authBridgeIssuer" }),
78
78
  r("AUTH_BRIDGE_JWKS_URL", "auth", "url", "registry SSO-JWT 的 JWKS 地址(缺省从 issuer 派生)", { danger: SEC }),
79
79
  r("AUTONOMY", "auth", "enum", "运维 autonomy 模式,tighten-only 叠到每个 TaskSpec(read-only/plan ⇒ 只读手,ask ⇒ 恒 shellGate);center 腿热且 non-sticky", { danger: SEC, enumValues: AUTONOMY_MODES, configKey: "autonomy", center: cGov("autonomy") }),
80
80
  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" }),
@@ -394,7 +394,8 @@ export const CONFIG_CATALOG = [
394
394
  r("SEMA_PIP_INDEX", "orchestration", "url", "SANDBOX_PKG_SOURCE=custom 的显式 pip index 地址"),
395
395
  r("SEMA_REGISTRY_ADMIN_TOKEN", "integrations", "secret", "写 center 的管理凭证(仅 boot 上报执行公钥用;刻意不入 ServiceConfig —— 不进 configHash/日志面)", { danger: SEC, scope: "bootstrap-only" }),
396
396
  r("SEMA_REGISTRY_DRY_RUN", "integrations", "boolean", "center 腿 dry-run(拉而不应用)", { staticDefault: "false" }),
397
- r("SEMA_REGISTRY_TOKEN", "integrations", "secret", "center 拉取凭据", { danger: SEC }),
397
+ r("SEMA_REGISTRY_TOKEN", "integrations", "secret", "center 拉取凭据(值形;与 SEMA_REGISTRY_TOKEN_FILE 二选一,都设=拒启)", { danger: SEC }),
398
+ r("SEMA_REGISTRY_TOKEN_FILE", "integrations", "path", "center 拉取凭据文件(每条中心请求都重读 ⇒ 轮换 / 换账号不重启;与 SEMA_REGISTRY_TOKEN 二选一,都设=拒启;文件不可读=响亮 + 健康态 unreadable,不当作无凭证)", { danger: SEC }),
398
399
  r("SEMA_REGISTRY_URL", "integrations", "url", "sema-registry 地址(设了=remote 配置源开关)"),
399
400
  r("SEMA_REGISTRY_WORKER", "integrations", "string", "本 worker 在 center 的名字(per-worker effective)"),
400
401
  r("SEMA_UV_INDEX", "orchestration", "url", "SANDBOX_PKG_SOURCE=custom 的显式 uv index 地址"),
@@ -0,0 +1,60 @@
1
+ import { type CenterCredentialSource } from "../center-credential.js";
2
+ export declare const SCHEMA_MAJOR_HEADER = "x-sema-schema-major";
3
+ /** 已装 settings-schema 的 major(机器派生)。版本号不成形 ⇒ 模块装载即抛(装坏了的包不该带着一个猜出来的
4
+ * major 去跟中心协商 wire 形)。 */
5
+ export declare const SETTINGS_SCHEMA_MAJOR: string;
6
+ /** 一个中心端点 = 基址 + 凭证来源(`ServiceConfig.configCenter` 结构上满足)。 */
7
+ export interface CenterEndpoint {
8
+ readonly baseUrl: string;
9
+ readonly credential: CenterCredentialSource;
10
+ }
11
+ /** 中心路由闭集。 */
12
+ export type CenterRoute = {
13
+ readonly kind: "effective";
14
+ readonly worker?: string | undefined;
15
+ readonly principal?: string | undefined;
16
+ } | {
17
+ readonly kind: "skill-content";
18
+ readonly contentHash: string;
19
+ } | {
20
+ readonly kind: "prompt-artifact";
21
+ readonly digest: string;
22
+ } | {
23
+ readonly kind: "prompt-blob";
24
+ readonly digest: string;
25
+ } | {
26
+ readonly kind: "fleet-leases";
27
+ } | {
28
+ readonly kind: "fleet-instances";
29
+ } | {
30
+ readonly kind: "fleet-usage";
31
+ } | {
32
+ readonly kind: "execution-keys";
33
+ };
34
+ /**
35
+ * 拉取视图(`/api/config/effective`)查询串的**唯一拼装点**:`?principal=`(per-principal 视图)、`?worker=`
36
+ * (worker 作用域视图;对 `wpt_<self>` 是授权必需,对全量令牌无害)。
37
+ * [ref] E-15:执行体绑 scope 的参数将来加在这里(契约字段 settings-schema 6.0.0;本批不铸)。
38
+ */
39
+ export declare function pullViewQuery(view: {
40
+ readonly worker?: string | undefined;
41
+ readonly principal?: string | undefined;
42
+ }): string;
43
+ export interface CenterRequestInit {
44
+ readonly method?: "GET" | "POST" | "DELETE";
45
+ /** JSON 请求体(在场 ⇒ `content-type: application/json`)。 */
46
+ readonly json?: unknown;
47
+ /** 条件请求(`if-none-match`)。 */
48
+ readonly etag?: string | undefined;
49
+ readonly timeoutMs: number;
50
+ readonly fetchImpl?: typeof fetch;
51
+ }
52
+ /**
53
+ * 对中心发一条请求。返回响应与**这次请求所用凭证的身份**(调用方据此给拿回来的候选打分箱标记,
54
+ * 见 `boot/config-center.ts` 的 LKG / prompt 状态分箱)。
55
+ */
56
+ export declare function centerFetch(center: CenterEndpoint, route: CenterRoute, init: CenterRequestInit): Promise<{
57
+ res: Response;
58
+ identity: string;
59
+ }>;
60
+ //# sourceMappingURL=center-request.d.ts.map
@@ -0,0 +1,71 @@
1
+ import settingsSchemaPackage from "@sema-agent/settings-schema/package.json" with { type: "json" };
2
+ import { CenterCredentialUnavailableError } from "../center-credential.js";
3
+ export const SCHEMA_MAJOR_HEADER = "x-sema-schema-major";
4
+ export const SETTINGS_SCHEMA_MAJOR = (() => {
5
+ const version = settingsSchemaPackage.version;
6
+ const m = /^(\d+)\.\d+\.\d+/.exec(version);
7
+ if (m === null)
8
+ throw new Error(`cannot derive ${SCHEMA_MAJOR_HEADER}: @sema-agent/settings-schema package.json has version ${JSON.stringify(version)}`);
9
+ return m[1];
10
+ })();
11
+ export function pullViewQuery(view) {
12
+ const parts = [];
13
+ if (view.principal !== undefined)
14
+ parts.push(`principal=${encodeURIComponent(view.principal)}`);
15
+ if (view.worker)
16
+ parts.push(`worker=${encodeURIComponent(view.worker)}`);
17
+ return parts.length === 0 ? "" : `?${parts.join("&")}`;
18
+ }
19
+ function contentRoot(kind) {
20
+ switch (kind) {
21
+ case "user-jwt":
22
+ return "/api/v1/me";
23
+ case "pull-token":
24
+ case "none":
25
+ return "/api/config";
26
+ }
27
+ }
28
+ function routePath(route, kind) {
29
+ switch (route.kind) {
30
+ case "effective":
31
+ return `/api/config/effective${pullViewQuery(route)}`;
32
+ case "skill-content":
33
+ return `${contentRoot(kind)}/skills/content/${encodeURIComponent(route.contentHash)}`;
34
+ case "prompt-artifact":
35
+ return `${contentRoot(kind)}/prompts/epoch-artifacts/${encodeURIComponent(route.digest)}`;
36
+ case "prompt-blob":
37
+ return `${contentRoot(kind)}/prompts/blobs/${encodeURIComponent(route.digest)}`;
38
+ case "fleet-leases":
39
+ return "/api/fleet/leases";
40
+ case "fleet-instances":
41
+ return "/api/fleet/instances";
42
+ case "fleet-usage":
43
+ return "/api/fleet/usage";
44
+ case "execution-keys":
45
+ return "/api/config/execution-keys";
46
+ }
47
+ }
48
+ export async function centerFetch(center, route, init) {
49
+ const scheme = new URL(center.baseUrl).protocol;
50
+ if (scheme !== "http:" && scheme !== "https:")
51
+ throw new Error(`SEMA_REGISTRY_URL must be http(s), got "${scheme}"`);
52
+ const cred = center.credential.read();
53
+ if (!cred.ok)
54
+ throw new CenterCredentialUnavailableError(cred.reason);
55
+ const headers = { [SCHEMA_MAJOR_HEADER]: SETTINGS_SCHEMA_MAJOR };
56
+ if (cred.kind !== "none")
57
+ headers.authorization = `Bearer ${cred.bearer}`;
58
+ if (init.json !== undefined)
59
+ headers["content-type"] = "application/json";
60
+ if (init.etag)
61
+ headers["if-none-match"] = init.etag;
62
+ const url = `${center.baseUrl.replace(/\/+$/, "")}${routePath(route, cred.kind)}`;
63
+ const res = await (init.fetchImpl ?? fetch)(url, {
64
+ method: init.method ?? "GET",
65
+ headers,
66
+ ...(init.json !== undefined ? { body: JSON.stringify(init.json) } : {}),
67
+ signal: AbortSignal.timeout(init.timeoutMs),
68
+ });
69
+ return { res, identity: cred.identity };
70
+ }
71
+ //# sourceMappingURL=center-request.js.map
@@ -1,6 +1,7 @@
1
1
  import { type EffectiveReadWarning, type EntitlementRuntimeCaps } from "@sema-agent/settings-schema";
2
2
  import type { ScenarioRuling } from "../capabilities/scenarios.js";
3
3
  import type { EffectiveConfig, ExecutionRuling } from "./types.js";
4
+ import { type CenterEndpoint } from "./center-request.js";
4
5
  /** GET the effective config (Bearer + ETag). null = 304 (unchanged). Throws on transport/HTTP error, and on a
5
6
  * payload that is not an effective config at all(非对象 / version 非有限数 / gate 域坏形——[ref] 裁B)。
6
7
  * `domainErrors`:坏 catalog 域(schema default 已落)逐域单列——与本地腿 `FetchEffectiveResult` 同键同义,
@@ -12,8 +13,11 @@ import type { EffectiveConfig, ExecutionRuling } from "./types.js";
12
13
  * `legacy-governance-lifted` 铸出来,理由逐字写在 `legacyGovernanceFromRuntime` 的 SUNSET 注里:
13
14
  * 「关窗的前提是有人知道它还开着」。两条读腿各返一份原流,那句话在本仓才成立。
14
15
  * 🔐 原流的 `error` 字段**未经** `redactConfigError`(那道脱敏挂在真正进日志流的 `domainErrors` 产出边界上)
15
- * —— 任何把本键路由进结构化日志/审计流的消费者必须自己先脱敏。 */
16
- export declare function fetchEffective(baseUrl: string, token: string, etag: string | undefined, fetchImpl?: typeof fetch, worker?: string): Promise<{
16
+ * —— 任何把本键路由进结构化日志/审计流的消费者必须自己先脱敏。
17
+ *
18
+ * `credentialIdentity`(S-651 / [ref] E-15):这次拉取所用凭证的身份(`center-credential.ts`)。调用方拿它给
19
+ * 候选打分箱标记 —— LKG / prompt 活动状态只许落进**拉到它的那个身份**的箱。 */
20
+ export declare function fetchEffective(center: CenterEndpoint, etag: string | undefined, fetchImpl?: typeof fetch, worker?: string): Promise<{
17
21
  effective: EffectiveConfig;
18
22
  etag?: string;
19
23
  domainErrors?: Array<{
@@ -21,6 +25,7 @@ export declare function fetchEffective(baseUrl: string, token: string, etag: str
21
25
  error: string;
22
26
  }>;
23
27
  readWarnings: readonly EffectiveReadWarning[];
28
+ credentialIdentity: string;
24
29
  } | null>;
25
30
  /**
26
31
  * [ref] §K (core 1.157 `RunnerDeps.runtimeCapsResolver`) — GET the PER-PRINCIPAL runtime
@@ -41,7 +46,7 @@ export declare function fetchEffective(baseUrl: string, token: string, etag: str
41
46
  * fail-closes (core then DENIES the optional `allowWorkflows` cap — an outage must never silently GRANT). ETag is
42
47
  * content-hashed (`p-<hash>`); an `if-none-match` poll is cheap. Mirrors `fetchEffective` (Bearer + 8s + scheme guard).
43
48
  */
44
- export declare function fetchPrincipalCaps(baseUrl: string, token: string, principal: string, etag: string | undefined, fetchImpl?: typeof fetch, worker?: string): Promise<{
49
+ export declare function fetchPrincipalCaps(center: CenterEndpoint, principal: string, etag: string | undefined, fetchImpl?: typeof fetch, worker?: string): Promise<{
45
50
  runtimeCaps: EntitlementRuntimeCaps | null;
46
51
  configured: boolean;
47
52
  scenario?: ScenarioRuling;
@@ -68,7 +73,7 @@ export declare function fetchPrincipalCaps(baseUrl: string, token: string, princ
68
73
  * 段在场 ⇒ 原值(空表 = 取数成功的负结果,与缺席不同格)。条件请求不适用(本腿恒不带 etag),
69
74
  * 因此 304 在传输层就是 fail-loud。
70
75
  */
71
- export declare function fetchPrincipalOrgMemory(baseUrl: string, token: string, principal: string, fetchImpl?: typeof fetch, worker?: string): Promise<unknown>;
76
+ export declare function fetchPrincipalOrgMemory(center: CenterEndpoint, principal: string, fetchImpl?: typeof fetch, worker?: string): Promise<unknown>;
72
77
  /** An HTTP error from a config-center fetch that carries the response status so callers can branch on it
73
78
  * (e.g. translate a 404 to "unknown hash → undefined") WITHOUT parsing the message string. */
74
79
  export declare class ConfigCenterHttpError extends Error {
@@ -76,18 +81,21 @@ export declare class ConfigCenterHttpError extends Error {
76
81
  constructor(message: string, status: number);
77
82
  }
78
83
  /**
79
- * Fetch one skill's body by content hash (`GET /api/config/skills/content/<hash>`, Bearer) and VERIFY its
84
+ * Fetch one skill's body by content hash (`GET <content root>/skills/content/<hash>`, Bearer; the root is
85
+ * `/api/v1/me` for a user-JWT credential and `/api/config` otherwise — center-request.ts) and VERIFY its
80
86
  * sha256 matches the manifest hash (integrity + rewrite check). The content is immutable per hash, so the
81
87
  * caller caches by hash and never re-fetches an unchanged skill. Throws a {@link ConfigCenterHttpError}
82
88
  * (carrying `.status`) on an HTTP error — INCLUDING 404 — or a plain Error on hash mismatch. Callers that
83
89
  * want "unknown hash → undefined" parity with the local backend inspect `.status === 404` and swallow it;
84
90
  * the legacy `applyCenterSkills` path keeps treating every throw as "skip this skill, keep baseline".
85
91
  */
86
- export declare function fetchSkillContent(baseUrl: string, token: string, contentHash: string, fetchImpl?: typeof fetch): Promise<string>;
92
+ export declare function fetchSkillContent(center: CenterEndpoint, contentHash: string, fetchImpl?: typeof fetch): Promise<string>;
87
93
  /** [ref]b / [ref]① — §9.4 by-digest 补拉二函(fetchSkillContent 同姿势:HTTP+Bearer+8s 超时+
88
94
  * 消费侧自证)。artifact=envelope JSON(验真=调用方过 core verifyPromptArtifact,canonical 重算——
89
95
  * 本函数只做传输与形读,不超范围解释);blob=段文本 raw-byte(sha256 逐位自证,fetchSkillContent 同款)。
90
- * digest 参数收带前缀/裸 hex 两形([ref] 参数口径)。 */
91
- export declare function fetchPromptArtifact(baseUrl: string, token: string, artifactDigest: string, fetchImpl?: typeof fetch): Promise<unknown>;
92
- export declare function fetchPromptBlob(baseUrl: string, token: string, contentDigest: string, fetchImpl?: typeof fetch): Promise<string>;
96
+ * digest 参数收带前缀/裸 hex 两形([ref] 参数口径)。路径根按凭证种类(B-5,center-request.ts)。
97
+ * ⚠️ 现状(S-651 取证车MS 总表第 8 条):`fetchPromptBlob` 在 `src/` 里**没有生产调用点**,真走得到的内容
98
+ * 拉取只有 skill 正文与 prompt 产物两条;它与另两条共用同一张路由表,本批不为它新接线。 */
99
+ export declare function fetchPromptArtifact(center: CenterEndpoint, artifactDigest: string, fetchImpl?: typeof fetch): Promise<unknown>;
100
+ export declare function fetchPromptBlob(center: CenterEndpoint, contentDigest: string, fetchImpl?: typeof fetch): Promise<string>;
93
101
  //# sourceMappingURL=http-client.d.ts.map
@@ -2,15 +2,11 @@ import { createHash } from "node:crypto";
2
2
  import { readEffectiveWire, PrincipalCapsWire } from "@sema-agent/settings-schema";
3
3
  import { centerPromptsFromEffective } from "../prompts-domain-validate.js";
4
4
  import { foldEffectiveReadWarning } from "./read-warnings.js";
5
- export async function fetchEffective(baseUrl, token, etag, fetchImpl = fetch, worker) {
6
- const scheme = new URL(baseUrl).protocol;
7
- if (scheme !== "http:" && scheme !== "https:")
8
- throw new Error(`SEMA_REGISTRY_URL must be http(s), got "${scheme}"`);
9
- const url = `${baseUrl.replace(/\/+$/, "")}/api/config/effective${worker ? `?worker=${encodeURIComponent(worker)}` : ""}`;
10
- const res = await fetchImpl(url, {
11
- headers: { authorization: `Bearer ${token}`, ...(etag ? { "if-none-match": etag } : {}) },
12
- signal: AbortSignal.timeout(8000),
13
- });
5
+ import { centerFetch } from "./center-request.js";
6
+ import { answerStillCurrent } from "../center-credential.js";
7
+ const CENTER_PULL_TIMEOUT_MS = 8000;
8
+ export async function fetchEffective(center, etag, fetchImpl = fetch, worker) {
9
+ const { res, identity } = await centerFetch(center, { kind: "effective", worker }, { etag, timeoutMs: CENTER_PULL_TIMEOUT_MS, fetchImpl });
14
10
  if (res.status === 304)
15
11
  return null;
16
12
  if (!res.ok)
@@ -29,10 +25,10 @@ export async function fetchEffective(baseUrl, token, etag, fetchImpl = fetch, wo
29
25
  domainErrors.push(f);
30
26
  }
31
27
  const effective = wire;
32
- return { effective, etag: res.headers.get("etag") ?? undefined, readWarnings: warnings, ...(domainErrors.length > 0 ? { domainErrors } : {}) };
28
+ return { effective, etag: res.headers.get("etag") ?? undefined, readWarnings: warnings, credentialIdentity: identity, ...(domainErrors.length > 0 ? { domainErrors } : {}) };
33
29
  }
34
- export async function fetchPrincipalCaps(baseUrl, token, principal, etag, fetchImpl = fetch, worker) {
35
- const raw = await requestPrincipalView(baseUrl, token, principal, etag, fetchImpl, worker);
30
+ export async function fetchPrincipalCaps(center, principal, etag, fetchImpl = fetch, worker) {
31
+ const raw = await requestPrincipalView(center, principal, etag, fetchImpl, worker);
36
32
  if (raw === null)
37
33
  return null;
38
34
  const parsedBody = PrincipalCapsWire.safeParse(raw.json);
@@ -84,31 +80,29 @@ function assertPrincipalEcho(echoed, principal) {
84
80
  throw new Error(`config-center principal-caps response echoes principal ${JSON.stringify(echoed)} but ${JSON.stringify(principal)} was requested — discarding the whole response`);
85
81
  }
86
82
  }
87
- async function requestPrincipalView(baseUrl, token, principal, etag, fetchImpl, worker) {
88
- const scheme = new URL(baseUrl).protocol;
89
- if (scheme !== "http:" && scheme !== "https:")
90
- throw new Error(`SEMA_REGISTRY_URL must be http(s), got "${scheme}"`);
91
- const params = new URLSearchParams({ principal });
92
- if (worker)
93
- params.set("worker", worker);
94
- const url = `${baseUrl.replace(/\/+$/, "")}/api/config/effective?${params.toString()}`;
95
- const res = await fetchImpl(url, {
96
- headers: { authorization: `Bearer ${token}`, ...(etag ? { "if-none-match": etag } : {}) },
97
- signal: AbortSignal.timeout(8000),
98
- });
83
+ async function requestPrincipalView(center, principal, etag, fetchImpl, worker) {
84
+ const { res, identity } = await centerFetch(center, { kind: "effective", principal, worker }, { etag, timeoutMs: CENTER_PULL_TIMEOUT_MS, fetchImpl });
85
+ const stillCurrent = () => {
86
+ if (!answerStillCurrent(center.credential, identity)) {
87
+ throw new Error("center credential identity changed while the principal view was in flight — response discarded; the next lookup runs under the current credential");
88
+ }
89
+ };
99
90
  if (res.status === 304) {
100
91
  if (!etag)
101
92
  throw new Error("config-center returned 304 to a non-conditional principal-caps request");
93
+ stillCurrent();
102
94
  return null;
103
95
  }
104
96
  if (!res.ok)
105
97
  throw new Error(`config-center principal-caps HTTP ${res.status}`);
106
98
  const responseEtag = res.headers.get("etag");
107
- return { json: await res.json(), ...(responseEtag !== null ? { etag: responseEtag } : {}) };
99
+ const json = await res.json();
100
+ stillCurrent();
101
+ return { json, ...(responseEtag !== null ? { etag: responseEtag } : {}) };
108
102
  }
109
103
  const PrincipalOrgMemoryEnvelope = PrincipalCapsWire.pick({ principal: true, orgMemory: true });
110
- export async function fetchPrincipalOrgMemory(baseUrl, token, principal, fetchImpl = fetch, worker) {
111
- const raw = await requestPrincipalView(baseUrl, token, principal, undefined, fetchImpl, worker);
104
+ export async function fetchPrincipalOrgMemory(center, principal, fetchImpl = fetch, worker) {
105
+ const raw = await requestPrincipalView(center, principal, undefined, fetchImpl, worker);
112
106
  if (raw === null)
113
107
  throw new Error("config-center returned 304 to a non-conditional org-directory fetch");
114
108
  const parsed = PrincipalOrgMemoryEnvelope.safeParse(raw.json);
@@ -126,9 +120,8 @@ export class ConfigCenterHttpError extends Error {
126
120
  this.name = "ConfigCenterHttpError";
127
121
  }
128
122
  }
129
- export async function fetchSkillContent(baseUrl, token, contentHash, fetchImpl = fetch) {
130
- const url = `${baseUrl.replace(/\/+$/, "")}/api/config/skills/content/${encodeURIComponent(contentHash)}`;
131
- const res = await fetchImpl(url, { headers: { authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(8000) });
123
+ export async function fetchSkillContent(center, contentHash, fetchImpl = fetch) {
124
+ const { res } = await centerFetch(center, { kind: "skill-content", contentHash }, { timeoutMs: CENTER_PULL_TIMEOUT_MS, fetchImpl });
132
125
  if (!res.ok)
133
126
  throw new ConfigCenterHttpError(`skill content HTTP ${res.status} for ${contentHash}`, res.status);
134
127
  const content = await res.text();
@@ -138,16 +131,14 @@ export async function fetchSkillContent(baseUrl, token, contentHash, fetchImpl =
138
131
  throw new Error(`skill content hash mismatch for ${contentHash}: computed sha256:${got}`);
139
132
  return content;
140
133
  }
141
- export async function fetchPromptArtifact(baseUrl, token, artifactDigest, fetchImpl = fetch) {
142
- const url = `${baseUrl.replace(/\/+$/, "")}/api/config/prompts/epoch-artifacts/${encodeURIComponent(artifactDigest)}`;
143
- const res = await fetchImpl(url, { headers: { authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(8000) });
134
+ export async function fetchPromptArtifact(center, artifactDigest, fetchImpl = fetch) {
135
+ const { res } = await centerFetch(center, { kind: "prompt-artifact", digest: artifactDigest }, { timeoutMs: CENTER_PULL_TIMEOUT_MS, fetchImpl });
144
136
  if (!res.ok)
145
137
  throw new ConfigCenterHttpError(`prompt artifact HTTP ${res.status} for ${artifactDigest}`, res.status);
146
138
  return await res.json();
147
139
  }
148
- export async function fetchPromptBlob(baseUrl, token, contentDigest, fetchImpl = fetch) {
149
- const url = `${baseUrl.replace(/\/+$/, "")}/api/config/prompts/blobs/${encodeURIComponent(contentDigest)}`;
150
- const res = await fetchImpl(url, { headers: { authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(8000) });
140
+ export async function fetchPromptBlob(center, contentDigest, fetchImpl = fetch) {
141
+ const { res } = await centerFetch(center, { kind: "prompt-blob", digest: contentDigest }, { timeoutMs: CENTER_PULL_TIMEOUT_MS, fetchImpl });
151
142
  if (!res.ok)
152
143
  throw new ConfigCenterHttpError(`prompt blob HTTP ${res.status} for ${contentDigest}`, res.status);
153
144
  const bytes = new Uint8Array(await res.arrayBuffer());
@@ -2,6 +2,7 @@ import type { A2aServerSpec, McpServerSpec } from "@sema-agent/core";
2
2
  import type { CenterMcp, ScopedA2aPeer } from "../config-types.js";
3
3
  import type { Logger } from "../observability/logger.js";
4
4
  import type { LoadedSkill } from "../capabilities/skills.js";
5
+ import type { CenterEndpoint } from "./center-request.js";
5
6
  import type { CenterA2aPeer, CenterMcpServer, CenterSkillManifest } from "./types.js";
6
7
  /**
7
8
  * Merge center skills OVER the image baseline ([ref]-sibling B1; `sema-registry docs/MCP-SKILLS.md`). Load order =
@@ -13,7 +14,9 @@ import type { CenterA2aPeer, CenterMcpServer, CenterSkillManifest } from "./type
13
14
  */
14
15
  export declare function applyCenterSkills(baseline: LoadedSkill[], manifest: {
15
16
  skills: CenterSkillManifest[];
16
- }, baseUrl: string, token: string, logger?: Logger, fetchImpl?: typeof fetch,
17
+ },
18
+ /** S-651:中心端点(基址 + 凭证来源;每次正文拉取取一次值)。 */
19
+ center: CenterEndpoint, logger?: Logger, fetchImpl?: typeof fetch,
17
20
  /** LKG 姊妹面(clay 拍 2026-07-18):skill 正文的 content-addressed 盘缓存目录。读序=内存→盘(验
18
21
  * hash,坏文件忽略)→中心(成功后 best-effort 回写)。让 LKG-boot 在中心不可达时不再于正文拉取上
19
22
  * 挂数秒(live 实测黑洞 +5.3s/skill 批),顺带省正常 refresh 的重复拉取。undefined=无盘缓存(原行为)。 */
@@ -5,7 +5,7 @@ import { CONTENT_ORIGIN_WORDS, describeBadContentOrigin, isContentOriginWord } f
5
5
  import { fetchSkillContent } from "./http-client.js";
6
6
  import { SHA256_HEX_RE } from "../digest-form.js";
7
7
  import { describeBadWireName, isUsableWireName } from "../wire-name-bound.js";
8
- export async function applyCenterSkills(baseline, manifest, baseUrl, token, logger, fetchImpl = fetch, diskCacheDir) {
8
+ export async function applyCenterSkills(baseline, manifest, center, logger, fetchImpl = fetch, diskCacheDir) {
9
9
  const { promises: fsp } = await import("node:fs");
10
10
  const { join: joinPath } = await import("node:path");
11
11
  const diskRead = async (hash) => {
@@ -50,7 +50,7 @@ export async function applyCenterSkills(baseline, manifest, baseUrl, token, logg
50
50
  if (content === undefined)
51
51
  content = await diskRead(m.contentHash);
52
52
  if (content === undefined) {
53
- content = await fetchSkillContent(baseUrl, token, m.contentHash, fetchImpl);
53
+ content = await fetchSkillContent(center, m.contentHash, fetchImpl);
54
54
  diskWrite(m.contentHash, content);
55
55
  }
56
56
  contentCache.set(m.contentHash, content);
@@ -28,9 +28,9 @@ import type { ServiceConfigFlat } from "./config-types.js";
28
28
  */
29
29
  export declare const LIVENESS_HEARTBEAT_MS = 30000;
30
30
  /** 闭集:新增一条不变量必须在这里加词(拼错 = 编译红,而不是运行期悄悄不判)。 */
31
- 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" | "leader-durable-store" | "device-durable-store" | "device-durable-approval" | "model-provider-word" | "gateway-url-userinfo" | "model-route-endpoint" | "require-execution-lane" | "hosted-requires-principal";
31
+ 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" | "leader-durable-store" | "device-durable-store" | "device-durable-approval" | "model-provider-word" | "gateway-url-userinfo" | "model-route-endpoint" | "require-execution-lane";
32
32
  /** 表读的键集(取自平铺配置型 ⇒ 键名/类型永不与真配置漂)。 */
33
- 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" | "authBridgeIssuer">>;
33
+ 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">>;
34
34
  export interface CrossInvariantInput {
35
35
  /** 现役配置(boot 期 = 候选自身)。 */
36
36
  readonly live: CrossInvariantView;
@@ -1,5 +1,4 @@
1
1
  import { assertGuardPatternsUsable } from "./deployment-governance.js";
2
- import { hostedPrincipalRefusal } from "./hosted-posture.js";
3
2
  import { modelProviderRefusal } from "./model-provider.js";
4
3
  import { modelRouteUrlRefusal } from "./model-route-url.js";
5
4
  import { modelRouteEndpointRefusal } from "./model-route-endpoint.js";
@@ -82,13 +81,6 @@ export const CONFIG_CROSS_INVARIANTS = [
82
81
  ? "IMAGE_BAKES_ENABLED with an EMPTY OPERATOR_PRINCIPALS is a world-writable bake door (the runner runs build.sh on the privileged build host)"
83
82
  : undefined,
84
83
  },
85
- {
86
- id: "hosted-requires-principal",
87
- keys: ["requirePrincipal", "principalJwtPubkeys", "principalJwtIss", "principalJwtAud", "authBridgeIssuer"],
88
- restartReason: "hosted-posture",
89
- bootAnchor: { file: "src/config.ts", marker: "CROSS-INVARIANT:hosted-requires-principal" },
90
- check: ({ next }) => hostedPrincipalRefusal(next),
91
- },
92
84
  {
93
85
  id: "sensitive-write-patterns",
94
86
  keys: ["sensitiveWritePatterns"],
@@ -1,8 +1,10 @@
1
1
  /** 落盘信封。`effective`/`etag` 逐字=当时的 fetchEffective 返回(JSON verbatim 面)。 */
2
2
  export interface PersistedLkg {
3
- formatVersion: 1;
3
+ formatVersion: 2;
4
4
  savedAt: string;
5
5
  worker: string;
6
+ /** 拉到这份候选时所用凭证的身份(`center-credential.ts`;摘要形,不含凭证原文)。 */
7
+ identity: string;
6
8
  etag?: string;
7
9
  effective: Record<string, unknown>;
8
10
  }
@@ -10,21 +12,58 @@ export interface PersistedLkg {
10
12
  * hash 另拉)——黑洞中心下 LKG-boot 会在 skill 正文拉取上再挂数秒(live 实测 +5.3s)。正文按
11
13
  * `<dir>/<sha-hex>` 落盘,读侧**必验 hash**(content-addressed 的全部意义;坏文件=忽略重拉)。 */
12
14
  export declare function defaultSkillCacheDir(): string;
13
- /** 默认路径(scheduler store 同门:SEMA_CONFIG_DIR → ~/.sema)。`CONFIG_LKG_PATH` 覆写;
14
- * `CONFIG_LKG_ENABLED=false` 关闭(读写双关)。🪦 正名前的 `CONFIG_LKG_DISABLED` 自 server 3.0.0 起是
15
- * **fail-loud 墓碑**——设了(任何值)即拒启并指路新名,不再被读取。 */
16
- export declare function defaultLkgPath(worker: string | undefined): string;
15
+ /** 默认路径(scheduler store 同门:SEMA_CONFIG_DIR → ~/.sema),按「worker + 凭证身份」分箱。`CONFIG_LKG_PATH`
16
+ * 覆写(单一路径 ⇒ 不分箱,信封的 worker + identity 核仍守读回);`CONFIG_LKG_ENABLED=false` 关闭(读写双关)。
17
+ * 🪦 正名前的 `CONFIG_LKG_DISABLED` 自 server 3.0.0 起是**fail-loud 墓碑**——设了(任何值)即拒启并指路新名,不再被读取。 */
18
+ export declare function defaultLkgPath(worker: string | undefined, identity: string): string;
17
19
  /** 原子落盘一个干净候选(tmp+rename,0600)。BEST-EFFORT:失败=一条 warn 的素材(调用方发),绝不
18
20
  * 影响在飞的应用链——LKG 缓存丢失的代价只是下次冷启动回到 env 兜底。 */
19
- export declare function saveLkg(path: string, worker: string | undefined, effective: Record<string, unknown>, etag: string | undefined): Promise<void>;
21
+ export declare function saveLkg(path: string, worker: string | undefined, identity: string, effective: Record<string, unknown>, etag: string | undefined): Promise<void>;
20
22
  /**
21
23
  * 读盘上 LKG。返回 undefined 的所有情形都「安静地回 env 兜底」(调用方 warn 点名 reason):
22
- * 文件缺席/JSON 坏/信封形不对/worker 不匹配(防同名文件跨 worker 串包)/prompts 面复判失败。
24
+ * 文件缺席/JSON 坏/信封形不对/worker 或凭证身份不匹配(防同名文件跨 worker / 跨账号串包;`CONFIG_LKG_PATH`
25
+ * 单一路径时这是唯一的一道)/prompts 面复判失败。
23
26
  */
24
- export declare function loadLkg(path: string, worker: string | undefined): Promise<{
27
+ export declare function loadLkg(path: string, worker: string | undefined, identity: string): Promise<{
25
28
  lkg: PersistedLkg;
26
29
  } | {
27
30
  lkg?: undefined;
28
31
  reason: string;
29
32
  }>;
33
+ /** 7.98.0 及以前的默认 LKG 路径(按当时的写法逐字保留 —— 这是盘上已有文件的名字,不是现行分箱规则)。 */
34
+ export declare function legacyV1LkgPath(worker: string | undefined): string;
35
+ export type LegacyLkgMigration = {
36
+ readonly outcome: "none";
37
+ }
38
+ /** `retireError`:新箱已写好,只是旧文件没退役成(改名 / 留副本失败)—— 只响一行,不影响已写好的箱。 */
39
+ | {
40
+ readonly outcome: "migrated";
41
+ readonly from: string;
42
+ readonly retiredTo: string;
43
+ readonly retireError?: string;
44
+ } | {
45
+ readonly outcome: "not-migrated";
46
+ readonly path: string;
47
+ readonly reason: string;
48
+ };
49
+ /**
50
+ * formatVersion 1 LKG 的一次性迁移(规则一句:**身份推得出来才迁,推不出来就响亮拒**)。判序:
51
+ * 1. `legacyPath` ≠ `binPath` 时**先看本身份的箱**:箱里已有文件 ⇒ `none`(箱自己说话,旧文件是好是坏都不再相关,
52
+ * 不许一个坏旧文件把有效的箱挡成 env 兜底 —— 7.99.0 修复验证 N4)。箱查不了(非 ENOENT)⇒ 响亮拒。
53
+ * 2. 旧文件没有(ENOENT / ENOTDIR)或不是 formatVersion 1 信封 ⇒ `none`。旧默认路径上却读不了 / 不是 JSON ⇒
54
+ * `not-migrated`(响亮,不当它不存在);`CONFIG_LKG_PATH` 同路径时这两形由 loadLkg 自己报(不重复打一行)。
55
+ * 3. v1 信封只记 worker:唯一推得出身份的形是拉取令牌(身份 = worker,`pullTokenIdentity`)。此刻身份正是它、worker
56
+ * 对得上、载荷过 {@link lkgPayloadProblem} ⇒ **先写后退役**(修复验证 N9):先按新格式把它写进 `binPath`(原子写,
57
+ * 0600),写成了才把旧文件改名 `<legacyPath>.migrated-v1`。写失败 ⇒ `not-migrated`,旧文件原样留在原位,下一次
58
+ * 照常可迁;写成之后改名失败 ⇒ 仍是 `migrated`(`retireError` 响一行),箱已在 ⇒ 下一次按判序 1 不再看旧文件。
59
+ * 同路径(`CONFIG_LKG_PATH`)时新箱的写入会原子替换旧文件本身,所以旧字节在写之前留一份 `.migrated-v1` 副本
60
+ * (副本失败同样只进 `retireError`:载荷正要写进新箱,不是丢数据)。
61
+ * 4. 其余一律 `not-migrated`:拒因点名旧文件路径与出路(删掉它;中心下一份干净候选会写本身份自己的 LKG),不改名、不读回。
62
+ */
63
+ export declare function migrateV1Lkg(o: {
64
+ legacyPath: string;
65
+ binPath: string;
66
+ worker: string | undefined;
67
+ identity: string;
68
+ }): Promise<LegacyLkgMigration>;
30
69
  //# sourceMappingURL=config-lkg.d.ts.map