@sema-agent/server 7.55.0 → 7.57.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 +3 -3
- package/README.md +14 -7
- package/README.zh-CN.md +10 -6
- package/USAGE.md +207 -59
- package/deploy/sema-up/chart/values.yaml +1 -1
- package/dist/approval-ask-audit-store.d.ts +100 -1
- package/dist/approval-ask-audit-store.js +103 -2
- package/dist/approval-card.d.ts +148 -13
- package/dist/approval-card.js +82 -13
- package/dist/approval-content-kind.d.ts +22 -0
- package/dist/approval-content-kind.js +5 -0
- package/dist/approval.d.ts +31 -0
- package/dist/approval.js +18 -0
- package/dist/auto-mode-face.d.ts +111 -0
- package/dist/auto-mode-face.js +99 -0
- package/dist/bench/s1/arms.js +5 -5
- package/dist/bench/s1/live-deps.js +11 -11
- package/dist/bench/s1/run-firm.js +3 -0
- package/dist/bench/s1/runner-ctx.d.ts +4 -0
- package/dist/bench/s1/runner-ctx.js +7 -0
- package/dist/boot/config-center.js +23 -1
- package/dist/boot/coordinators.js +13 -1
- package/dist/boot/leader.d.ts +21 -0
- package/dist/boot/leader.js +6 -0
- package/dist/boot/parked-revive-gate.d.ts +7 -2
- package/dist/boot/parked-revive-gate.js +12 -1
- package/dist/boot/resolve-spec.d.ts +3 -0
- package/dist/boot/resolve-spec.js +3 -1
- package/dist/boot/runner-deps.d.ts +12 -0
- package/dist/boot/runner-deps.js +28 -3
- package/dist/boot/runtime-caps.d.ts +19 -8
- package/dist/boot/runtime-caps.js +50 -20
- package/dist/boot/session-shell-gate-registry.d.ts +39 -0
- package/dist/boot/session-shell-gate-registry.js +21 -0
- package/dist/config-catalog.js +14 -5
- package/dist/config-center/types.d.ts +2 -1
- package/dist/config-provider.js +1 -0
- package/dist/config-types.d.ts +41 -10
- package/dist/config.d.ts +21 -0
- package/dist/config.js +40 -5
- package/dist/http/route-ctx.d.ts +90 -3
- package/dist/http/routes/a2a-serve.js +5 -3
- package/dist/http/routes/approvals-assistant.js +21 -8
- package/dist/http/routes/capabilities.js +21 -4
- package/dist/http/routes/leader.js +2 -2
- package/dist/http/routes/memory-origin.d.ts +2 -2
- package/dist/http/routes/rules.js +3 -2
- package/dist/http/routes/runs.d.ts +0 -14
- package/dist/http/routes/runs.js +20 -8
- package/dist/http/routes/tasks.js +34 -8
- package/dist/http/routes/workflows.js +20 -5
- package/dist/http/server.d.ts +11 -1
- package/dist/http/server.js +220 -34
- package/dist/http/wire-types.d.ts +9 -4
- package/dist/leader/diffout.js +2 -1
- package/dist/leader/endpoint.js +49 -15
- package/dist/leader/fanout.js +6 -5
- package/dist/leader/leader.js +17 -14
- package/dist/leader/merge.js +17 -12
- package/dist/leader/planner.js +3 -2
- package/dist/leader/repair-oracle.js +6 -4
- package/dist/leader/repair-wire.js +5 -3
- package/dist/leader/wire.d.ts +80 -1
- package/dist/leader/wire.js +55 -26
- package/dist/main.js +44 -8
- package/dist/observability/err-text.d.ts +6 -0
- package/dist/observability/err-text.js +10 -0
- package/dist/observability/fail-open.d.ts +40 -0
- package/dist/observability/fail-open.js +19 -0
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/run-terminal-log.d.ts +120 -0
- package/dist/observability/run-terminal-log.js +360 -0
- package/dist/orchestration/workflow-completion-inbox.d.ts +9 -0
- package/dist/orchestration/workflow-completion-inbox.js +8 -0
- package/dist/permission-rule-vocab.d.ts +40 -0
- package/dist/permission-rule-vocab.js +17 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +26 -0
- package/dist/plugins/checkpoint-store-sql.js +23 -4
- package/dist/plugins/file-run-store.d.ts +3 -34
- package/dist/plugins/file-run-store.js +21 -0
- package/dist/plugins/local-checkpoint-store.d.ts +10 -0
- package/dist/plugins/local-checkpoint-store.js +5 -0
- package/dist/plugins/local-session-store.d.ts +9 -9
- package/dist/plugins/local-session-store.js +5 -3
- package/dist/plugins/memory-run-store.d.ts +3 -15
- package/dist/plugins/memory-run-store.js +21 -0
- package/dist/plugins/permission-rule-store-file.d.ts +22 -4
- package/dist/plugins/permission-rule-store-file.js +25 -11
- package/dist/plugins/permission-rule-store-sql.d.ts +60 -30
- package/dist/plugins/permission-rule-store-sql.js +39 -23
- package/dist/plugins/pg-session-storage.js +17 -13
- package/dist/plugins/remote-scratchpad.js +3 -2
- package/dist/plugins/run-store-sql.d.ts +3 -42
- package/dist/plugins/run-store-sql.js +36 -5
- package/dist/plugins/store-backend.d.ts +1 -1
- package/dist/plugins/store-contracts.d.ts +70 -1
- package/dist/plugins/tidb-session-store.js +18 -14
- package/dist/plugins/workflow-run-store-sql.d.ts +5 -0
- package/dist/plugins/workflow-run-store-sql.js +10 -2
- package/dist/rules-consent.d.ts +5 -4
- package/dist/rules-consent.js +25 -46
- package/dist/run-cancel-context.d.ts +13 -0
- package/dist/run-cancel-context.js +15 -0
- package/dist/run-local.js +2 -2
- package/dist/runs.d.ts +4 -1
- package/dist/runs.js +41 -10
- package/dist/runtime-caps-resolver.d.ts +133 -17
- package/dist/runtime-caps-resolver.js +44 -4
- package/dist/security.d.ts +7 -0
- package/dist/task-settings.d.ts +57 -3
- package/dist/task-settings.js +78 -5
- package/dist/task-workflow.d.ts +32 -2
- package/dist/task-workflow.js +8 -3
- package/dist/tool-approval.d.ts +38 -63
- package/dist/tool-approval.js +102 -74
- package/dist/trace/core-keyset-guard.d.ts +2 -2
- package/dist/trace/ledger-sink.d.ts +13 -4
- package/dist/trace/ledger-sink.js +14 -6
- package/dist/trace/project.d.ts +13 -0
- package/dist/trace/project.js +10 -1
- package/dist/trace/redact.d.ts +22 -11
- package/dist/trace/redact.js +655 -20
- package/dist/trace/sema-provenance.d.ts +26 -0
- package/dist/trace/sema-provenance.js +11 -0
- package/dist/turn-activity.d.ts +32 -2
- package/dist/turn-activity.js +29 -4
- package/package.json +3 -3
- package/skills/find-skills.md +1 -1
- package/skills/loop.md +1 -1
package/dist/leader/wire.js
CHANGED
|
@@ -3,7 +3,7 @@ import { execFileSync } from "node:child_process";
|
|
|
3
3
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
|
-
import { Runner, combinePolicies, createDurableQuestionPolicy, QUESTION_AWAITS_RESUME, delimitUntrusted, } from "@sema-agent/core";
|
|
6
|
+
import { Runner, combinePolicies, createDurableQuestionPolicy, QUESTION_AWAITS_RESUME, delimitUntrusted, hasDestroy, } from "@sema-agent/core";
|
|
7
7
|
import { RemoteContainerExecutionEnv } from "../plugins/remote-env-e2b.js";
|
|
8
8
|
import { createDurableAskPolicy } from "../approval.js";
|
|
9
9
|
import { presignS3Url } from "../plugins/s3-presign.js";
|
|
@@ -13,6 +13,8 @@ import { runLeaderTask } from "./leader.js";
|
|
|
13
13
|
import { attachRepairLoopDeps } from "./repair-wire.js";
|
|
14
14
|
import { routePlanWithFallback, validateSubtasks } from "./planner.js";
|
|
15
15
|
import { createEngineNoticeSeat } from "../boot/runner-deps.js";
|
|
16
|
+
import { currentPrincipal } from "../observability/principal-context.js";
|
|
17
|
+
import { redactHead, redactPatchHead } from "../observability/run-terminal-log.js";
|
|
16
18
|
const REPAIR_LOOP_WIRED = true;
|
|
17
19
|
const sh = (env) => async (cmd) => {
|
|
18
20
|
const r = await env.exec(`bash -lc ${JSON.stringify(cmd)}`);
|
|
@@ -30,6 +32,24 @@ const asIntegrationEnv = (env) => ({
|
|
|
30
32
|
writeFile: async (p, c) => { const r = await env.writeFile(p, c); if (!r.ok)
|
|
31
33
|
throw r.error; },
|
|
32
34
|
});
|
|
35
|
+
export function buildConflictResolverObjective(args) {
|
|
36
|
+
const { workerId, repoDir, patch, conflictText } = args;
|
|
37
|
+
return [
|
|
38
|
+
`A parallel worker '${workerId}' ported a module on its own branch, but applying its patch onto the already-integrated tree at ${repoDir} produced a MERGE CONFLICT (git apply --3way). The OTHER workers' patches already applied cleanly — integrate THIS worker's changes too, resolving the overlap.`,
|
|
39
|
+
`Resolve EVERY conflict in the working tree: open each file containing conflict markers (<<<<<<< / ======= / >>>>>>>) and merge BOTH sides' real intent (keep both workers' behaviour — never drop one side just to make it apply). Then apply any rejected hunks recorded in *.rej files by hand, and DELETE every *.rej and *.orig file.`,
|
|
40
|
+
`When done, NO conflict markers and NO *.rej files may remain under ${repoDir}. Verify yourself before finishing: grep -rIlE '^(<<<<<<<|>>>>>>>)' . ; find . -name '*.rej' — BOTH must print nothing.`,
|
|
41
|
+
`HARD RULES: do NOT git commit / git add / git push (the coordinator squashes at the end); do NOT modify build configuration; do NOT call AskUserQuestion — decide autonomously. Preserve both sides' ported semantics.`,
|
|
42
|
+
``,
|
|
43
|
+
delimitUntrusted("CONFLICTING WORKER PATCH (untrusted)", redactPatchHead(patch, 6000)),
|
|
44
|
+
delimitUntrusted("CONFLICT DETAILS (untrusted)", conflictText),
|
|
45
|
+
].join("\n");
|
|
46
|
+
}
|
|
47
|
+
export function createEnvReaper(env, what) {
|
|
48
|
+
if (!hasDestroy(env)) {
|
|
49
|
+
throw new Error(`${what}: the execution-env factory returned an env with no destroy() — the leader OWNS this dedicated sandbox and this closure is its only teardown, so it would leak (and keep billing) until the backend's own timeout reaps it. Deploy a factory whose env implements RemoteExecutionEnv.destroy for the leader lane.`);
|
|
50
|
+
}
|
|
51
|
+
return async () => void (await env.destroy());
|
|
52
|
+
}
|
|
33
53
|
export async function stageWorkerEnv(env, opts) {
|
|
34
54
|
const { seedCmd, repo, branch, uploadScriptPath, uploadScript } = opts;
|
|
35
55
|
const probe = await env.exec(`bash -lc ${JSON.stringify(`if [ -f ${uploadScriptPath} ]; then exit 0; fi; ` +
|
|
@@ -130,6 +150,14 @@ export function createLeaderRunner(cfg) {
|
|
|
130
150
|
const onNoticeSeat = createEngineNoticeSeat(cfg.logger);
|
|
131
151
|
const governanceSeat = cfg.governance ?? {};
|
|
132
152
|
const deploymentPostureSeat = cfg.deploymentPosture ?? {};
|
|
153
|
+
const tracerSeat = cfg.tracer ? { tracer: cfg.tracer } : {};
|
|
154
|
+
const usageWindowSeat = cfg.usageWindows && cfg.usageWindowStore ? { usageWindows: cfg.usageWindows, usageWindowStore: cfg.usageWindowStore } : {};
|
|
155
|
+
const promptSourceSeat = cfg.promptSource ? { promptSource: cfg.promptSource } : {};
|
|
156
|
+
const onErrorSeat = cfg.onError ? { onError: cfg.onError } : {};
|
|
157
|
+
const principalSpec = () => {
|
|
158
|
+
const p = currentPrincipal();
|
|
159
|
+
return p ? { principal: p } : {};
|
|
160
|
+
};
|
|
133
161
|
const mkRepair = (rawEnv, repoDir, oracleFiles) => {
|
|
134
162
|
if (repairRounds <= 0)
|
|
135
163
|
return undefined;
|
|
@@ -143,7 +171,7 @@ export function createLeaderRunner(cfg) {
|
|
|
143
171
|
throw new Error(`oracle still present after remove (${f.path}) — refusing repair (measurement integrity)`);
|
|
144
172
|
}
|
|
145
173
|
try {
|
|
146
|
-
const runner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat, executionEnv: rawEnv, rootPath: repoDir });
|
|
174
|
+
const runner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat, ...tracerSeat, ...usageWindowSeat, ...promptSourceSeat, ...onErrorSeat, executionEnv: rawEnv, rootPath: repoDir });
|
|
147
175
|
const objective = [
|
|
148
176
|
`The integrated project at ${repoDir} fails its build/test. Make the MINIMAL change to the working tree so this command exits 0 (cd into the repo and run it yourself to confirm):`,
|
|
149
177
|
` ${testCmd}`,
|
|
@@ -155,6 +183,7 @@ export function createLeaderRunner(cfg) {
|
|
|
155
183
|
const res = await runner
|
|
156
184
|
.runTaskStream({
|
|
157
185
|
objective,
|
|
186
|
+
...principalSpec(),
|
|
158
187
|
sessionId: `leader-repair-${Date.now()}-${round}`,
|
|
159
188
|
limits: { maxCostUsd: repairBudgetUsd, maxWalltimeMs: Math.max(300_000, Math.floor(leaderTimeoutMs / 2)) },
|
|
160
189
|
})
|
|
@@ -186,19 +215,12 @@ export function createLeaderRunner(cfg) {
|
|
|
186
215
|
throw new Error(`oracle still present after remove (${f.path}) — refusing conflict-resolve (measurement integrity)`);
|
|
187
216
|
}
|
|
188
217
|
try {
|
|
189
|
-
const runner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat, executionEnv: rawEnv, rootPath: repoDir });
|
|
190
|
-
const objective =
|
|
191
|
-
`A parallel worker '${workerId}' ported a module on its own branch, but applying its patch onto the already-integrated tree at ${repoDir} produced a MERGE CONFLICT (git apply --3way). The OTHER workers' patches already applied cleanly — integrate THIS worker's changes too, resolving the overlap.`,
|
|
192
|
-
`Resolve EVERY conflict in the working tree: open each file containing conflict markers (<<<<<<< / ======= / >>>>>>>) and merge BOTH sides' real intent (keep both workers' behaviour — never drop one side just to make it apply). Then apply any rejected hunks recorded in *.rej files by hand, and DELETE every *.rej and *.orig file.`,
|
|
193
|
-
`When done, NO conflict markers and NO *.rej files may remain under ${repoDir}. Verify yourself before finishing: grep -rIlE '^(<<<<<<<|>>>>>>>)' . ; find . -name '*.rej' — BOTH must print nothing.`,
|
|
194
|
-
`HARD RULES: do NOT git commit / git add / git push (the coordinator squashes at the end); do NOT modify build configuration; do NOT call AskUserQuestion — decide autonomously. Preserve both sides' ported semantics.`,
|
|
195
|
-
``,
|
|
196
|
-
delimitUntrusted("CONFLICTING WORKER PATCH (untrusted)", patch.slice(0, 6000)),
|
|
197
|
-
delimitUntrusted("CONFLICT DETAILS (untrusted)", conflictText),
|
|
198
|
-
].join("\n");
|
|
218
|
+
const runner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat, ...tracerSeat, ...usageWindowSeat, ...promptSourceSeat, ...onErrorSeat, executionEnv: rawEnv, rootPath: repoDir });
|
|
219
|
+
const objective = buildConflictResolverObjective({ workerId, repoDir, patch, conflictText });
|
|
199
220
|
const res = await runner
|
|
200
221
|
.runTaskStream({
|
|
201
222
|
objective,
|
|
223
|
+
...principalSpec(),
|
|
202
224
|
sessionId: `leader-conflict-${Date.now()}-${round}`,
|
|
203
225
|
limits: { maxCostUsd: repairBudgetUsd, maxWalltimeMs: Math.max(300_000, Math.floor(leaderTimeoutMs / 2)) },
|
|
204
226
|
})
|
|
@@ -249,10 +271,11 @@ export function createLeaderRunner(cfg) {
|
|
|
249
271
|
if (Array.isArray(body.subtasks) && body.subtasks.length > 0) {
|
|
250
272
|
return validateSubtasks(body.subtasks);
|
|
251
273
|
}
|
|
252
|
-
const planRunner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat });
|
|
274
|
+
const planRunner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat, ...tracerSeat, ...usageWindowSeat, ...promptSourceSeat, ...onErrorSeat });
|
|
253
275
|
const runRoute = async (prompt) => {
|
|
254
276
|
const res = await planRunner.runTaskStream({
|
|
255
277
|
objective: prompt,
|
|
278
|
+
...principalSpec(),
|
|
256
279
|
sessionId: `leader-route-${Date.now()}`,
|
|
257
280
|
...(cfg.routerModel ? { model: cfg.routerModel } : {}),
|
|
258
281
|
}).result();
|
|
@@ -278,7 +301,7 @@ export function createLeaderRunner(cfg) {
|
|
|
278
301
|
onQuestion: QUESTION_AWAITS_RESUME,
|
|
279
302
|
};
|
|
280
303
|
await d.checkpointStore.putCtx(sessionId, { body: { objective: sub.spec.objective } }).catch((e) => {
|
|
281
|
-
cfg.logger?.warn?.("leader_putctx_failed", { workerId: sub.workerId, err: String(e) });
|
|
304
|
+
cfg.logger?.warn?.("leader_putctx_failed", { workerId: sub.workerId, err: redactHead(e instanceof Error ? e.message : String(e), 2000) });
|
|
282
305
|
});
|
|
283
306
|
}
|
|
284
307
|
let resourceSpec = {};
|
|
@@ -339,15 +362,15 @@ export function createLeaderRunner(cfg) {
|
|
|
339
362
|
workerId: sub.workerId, sessionId, branch: sub.branch,
|
|
340
363
|
baseSha,
|
|
341
364
|
runner: keepCtxWarm(new Runner({
|
|
342
|
-
brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat,
|
|
365
|
+
brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat, ...tracerSeat, ...usageWindowSeat, ...promptSourceSeat, ...onErrorSeat,
|
|
343
366
|
...(cfg.toolResultStore ? { toolResultStore: cfg.toolResultStore } : {}),
|
|
344
367
|
...(cfg.sessionStore ? { sessionStore: cfg.sessionStore } : {}),
|
|
345
|
-
executionEnvFactory: (ctx) => withStaging(envFactory(ctx), stage, async (e) => {
|
|
368
|
+
executionEnvFactory: async (ctx) => withStaging(await envFactory(ctx), stage, async (e) => {
|
|
346
369
|
await sh(e)(`cd ${repo} && git add -A && git -c user.name='leader-belt' -c user.email='belt@leader' commit -qm 'belt: auto-commit uncommitted worker output' || true; sh ${W}/.leader-upload.sh`);
|
|
347
370
|
}),
|
|
348
371
|
})),
|
|
349
372
|
fetchDiff: () => fetchUploadedDiff(getUrl),
|
|
350
|
-
spec: { ...workerLimits, ...sub.spec, ...durableSpec, ...resourceSpec, objective: `${sub.spec.objective}${uploadStepSuffix(W)}` },
|
|
373
|
+
spec: { ...workerLimits, ...sub.spec, ...durableSpec, ...resourceSpec, ...principalSpec(), objective: `${sub.spec.objective}${uploadStepSuffix(W)}` },
|
|
351
374
|
};
|
|
352
375
|
}
|
|
353
376
|
if (!cfg.e2bApiKey)
|
|
@@ -359,25 +382,30 @@ export function createLeaderRunner(cfg) {
|
|
|
359
382
|
const baseSha = await sh(env)(`cd ${repo} && git rev-parse HEAD`);
|
|
360
383
|
return {
|
|
361
384
|
workerId: sub.workerId, sessionId, branch: sub.branch, baseSha,
|
|
362
|
-
runner: keepCtxWarm(new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat, ...(cfg.toolResultStore ? { toolResultStore: cfg.toolResultStore } : {}), ...(cfg.sessionStore ? { sessionStore: cfg.sessionStore } : {}), executionEnv: env })),
|
|
385
|
+
runner: keepCtxWarm(new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat, ...tracerSeat, ...usageWindowSeat, ...promptSourceSeat, ...onErrorSeat, ...(cfg.toolResultStore ? { toolResultStore: cfg.toolResultStore } : {}), ...(cfg.sessionStore ? { sessionStore: cfg.sessionStore } : {}), executionEnv: env })),
|
|
363
386
|
diffEnv: env,
|
|
364
387
|
destroy: () => env.destroy().then(() => { }),
|
|
365
|
-
spec: { ...workerLimits, ...sub.spec, ...durableSpec, ...resourceSpec },
|
|
388
|
+
spec: { ...workerLimits, ...sub.spec, ...durableSpec, ...resourceSpec, ...principalSpec() },
|
|
366
389
|
};
|
|
367
390
|
};
|
|
368
391
|
const provisionIntegrationSandbox = async () => {
|
|
369
392
|
if (cfg.envFactory) {
|
|
370
393
|
const integSessionId = `leader-integ-${Date.now()}`;
|
|
371
|
-
const env = cfg.envFactory({ sessionId: integSessionId, placementRootSessionId: integSessionId });
|
|
394
|
+
const env = await cfg.envFactory({ sessionId: integSessionId, placementRootSessionId: integSessionId });
|
|
395
|
+
const reap = createEnvReaper(env, "leader integration sandbox");
|
|
372
396
|
try {
|
|
373
397
|
await sh(env)(body.seedCmd);
|
|
374
398
|
await injectOracles(env);
|
|
375
399
|
}
|
|
376
400
|
catch (e) {
|
|
377
|
-
await
|
|
401
|
+
await reap().catch(() => { });
|
|
378
402
|
throw e;
|
|
379
403
|
}
|
|
380
|
-
|
|
404
|
+
const rawEnv = env;
|
|
405
|
+
const oracleFiles = body.oracleFiles ?? [];
|
|
406
|
+
const repair = mkRepair(rawEnv, repo, oracleFiles);
|
|
407
|
+
const conflictResolver = mkConflictResolver(rawEnv, repo, oracleFiles);
|
|
408
|
+
return { env: asIntegrationEnv(env), destroy: reap, ...(repair ? { repair } : {}), ...(conflictResolver ? { conflictResolver } : {}) };
|
|
381
409
|
}
|
|
382
410
|
if (!cfg.e2bApiKey)
|
|
383
411
|
throw new Error("leader wire: no envFactory and no e2bApiKey");
|
|
@@ -415,7 +443,7 @@ export function createLeaderRunner(cfg) {
|
|
|
415
443
|
let env;
|
|
416
444
|
if (cfg.envFactory) {
|
|
417
445
|
const graderSessionId = `leader-grader-${Date.now()}`;
|
|
418
|
-
env = cfg.envFactory({ sessionId: graderSessionId, placementRootSessionId: graderSessionId });
|
|
446
|
+
env = await cfg.envFactory({ sessionId: graderSessionId, placementRootSessionId: graderSessionId });
|
|
419
447
|
}
|
|
420
448
|
else {
|
|
421
449
|
if (!cfg.e2bApiKey)
|
|
@@ -427,6 +455,7 @@ export function createLeaderRunner(cfg) {
|
|
|
427
455
|
}
|
|
428
456
|
env = e2b;
|
|
429
457
|
}
|
|
458
|
+
const reapGrader = createEnvReaper(env, "leader repair-loop grader");
|
|
430
459
|
let integBase;
|
|
431
460
|
try {
|
|
432
461
|
await sh(env)(body.seedCmd);
|
|
@@ -434,11 +463,11 @@ export function createLeaderRunner(cfg) {
|
|
|
434
463
|
integBase = await sh(env)(`cd ${repo} && git rev-parse HEAD`);
|
|
435
464
|
}
|
|
436
465
|
catch (e) {
|
|
437
|
-
await
|
|
466
|
+
await reapGrader().catch(() => { });
|
|
438
467
|
throw e;
|
|
439
468
|
}
|
|
440
469
|
const grader = {
|
|
441
|
-
env
|
|
470
|
+
env,
|
|
442
471
|
writeFile: async (p, c) => { const r = await env.writeFile(p, c); if (!r.ok)
|
|
443
472
|
throw r.error; },
|
|
444
473
|
repoDir: repo,
|
|
@@ -463,7 +492,7 @@ export function createLeaderRunner(cfg) {
|
|
|
463
492
|
return {
|
|
464
493
|
runner: solo.runner,
|
|
465
494
|
grader,
|
|
466
|
-
graderDestroy:
|
|
495
|
+
graderDestroy: reapGrader,
|
|
467
496
|
pullWorkerDiff,
|
|
468
497
|
oracleSteps,
|
|
469
498
|
passTier,
|
package/dist/main.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { Runner, InMemoryToolResultStore, TtlSessionStore, uuidv7, defaultTaskRegistry, createAllowDenyPolicy, workflowsCapability, probeSearchBackend, describeStaticWiring, DEFAULT_SUBAGENT_TOOL_NAME } from "@sema-agent/core";
|
|
4
|
+
import { Runner, InMemoryToolResultStore, TtlSessionStore, uuidv7, defaultTaskRegistry, createAllowDenyPolicy, workflowsCapability, probeSearchBackend, describeStaticWiring, expandTiers, DEFAULT_SUBAGENT_TOOL_NAME } from "@sema-agent/core";
|
|
5
5
|
import { createSessionTitler } from "./session-titler.js";
|
|
6
6
|
import { posIntEnv } from "./session-watch.js";
|
|
7
7
|
import { selectEnvironmentTool } from "./capabilities/select-environment-tool.js";
|
|
8
8
|
import { subagentSendUserFileExtraTools } from "./capabilities/send-user-file-tool.js";
|
|
9
9
|
import { brainSummary } from "./brain.js";
|
|
10
|
-
import { loadConfig, logConfigDiagnostics, resolveBindHost, splitLocalRoots, parseCapEnv,
|
|
10
|
+
import { loadConfig, logConfigDiagnostics, resolveBindHost, splitLocalRoots, parseCapEnv, parseNumOrFailNonNegative } from "./config.js";
|
|
11
11
|
import { cardRuleScopeRoot } from "./task-cwd.js";
|
|
12
12
|
import { isPricingConfigured } from "./budget.js";
|
|
13
13
|
import { drainNumEnvWarnings } from "./plugins/remote-shell.js";
|
|
@@ -15,7 +15,7 @@ import { ensureChildSessionDurableWithPromotion } from "./plugins/session-store.
|
|
|
15
15
|
import { ForkRoutingSessionStore } from "./plugins/fork-routing-session-store.js";
|
|
16
16
|
import { webSearchConfigFromEnv, createWebSearchBackend, setWebSearchBadPayloadObserver, shouldProbeWebSearchOnBoot } from "./plugins/web-search.js";
|
|
17
17
|
import { createAuthorizer, encodeCheckpointScope } from "./security.js";
|
|
18
|
-
import { assertGateIntentServiceable, hasOperatorGateIntent } from "./approval.js";
|
|
18
|
+
import { assertGateIntentServiceable, buildForceDurableGateInertNotice, hasOperatorGateIntent } from "./approval.js";
|
|
19
19
|
import { loadSkills } from "./capabilities/skills.js";
|
|
20
20
|
import { createRepoClient } from "./capabilities/repo-tools.js";
|
|
21
21
|
import { buildScenarios, builtinScenarioDetails } from "./capabilities/scenarios.js";
|
|
@@ -26,11 +26,13 @@ import { createRegistryJwtVerifier } from "./auth-bridge.js";
|
|
|
26
26
|
import { createMetrics } from "./observability/metrics.js";
|
|
27
27
|
import { installFailOpenRecorder } from "./observability/fail-open.js";
|
|
28
28
|
import { installSecretEnvScrubRecorder } from "./observability/secret-env-scrub.js";
|
|
29
|
+
import { setRunTerminalObserver } from "./observability/run-terminal-log.js";
|
|
29
30
|
import { setRedactionObserver, redactSecrets } from "./trace/redact.js";
|
|
30
31
|
import { RateLimiter } from "./observability/rate-limit.js";
|
|
31
32
|
import { createHttpServer, explicitOperatorOk, hasServiceAuth } from "./http/server.js";
|
|
32
33
|
import { exportMemoryScope } from "./memory-export.js";
|
|
33
|
-
import { workflowModelAllowlistFor } from "./task-workflow.js";
|
|
34
|
+
import { workflowModelAllowlistFor, selfOrchestrationDenial } from "./task-workflow.js";
|
|
35
|
+
import { centerEntitlementSourceWired } from "./runtime-caps-resolver.js";
|
|
34
36
|
import { createMemoryBundleFaces } from "./memory-bundle-engine.js";
|
|
35
37
|
import { backendOwnsControlPlane, createMemoryComplianceFaces, createMemoryOriginFaces, createSessionMemoryStatusFace } from "./memory-operator-faces.js";
|
|
36
38
|
import { performMemorySync } from "./memory-sync.js";
|
|
@@ -63,6 +65,8 @@ import { createExecutionEnv } from "./boot/execution-env.js";
|
|
|
63
65
|
import { createWorkflowOrchestration } from "./boot/workflow-orchestration.js";
|
|
64
66
|
import { createLiveCoordinators } from "./boot/coordinators.js";
|
|
65
67
|
import { createRuntimeCaps } from "./boot/runtime-caps.js";
|
|
68
|
+
import { classifierRouteFor, coreArmsAutoOnIntent } from "./auto-mode-face.js";
|
|
69
|
+
import { createSessionShellGateRegistry } from "./boot/session-shell-gate-registry.js";
|
|
66
70
|
import { createRunnerDeps, createRunnerDepsOnAsk, createSharedRunnerDeps } from "./boot/runner-deps.js";
|
|
67
71
|
import { createOrgMemoryAdmissionWiring } from "./boot/org-memory.js";
|
|
68
72
|
import { createGovernanceSeams } from "./boot/governance-seams.js";
|
|
@@ -98,11 +102,17 @@ async function main() {
|
|
|
98
102
|
...(process.env.MODEL_VERIFIER ? { rejected: process.env.MODEL_VERIFIER, reason: "not in catalog" } : {}),
|
|
99
103
|
});
|
|
100
104
|
}
|
|
101
|
-
setRedactionObserver((pattern, n) => metrics.inc("redactions_applied_total", { pattern }, n));
|
|
105
|
+
setRedactionObserver((pattern, n, meta) => metrics.inc("redactions_applied_total", { pattern, arm: meta.arm }, n));
|
|
102
106
|
setWebSearchBadPayloadObserver((provider) => {
|
|
103
107
|
metrics.inc("web_search_bad_payload_total", { provider });
|
|
104
108
|
logger.warn("web_search_non_array_payload", { provider });
|
|
105
109
|
});
|
|
110
|
+
setRunTerminalObserver((level, fields) => {
|
|
111
|
+
if (level === "error")
|
|
112
|
+
logger.error("run_failed", fields);
|
|
113
|
+
else
|
|
114
|
+
logger.info("run_failed", fields);
|
|
115
|
+
});
|
|
106
116
|
const localRoot = config.configLocalDir ?? process.env.AGENT_DATA_DIR ?? join(homedir(), ".ai-agent");
|
|
107
117
|
const rootsSplit = splitLocalRoots({
|
|
108
118
|
localDataRoot: config.localDataRoot,
|
|
@@ -171,7 +181,7 @@ async function main() {
|
|
|
171
181
|
ruleScopeRootFor: (sessionId) => cardRuleScopeRoot(sessionId !== undefined ? perSessionCwd.get(sessionId) : undefined, config),
|
|
172
182
|
});
|
|
173
183
|
const memoryOptOutGrantStore = backend?.memoryOptOutGrant();
|
|
174
|
-
const { principalCaps, centerRuntimeCapsResolver, runtimeCapsResolver } = createRuntimeCaps({ config, logger, memoryOptOutGrant: memoryOptOutGrantStore });
|
|
184
|
+
const { principalCaps, centerRuntimeCapsResolver, runtimeCapsResolver, runtimeCapsPeekResolver } = createRuntimeCaps({ config, logger, memoryOptOutGrant: memoryOptOutGrantStore });
|
|
175
185
|
const orgMemoryAdmission = createOrgMemoryAdmissionWiring({ config, logger, metrics });
|
|
176
186
|
const sharedMemoryStore = orgMemoryAdmission.directory !== undefined
|
|
177
187
|
? backend?.sharedMemoryStore?.(createSharedMemoryScopeAuthorizer({
|
|
@@ -248,9 +258,11 @@ async function main() {
|
|
|
248
258
|
});
|
|
249
259
|
const mcpRevocations = configCenter.takeMcpRevocations();
|
|
250
260
|
const workflowModelAllowlistSnapshot = workflowModelAllowlistFor(config) ?? [];
|
|
261
|
+
const sessionShellGateRegistry = createSessionShellGateRegistry();
|
|
251
262
|
const runnerDeps = createRunnerDeps({
|
|
252
263
|
hands: commitHands,
|
|
253
264
|
mcpRevocations,
|
|
265
|
+
sessionShellGateExplicitlyOff: sessionShellGateRegistry.explicitlyOff,
|
|
254
266
|
config, logger, metrics, localRoot, promptSource: configCenter.promptSource, rosterStore, backgroundAgentStore, mailboxStore, usageWindowStore, brain,
|
|
255
267
|
pricing, tracer, outcomeSink, elicitation, question, toolApproval, sessionStore, memoryEngine,
|
|
256
268
|
sessionCaptureRecordStore,
|
|
@@ -344,7 +356,7 @@ async function main() {
|
|
|
344
356
|
const runStore = backend ? backend.run() : undefined;
|
|
345
357
|
const resumeAnchorStore = backend ? backend.resumeAnchor() : undefined;
|
|
346
358
|
const approvalExemptionStore = backend ? backend.approvalExemption() : undefined;
|
|
347
|
-
const taskTimeoutSec = Math.
|
|
359
|
+
const taskTimeoutSec = Math.floor(parseNumOrFailNonNegative("TASK_TIMEOUT_SEC", process.env.TASK_TIMEOUT_SEC || "0"));
|
|
348
360
|
const capEnv = (name) => {
|
|
349
361
|
const r = parseCapEnv(name, process.env[name]);
|
|
350
362
|
if (r.invalid !== undefined) {
|
|
@@ -408,6 +420,17 @@ async function main() {
|
|
|
408
420
|
logger.info("direct_door_active", { iss: config.principalJwtIss, aud: config.principalJwtAud, operators: config.operatorPrincipals.length });
|
|
409
421
|
if (config.requirePrincipal && config.selfOrchestrationEnabled && !config.selfOrchestrationWorkerIsolation)
|
|
410
422
|
logger.warn("self_orchestration_no_worker_isolation", { detail: "multi-tenant self-orchestration without SELF_ORCHESTRATION_WORKER_ISOLATION=true — a tenant workflow can starve the host event loop (availability risk). Set it to enable worker_thread resource isolation." });
|
|
423
|
+
if (selfOrchestrationDenial(config, centerEntitlementSourceWired(config)) !== null)
|
|
424
|
+
logger.warn("self_orchestration_no_entitlement_source", { detail: "SELF_ORCHESTRATION_ENABLED is on and REQUIRE_PRINCIPAL is on, but no per-principal entitlement source is wired (no config center, or it is in dry-run). core's allowWorkflows is tighten-only — an absent resolver leaves it ungated — so this service fail-closes self-orchestration for EVERY principal: no task on this worker can ever get the workflow engine. /v1/capabilities now reports workflows:false (workflowsGate.denial=\"entitlement_resolver_absent\") and a submit carrying selfOrchestration/settings.ultracode is refused 501 instead of silently downgraded. Wire a config center (SEMA_REGISTRY_URL + token, not dry-run), or set SELF_ORCHESTRATION_ENABLED=false." });
|
|
425
|
+
{
|
|
426
|
+
const inert = buildForceDurableGateInertNotice({
|
|
427
|
+
entitlementSourceWired: centerEntitlementSourceWired(config),
|
|
428
|
+
checkpointStorePresent: checkpointStore !== undefined,
|
|
429
|
+
durableApproval: config.durableApproval,
|
|
430
|
+
});
|
|
431
|
+
if (inert)
|
|
432
|
+
logger.warn(inert.event, inert.fields);
|
|
433
|
+
}
|
|
411
434
|
if (config.requirePrincipal && !config.authToken && Object.keys(config.authTokens ?? {}).length === 0 && !config.traceToken)
|
|
412
435
|
logger.warn("multi_tenant_no_service_token", { detail: "REQUIRE_PRINCIPAL is on but no SERVICE_AUTH_TOKEN/authTokens/TRACE_TOKEN — fleet/trace/session-list are owner-gated only (never fleet-wide, tenant-isolation safe). Set a token for cross-tenant ops visibility." });
|
|
413
436
|
const rateLimiter = backend?.rateLimiter
|
|
@@ -536,7 +559,7 @@ async function main() {
|
|
|
536
559
|
resumeAnchorStore, approvalExemptionStore, sessionPolicyStore, taskAttachmentStore, fileHistoryStore,
|
|
537
560
|
workflowCompletionInbox, taskListLane,
|
|
538
561
|
});
|
|
539
|
-
const leaderEndpoint = createLeaderFace({ config, logger, brain, pricing, executionEnvFactory, toolResultStore, sessionStore, checkpointStore, governanceSeams, backend });
|
|
562
|
+
const leaderEndpoint = createLeaderFace({ config, logger, brain, pricing, executionEnvFactory, toolResultStore, sessionStore, checkpointStore, governanceSeams, backend, tracer, usageWindowStore, promptSource: configCenter.promptSource, onError: runnerDeps.onError });
|
|
540
563
|
const drainState = { draining: false };
|
|
541
564
|
const authBridgeIssuer = process.env.AUTH_BRIDGE_ISSUER || config.configCenter?.baseUrl;
|
|
542
565
|
const registryJwtVerifier = authBridgeIssuer
|
|
@@ -592,6 +615,7 @@ async function main() {
|
|
|
592
615
|
question: question ? question : undefined,
|
|
593
616
|
toolApproval: toolApproval ? toolApproval : undefined,
|
|
594
617
|
approvalAskAudit: approvalAskAudit ? approvalAskAudit : undefined,
|
|
618
|
+
approvalLateSettle: approvalAskAudit ? approvalAskAudit : undefined,
|
|
595
619
|
workflowAgentRegistry: workflowAgentRegistry ? workflowAgentRegistry : undefined,
|
|
596
620
|
subagentSteerRegistry,
|
|
597
621
|
workflowCompletionInbox: workflowCompletionInbox ? workflowCompletionInbox : undefined,
|
|
@@ -647,6 +671,17 @@ async function main() {
|
|
|
647
671
|
costQuota,
|
|
648
672
|
fleetLease: fleetLease ? fleetLease : undefined,
|
|
649
673
|
usageWindowStore: usageWindowStore ? usageWindowStore : undefined,
|
|
674
|
+
autoModeFace: {
|
|
675
|
+
seatMounted: runnerDeps.autoMode !== undefined,
|
|
676
|
+
engineArmsOnIntent: coreArmsAutoOnIntent(),
|
|
677
|
+
resolveCaps: runtimeCapsPeekResolver,
|
|
678
|
+
classifierModel: () => {
|
|
679
|
+
const route = classifierRouteFor({ models: expandTiers(config.models, config.tiers ?? {}) ?? config.models, roles: config.roles });
|
|
680
|
+
if (route.fault !== undefined)
|
|
681
|
+
logger.debug("auto_mode_classifier_route_unresolved", { fault: route.fault });
|
|
682
|
+
return route.id;
|
|
683
|
+
},
|
|
684
|
+
},
|
|
650
685
|
};
|
|
651
686
|
const storeLiveProbe = backend && backend.kind !== "local" && backend.dbNowMs && config.storeProbeIntervalMs > 0
|
|
652
687
|
? createStoreLiveProbe({ probe: () => backend.dbNowMs(), intervalMs: config.storeProbeIntervalMs, logger })
|
|
@@ -692,6 +727,7 @@ async function main() {
|
|
|
692
727
|
getKeyResolver: () => configCenter.getKeyResolver(),
|
|
693
728
|
taskAttachmentStore, perSessionCwd, setSessionCwd, setSessionShellEnv, fenceSessionOwner,
|
|
694
729
|
hookLlm, hookAgent, fleetBus, hookWakeBus, resumeAnchorStore, ownerAware, taskLimitCaps, taskTimeoutSec,
|
|
730
|
+
noteSessionShellGate: sessionShellGateRegistry.note,
|
|
695
731
|
selectEnvTool, sendUserFileToolSpec, memoryEngine, durableEnabled, approvalExemptionStore,
|
|
696
732
|
singleUserAutoAcceptBaseline, checkpointStore, deploymentHooks, imageIndex, perTaskImage,
|
|
697
733
|
sessionEnvSelection,
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** 把任意 rejection 值变成**一定能得到的字符串**——终局链(setTerminal 的 errorMessage 座、留痕腿)专用。
|
|
2
|
+
* S-107 codex r28/r31 + 第三十一/三十二轮重扫:rejection 值可以是 message getter / toString / Symbol.toPrimitive 会抛的对象,
|
|
3
|
+
* 或 `Error` 实例的 message getter 返回带会抛 length getter 的非字符串——裸写 `e instanceof Error ? e.message : String(e)`
|
|
4
|
+
* 在 catch 里自己抛,外层 catch 吞掉 setTerminal ⇒ run 卡 running、claim 悬挂到 reaper。这里**不抛、恒返字符串**。 */
|
|
5
|
+
export declare function errorText(e: unknown): string;
|
|
6
|
+
//# sourceMappingURL=err-text.d.ts.map
|
|
@@ -13,6 +13,10 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
13
13
|
readonly cls: "F";
|
|
14
14
|
readonly note: "design/383 S-1(applyMemoryOptOutGrant):本地 grant 腿故障时的**披露钩子**(onFault → warn 日志)自己抛了。钩子只是观测面:吞掉它保住的是「resolver 不因一行日志失败而 reject」(那会把一条腿的故障扩成整车故障 —— core 的 resolver-throw 降级臂作废同一次解析里 center 已答好的 allowWorkflows/allowFork);代价是**那一行披露丢了**,而 `allowMemoryOptOut` 键缺席这件事本身不受影响(fail-closed/fail-safe 极性由 core 按姿态判)。本计数让「披露钩子一直在炸」显形。";
|
|
15
15
|
};
|
|
16
|
+
readonly "server.memory-optout-verdict-absence.warn-hook-threw": {
|
|
17
|
+
readonly cls: "F";
|
|
18
|
+
readonly note: "S-76(applyGovernedVerdictAbsenceWarning):`governed` + center-only verdict 源的部署上,**首次**解析到 `allowMemoryOptOut` 缺席时那一行**一次性披露**钩子自己抛了。与上一条同族、同理由:钩子只是观测面,吞掉保住的是「resolver 不因一行日志失败而 reject」(core 的 resolver-throw 降级臂会作废同一次解析里 center 已答好的 allowWorkflows/allowFork);代价是**「center 从不发这个键」这件事少了一行运行期证据**(启动期那行 `memory_capture_governed_center_only_source` 仍在,拒/放的极性也不受影响 —— 缺席由 core 按姿态判 fail-closed)。本计数让「披露钩子一直在炸」显形。";
|
|
19
|
+
};
|
|
16
20
|
readonly "server.run-store.file-hydrate-unreadable": {
|
|
17
21
|
readonly cls: "P-DEBT";
|
|
18
22
|
readonly note: "件6(#43 案 [4281]):`FileRunStore.hydrate()` 的 `readdirSync(runs/)` 失败 ⇒ 引擎以**空账**起动。旧形是空 catch + return:data root 不可读(权限/挂载丢失/路径被换走)时零告警,此后每一次 getRun 恒 miss —— 而「恒 miss」正是 #43 现场看到的形,一次环境故障被伪装成「本来就没有这条 run」。方向问题在于吞掉之后被读成的是「证不出有行 ⇒ 没有行」,两句话不等价。**缺省不拒启是刻意保留的行为**,所以这条留作债:响亮化(error 级启动日志带 runs 绝对路径+异常文案)+ 本计数,让它在遥测里显形而不是只活在一行日志里。⚠️ 2026-08-29(S-46 件B)起终局格已兑现:部署可用 `RUN_STORE_STRICT_HYDRATE=on` 把这一格换成 **fail-closed 启动门**(拒启,遗言同样带绝对路径+异常文案);本计数因此只在**缺省 off** 档上产生 —— 读遥测时别把「计数为零」直接读成「没有部署踩到这条路」,也可能是它们开了拒启门、根本起不来。";
|
|
@@ -61,6 +65,10 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
61
65
|
readonly cls: "F";
|
|
62
66
|
readonly note: "被拒的 CC 导入 prepare(候选超帽)未能收掉 core 已落盘的那条 pending 审批记录。放行的最坏后果 = 共享库里留一条**永远没人要的** pending 行(零权限影响:没有票就兑不动它,而且它连确认都没过)。不留痕就没人知道清理面在漏,故记 F 类;真解是给 permission_rule_approval / permission_rule_ticket 加保留期清扫腿(已列后续件)。";
|
|
63
67
|
};
|
|
68
|
+
readonly "server.rule-offers.uncovered-detail-dropped": {
|
|
69
|
+
readonly cls: "F";
|
|
70
|
+
readonly note: "design/382 §3.5(S-44):batch offer 的 `uncoveredDetail` 明细座形不合(集外 reason / 非数组 / 行数与 count 座不等 / 超 MAX_UNCOVERED_DETAIL_ROWS)⇒ 只丢这一座、batch 本体照投。放行的最坏后果 = 卡上少几行「这段为什么还会问」的解释(count 座仍在,core 契约明写明细缺席不是断言);把整只 batch 判假才是错的方向(一只真可兑的合取批从卡上消失)。纯展示轴 ⇒ F 类;必须留痕,否则上游明细座漂形在遥测里零痕迹。";
|
|
71
|
+
};
|
|
64
72
|
readonly "server.rules.ticket-claim-release-failed": {
|
|
65
73
|
readonly cls: "F";
|
|
66
74
|
readonly note: "CC 规则导入票的**认领回滚**失败(认领之后的某一步没成 ⇒ 本该把认领放回去,而这次放回本身也抛了)。放行的最坏后果 = 这张票留在已认领态、属主这一轮不能重试 —— 恰好等于加认领回滚**之前**的行为,不是新损失;票随 TTL 自然消失,属主重走一次 prepare 即可拿新票(导入按 core 的设计幂等:同一条规则再兑付只是同一个 dot 的重放)。方向上没有任何权限被放宽(规则**没有**落地才走到这条臂),故 F 类;必须留痕,否则「票为什么突然不能用了」在遥测里没有任何痕迹。";
|
|
@@ -165,6 +173,38 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
165
173
|
readonly cls: "F";
|
|
166
174
|
readonly note: "S-38 裁 (c) 审计半场:local 车道 File ask 审计店(`approval-ask-audit-store.ts`)的**运行期** append(铸造/决议/腿闭/收敛四类行)失败(盘满/只读/fd 失效)⇒ 丢**这一条**审计行,审批路径照常。方向刻意 fail-open:审批可用性不押在审计盘上 —— 盘满时人还得能批,把一次审计写失败升级成审批失败是把「痕迹缺一条」换成「门整个不可用」,方向更坏。boot 期(mkdir/重放)失败则**拒启**(File 店族同律,store 头注成文),不经本 tag。放行的最坏后果 = 该 ask 在崩溃收敛读面上缺席或分臂失真(与 A-075.55 的零痕迹病同向)—— 所以必须留痕:「审计一直在漏写」与「没有审批发生」在读面上同形,不计数则病灶永不显形。detail 带行类与异常文案。";
|
|
167
175
|
};
|
|
176
|
+
readonly "server.run-terminal.redact-threw": {
|
|
177
|
+
readonly cls: "F";
|
|
178
|
+
readonly note: "四个发射座(errorMessage 终态文案 / 诊断文本 diag / 补丁头 patch-head / 工件 artifact,probe 前缀判别;普查 56 行):S-105 最终轮 [medium]:终态 errorMessage/blockedReason/remoteEnvFailures[].message 脱敏抛出(关键字/前缀臂对 ≥2^23 单段 RangeError,既有,S-106)⇒ 落固定占位 «redacted:oversized»——绝不落原文(安全轴 fail-closed),也绝不阻断终态翻转与 claim 释放(可用性)。方向刻意:一条超大错误体不得把 run 卡在 running。";
|
|
179
|
+
};
|
|
180
|
+
readonly "server.run-terminal.redact-capped": {
|
|
181
|
+
readonly cls: "F";
|
|
182
|
+
readonly note: "S-107:终态自由文本(errorMessage/blockedReason/remoteEnvFailures[].message/error 列/done·failed 行)长度 > REDACT_INPUT_CAP(64KB)⇒ 整段换 «redacted:oversized»(fail-closed 取证丢弃,不截断不扫);计数=多少条终态文案被整段丢弃";
|
|
183
|
+
};
|
|
184
|
+
readonly "server.run-terminal.artifact-capped": {
|
|
185
|
+
readonly cls: "F";
|
|
186
|
+
readonly note: "S-107:leader_run 行上的工件字节(salvaged[].patch / candidatePatch.patch / merge.candidatePatch / merge.rej)长度 > ARTIFACT_INPUT_CAP(256KB)⇒ 整份换 «redacted:oversized-artifact»(fail-closed 取证丢弃;原字节仍在 worker 分支/merge 咽喉);计数=多少份工件被整份丢弃";
|
|
187
|
+
};
|
|
188
|
+
readonly "server.run-terminal.diag-capped": {
|
|
189
|
+
readonly cls: "F";
|
|
190
|
+
readonly note: "S-107:诊断文本座(merge details/measure/grader trace、leader 日志座、planner 样本、journal 预览、workflow_run error cut)长度 > DIAG_INPUT_CAP(256KB)⇒ 整段换 «redacted:oversized-diagnostic» [len=N](fail-closed 取证丢弃;固定窗不 fail-closed 故不开窗);计数=多少段诊断文本被整段丢弃";
|
|
191
|
+
};
|
|
192
|
+
readonly "server.run-terminal.patch-head-cut": {
|
|
193
|
+
readonly cls: "F";
|
|
194
|
+
readonly note: "S-107:进模型的 worker 补丁(collapse 目标 / 冲突解决目标)> DIAG_INPUT_CAP(256KB)⇒ **不内联**,整份换 «redacted:oversized-patch-not-inlined» [len=N](第十三轮开的 256KB 头窗及其裁切守卫在五轮复审后撤回);计数=多少份补丁因超限未内联";
|
|
195
|
+
};
|
|
196
|
+
readonly "server.run-terminal.output-truncated": {
|
|
197
|
+
readonly cls: "F";
|
|
198
|
+
readonly note: "S-107:持久 error 脱后 UTF-8 字节 > 65,000(TiDB TEXT 65,535 字节)⇒ 脱后切并带标记(切在占位里不是泄漏);计数=多少条终态文案被字节封顶截断";
|
|
199
|
+
};
|
|
200
|
+
readonly "server.runs.terminal-ledger-write-failed": {
|
|
201
|
+
readonly cls: "F";
|
|
202
|
+
readonly note: "S-107(全量门 silent-degradation 棘轮抓出的五处静默 `catch {}`):run 终局链里**账本腿**(flush 缓冲 text/reasoning、append `failed` 行)与终态翻转(setTerminal)已不共命运——账本 INSERT 抛不再让 run 卡 running 到 reaper;但那条抛掉的账本行之前是**静默**吞的。detail `arm=` 四臂(flush-clean-cancel / append-clean-cancel / flush-catch / append-thrown-cancel / append-failed):缺的是 run_events 尾巴(缓冲文本或 failed 事件),run 行本身仍是 durable truth,读面/reaper 容忍缺失,故 F 类;计数=多少次终局账本写失败。";
|
|
203
|
+
};
|
|
204
|
+
readonly "server.redact.ppk-eat-to-end": {
|
|
205
|
+
readonly cls: "F";
|
|
206
|
+
readonly note: "S-107 第三十二轮重扫:PuTTY .ppk 容器扫描器(redact.ts redactPpkContainers)在「无合格终止 / 收口后同容器范围再见私钥字段 / 收口后到下一版本头之间夹非空白」三形下从头**吃到输入末尾**(fail-closed 取证丢弃)——终态 errorMessage 座上=整条错误文案变成 «redacted:private-key»,与 redactions_applied_total{private-key} 的诚实替换同形不可分,故要有自己的计数/探针;影子扫不计;计数=多少次整段吃尾。";
|
|
207
|
+
};
|
|
168
208
|
readonly "server.pg-pool.idle-client-error": {
|
|
169
209
|
readonly cls: "F";
|
|
170
210
|
readonly note: "S-55(test [5864] 独立复现):pg 连接池里一条连接被外部终止(pg_terminate_backend / 云端故障切换 / LB 空闲回收)或自身网络错误。**两态一 tag,detail 判别**(`state=idle` / `state=checked-out`;先例=park 墓碑行的两臂 detail 判别):①idle 态 —— node-postgres 语义(pg-pool@3.14.0 makeIdleListener):池在 emit 'error' **之前**已 `_remove` 掉该 client,坏连接自然淘汰,下次 acquire 发新连接,在飞查询零影响;修前 Pool 上无 'error' 监听 ⇒ EventEmitter 把事件转 throw ⇒ uncaughtException ⇒ 整进程 exit 1。②checked-out 态(codex R1-[high] 补,同批修)—— pg-pool 借出时摘 idle 监听(index.js:344),`pool.connect()` 显式持有的连接(事务)在 checkout 期死时 client 直接 emit 'error' 同样打死进程;守卫只观察:中毒 client 的后续查询由 pg 以 `_queryable=false` 响亮拒,归还时池淘汰;同一次 checkout 的死可能双发事件(FATAL 消息 + socket end),warn 逐枚、计数按 checkout 去重。放行的最坏后果 = 下一次 acquire 多付一次建连,故 F 类。必须留痕:「连接不断被外部杀」(故障切换风暴 / 回收策略过激 / 有人在库上清连接)与「一切正常」在服务面同形;每次事件另有逐次结构化 warn(`pg_pool_idle_client_error` / `pg_pool_checked_out_client_error`,含 code/severity),本计数让频率曲线可读。`pool.query()` 快路不在洞内(pg-pool 自带 checkout 期 once('error') 守卫)。mysql2 孪生(tidb-pool)**不同病**:PoolConnection 构造器恒挂 once('error') 库内吞并淘汰、借出期同在(坐标成文在 createTidbPool 头注),故无同族 tag。";
|
|
@@ -4,6 +4,10 @@ export const FAIL_OPEN_TAGS = {
|
|
|
4
4
|
cls: "F",
|
|
5
5
|
note: "design/383 S-1(applyMemoryOptOutGrant):本地 grant 腿故障时的**披露钩子**(onFault → warn 日志)自己抛了。钩子只是观测面:吞掉它保住的是「resolver 不因一行日志失败而 reject」(那会把一条腿的故障扩成整车故障 —— core 的 resolver-throw 降级臂作废同一次解析里 center 已答好的 allowWorkflows/allowFork);代价是**那一行披露丢了**,而 `allowMemoryOptOut` 键缺席这件事本身不受影响(fail-closed/fail-safe 极性由 core 按姿态判)。本计数让「披露钩子一直在炸」显形。",
|
|
6
6
|
},
|
|
7
|
+
"server.memory-optout-verdict-absence.warn-hook-threw": {
|
|
8
|
+
cls: "F",
|
|
9
|
+
note: "S-76(applyGovernedVerdictAbsenceWarning):`governed` + center-only verdict 源的部署上,**首次**解析到 `allowMemoryOptOut` 缺席时那一行**一次性披露**钩子自己抛了。与上一条同族、同理由:钩子只是观测面,吞掉保住的是「resolver 不因一行日志失败而 reject」(core 的 resolver-throw 降级臂会作废同一次解析里 center 已答好的 allowWorkflows/allowFork);代价是**「center 从不发这个键」这件事少了一行运行期证据**(启动期那行 `memory_capture_governed_center_only_source` 仍在,拒/放的极性也不受影响 —— 缺席由 core 按姿态判 fail-closed)。本计数让「披露钩子一直在炸」显形。",
|
|
10
|
+
},
|
|
7
11
|
"server.run-store.file-hydrate-unreadable": {
|
|
8
12
|
cls: "P-DEBT",
|
|
9
13
|
note: "件6(#43 案 [4281]):`FileRunStore.hydrate()` 的 `readdirSync(runs/)` 失败 ⇒ 引擎以**空账**起动。旧形是空 catch + return:data root 不可读(权限/挂载丢失/路径被换走)时零告警,此后每一次 getRun 恒 miss —— 而「恒 miss」正是 #43 现场看到的形,一次环境故障被伪装成「本来就没有这条 run」。方向问题在于吞掉之后被读成的是「证不出有行 ⇒ 没有行」,两句话不等价。**缺省不拒启是刻意保留的行为**,所以这条留作债:响亮化(error 级启动日志带 runs 绝对路径+异常文案)+ 本计数,让它在遥测里显形而不是只活在一行日志里。⚠️ 2026-08-29(S-46 件B)起终局格已兑现:部署可用 `RUN_STORE_STRICT_HYDRATE=on` 把这一格换成 **fail-closed 启动门**(拒启,遗言同样带绝对路径+异常文案);本计数因此只在**缺省 off** 档上产生 —— 读遥测时别把「计数为零」直接读成「没有部署踩到这条路」,也可能是它们开了拒启门、根本起不来。",
|
|
@@ -52,6 +56,10 @@ export const FAIL_OPEN_TAGS = {
|
|
|
52
56
|
cls: "F",
|
|
53
57
|
note: "被拒的 CC 导入 prepare(候选超帽)未能收掉 core 已落盘的那条 pending 审批记录。放行的最坏后果 = 共享库里留一条**永远没人要的** pending 行(零权限影响:没有票就兑不动它,而且它连确认都没过)。不留痕就没人知道清理面在漏,故记 F 类;真解是给 permission_rule_approval / permission_rule_ticket 加保留期清扫腿(已列后续件)。",
|
|
54
58
|
},
|
|
59
|
+
"server.rule-offers.uncovered-detail-dropped": {
|
|
60
|
+
cls: "F",
|
|
61
|
+
note: "design/382 §3.5(S-44):batch offer 的 `uncoveredDetail` 明细座形不合(集外 reason / 非数组 / 行数与 count 座不等 / 超 MAX_UNCOVERED_DETAIL_ROWS)⇒ 只丢这一座、batch 本体照投。放行的最坏后果 = 卡上少几行「这段为什么还会问」的解释(count 座仍在,core 契约明写明细缺席不是断言);把整只 batch 判假才是错的方向(一只真可兑的合取批从卡上消失)。纯展示轴 ⇒ F 类;必须留痕,否则上游明细座漂形在遥测里零痕迹。",
|
|
62
|
+
},
|
|
55
63
|
"server.rules.ticket-claim-release-failed": {
|
|
56
64
|
cls: "F",
|
|
57
65
|
note: "CC 规则导入票的**认领回滚**失败(认领之后的某一步没成 ⇒ 本该把认领放回去,而这次放回本身也抛了)。放行的最坏后果 = 这张票留在已认领态、属主这一轮不能重试 —— 恰好等于加认领回滚**之前**的行为,不是新损失;票随 TTL 自然消失,属主重走一次 prepare 即可拿新票(导入按 core 的设计幂等:同一条规则再兑付只是同一个 dot 的重放)。方向上没有任何权限被放宽(规则**没有**落地才走到这条臂),故 F 类;必须留痕,否则「票为什么突然不能用了」在遥测里没有任何痕迹。",
|
|
@@ -156,6 +164,17 @@ export const FAIL_OPEN_TAGS = {
|
|
|
156
164
|
cls: "F",
|
|
157
165
|
note: "S-38 裁 (c) 审计半场:local 车道 File ask 审计店(`approval-ask-audit-store.ts`)的**运行期** append(铸造/决议/腿闭/收敛四类行)失败(盘满/只读/fd 失效)⇒ 丢**这一条**审计行,审批路径照常。方向刻意 fail-open:审批可用性不押在审计盘上 —— 盘满时人还得能批,把一次审计写失败升级成审批失败是把「痕迹缺一条」换成「门整个不可用」,方向更坏。boot 期(mkdir/重放)失败则**拒启**(File 店族同律,store 头注成文),不经本 tag。放行的最坏后果 = 该 ask 在崩溃收敛读面上缺席或分臂失真(与 A-075.55 的零痕迹病同向)—— 所以必须留痕:「审计一直在漏写」与「没有审批发生」在读面上同形,不计数则病灶永不显形。detail 带行类与异常文案。",
|
|
158
166
|
},
|
|
167
|
+
"server.run-terminal.redact-threw": {
|
|
168
|
+
cls: "F",
|
|
169
|
+
note: "四个发射座(errorMessage 终态文案 / 诊断文本 diag / 补丁头 patch-head / 工件 artifact,probe 前缀判别;普查 56 行):S-105 最终轮 [medium]:终态 errorMessage/blockedReason/remoteEnvFailures[].message 脱敏抛出(关键字/前缀臂对 ≥2^23 单段 RangeError,既有,S-106)⇒ 落固定占位 «redacted:oversized»——绝不落原文(安全轴 fail-closed),也绝不阻断终态翻转与 claim 释放(可用性)。方向刻意:一条超大错误体不得把 run 卡在 running。",
|
|
170
|
+
},
|
|
171
|
+
"server.run-terminal.redact-capped": { cls: "F", note: "S-107:终态自由文本(errorMessage/blockedReason/remoteEnvFailures[].message/error 列/done·failed 行)长度 > REDACT_INPUT_CAP(64KB)⇒ 整段换 «redacted:oversized»(fail-closed 取证丢弃,不截断不扫);计数=多少条终态文案被整段丢弃" },
|
|
172
|
+
"server.run-terminal.artifact-capped": { cls: "F", note: "S-107:leader_run 行上的工件字节(salvaged[].patch / candidatePatch.patch / merge.candidatePatch / merge.rej)长度 > ARTIFACT_INPUT_CAP(256KB)⇒ 整份换 «redacted:oversized-artifact»(fail-closed 取证丢弃;原字节仍在 worker 分支/merge 咽喉);计数=多少份工件被整份丢弃" },
|
|
173
|
+
"server.run-terminal.diag-capped": { cls: "F", note: "S-107:诊断文本座(merge details/measure/grader trace、leader 日志座、planner 样本、journal 预览、workflow_run error cut)长度 > DIAG_INPUT_CAP(256KB)⇒ 整段换 «redacted:oversized-diagnostic» [len=N](fail-closed 取证丢弃;固定窗不 fail-closed 故不开窗);计数=多少段诊断文本被整段丢弃" },
|
|
174
|
+
"server.run-terminal.patch-head-cut": { cls: "F", note: "S-107:进模型的 worker 补丁(collapse 目标 / 冲突解决目标)> DIAG_INPUT_CAP(256KB)⇒ **不内联**,整份换 «redacted:oversized-patch-not-inlined» [len=N](第十三轮开的 256KB 头窗及其裁切守卫在五轮复审后撤回);计数=多少份补丁因超限未内联" },
|
|
175
|
+
"server.run-terminal.output-truncated": { cls: "F", note: "S-107:持久 error 脱后 UTF-8 字节 > 65,000(TiDB TEXT 65,535 字节)⇒ 脱后切并带标记(切在占位里不是泄漏);计数=多少条终态文案被字节封顶截断" },
|
|
176
|
+
"server.runs.terminal-ledger-write-failed": { cls: "F", note: "S-107(全量门 silent-degradation 棘轮抓出的五处静默 `catch {}`):run 终局链里**账本腿**(flush 缓冲 text/reasoning、append `failed` 行)与终态翻转(setTerminal)已不共命运——账本 INSERT 抛不再让 run 卡 running 到 reaper;但那条抛掉的账本行之前是**静默**吞的。detail `arm=` 四臂(flush-clean-cancel / append-clean-cancel / flush-catch / append-thrown-cancel / append-failed):缺的是 run_events 尾巴(缓冲文本或 failed 事件),run 行本身仍是 durable truth,读面/reaper 容忍缺失,故 F 类;计数=多少次终局账本写失败。" },
|
|
177
|
+
"server.redact.ppk-eat-to-end": { cls: "F", note: "S-107 第三十二轮重扫:PuTTY .ppk 容器扫描器(redact.ts redactPpkContainers)在「无合格终止 / 收口后同容器范围再见私钥字段 / 收口后到下一版本头之间夹非空白」三形下从头**吃到输入末尾**(fail-closed 取证丢弃)——终态 errorMessage 座上=整条错误文案变成 «redacted:private-key»,与 redactions_applied_total{private-key} 的诚实替换同形不可分,故要有自己的计数/探针;影子扫不计;计数=多少次整段吃尾。" },
|
|
159
178
|
"server.pg-pool.idle-client-error": {
|
|
160
179
|
cls: "F",
|
|
161
180
|
note: "S-55(test [5864] 独立复现):pg 连接池里一条连接被外部终止(pg_terminate_backend / 云端故障切换 / LB 空闲回收)或自身网络错误。**两态一 tag,detail 判别**(`state=idle` / `state=checked-out`;先例=park 墓碑行的两臂 detail 判别):①idle 态 —— node-postgres 语义(pg-pool@3.14.0 makeIdleListener):池在 emit 'error' **之前**已 `_remove` 掉该 client,坏连接自然淘汰,下次 acquire 发新连接,在飞查询零影响;修前 Pool 上无 'error' 监听 ⇒ EventEmitter 把事件转 throw ⇒ uncaughtException ⇒ 整进程 exit 1。②checked-out 态(codex R1-[high] 补,同批修)—— pg-pool 借出时摘 idle 监听(index.js:344),`pool.connect()` 显式持有的连接(事务)在 checkout 期死时 client 直接 emit 'error' 同样打死进程;守卫只观察:中毒 client 的后续查询由 pg 以 `_queryable=false` 响亮拒,归还时池淘汰;同一次 checkout 的死可能双发事件(FATAL 消息 + socket end),warn 逐枚、计数按 checkout 去重。放行的最坏后果 = 下一次 acquire 多付一次建连,故 F 类。必须留痕:「连接不断被外部杀」(故障切换风暴 / 回收策略过激 / 有人在库上清连接)与「一切正常」在服务面同形;每次事件另有逐次结构化 warn(`pg_pool_idle_client_error` / `pg_pool_checked_out_client_error`,含 code/severity),本计数让频率曲线可读。`pool.query()` 快路不在洞内(pg-pool 自带 checkout 期 once('error') 守卫)。mysql2 孪生(tidb-pool)**不同病**:PoolConnection 构造器恒挂 once('error') 库内吞并淘汰、借出期同在(坐标成文在 createTidbPool 头注),故无同族 tag。",
|
|
@@ -256,7 +256,7 @@ export function createMetrics() {
|
|
|
256
256
|
m.counter("memory_knn_query_failed_total", "Memory KNN searchScored queries that failed at runtime (S22)");
|
|
257
257
|
m.counter("memory_dedup_folded_total", "Memory writes folded into an existing near-duplicate row (LOW)");
|
|
258
258
|
m.counter("memory_dedup_probe_failed_total", "Memory dedup KNN probes that failed and fell through to INSERT (LOW)");
|
|
259
|
-
m.counter("redactions_applied_total", "redactSecrets rewrites (S24), by pattern label");
|
|
259
|
+
m.counter("redactions_applied_total", "redactSecrets rewrites (S24), by pattern label and `arm` (S-88: full vs prefix-fragment arm — which RULE fired, NOT proof the text was upstream-truncated)");
|
|
260
260
|
m.counter("stale_cache_conflict_total", "Warm session-cache entries evicted by a cross-instance write while cached (S25)");
|
|
261
261
|
m.counter("breaker_writethrough_failed_total", "Circuit-breaker cross-replica write-through failures (LOW), by backend");
|
|
262
262
|
m.counter("web_search_bad_payload_total", "Web-search provider responses whose results field was not an array (LOW), by provider");
|