@sema-agent/server 7.98.0 → 7.100.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 +19 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +86 -60
- package/deploy/sema-up/lib/envf.sh +110 -42
- package/deploy/sema-up/sema-up.sh +76 -34
- package/dist/approval-ask-machine.d.ts +10 -0
- package/dist/approval-ask-machine.js +3 -0
- package/dist/approval-card.d.ts +74 -1
- package/dist/approval-card.js +41 -8
- package/dist/approval.d.ts +44 -20
- package/dist/approval.js +14 -6
- package/dist/boot/config-center.d.ts +16 -8
- package/dist/boot/config-center.js +206 -103
- package/dist/boot/execution-env.d.ts +1 -2
- package/dist/boot/execution-env.js +2 -2
- package/dist/boot/leader.js +12 -9
- package/dist/boot/memory-consolidation.d.ts +38 -0
- package/dist/boot/memory-consolidation.js +35 -0
- package/dist/boot/org-memory.d.ts +2 -1
- package/dist/boot/org-memory.js +9 -5
- package/dist/boot/resolve-spec.d.ts +0 -1
- package/dist/boot/resolve-spec.js +4 -3
- package/dist/boot/runner-deps.d.ts +2 -2
- package/dist/boot/runtime-caps.js +4 -4
- package/dist/boot/stage-05-execution-env.d.ts +1 -1
- package/dist/boot/stage-06-runners.d.ts +2 -2
- package/dist/boot/stage-06-runners.js +3 -3
- package/dist/boot/stage-07-capability-layer.d.ts +2 -3
- package/dist/boot/stage-07-capability-layer.js +6 -6
- package/dist/boot/stage-08-reapers.d.ts +3 -4
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +2 -3
- package/dist/boot/stage-10-http-server.d.ts +2 -3
- package/dist/boot/stage-10-http-server.js +2 -2
- package/dist/capabilities/center-plugins.d.ts +14 -2
- package/dist/capabilities/center-plugins.js +13 -5
- package/dist/capabilities/center-prompts.d.ts +3 -0
- package/dist/capabilities/center-prompts.js +1 -1
- package/dist/capabilities/hands-lane.js +2 -1
- package/dist/capabilities/scenarios.js +18 -5
- package/dist/capabilities/tool-defer.d.ts +2 -2
- package/dist/center-credential.d.ts +112 -0
- package/dist/center-credential.js +131 -0
- package/dist/config-catalog.d.ts +2 -2
- package/dist/config-catalog.js +15 -15
- package/dist/config-center/apply-effective.d.ts +34 -39
- package/dist/config-center/apply-effective.js +100 -64
- package/dist/config-center/apply-ledger.d.ts +35 -16
- package/dist/config-center/apply-ledger.js +25 -18
- package/dist/config-center/center-request.d.ts +60 -0
- package/dist/config-center/center-request.js +71 -0
- package/dist/config-center/facade.d.ts +11 -5
- package/dist/config-center/facade.js +1 -1
- package/dist/config-center/hot-keys-registry.d.ts +11 -4
- package/dist/config-center/hot-keys-registry.js +11 -7
- package/dist/config-center/http-client.d.ts +17 -9
- package/dist/config-center/http-client.js +27 -36
- package/dist/config-center/restart-signal.d.ts +21 -14
- package/dist/config-center/restart-signal.js +16 -22
- package/dist/config-center/skills-mcp.d.ts +4 -1
- package/dist/config-center/skills-mcp.js +2 -2
- package/dist/config-center/types.d.ts +3 -3
- package/dist/config-invariants.d.ts +2 -2
- package/dist/config-invariants.js +0 -8
- package/dist/config-lkg.d.ts +47 -8
- package/dist/config-lkg.js +107 -20
- package/dist/config-provider.d.ts +8 -7
- package/dist/config-provider.js +5 -5
- package/dist/config-types.d.ts +30 -11
- package/dist/config.js +11 -13
- package/dist/cross-session-settings.js +1 -1
- package/dist/fleet-client.d.ts +8 -8
- package/dist/fleet-client.js +7 -7
- package/dist/fleet-lease.d.ts +11 -11
- package/dist/fleet-lease.js +17 -13
- package/dist/host-lsp-manager.d.ts +29 -0
- package/dist/host-lsp-manager.js +14 -0
- package/dist/hosted-posture.d.ts +49 -93
- package/dist/hosted-posture.js +1 -25
- package/dist/http/active-run-conflict.d.ts +2 -12
- package/dist/http/active-run-conflict.js +1 -6
- package/dist/http/admission.js +8 -0
- package/dist/http/resume-legs.d.ts +1 -1
- package/dist/http/resume-legs.js +14 -10
- package/dist/http/route-ctx.d.ts +2 -2
- package/dist/http/routes/approvals-assistant.d.ts +1 -1
- package/dist/http/routes/approvals-assistant.js +2 -2
- package/dist/http/routes/memory-compliance.js +5 -0
- package/dist/http/routes/memory-origin.js +5 -0
- package/dist/http/routes/sessions.js +6 -0
- package/dist/http/routes/tasks.js +7 -6
- package/dist/http/server.d.ts +6 -5
- package/dist/http/wire-types.d.ts +6 -3
- package/dist/leader/wire.d.ts +20 -12
- package/dist/leader/wire.js +7 -6
- package/dist/memory-layer-preflight.d.ts +46 -0
- package/dist/memory-layer-preflight.js +121 -0
- package/dist/memory-operator-faces.js +17 -1
- package/dist/observability/corrupt-read-seat.d.ts +21 -4
- package/dist/observability/corrupt-read-seat.js +31 -0
- package/dist/observability/fail-open.d.ts +8 -4
- package/dist/observability/fail-open.js +8 -4
- package/dist/observability/run-terminal.js +1 -0
- package/dist/observability/secret-env-scrub.d.ts +3 -26
- package/dist/observability/secret-env-scrub.js +3 -2
- package/dist/orchestration/workflow-completion-inbox.d.ts +31 -23
- package/dist/orchestration/workflow-completion-inbox.js +8 -3
- package/dist/plugins/approval-ask-store-file.d.ts +20 -2
- package/dist/plugins/approval-ask-store-file.js +21 -3
- package/dist/plugins/approval-ask-store-memory.d.ts +2 -0
- package/dist/plugins/approval-ask-store-memory.js +39 -13
- package/dist/plugins/approval-ask-store-sql.d.ts +24 -5
- package/dist/plugins/approval-ask-store-sql.js +27 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +9 -3
- package/dist/plugins/checkpoint-store-sql.js +19 -2
- package/dist/plugins/local-checkpoint-store.js +2 -0
- package/dist/plugins/mailbox-store-sql.d.ts +7 -5
- package/dist/plugins/mailbox-store-sql.js +6 -4
- package/dist/plugins/permission-rule-store-file.js +3 -2
- package/dist/plugins/remote-env-host.d.ts +3 -1
- package/dist/plugins/remote-env-host.js +3 -2
- package/dist/plugins/sql-json-column.d.ts +16 -3
- package/dist/plugins/sql-json-column.js +15 -4
- package/dist/plugins/web-search.d.ts +42 -22
- package/dist/plugins/web-search.js +141 -49
- package/dist/project-memory.d.ts +28 -1
- package/dist/project-memory.js +22 -22
- package/dist/run-local.js +6 -7
- package/dist/runs.js +6 -5
- package/dist/runtime-caps-resolver.d.ts +8 -8
- package/dist/runtime-caps-resolver.js +16 -14
- package/dist/runtime-governance.d.ts +50 -3
- package/dist/runtime-governance.js +5 -0
- package/dist/sealed-key.js +3 -7
- package/dist/server-secret-env.d.ts +43 -0
- package/dist/server-secret-env.js +18 -0
- package/dist/task-settings.d.ts +1 -1
- package/dist/tool-approval.d.ts +144 -9
- package/dist/tool-approval.js +74 -18
- package/dist/trace/core-keyset-guard.d.ts +5 -5
- package/dist/trace/ledger-sink.js +2 -2
- package/dist/trace/project.d.ts +13 -0
- package/dist/trace/project.js +22 -5
- package/dist/trace/projection-drop.d.ts +2 -0
- package/dist/trace/projection-drop.js +6 -0
- package/dist/trace/sema-provenance.d.ts +4 -1
- package/dist/trace/sema-provenance.js +1 -1
- package/dist/trace/task-notification-facets.d.ts +65 -0
- package/dist/trace/task-notification-facets.js +31 -0
- package/dist/trace/wire-projection-faces.d.ts +3 -3
- package/dist/trace/wire-projection-faces.js +3 -3
- package/package.json +5 -4
package/dist/config-lkg.js
CHANGED
|
@@ -3,22 +3,22 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { join, dirname } from "node:path";
|
|
4
4
|
import { randomBytes } from "node:crypto";
|
|
5
5
|
import { validatePromptsDomain } from "./prompts-domain-validate.js";
|
|
6
|
+
import { centerStateBin, pullTokenIdentity } from "./center-credential.js";
|
|
6
7
|
export function defaultSkillCacheDir() {
|
|
7
8
|
return join(process.env.SEMA_CONFIG_DIR ?? join(homedir(), ".sema"), "skill-cache");
|
|
8
9
|
}
|
|
9
|
-
export function defaultLkgPath(worker) {
|
|
10
|
+
export function defaultLkgPath(worker, identity) {
|
|
10
11
|
const dir = process.env.SEMA_CONFIG_DIR ?? join(homedir(), ".sema");
|
|
11
|
-
|
|
12
|
-
return join(dir, `config-lkg-${key}.json`);
|
|
12
|
+
return join(dir, `config-lkg-${centerStateBin(worker, identity)}.json`);
|
|
13
13
|
}
|
|
14
|
-
export async function saveLkg(path, worker, effective, etag) {
|
|
15
|
-
const body = { formatVersion:
|
|
14
|
+
export async function saveLkg(path, worker, identity, effective, etag) {
|
|
15
|
+
const body = { formatVersion: 2, savedAt: new Date().toISOString(), worker: worker ?? "global", identity, ...(etag !== undefined ? { etag } : {}), effective };
|
|
16
16
|
const tmp = `${path}.${randomBytes(6).toString("hex")}.tmp`;
|
|
17
17
|
await fs.mkdir(dirname(path), { recursive: true });
|
|
18
18
|
await fs.writeFile(tmp, JSON.stringify(body), { mode: 0o600 });
|
|
19
19
|
await fs.rename(tmp, path);
|
|
20
20
|
}
|
|
21
|
-
export async function loadLkg(path, worker) {
|
|
21
|
+
export async function loadLkg(path, worker, identity) {
|
|
22
22
|
let text;
|
|
23
23
|
try {
|
|
24
24
|
text = await fs.readFile(path, "utf8");
|
|
@@ -36,38 +36,125 @@ export async function loadLkg(path, worker) {
|
|
|
36
36
|
if (parsed === null || typeof parsed !== "object")
|
|
37
37
|
return { reason: "bad envelope" };
|
|
38
38
|
const p = parsed;
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
const formatVersion = p.formatVersion;
|
|
40
|
+
if (formatVersion === 1)
|
|
41
|
+
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" };
|
|
42
|
+
if (formatVersion !== 2)
|
|
43
|
+
return { reason: `unknown formatVersion ${String(formatVersion)}` };
|
|
41
44
|
if (typeof p.worker !== "string" || p.worker !== (worker ?? "global"))
|
|
42
45
|
return { reason: `worker mismatch (file=${String(p.worker)})` };
|
|
43
|
-
if (p.
|
|
44
|
-
return { reason: "
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
if (typeof p.identity !== "string" || p.identity !== identity)
|
|
47
|
+
return { reason: "credential identity mismatch (the LKG was fetched under another credential identity — not read back)" };
|
|
48
|
+
const problem = lkgPayloadProblem(p.effective, p.etag);
|
|
49
|
+
return problem === undefined ? { lkg: p } : { reason: problem };
|
|
50
|
+
}
|
|
51
|
+
function lkgPayloadProblem(effective, etag) {
|
|
52
|
+
if (effective === null || typeof effective !== "object")
|
|
53
|
+
return "bad effective";
|
|
54
|
+
if (etag !== undefined && typeof etag !== "string")
|
|
55
|
+
return "bad etag";
|
|
56
|
+
const eff = effective;
|
|
48
57
|
if (typeof eff.version !== "number")
|
|
49
|
-
return
|
|
58
|
+
return "bad effective.version";
|
|
50
59
|
for (const [domain, arrayKey] of [["models", "models"], ["skills", "skills"], ["mcp", "servers"], ["scenarios", "scenarios"], ["runtime", null]]) {
|
|
51
60
|
const v = eff[domain];
|
|
52
61
|
if (v === undefined)
|
|
53
62
|
continue;
|
|
54
63
|
if (v === null || typeof v !== "object" || Array.isArray(v))
|
|
55
|
-
return
|
|
64
|
+
return `bad effective.${domain}`;
|
|
56
65
|
if (arrayKey !== null) {
|
|
57
66
|
const arr = v[arrayKey];
|
|
58
67
|
if (arr !== undefined && !Array.isArray(arr))
|
|
59
|
-
return
|
|
68
|
+
return `bad effective.${domain}.${arrayKey}`;
|
|
60
69
|
}
|
|
61
70
|
}
|
|
62
71
|
{
|
|
63
72
|
const rt = eff.runtime;
|
|
64
73
|
if (rt && (rt.autonomy !== undefined || rt.commandPolicy !== undefined || rt.approvalRequire !== undefined)) {
|
|
65
|
-
return
|
|
74
|
+
return "legacy runtime governance keys (pre-0.7.1 mirror shape; no contract producer since LKG shipped) — refusing rather than silently dropping governance";
|
|
66
75
|
}
|
|
67
76
|
}
|
|
68
|
-
const promptsRaw =
|
|
77
|
+
const promptsRaw = eff.prompts;
|
|
69
78
|
if (promptsRaw !== undefined && !validatePromptsDomain(promptsRaw).ok)
|
|
70
|
-
return
|
|
71
|
-
return
|
|
79
|
+
return "prompts face fails re-validation";
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
export function legacyV1LkgPath(worker) {
|
|
83
|
+
const dir = process.env.SEMA_CONFIG_DIR ?? join(homedir(), ".sema");
|
|
84
|
+
const key = (worker ?? "global").replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 64);
|
|
85
|
+
return join(dir, `config-lkg-${key}.json`);
|
|
86
|
+
}
|
|
87
|
+
export async function migrateV1Lkg(o) {
|
|
88
|
+
const refuse = (why) => ({
|
|
89
|
+
outcome: "not-migrated",
|
|
90
|
+
path: o.legacyPath,
|
|
91
|
+
reason: `pre-identity LKG (formatVersion 1) at ${o.legacyPath} not migrated: ${why}. Way out: delete that file — the next clean candidate from the center writes this credential identity's own LKG (${o.binPath})`,
|
|
92
|
+
});
|
|
93
|
+
const sameFile = o.legacyPath === o.binPath;
|
|
94
|
+
if (!sameFile) {
|
|
95
|
+
try {
|
|
96
|
+
await fs.access(o.binPath);
|
|
97
|
+
return { outcome: "none" };
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
const code = errnoOf(err);
|
|
101
|
+
if (code !== "ENOENT")
|
|
102
|
+
return refuse(`this credential identity's bin ${o.binPath} could not be checked (${code})`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
let text;
|
|
106
|
+
try {
|
|
107
|
+
text = await fs.readFile(o.legacyPath, "utf8");
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
const code = errnoOf(err);
|
|
111
|
+
return code === "ENOENT" || code === "ENOTDIR" || sameFile ? { outcome: "none" } : refuse(`it could not be read (${code})`);
|
|
112
|
+
}
|
|
113
|
+
let parsed;
|
|
114
|
+
try {
|
|
115
|
+
parsed = JSON.parse(text);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
return sameFile ? { outcome: "none" } : refuse(`it is not valid JSON (${err instanceof Error ? err.name : "parse error"})`);
|
|
119
|
+
}
|
|
120
|
+
if (parsed === null || typeof parsed !== "object" || !("formatVersion" in parsed) || parsed.formatVersion !== 1)
|
|
121
|
+
return { outcome: "none" };
|
|
122
|
+
const v1 = parsed;
|
|
123
|
+
if (v1.worker !== (o.worker ?? "global"))
|
|
124
|
+
return refuse(`it belongs to worker ${JSON.stringify(v1.worker)}`);
|
|
125
|
+
if (o.identity !== pullTokenIdentity(o.worker)) {
|
|
126
|
+
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");
|
|
127
|
+
}
|
|
128
|
+
const problem = lkgPayloadProblem(v1.effective, v1.etag);
|
|
129
|
+
if (problem !== undefined)
|
|
130
|
+
return refuse(`its payload is unusable (${problem})`);
|
|
131
|
+
const retiredTo = `${o.legacyPath}.migrated-v1`;
|
|
132
|
+
let retireError;
|
|
133
|
+
if (sameFile) {
|
|
134
|
+
try {
|
|
135
|
+
await fs.copyFile(o.legacyPath, retiredTo);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
retireError = `the formatVersion 1 bytes could not be kept as ${retiredTo} (${errnoOf(err)})`;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
await saveLkg(o.binPath, o.worker, o.identity, v1.effective, typeof v1.etag === "string" ? v1.etag : undefined);
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
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`);
|
|
146
|
+
}
|
|
147
|
+
if (!sameFile) {
|
|
148
|
+
try {
|
|
149
|
+
await fs.rename(o.legacyPath, retiredTo);
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
retireError = `the old file could not be renamed to ${retiredTo} (${errnoOf(err)}) — harmless: the bin now exists, so the old file is never read again`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { outcome: "migrated", from: o.legacyPath, retiredTo, ...(retireError !== undefined ? { retireError } : {}) };
|
|
156
|
+
}
|
|
157
|
+
function errnoOf(err) {
|
|
158
|
+
return err instanceof Error && "code" in err && typeof err.code === "string" ? err.code : "unknown";
|
|
72
159
|
}
|
|
73
160
|
//# sourceMappingURL=config-lkg.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FileConfigStore } from "@sema-agent/settings-schema/node";
|
|
2
2
|
import { type EffectiveConfig as AgentConfigEffective, type EffectiveReadWarning } from "@sema-agent/settings-schema";
|
|
3
3
|
import { fetchEffective as remoteFetchEffective, fetchSkillContent as remoteFetchSkillContent, type EffectiveConfig } from "./config-center/facade.js";
|
|
4
|
+
import type { CenterEndpoint } from "./config-center/center-request.js";
|
|
4
5
|
/** Result of a `fetchEffective` — EXACTLY the config-center's return: the effective config + its etag, or
|
|
5
6
|
* `null` for "unchanged" (remote 304; local: the version matched the caller's prior etag).
|
|
6
7
|
* `domainErrors`([ref] registry-core 0.10.12 tolerant seam,local lane only):catalog 域坏文件不再
|
|
@@ -17,6 +18,8 @@ import { fetchEffective as remoteFetchEffective, fetchSkillContent as remoteFetc
|
|
|
17
18
|
* ——缺席/空两形并存就是又一处二义。
|
|
18
19
|
* 🔐 原流的 `error` 未经 {@link redactConfigError}(那道脱敏挂在真正进日志流的 `domainErrors` 产出边界上),
|
|
19
20
|
* 路由进日志/审计流的消费者必须自己先脱敏。 */
|
|
21
|
+
/** `credentialIdentity`(S-651 / [ref] E-15):远程腿恒在场 = 这次拉取所用凭证的身份(分箱键来源);本地腿
|
|
22
|
+
* 缺席(没有凭证 —— config.d 就在本机盘上)。 */
|
|
20
23
|
export type FetchEffectiveResult = {
|
|
21
24
|
effective: EffectiveConfig;
|
|
22
25
|
etag?: string;
|
|
@@ -25,6 +28,7 @@ export type FetchEffectiveResult = {
|
|
|
25
28
|
error: string;
|
|
26
29
|
}>;
|
|
27
30
|
readWarnings: readonly EffectiveReadWarning[];
|
|
31
|
+
credentialIdentity?: string;
|
|
28
32
|
} | null;
|
|
29
33
|
/** Sentinel returned by {@link raceBootFetch} when the boot pull outlived its budget — the caller
|
|
30
34
|
* starts serving on the env fallback and the SAME promise settles in the background (never cancelled). */
|
|
@@ -67,9 +71,7 @@ export interface ConfigProviderInput {
|
|
|
67
71
|
/** Set when SEMA_REGISTRY_URL (legacy CONFIG_CENTER_URL now boot-rejects, no dual-read) is configured (config.ts `configCenter`). `dryRun` is carried through so a
|
|
68
72
|
* remote↔local swap via this seam does NOT drop the dry-run posture main.ts branches on (config.ts
|
|
69
73
|
* `configCenter.dryRun`); the provider itself does not consume it (the caller still branches on it). */
|
|
70
|
-
configCenter?: {
|
|
71
|
-
baseUrl: string;
|
|
72
|
-
token: string;
|
|
74
|
+
configCenter?: CenterEndpoint & {
|
|
73
75
|
worker?: string;
|
|
74
76
|
dryRun?: boolean;
|
|
75
77
|
};
|
|
@@ -84,16 +86,15 @@ export interface ConfigProviderInput {
|
|
|
84
86
|
}
|
|
85
87
|
/**
|
|
86
88
|
* RemoteConfigProvider — DELEGATES to the exported config-center HTTP functions. No HTTP is reimplemented;
|
|
87
|
-
* baseUrl
|
|
89
|
+
* the center endpoint (baseUrl + credential SOURCE — S-651: read per request, never a bound token) and worker
|
|
90
|
+
* are passed through. The two delegate fns are injectable (defaulting
|
|
88
91
|
* to the real config-center exports) so a unit test can assert delegation without module-mocking.
|
|
89
92
|
*/
|
|
90
93
|
export declare class RemoteConfigProvider implements ConfigProvider {
|
|
91
94
|
private readonly cc;
|
|
92
95
|
private readonly deps;
|
|
93
96
|
readonly kind: "remote";
|
|
94
|
-
constructor(cc: {
|
|
95
|
-
baseUrl: string;
|
|
96
|
-
token: string;
|
|
97
|
+
constructor(cc: CenterEndpoint & {
|
|
97
98
|
worker?: string;
|
|
98
99
|
}, deps?: {
|
|
99
100
|
fetchEffective?: typeof remoteFetchEffective;
|
package/dist/config-provider.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readMcpSourceLabel } from "./mcp-source-label.js";
|
|
|
2
2
|
import { createHmac, randomBytes } from "node:crypto";
|
|
3
3
|
import { foldEffectiveReadWarning } from "./config-center/read-warnings.js";
|
|
4
4
|
import { FileConfigStore } from "@sema-agent/settings-schema/node";
|
|
5
|
-
import { findSkillContent, skillContentHash, refIntegrityIssues, createSiblingResolver, } from "@sema-agent/settings-schema";
|
|
5
|
+
import { findSkillContent, skillContentHash, refIntegrityIssues, createSiblingResolver, formatConfigPath, REF_CHECKED_DOMAINS, } from "@sema-agent/settings-schema";
|
|
6
6
|
import { redactSecrets } from "./trace/redact.js";
|
|
7
7
|
import { fetchEffective as remoteFetchEffective, fetchSkillContent as remoteFetchSkillContent, ConfigCenterHttpError, } from "./config-center/facade.js";
|
|
8
8
|
import { createLogger } from "./observability/logger.js";
|
|
@@ -32,7 +32,7 @@ export class RemoteConfigProvider {
|
|
|
32
32
|
}
|
|
33
33
|
async fetchEffective(etag) {
|
|
34
34
|
const fn = this.deps.fetchEffective ?? remoteFetchEffective;
|
|
35
|
-
const r = await fn(this.cc
|
|
35
|
+
const r = await fn(this.cc, etag, undefined, this.cc.worker);
|
|
36
36
|
if (r === null || r.domainErrors === undefined)
|
|
37
37
|
return r;
|
|
38
38
|
return { ...r, domainErrors: r.domainErrors.map((de) => ({ domain: de.domain, error: redactConfigError(de.error) })) };
|
|
@@ -40,7 +40,7 @@ export class RemoteConfigProvider {
|
|
|
40
40
|
async fetchSkillContent(contentHash) {
|
|
41
41
|
const fn = this.deps.fetchSkillContent ?? remoteFetchSkillContent;
|
|
42
42
|
try {
|
|
43
|
-
return await fn(this.cc
|
|
43
|
+
return await fn(this.cc, contentHash);
|
|
44
44
|
}
|
|
45
45
|
catch (err) {
|
|
46
46
|
if (err instanceof ConfigCenterHttpError && err.status === 404)
|
|
@@ -77,10 +77,10 @@ export class LocalConfigProvider {
|
|
|
77
77
|
return null;
|
|
78
78
|
const refErrors = [];
|
|
79
79
|
const resolve = createSiblingResolver(store, eff);
|
|
80
|
-
for (const d of
|
|
80
|
+
for (const d of REF_CHECKED_DOMAINS) {
|
|
81
81
|
const issues = await refIntegrityIssues(d, eff[d], resolve);
|
|
82
82
|
if (issues.length > 0)
|
|
83
|
-
refErrors.push({ domain: d, error: issues.map((i) => `${i.path
|
|
83
|
+
refErrors.push({ domain: d, error: issues.map((i) => `${formatConfigPath(i.path)}: ${redactConfigError(i.message)}`).join("; ") });
|
|
84
84
|
}
|
|
85
85
|
const parseErrors = parseWarnings.map((w) => ({ domain: w.domain, error: redactConfigError(w.error) }));
|
|
86
86
|
const errors = [...domainErrors.map((de) => ({ domain: de.domain, error: redactConfigError(de.error) })), ...parseErrors, ...refErrors];
|
package/dist/config-types.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ import type { QuestionThrottle } from "./question.js";
|
|
|
15
15
|
import type { UnattendedApprovalPolicy } from "./tool-approval.js";
|
|
16
16
|
import type { InfraCostRates } from "./observability/cost-taxonomy.js";
|
|
17
17
|
import type { Autonomy, CommandRule } from "./runtime-governance.js";
|
|
18
|
+
import type { CenterCredentialSource } from "./center-credential.js";
|
|
18
19
|
import type { GitApiKind } from "./git-api-kind.js";
|
|
19
20
|
/** [ref]:记忆向量面的 embedder 坐标(env `MEMORY_EMBEDDER_*` 解析结果;单一属主 = 本类型,
|
|
20
21
|
* `plugins/memory-embedder.ts` 的工厂直接消费它,不复制形状)。语义与三问见
|
|
@@ -471,8 +472,9 @@ export interface ServiceConfigFlat {
|
|
|
471
472
|
/** registry-core 0.9.0 档位组: the resolved active tier table (`tier word → catalog model name`,
|
|
472
473
|
* e.g. `{pro: "ds-pro"}`) fed to `RunnerDeps.tiers` (core 1.265 expandTiers — tier words + CC aliases become
|
|
473
474
|
* catalog keys). NO env source — populated ONLY by applyEffective from `models.tierGroups`/`activeTierGroup`
|
|
474
|
-
* via registry-core `resolveActiveTiers` (single semantic source). Empty = INERT (core contract).
|
|
475
|
-
* Runner
|
|
475
|
+
* via registry-core `resolveActiveTiers` (single semantic source). Empty = INERT (core contract). Expanded at
|
|
476
|
+
* Runner construction and re-expanded on every refresh-time plane swap (`Runner.swapDeps`) — HOT, same as
|
|
477
|
+
* models/roles; mutateInPlace keeps the reference. */
|
|
476
478
|
tiers: Record<string, string>;
|
|
477
479
|
/** 142-S4(registry-core 0.10.0 projects 域):center 项目登记簿的 service 消费面 —
|
|
478
480
|
* 键=projectId(PROJECT_ID_REGEX,generic lowercase UUID)。消费点:①`memoryScopeFor` 第三参数
|
|
@@ -1240,8 +1242,8 @@ export interface ServiceConfigFlat {
|
|
|
1240
1242
|
principalJwtAud?: string;
|
|
1241
1243
|
/** S-591:registry SSO-JWT 的**显式** issuer(`AUTH_BRIDGE_ISSUER`)。
|
|
1242
1244
|
* 🔴 **只是显式 env,不含 boot 期那条 `?? configCenter.baseUrl` 回退**(回退仍在
|
|
1243
|
-
* `boot/stage-09-leader.ts` 的桥装配处)——
|
|
1244
|
-
*
|
|
1245
|
+
* `boot/stage-09-leader.ts` 的桥装配处)。唯一消费方是桥;S-671([ref] ①)起它**不是**托管证据 ——
|
|
1246
|
+
* 显式与回退 issuer 在租户轴上同判(理由见 `hosted-posture.ts`)。 */
|
|
1245
1247
|
authBridgeIssuer?: string;
|
|
1246
1248
|
/** Max accepted principal-JWT lifetime (exp − now), seconds — caps the replay window past the short-lived mandate.
|
|
1247
1249
|
* 0 = no cap. `PRINCIPAL_JWT_MAX_TTL_SEC` (default 120). */
|
|
@@ -1737,6 +1739,12 @@ export interface ServiceConfigFlat {
|
|
|
1737
1739
|
* 位:与「查无活流」同一件事实的两个时刻,详见 `askBroadcast` 的该臂注))。`"timeout"` 宿主自报只留在 (b)(c) ——
|
|
1738
1740
|
* 结束等待的确实是本仓的窗;其余三臂结束等待的是容量/连接/装配,自报恒缺席。
|
|
1739
1741
|
*
|
|
1742
|
+
* 🔴 **`deny` 交给引擎的形(S-689)**:部署政策拒对象臂 `{allow:false, settledBy:"policy", reason}` ⇒ 引擎结算
|
|
1743
|
+
* `policy_refused`(`who.party:"none"`)、模型读策略句(「部署政策拒了、此处没有交互审批,别重试、做不需审批的
|
|
1744
|
+
* 部分」)、run 不以「用户拒绝」收束而是继续。修前是裸 `false` —— 引擎契约里那就是「一个人拒了」
|
|
1745
|
+
* (`human_refused` / `haltedOnUserRejection`),与本键「没有人会来」的自声明正好相反。(b)(c) 两臂照旧带
|
|
1746
|
+
* `"timeout"` 自报(`approval_window_expired`)。
|
|
1747
|
+
*
|
|
1740
1748
|
* 🔴 **射程边界(成文,不是遗漏)**:
|
|
1741
1749
|
* · 唯一让本旋钮**无处施加**的键是 `TOOL_APPROVAL_ENABLED=false`(协调器整体不构造,core 走它自己的
|
|
1742
1750
|
* headless 姿态)。**不造推断腿**:旋钮只在协调器在场时有意义,这一句是成文而不是代码分支。
|
|
@@ -1746,16 +1754,24 @@ export interface ServiceConfigFlat {
|
|
|
1746
1754
|
* 而不是 server 的 ask 行,「无 askStore 但有 durable 设施」正是 R-13 点名的**主受益部署**。
|
|
1747
1755
|
* 要回滚 R-13 的行为面,键是 `UNATTENDED_APPROVAL_POLICY=deny`,**不是**协议开关。
|
|
1748
1756
|
* 射程钉:`test/unattended-approval-policy.test.ts` 的 codex R2-F2 两格。
|
|
1749
|
-
* · APPROVAL_REQUIRE
|
|
1750
|
-
*
|
|
1757
|
+
* · APPROVAL_REQUIRE 名单工具:**要不要 ask** 由 `createApprovalBaselinePolicy` 的 durable gate 类裁决,那一层
|
|
1758
|
+
* 结构上读不到本旋钮;但那只 ask 的**答法**在协调器在场时与治理 ask 同走流内臂、同政策(引擎先问活席
|
|
1759
|
+
* `onAsk`,答 `"unavailable"` 才 park)⇒ `deny` 部署对名单工具同样当场拒。旧注「不经流内臂、不受本旋钮
|
|
1760
|
+
* 影响」失实(test 黑盒证伪;S-689 的真 HTTP 格 `test/unattended-deny-settlement-e2e.test.ts` 用的正是
|
|
1761
|
+
* 名单工具)。
|
|
1751
1762
|
* · **持久回放**(窗到期后向真源收敛、`ensureAsk` 幂等重入撞上已终结的行)读出 PARKING/PARKED 时
|
|
1752
1763
|
* **同样按旋钮改判** —— 它们与 (a) 是同一件事(赢家可能是别的副本或上一次调用),不统一的话同一只
|
|
1753
1764
|
* ask 的终局会取决于「谁赢了那把 CAS / 重试没重试」。人的真决议回放不受影响。
|
|
1754
1765
|
* · 唯一**不受**改判的是 `ensureAsk` 身份未定 / 坏行两臂(「身份不确定」的处置,不是「没有人」的
|
|
1755
1766
|
* 处置),于是 `deny` 部署上仍可能落极少数 park(店抖动期),已知且有意的残余。
|
|
1756
|
-
* · `deny`
|
|
1757
|
-
*
|
|
1758
|
-
*
|
|
1767
|
+
* · 🔴 `deny` 部署上政策结算**当场落行**(NP-2,7.100.0):窗到期 / emit 全灭两臂的店终局 claim 是 `refuse` 意图
|
|
1768
|
+
* —— 这一只 ask 行 CAS 成 DECIDED(deny) + `settled_by="policy"`(按 ask 作用域,不碰批、不连坐兄弟),与交给
|
|
1769
|
+
* 引擎的那一只同一个来源词 ⇒ 重入 / 收敛后重入回放同一个政策拒,不退化成裸 `false`(人拒)。修前行停 PARKING,
|
|
1770
|
+
* run 完成后收敛器转 DENIED 且不带来源。仍按 PARKING 记账的只剩别的写者推进的行(park 政策副本 / 切换政策之前)
|
|
1771
|
+
* 与崩溃后收敛器孤儿腿代打 expire 的行(后者 run 终局后成 DENIED、归因 `routing_failure`,已知残余)。
|
|
1772
|
+
* · 🔴 `deny` 部署上**批是单 ask 批**(S-689 codex R1-F1;`deriveAskScopedBatchId`):批是 park 路由的单位,
|
|
1773
|
+
* 整腿一批的前提是「批一关腿就随 park 结束」;deny 下引擎拿到部署政策拒后腿继续跑,整腿一批会让首只到期
|
|
1774
|
+
* 把批关掉(同腿后续的卡人批不动)、把兄弟连坐成 VOID(裸 `false` = 人拒)。
|
|
1759
1775
|
*/
|
|
1760
1776
|
unattendedApprovalPolicy: UnattendedApprovalPolicy;
|
|
1761
1777
|
/** [ref]① (clay A 案 = workflow 权限全面 CC parity): workflow 子 agent 的默认权限基线与主 LLM 同权(base `{}`)。
|
|
@@ -1907,10 +1923,13 @@ export interface ServiceConfigFlat {
|
|
|
1907
1923
|
* `dryRun` (SEMA_REGISTRY_DRY_RUN=true; legacy CONFIG_CENTER_DRY_RUN now boot-rejects, no dual-read): pull + LOG what the registry would change vs the env-derived
|
|
1908
1924
|
* config, but do NOT apply it — a safe-rollout verification step before flipping to live.
|
|
1909
1925
|
* `worker` (SEMA_REGISTRY_WORKER=<name>; legacy CONFIG_CENTER_WORKER now boot-rejects, no dual-read; injected per-worker by the reconciler): scopes the pull to
|
|
1910
|
-
* that worker's roster via `/effective?worker=<name>`. Unset → the global roster (backward compatible).
|
|
1926
|
+
* that worker's roster via `/effective?worker=<name>`. Unset → the global roster (backward compatible).
|
|
1927
|
+
* `credential`(S-651,BREAKING:取代 7.98.0 及以前的 `token: string` 启动快照):凭证来源活对象
|
|
1928
|
+
* (`center-credential.ts`),每条出站请求经它取一次值 —— `SEMA_REGISTRY_TOKEN`(值)或
|
|
1929
|
+
* `SEMA_REGISTRY_TOKEN_FILE`(每次读文件,轮换 / 换账号不重启)二选一。 */
|
|
1911
1930
|
configCenter?: {
|
|
1912
1931
|
baseUrl: string;
|
|
1913
|
-
|
|
1932
|
+
credential: CenterCredentialSource;
|
|
1914
1933
|
dryRun: boolean;
|
|
1915
1934
|
worker?: string;
|
|
1916
1935
|
};
|
package/dist/config.js
CHANGED
|
@@ -16,10 +16,10 @@ import { normalizeSshFingerprint } from "./ssh-host-key.js";
|
|
|
16
16
|
import { readFaceForPosture } from "./read-face-posture.js";
|
|
17
17
|
import { THINKING_LEVELS, THINKING_FORMATS, buildModelCompat, isThinkingFormat, isThinkingTier } from "./model-compat.js";
|
|
18
18
|
import {} from "./posture-source.js";
|
|
19
|
-
import { hostedPrincipalRefusal } from "./hosted-posture.js";
|
|
20
19
|
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
21
20
|
import { REMOTE_EXEC_PROVIDER_WORDS, executionLaneOf } from "./execution-lane-caps.js";
|
|
22
21
|
import { AUTONOMY_MODES } from "./runtime-governance.js";
|
|
22
|
+
import { createCenterCredentialSource, readCenterCredentialDeclaration } from "./center-credential.js";
|
|
23
23
|
import { modelProviderRefusal } from "./model-provider.js";
|
|
24
24
|
import { modelRouteUrlRefusal } from "./model-route-url.js";
|
|
25
25
|
import { modelRouteEndpointRefusal } from "./model-route-endpoint.js";
|
|
@@ -1965,19 +1965,21 @@ function parseIntegrationsDomain() {
|
|
|
1965
1965
|
gitApiKind: parseGitApiKind(process.env.GIT_API_KIND, process.env.GIT_API_BASEURL),
|
|
1966
1966
|
defaultScenario: env("DEFAULT_SCENARIO", "code"),
|
|
1967
1967
|
skillsDir: env("SKILLS_DIR", defaultSkillsDir()),
|
|
1968
|
-
configCenter: process.env.SEMA_REGISTRY_URL
|
|
1969
|
-
? {
|
|
1970
|
-
baseUrl: env("SEMA_REGISTRY_URL"),
|
|
1971
|
-
token: process.env.SEMA_REGISTRY_TOKEN || "",
|
|
1972
|
-
dryRun: boolEnv("SEMA_REGISTRY_DRY_RUN", false),
|
|
1973
|
-
worker: process.env.SEMA_REGISTRY_WORKER || undefined,
|
|
1974
|
-
}
|
|
1975
|
-
: undefined,
|
|
1968
|
+
configCenter: process.env.SEMA_REGISTRY_URL ? parseConfigCenter(env("SEMA_REGISTRY_URL")) : undefined,
|
|
1976
1969
|
configProvider: process.env.CONFIG_PROVIDER || undefined,
|
|
1977
1970
|
configLocalDir: process.env.CONFIG_LOCAL_DIR || undefined,
|
|
1978
1971
|
a2aServe: parseA2aServe(),
|
|
1979
1972
|
};
|
|
1980
1973
|
}
|
|
1974
|
+
function parseConfigCenter(baseUrl) {
|
|
1975
|
+
const worker = process.env.SEMA_REGISTRY_WORKER || undefined;
|
|
1976
|
+
return {
|
|
1977
|
+
baseUrl,
|
|
1978
|
+
credential: createCenterCredentialSource(readCenterCredentialDeclaration(), worker),
|
|
1979
|
+
dryRun: boolEnv("SEMA_REGISTRY_DRY_RUN", false),
|
|
1980
|
+
worker,
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1981
1983
|
const A2A_SERVE_MAX_SKILLS = 64;
|
|
1982
1984
|
const A2A_SERVE_MAX_ID_CHARS = 128;
|
|
1983
1985
|
const A2A_SERVE_MAX_TEXT_CHARS = 2048;
|
|
@@ -2195,10 +2197,6 @@ export function loadConfig() {
|
|
|
2195
2197
|
if (dgDurable && requirePrincipal && dgOperators.length === 0) {
|
|
2196
2198
|
throw new Error("DURABLE_APPROVAL=true + REQUIRE_PRINCIPAL=true but OPERATOR_PRINCIPALS is empty — on a multi-tenant worker the empty operator set makes isOperator() true-for-all, so every authenticated tenant could read every other tenant's pending-approval queue (GET /v1/approvals & siblings). Set OPERATOR_PRINCIPALS, or unset REQUIRE_PRINCIPAL if this is a single-tenant deployment (#157-① boot invariant)");
|
|
2197
2199
|
}
|
|
2198
|
-
const hostedRefusal = hostedPrincipalRefusal({ requirePrincipal, principalJwtPubkeys, principalJwtIss, principalJwtAud, authBridgeIssuer: auth.authBridgeIssuer });
|
|
2199
|
-
if (hostedRefusal !== undefined) {
|
|
2200
|
-
throw new Error(hostedRefusal);
|
|
2201
|
-
}
|
|
2202
2200
|
if (imageBakesEnabled && dgOperators.length === 0) {
|
|
2203
2201
|
throw new Error("IMAGE_BAKES_ENABLED=true but OPERATOR_PRINCIPALS is empty — the bake door runs build.sh on the privileged build host; an empty operator set makes isOperator() true-for-all (world-writable RCE). Set OPERATOR_PRINCIPALS or disable bakes (IMAGE-API-DESIGN.md §P2.4a boot invariant)");
|
|
2204
2202
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const CROSS_SESSION_INBOUND_LAYER_NAMES = ["managed", "user", "repo"];
|
|
2
2
|
export const CROSS_SESSION_INBOUND_LAYER_SOURCES = {
|
|
3
3
|
managed: {
|
|
4
|
-
source: "CROSS_SESSION_INBOUND —— 部署/组织层旋钮(
|
|
4
|
+
source: "CROSS_SESSION_INBOUND —— 部署/组织层旋钮(词表取自 core CROSS_SESSION_INBOUND_SETTINGS:7.30.0 起 wake|next-turn|hold|refuse,旧词 accept 拒启;未设=本层不设)",
|
|
5
5
|
wired: true,
|
|
6
6
|
},
|
|
7
7
|
user: {
|
package/dist/fleet-client.d.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* - 全部 fire-and-forget + swallow-guarded:fleet 面是运维旁路,绝不影响任务面;网络错误只 warn 一次/连败类。
|
|
14
14
|
*/
|
|
15
15
|
import { UsageReport } from "@sema-agent/settings-schema/fleet";
|
|
16
|
+
import { type CenterEndpoint } from "./config-center/center-request.js";
|
|
16
17
|
/** Bound on distinct principal×model keys per window — a runaway/multi-tenant burst collapses into
|
|
17
18
|
* `__other__` instead of growing unbounded (same posture as budget.ts cardinalityGuard). */
|
|
18
19
|
export declare const FLEET_USAGE_KEY_CAP = 1024;
|
|
@@ -55,9 +56,8 @@ export declare class FleetUsageAccumulator {
|
|
|
55
56
|
get size(): number;
|
|
56
57
|
}
|
|
57
58
|
export interface FleetClientOpts {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
token: string;
|
|
59
|
+
/** S-651:凭证是**来源**(每条公告 / 用量 / 注销请求各取一次值),不是启动期烧进 header 的字符串。 */
|
|
60
|
+
center: CenterEndpoint & {
|
|
61
61
|
worker: string;
|
|
62
62
|
};
|
|
63
63
|
instanceId: string;
|
|
@@ -88,16 +88,16 @@ export interface FleetClient {
|
|
|
88
88
|
*/
|
|
89
89
|
export declare function startFleetClient(opts: FleetClientOpts): FleetClient;
|
|
90
90
|
/**
|
|
91
|
-
* Env/config gate (D3 可选分层): start ONLY when the sema-registry lane is configured (baseUrl+
|
|
91
|
+
* Env/config gate (D3 可选分层): start ONLY when the sema-registry lane is configured (baseUrl + a DECLARED credential
|
|
92
|
+
* + worker — S-651: declared, not "currently readable": a token file that is unreadable right now still starts the
|
|
93
|
+
* client, every request re-reads it)
|
|
92
94
|
* AND the operator explicitly declared this instance's reachable address. Anything missing → undefined (zero
|
|
93
95
|
* behavior change for non-fleet deployments); worker missing is warned when the rest is present (half-configured).
|
|
94
96
|
*/
|
|
95
97
|
export declare function startFleetClientFromEnv(cfg: {
|
|
96
|
-
configCenter?: {
|
|
97
|
-
baseUrl: string;
|
|
98
|
-
token: string;
|
|
98
|
+
configCenter?: (CenterEndpoint & {
|
|
99
99
|
worker?: string;
|
|
100
|
-
};
|
|
100
|
+
}) | undefined;
|
|
101
101
|
}, opts: Omit<FleetClientOpts, "center" | "advertiseAddress"> & {
|
|
102
102
|
advertiseAddress?: string;
|
|
103
103
|
}): FleetClient | undefined;
|
package/dist/fleet-client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { InstanceAnnouncement, UsageReport, DEFAULT_HEARTBEAT_MS } from "@sema-agent/settings-schema/fleet";
|
|
2
2
|
import { buildDisplaySafeUrl } from "./trace/redact.js";
|
|
3
|
+
import { centerFetch } from "./config-center/center-request.js";
|
|
3
4
|
export const FLEET_USAGE_KEY_CAP = 1024;
|
|
4
5
|
export const FLEET_CLIENT_FETCH_TIMEOUT_MS = 5000;
|
|
5
6
|
export class FleetUsageAccumulator {
|
|
@@ -77,8 +78,7 @@ export function startFleetClient(opts) {
|
|
|
77
78
|
const f = opts.fetchImpl ?? fetch;
|
|
78
79
|
const heartbeatMs = opts.heartbeatMs ?? DEFAULT_HEARTBEAT_MS;
|
|
79
80
|
const usageWindowMs = opts.usageWindowMs ?? 60_000;
|
|
80
|
-
const
|
|
81
|
-
const headers = { authorization: `Bearer ${opts.center.token}`, "content-type": "application/json" };
|
|
81
|
+
const send = (route, method, json) => centerFetch(opts.center, { kind: route }, { method, json, timeoutMs: FLEET_CLIENT_FETCH_TIMEOUT_MS, fetchImpl: f });
|
|
82
82
|
let stopped = false;
|
|
83
83
|
let failStreak = 0;
|
|
84
84
|
const announceNow = async () => {
|
|
@@ -92,7 +92,7 @@ export function startFleetClient(opts) {
|
|
|
92
92
|
heartbeatMs,
|
|
93
93
|
};
|
|
94
94
|
try {
|
|
95
|
-
const res = await
|
|
95
|
+
const { res } = await send("fleet-instances", "POST", body);
|
|
96
96
|
if (!res.ok)
|
|
97
97
|
throw new Error(`announce ${res.status}${await rejectionSnippet(res)}`);
|
|
98
98
|
if (failStreak > 0)
|
|
@@ -117,7 +117,7 @@ export function startFleetClient(opts) {
|
|
|
117
117
|
return;
|
|
118
118
|
const report = { worker: opts.center.worker, instanceId: opts.instanceId, windowStartMs: start, windowEndMs, entries };
|
|
119
119
|
try {
|
|
120
|
-
const res = await
|
|
120
|
+
const { res } = await send("fleet-usage", "POST", { reports: [report] });
|
|
121
121
|
if (!res.ok)
|
|
122
122
|
throw new Error(`usage ${res.status}${await rejectionSnippet(res)}`);
|
|
123
123
|
}
|
|
@@ -140,7 +140,7 @@ export function startFleetClient(opts) {
|
|
|
140
140
|
clearInterval(uw);
|
|
141
141
|
await flushUsage();
|
|
142
142
|
try {
|
|
143
|
-
await
|
|
143
|
+
await send("fleet-instances", "DELETE", { instanceId: opts.instanceId, worker: opts.center.worker });
|
|
144
144
|
}
|
|
145
145
|
catch {
|
|
146
146
|
}
|
|
@@ -150,7 +150,7 @@ export function startFleetClient(opts) {
|
|
|
150
150
|
export function startFleetClientFromEnv(cfg, opts) {
|
|
151
151
|
const cc = cfg.configCenter;
|
|
152
152
|
const address = opts.advertiseAddress ?? process.env.FLEET_ADVERTISE_ADDRESS;
|
|
153
|
-
if (!cc?.baseUrl || !cc.
|
|
153
|
+
if (!cc?.baseUrl || !cc.credential.declared || !address)
|
|
154
154
|
return undefined;
|
|
155
155
|
let parsed;
|
|
156
156
|
try {
|
|
@@ -166,6 +166,6 @@ export function startFleetClientFromEnv(cfg, opts) {
|
|
|
166
166
|
opts.logger?.warn("fleet_client_no_worker_scope", { note: "FLEET_ADVERTISE_ADDRESS set but SEMA_REGISTRY_WORKER missing — announce needs the worker (dispatch key); fleet client not started" });
|
|
167
167
|
return undefined;
|
|
168
168
|
}
|
|
169
|
-
return startFleetClient({ ...opts, center: { baseUrl: cc.baseUrl,
|
|
169
|
+
return startFleetClient({ ...opts, center: { baseUrl: cc.baseUrl, credential: cc.credential, worker: cc.worker }, advertiseAddress: address });
|
|
170
170
|
}
|
|
171
171
|
//# sourceMappingURL=fleet-client.js.map
|
package/dist/fleet-lease.d.ts
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* - 全部 swallow-guarded:lease 面绝不 fault 任务提交路径。
|
|
25
25
|
*/
|
|
26
26
|
import { QuotaLease } from "@sema-agent/settings-schema/fleet";
|
|
27
|
+
import { type CenterEndpoint } from "./config-center/center-request.js";
|
|
27
28
|
/** 负缓存 TTL:非 lease-enforced 的 principal 多久后再问一次 center(entitlement 升级为 lease 的收敛上界)。 */
|
|
28
29
|
export declare const NOT_LEASE_TTL_MS: number;
|
|
29
30
|
/** center 判 budget exhausted 后,多久内的重复提交直接拒绝而不再打 center(防 per-request 打爆)。 */
|
|
@@ -62,9 +63,8 @@ export type LeaseAdmission = {
|
|
|
62
63
|
detail?: QuotaDenyDetail;
|
|
63
64
|
};
|
|
64
65
|
export interface FleetLeaseOpts {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
token: string;
|
|
66
|
+
/** S-651:凭证是**来源**(每次协商取一次值),不是构造期烧进 header 的字符串。 */
|
|
67
|
+
center: CenterEndpoint & {
|
|
68
68
|
worker: string;
|
|
69
69
|
};
|
|
70
70
|
fetchImpl?: typeof fetch;
|
|
@@ -82,8 +82,6 @@ export declare class FleetLeaseManager {
|
|
|
82
82
|
private readonly state;
|
|
83
83
|
private readonly inflight;
|
|
84
84
|
private readonly f;
|
|
85
|
-
private readonly base;
|
|
86
|
-
private readonly headers;
|
|
87
85
|
private readonly now;
|
|
88
86
|
private failStreak;
|
|
89
87
|
constructor(opts: FleetLeaseOpts);
|
|
@@ -108,21 +106,23 @@ export declare class FleetLeaseManager {
|
|
|
108
106
|
}): void;
|
|
109
107
|
/** Test/observability hook: the current state kind for a principal (never exposes the token). */
|
|
110
108
|
peek(principal: string): PrincipalState["kind"] | undefined;
|
|
109
|
+
/** 🔴 复审 O2(Opus)的鉴权失败臂,S-651 起两个入口共用(中心 401/403/400;凭证文件不可读):运维配置事故,
|
|
110
|
+
* 不是可用性事件。仍 fail-open(凭证抖一下不许把租户锁在门外),但**每次**都 warn + 专用计数,且**不缓存**。 */
|
|
111
|
+
private authFailOpen;
|
|
111
112
|
private set;
|
|
112
113
|
private issueOrRenew;
|
|
113
114
|
private issueOrRenewInner;
|
|
114
115
|
}
|
|
115
116
|
/**
|
|
116
117
|
* Env/config gate(与 startFleetClientFromEnv 同 D3 分层姿势,但 lease 不需要 FLEET_ADVERTISE_ADDRESS ——
|
|
117
|
-
* 它是出站消费面,不是入站可达性声明):sema-registry lane 配齐(baseUrl+
|
|
118
|
-
* undefined,非 fleet 部署零行为变化。
|
|
118
|
+
* 它是出站消费面,不是入站可达性声明):sema-registry lane 配齐(baseUrl + **声明了**凭证 + worker)即启;任一缺失 →
|
|
119
|
+
* undefined,非 fleet 部署零行为变化。S-651:按**声明**装、不按内容装 —— `SEMA_REGISTRY_TOKEN_FILE` 在场而
|
|
120
|
+
* 文件此刻不可读,管理器照样装上(每次协商取值,失败走响亮的鉴权失败臂;文件恢复即恢复)。
|
|
119
121
|
*/
|
|
120
122
|
export declare function createFleetLeaseFromEnv(cfg: {
|
|
121
|
-
configCenter?: {
|
|
122
|
-
baseUrl: string;
|
|
123
|
-
token: string;
|
|
123
|
+
configCenter?: (CenterEndpoint & {
|
|
124
124
|
worker?: string;
|
|
125
|
-
};
|
|
125
|
+
}) | undefined;
|
|
126
126
|
}, opts: Omit<FleetLeaseOpts, "center">): FleetLeaseManager | undefined;
|
|
127
127
|
export {};
|
|
128
128
|
//# sourceMappingURL=fleet-lease.d.ts.map
|