@sema-agent/server 7.69.0 → 7.71.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/README.md +4 -1
- package/README.zh-CN.md +4 -1
- package/USAGE.md +109 -10
- package/dist/approval-ask-machine.d.ts +1 -1
- package/dist/approval-card.d.ts +3 -2
- package/dist/approval-content-kind.d.ts +13 -7
- package/dist/approval-content-kind.js +3 -1
- package/dist/approval-reconciler.d.ts +0 -54
- package/dist/approval-reconciler.js +2 -2
- package/dist/approval.js +3 -2
- package/dist/bench/s1/live-deps.js +2 -0
- package/dist/boot/execution-env.js +12 -2
- package/dist/boot/memory-consolidation.d.ts +2 -1
- package/dist/boot/parked-revive-gate.d.ts +2 -0
- package/dist/boot/parked-revive-gate.js +15 -10
- package/dist/boot/resolve-spec.js +8 -3
- package/dist/boot/runner-deps.d.ts +50 -46
- package/dist/boot/runner-deps.js +12 -10
- package/dist/brain.d.ts +3 -1
- package/dist/brain.js +2 -1
- package/dist/budget.js +3 -2
- package/dist/config-catalog.d.ts +31 -2
- package/dist/config-catalog.js +44 -24
- package/dist/config-types.d.ts +31 -1
- package/dist/config-types.js +1 -0
- package/dist/config.js +15 -3
- package/dist/deployment-governance.d.ts +6 -3
- package/dist/deployment-governance.js +2 -1
- package/dist/execution-lane-caps.d.ts +51 -37
- package/dist/execution-lane-caps.js +23 -0
- package/dist/fleet/fleet-bus.d.ts +22 -1
- package/dist/fleet/fleet-bus.js +2 -1
- package/dist/http/routes/approvals-assistant.js +3 -12
- package/dist/http/routes/diagnostics.js +4 -0
- package/dist/http/routes/runs.js +2 -3
- package/dist/http/routes/tasks.js +22 -18
- package/dist/http/server.js +21 -7
- package/dist/index.d.ts +1 -1
- package/dist/leader/wire.js +4 -3
- package/dist/main.js +6 -4
- package/dist/memory-operator-faces.d.ts +1 -1
- package/dist/memory-scope.d.ts +24 -0
- package/dist/memory-scope.js +12 -3
- package/dist/observability/metrics.d.ts +4 -6
- package/dist/observability/metrics.js +5 -1
- package/dist/observability/permission-rule-events.d.ts +20 -0
- package/dist/observability/permission-rule-events.js +28 -0
- package/dist/observability/run-terminal-log.d.ts +13 -22
- package/dist/observability/run-terminal-log.js +187 -47
- package/dist/observability/tool-trace.d.ts +12 -0
- package/dist/observability/tool-trace.js +5 -1
- package/dist/orchestration/workflow-agent-session-index.d.ts +85 -2
- package/dist/orchestration/workflow-agent-session-index.js +38 -5
- package/dist/parked-decide.d.ts +40 -1
- package/dist/parked-decide.js +19 -2
- package/dist/plugins/checkpoint-store-sql.js +5 -5
- package/dist/plugins/local-checkpoint-store.js +3 -3
- package/dist/plugins/remote-env-adb.d.ts +11 -0
- package/dist/plugins/remote-env-adb.js +2 -0
- package/dist/plugins/remote-env-device.d.ts +9 -0
- package/dist/plugins/remote-env-device.js +3 -0
- package/dist/plugins/remote-env-e2b.d.ts +11 -0
- package/dist/plugins/remote-env-e2b.js +2 -0
- package/dist/plugins/remote-env-host.d.ts +11 -0
- package/dist/plugins/remote-env-host.js +2 -0
- package/dist/plugins/remote-env-k8s.d.ts +11 -0
- package/dist/plugins/remote-env-k8s.js +2 -0
- package/dist/plugins/remote-env-local-docker.d.ts +11 -0
- package/dist/plugins/remote-env-local-docker.js +2 -0
- package/dist/plugins/remote-env-ssh.d.ts +11 -0
- package/dist/plugins/remote-env-ssh.js +2 -0
- package/dist/plugins/store-backend.d.ts +36 -13
- package/dist/plugins/store-backend.js +12 -3
- package/dist/plugins/store-contracts.d.ts +54 -18
- package/dist/plugins/store-contracts.js +17 -30
- package/dist/plugins/workflow-journal-store-sql.d.ts +1 -1
- package/dist/plugins/workflow-journal-store-sql.js +3 -10
- package/dist/plugins/workflow-run-store-sql.d.ts +31 -9
- package/dist/plugins/workflow-run-store-sql.js +28 -1
- package/dist/posture-source.d.ts +23 -3
- package/dist/posture-source.js +1 -1
- package/dist/read-face-posture.d.ts +8 -1
- package/dist/read-face-posture.js +1 -0
- package/dist/run-cancel-context.d.ts +50 -1
- package/dist/run-cancel-context.js +22 -0
- package/dist/run-local.d.ts +14 -6
- package/dist/run-local.js +4 -1
- package/dist/runs.d.ts +0 -51
- package/dist/runs.js +40 -25
- package/dist/task-a2a.js +2 -2
- package/dist/task-mcp.js +2 -2
- package/dist/task-settings.d.ts +21 -8
- package/dist/task-settings.js +2 -2
- package/dist/tool-approval.d.ts +14 -8
- package/dist/tool-approval.js +3 -3
- package/dist/trace/core-keyset-guard.d.ts +135 -11
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +1 -0
- package/dist/trace/ledger-events.d.ts +22 -2
- package/dist/trace/ledger-events.js +6 -1
- package/dist/trace/ledger-sink.d.ts +10 -8
- package/dist/trace/ledger-sink.js +10 -2
- package/dist/trace/project.d.ts +24 -6
- package/dist/trace/project.js +5 -3
- package/dist/trace/redact.d.ts +28 -0
- package/dist/trace/redact.js +58 -30
- package/package.json +3 -3
package/dist/leader/wire.js
CHANGED
|
@@ -6,6 +6,7 @@ import { join } from "node:path";
|
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { Runner, combinePolicies, createDurableQuestionPolicy, QUESTION_AWAITS_RESUME, delimitUntrusted, hasDestroy, } from "@sema-agent/core";
|
|
8
8
|
import { RemoteContainerExecutionEnv } from "../plugins/remote-env-e2b.js";
|
|
9
|
+
import { E2B_SANDBOX_HOME } from "../execution-lane-caps.js";
|
|
9
10
|
import { createDurableAskPolicy } from "../approval.js";
|
|
10
11
|
import { presignS3Url } from "../plugins/s3-presign.js";
|
|
11
12
|
import { buildUploadScript, uploadStepSuffix, fetchUploadedDiff, withStaging } from "./diffup.js";
|
|
@@ -376,7 +377,7 @@ export function createLeaderRunner(cfg) {
|
|
|
376
377
|
}
|
|
377
378
|
if (!cfg.e2bApiKey)
|
|
378
379
|
throw new Error("leader wire: no envFactory+s3 (factory mode) and no e2bApiKey (static mode)");
|
|
379
|
-
const env = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs });
|
|
380
|
+
const env = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs, homeDir: E2B_SANDBOX_HOME });
|
|
380
381
|
if (!(await env.connect()).ok)
|
|
381
382
|
throw new Error(`connect failed for ${sub.workerId}`);
|
|
382
383
|
await sh(env)(`${body.seedCmd}; cd ${repo}; git checkout -q -b ${sub.branch}`);
|
|
@@ -410,7 +411,7 @@ export function createLeaderRunner(cfg) {
|
|
|
410
411
|
}
|
|
411
412
|
if (!cfg.e2bApiKey)
|
|
412
413
|
throw new Error("leader wire: no envFactory and no e2bApiKey");
|
|
413
|
-
const env = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs });
|
|
414
|
+
const env = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs, homeDir: E2B_SANDBOX_HOME });
|
|
414
415
|
if (!(await env.connect()).ok) {
|
|
415
416
|
await env.destroy().catch(() => { });
|
|
416
417
|
throw new Error("integ connect failed");
|
|
@@ -449,7 +450,7 @@ export function createLeaderRunner(cfg) {
|
|
|
449
450
|
else {
|
|
450
451
|
if (!cfg.e2bApiKey)
|
|
451
452
|
throw new Error("leader wire: repair-loop grader needs an envFactory or e2bApiKey");
|
|
452
|
-
const e2b = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs });
|
|
453
|
+
const e2b = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs, homeDir: E2B_SANDBOX_HOME });
|
|
453
454
|
if (!(await e2b.connect()).ok) {
|
|
454
455
|
await e2b.destroy().catch(() => { });
|
|
455
456
|
throw new Error("repair-loop grader connect failed");
|
package/dist/main.js
CHANGED
|
@@ -131,7 +131,8 @@ async function main() {
|
|
|
131
131
|
await configCenter.applyLocalRemoteExec();
|
|
132
132
|
const { backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore, taskAttachmentStore, mailboxStore, memoryExportBackend, memorySyncRunner, sessionStore, breakerState, usageWindowStore, memoryPosture, taskListLane, sqlEngineFacts, } = await openStores({ config, logger, metrics, localRoot });
|
|
133
133
|
const instanceId = uuidv7();
|
|
134
|
-
const
|
|
134
|
+
const sessionCaptureRecords = backend?.sessionCaptureRecords();
|
|
135
|
+
const sessionCaptureRecordStore = sessionCaptureRecords?.();
|
|
135
136
|
const memoryBundleFaces = memoryEngine ? createMemoryBundleFaces(memoryEngine, { ...(sessionCaptureRecordStore !== undefined ? { captureRecordStore: sessionCaptureRecordStore } : {}) }) : undefined;
|
|
136
137
|
const memoryComplianceFaces = memoryEngine
|
|
137
138
|
? createMemoryComplianceFaces(memoryEngine, {
|
|
@@ -271,8 +272,8 @@ async function main() {
|
|
|
271
272
|
sessionShellGateExplicitlyOff: sessionShellGateRegistry.explicitlyOff,
|
|
272
273
|
config, logger, metrics, localRoot, promptSource: configCenter.promptSource, rosterStore, backgroundAgentStore, mailboxStore, usageWindowStore, brain,
|
|
273
274
|
pricing, tracer, outcomeSink, elicitation, question, toolApproval, sessionStore, memoryEngine,
|
|
274
|
-
|
|
275
|
-
memorySyncRunner, toolResultStore: runnerOffloadStore, sessionPolicyStore, runtimeCapsResolver, fileHistoryStore,
|
|
275
|
+
sessionCaptureRecords,
|
|
276
|
+
memorySyncRunner, toolResultStore: runnerOffloadStore, checkpointStore, sessionPolicyStore, runtimeCapsResolver, fileHistoryStore,
|
|
276
277
|
legacyRewindBoundaryProbe: backend?.legacyRewindBoundaryProbe ? backend.legacyRewindBoundaryProbe.bind(backend) : undefined,
|
|
277
278
|
permissionRuleStore: permissionRuleProvider,
|
|
278
279
|
executionEnvFactory, lspManager, fleetBus, deploymentHooks, workflowRunStore, workflowJournalStore,
|
|
@@ -330,6 +331,7 @@ async function main() {
|
|
|
330
331
|
const subRunnerSessions = new ForkRoutingSessionStore(sessionStore, new TtlSessionStore({ defaultTtlDays: 1 / 24 }));
|
|
331
332
|
const subRunnerDeps = {
|
|
332
333
|
...createSharedRunnerDeps({
|
|
334
|
+
metrics,
|
|
333
335
|
config,
|
|
334
336
|
logger,
|
|
335
337
|
hands: commitHands,
|
|
@@ -345,6 +347,7 @@ async function main() {
|
|
|
345
347
|
rosterStore,
|
|
346
348
|
deploymentHooks,
|
|
347
349
|
toolResultStore: runnerOffloadStore,
|
|
350
|
+
checkpointStore,
|
|
348
351
|
sessionPolicyStore,
|
|
349
352
|
usageWindowStore,
|
|
350
353
|
orgMemoryAdmission,
|
|
@@ -356,7 +359,6 @@ async function main() {
|
|
|
356
359
|
loadProjectMemory: runnerDeps.loadProjectMemory ? runnerDeps.loadProjectMemory : undefined,
|
|
357
360
|
probeInstructionSources: runnerDeps.probeInstructionSources ? runnerDeps.probeInstructionSources : undefined,
|
|
358
361
|
onError: runnerDeps.onError ? runnerDeps.onError : undefined,
|
|
359
|
-
checkpointStore: checkpointStore ? checkpointStore : undefined,
|
|
360
362
|
};
|
|
361
363
|
const subRunner = new Runner(subRunnerDeps);
|
|
362
364
|
const handslessSubRunner = new Runner(withoutExecutionEnv(subRunnerDeps));
|
|
@@ -227,7 +227,7 @@ export interface SessionMemoryStatusFace {
|
|
|
227
227
|
*
|
|
228
228
|
* `captureRecordStore` = [ref]② 的 SQL 载体(main.ts 单实例,与 Runner/其余引擎同源 —— 全站同批注入律,
|
|
229
229
|
* 机器钉 capture-optout-lane ②):`captureOptedOut` / `optOutSource` 两键经它读,多副本部署上答的才是
|
|
230
|
-
*
|
|
230
|
+
* 跨副本的真记录。S-215 起 local 车道在引擎接线时也供(core 文件三腿,同一只控制面,单机字节不变);缺席 ⇒ 记忆面暗。
|
|
231
231
|
*
|
|
232
232
|
* 面**不抛**(core 契约:sessionMemoryStatus never throws —— 每个事实源各自 catch 折缺席键);
|
|
233
233
|
* 不接 onIncident:这条只读路径上今天零 sink 站点,而且缺席键本身就是它的披露形。
|
package/dist/memory-scope.d.ts
CHANGED
|
@@ -104,6 +104,30 @@ export declare function memoryEngineRootFor(config: Pick<ServiceConfig, "memoryE
|
|
|
104
104
|
* 所以它的根必须走这条链而不是 `memoryEngineRootFor` 的分派(codex 修复验证轮 [medium]:分派会把 run-local 的
|
|
105
105
|
* 记忆搬到 `<localDataRoot>/memory-work`,存量记忆与控制面升级后隐形)。 */
|
|
106
106
|
export declare function fileMemoryEngineRoot(config: Pick<ServiceConfig, "memoryEngineDir">, fallbackRoot?: string): string;
|
|
107
|
+
/** 本部署 file 记忆引擎的**平面三坐标**(根 / 记忆目录 / 控制面目录)—— {@link fileMemoryPlaneFor} 一次算齐。 */
|
|
108
|
+
export interface FileMemoryPlane {
|
|
109
|
+
readonly root: string;
|
|
110
|
+
readonly memoryDir: string;
|
|
111
|
+
readonly controlDir: string;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* S-215(B-085)—— **file 记忆引擎平面的单铸点**,兼这条腿的**接线门**。
|
|
115
|
+
*
|
|
116
|
+
* 为什么是一只函数而不是两处各自推导:平面坐标有**两个**消费者 —— ① {@link memoryEngineBackendFor}
|
|
117
|
+
* 钉给 `FileMemoryEngineBackend` 的 `memoryDir` + `controlDir`;② local 车道的 capture opt-out **记录店**
|
|
118
|
+
* (`StoreBackend.sessionCaptureRecords()` 喂 core 的 `fileSessionCaptureRecordStore(controlDir)`)。
|
|
119
|
+
* 两者必须是**同一只目录**,否则声明写进 A、引擎读 B ⇒ 一条被「受理」的隐私声明在引擎面根本不存在,
|
|
120
|
+
* 那比一句诚实的 409 更坏。两处各写一份推导链 = 哪天根链改了只改一处的必然漂移,所以推导(根 → 记忆目录 →
|
|
121
|
+
* 控制面目录,三步都在这里、只写一遍)与门合并在此(合并复审 R37:此前 `join(root, "memory")` 在本函数与
|
|
122
|
+
* `memoryEngineBackendFor` 各写一份 —— 记忆目录规则有了第二个写者,现已收回一处)。
|
|
123
|
+
*
|
|
124
|
+
* 返回 `undefined` ⇔ 本部署上 file 记忆引擎**不接线**(多租户 / `MEMORY_ENGINE=off` / 平面分裂车道未 allow)。
|
|
125
|
+
* 门与 `memoryEngineBackendFor` 逐字同源(它就读本函数),所以「有引擎 ⇔ 有控制面 ⇔ 供记录店」三者
|
|
126
|
+
* 在构造上不可能分家。
|
|
127
|
+
*/
|
|
128
|
+
export declare function fileMemoryPlaneFor(config: ServiceConfig, fallbackRoot?: string): FileMemoryPlane | undefined;
|
|
129
|
+
/** 控制面目录那一坐标(local 车道 capture 记录店的依托)—— 只是 {@link fileMemoryPlaneFor} 的投影,不是第二条推导。 */
|
|
130
|
+
export declare function fileMemoryControlDirFor(config: ServiceConfig, fallbackRoot?: string): string | undefined;
|
|
107
131
|
export declare function memoryEngineBackendFor(config: ServiceConfig, fallbackRoot?: string): {
|
|
108
132
|
backend: FileMemoryEngineBackend;
|
|
109
133
|
root: string;
|
package/dist/memory-scope.js
CHANGED
|
@@ -68,14 +68,23 @@ export function memoryEngineRootFor(config, fallbackRoot) {
|
|
|
68
68
|
export function fileMemoryEngineRoot(config, fallbackRoot) {
|
|
69
69
|
return resolveMemoryEngineRoot(config.memoryEngineDir ?? fallbackRoot);
|
|
70
70
|
}
|
|
71
|
-
export function
|
|
71
|
+
export function fileMemoryPlaneFor(config, fallbackRoot) {
|
|
72
72
|
if (config.requirePrincipal === true || !config.memoryEngineEnabled)
|
|
73
73
|
return undefined;
|
|
74
74
|
if (memoryEngineRemoteLanePosture(config)?.posture === "dark")
|
|
75
75
|
return undefined;
|
|
76
76
|
const root = fileMemoryEngineRoot(config, fallbackRoot);
|
|
77
|
-
const
|
|
78
|
-
return {
|
|
77
|
+
const memoryDir = join(root, "memory");
|
|
78
|
+
return { root, memoryDir, controlDir: deriveControlPlaneDir(root, memoryDir) };
|
|
79
|
+
}
|
|
80
|
+
export function fileMemoryControlDirFor(config, fallbackRoot) {
|
|
81
|
+
return fileMemoryPlaneFor(config, fallbackRoot)?.controlDir;
|
|
82
|
+
}
|
|
83
|
+
export function memoryEngineBackendFor(config, fallbackRoot) {
|
|
84
|
+
const plane = fileMemoryPlaneFor(config, fallbackRoot);
|
|
85
|
+
if (plane === undefined)
|
|
86
|
+
return undefined;
|
|
87
|
+
return { backend: new FileMemoryEngineBackend(plane.memoryDir, { controlDir: plane.controlDir }), root: plane.root };
|
|
79
88
|
}
|
|
80
89
|
export function memorySpecForRequest(scope, memoryWrite, defaultScopes, originPolicy, memoryCapture) {
|
|
81
90
|
if (!scope)
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tiny in-process metrics registry with Prometheus text exposition — no dependency.
|
|
3
|
-
*
|
|
4
|
-
* Per-instance (each container exposes its own counters); aggregate across instances at the scraper.
|
|
5
|
-
* Supports labeled counters, gauges (static + dynamic), and duration histograms.
|
|
6
|
-
*/
|
|
7
1
|
export type Labels = Record<string, string>;
|
|
8
2
|
export declare class Metrics {
|
|
9
3
|
private counters;
|
|
@@ -51,6 +45,10 @@ export interface MetricsSummary {
|
|
|
51
45
|
rateLimited: number;
|
|
52
46
|
costQuotaRejected: number;
|
|
53
47
|
budgetExceeded: Record<string, number>;
|
|
48
|
+
/** 答后撞顶(walltime/turns)的计数。⚠️ 与 {@link budgetExceeded} **口径不同**:本枚在 deps 级通告席上铸,
|
|
49
|
+
* 覆盖**每一条**引擎 run(后台子代 / 委派嵌套腿 / leader 车道);`budgetExceeded` 只由 HTTP 提交腿的
|
|
50
|
+
* `finalizeTaskResult` 递增(= 顶层 HTTP 提交的 run)。两枚相加只在后者那个较窄的总体里成立。 */
|
|
51
|
+
limitCeilingAfterAnswer: Record<string, number>;
|
|
54
52
|
degraded: Record<string, number>;
|
|
55
53
|
degenerate: Record<string, number>;
|
|
56
54
|
memoryConsolidationOps: Record<string, number>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildPermissionRuleEventsHelp } from "./permission-rule-events.js";
|
|
1
2
|
const DEFAULT_BUCKETS = [0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60];
|
|
2
3
|
function labelKey(labels) {
|
|
3
4
|
return Object.keys(labels)
|
|
@@ -173,6 +174,7 @@ export class Metrics {
|
|
|
173
174
|
rateLimited: ctotal("rate_limited_total"),
|
|
174
175
|
costQuotaRejected: ctotal("cost_quota_rejected_total"),
|
|
175
176
|
budgetExceeded: cby("budget_exceeded_total", "code"),
|
|
177
|
+
limitCeilingAfterAnswer: cby("limit_ceiling_after_answer_total", "origin"),
|
|
176
178
|
degraded: cby("degraded_total", "reason"),
|
|
177
179
|
degenerate: cby("degenerate_total", "class"),
|
|
178
180
|
memoryConsolidationOps: cby("memory_consolidation_ops_total", "op"),
|
|
@@ -214,6 +216,7 @@ export function createMetrics() {
|
|
|
214
216
|
]);
|
|
215
217
|
m.counter("tool_calls_total", "Tool invocations by name and outcome");
|
|
216
218
|
m.counter("budget_exceeded_total", "Tasks failed by a budget/limit ceiling, by errorCode");
|
|
219
|
+
m.counter("limit_ceiling_after_answer_total", "Runs where a walltime/turns ceiling fired AFTER the run had already answered (engine 7.13.0: these end completed with NO limits.* code). Dual of budget_exceeded_total, but NOT over the same population: this one is minted on the deps-level notice sink so it sees EVERY engine run (background children, delegated/nested legs, leader lanes); budget_exceeded_total is incremented only by the HTTP submit legs finalizeTaskResult, i.e. top-level HTTP-submitted runs. Sum them only within that narrower population; see the forwarder comment, by origin");
|
|
217
220
|
m.counter("cost_quota_rejected_total", "Tasks refused because the principal is over its cumulative cost quota");
|
|
218
221
|
m.counter("degraded_total", "Tasks degraded to a cheaper model (1.40), by reason (budget/rate_limit/breaker_open)");
|
|
219
222
|
m.counter("task_model_not_allowlisted_resume_total", "Resumed legs whose model/compactionModel is outside the @-mention allowlist (allowed to keep running by design — the allowlist gates fresh picks only)");
|
|
@@ -260,8 +263,9 @@ export function createMetrics() {
|
|
|
260
263
|
m.counter("stale_cache_conflict_total", "Warm session-cache entries evicted by a cross-instance write while cached (S25)");
|
|
261
264
|
m.counter("breaker_writethrough_failed_total", "Circuit-breaker cross-replica write-through failures (LOW), by backend");
|
|
262
265
|
m.counter("web_search_bad_payload_total", "Web-search provider responses whose results field was not an array (LOW), by provider");
|
|
266
|
+
m.counter("auto_mode_classifier_unavailable_total", "Auto-mode gate denials caused by a classifier OUTAGE, by cause (core AUTO_MODE_UNAVAILABLE_CAUSES: error/timeout; 'other' = this build cannot name the word). Distinct from permission_denied_total{deniedBy=\"classifier\"}, which also counts genuine safety blocks — a spike here means the classifier itself is failing (gateway/credential fault, or too slow), not that the model kept asking for dangerous things");
|
|
263
267
|
m.counter("permission_denied_total", "Tool-gate denials by the LAYER whose verdict is the deny (core's DeniedBy: policy/hook/org/classifier/plan_mode/compliance/write_protection/ask_resolution/persisted_rule; 'other' = this build cannot name the layer; label is `deniedBy` since server 7.64.0 — it was `source` over core's retired PermissionDeniedSource; goal B4 always-on deny meter)");
|
|
264
|
-
m.counter("permission_rule_events_total",
|
|
268
|
+
m.counter("permission_rule_events_total", buildPermissionRuleEventsHelp());
|
|
265
269
|
m.counter("compaction_events_total", "Compaction lifecycle events by outcome (started/completed/failed/skipped) and trigger");
|
|
266
270
|
m.counter("hook_llm_calls_total", "prompt/agent hook entries that completed a model call (hooks 3b), by type");
|
|
267
271
|
m.counter("auth_bridge_verify_total", "Registry SSO JWT verifications (auth-bridge), by outcome");
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** 计量面的 `event` 词表(顺序 = HELP 行里的出现顺序)。 */
|
|
2
|
+
export declare const PERMISSION_RULE_EVENT_TABLE: readonly [{
|
|
3
|
+
readonly event: "persisted_rule_allowed";
|
|
4
|
+
readonly meaning: "a persisted allow rule authorized the call and nobody was asked";
|
|
5
|
+
}, {
|
|
6
|
+
readonly event: "rule_store_unreadable";
|
|
7
|
+
readonly meaning: "the rule store failed to read, so the call was adjudicated with ZERO rules (expect an approval storm whose cause is the store, not the model)";
|
|
8
|
+
}, {
|
|
9
|
+
readonly event: "read_only_allowed";
|
|
10
|
+
readonly meaning: "the engine's read-only shell verdict cleared an ask that was NOT an operator-written rule (core decisionReason \"read_only\"; an operator-declared ask carries matchedAskRule and is never cleared this way)";
|
|
11
|
+
}];
|
|
12
|
+
/** 词表里的一个词(闭集)。 */
|
|
13
|
+
export type PermissionRuleEventWord = (typeof PERMISSION_RULE_EVENT_TABLE)[number]["event"];
|
|
14
|
+
/** 这一帧是否进 `permission_rule_events_total`(判别式与词表同源,不再手抄第二串 `===`)。 */
|
|
15
|
+
export declare function isPermissionRuleEventKind(kind: string): boolean;
|
|
16
|
+
/** 帧名 → `event` 标签值。调用方须先过 {@link isPermissionRuleEventKind}。 */
|
|
17
|
+
export declare function permissionRuleEventWordOf(kind: string): string;
|
|
18
|
+
/** `/metrics` 的 HELP 行文案(从词表派生 —— 加词自动出现,不存在「第二份会漂的散文」)。 */
|
|
19
|
+
export declare function buildPermissionRuleEventsHelp(): string;
|
|
20
|
+
//# sourceMappingURL=permission-rule-events.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const PERMISSION_TRACE_PREFIX = "permission.";
|
|
2
|
+
export const PERMISSION_RULE_EVENT_TABLE = [
|
|
3
|
+
{
|
|
4
|
+
event: "persisted_rule_allowed",
|
|
5
|
+
meaning: "a persisted allow rule authorized the call and nobody was asked",
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
event: "rule_store_unreadable",
|
|
9
|
+
meaning: "the rule store failed to read, so the call was adjudicated with ZERO rules (expect an approval storm whose cause is the store, not the model)",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
event: "read_only_allowed",
|
|
13
|
+
meaning: "the engine's read-only shell verdict cleared an ask that was NOT an operator-written rule (core decisionReason \"read_only\"; an operator-declared ask carries matchedAskRule and is never cleared this way)",
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
const PERMISSION_RULE_EVENT_KINDS = new Set(PERMISSION_RULE_EVENT_TABLE.map((r) => `${PERMISSION_TRACE_PREFIX}${r.event}`));
|
|
17
|
+
export function isPermissionRuleEventKind(kind) {
|
|
18
|
+
return PERMISSION_RULE_EVENT_KINDS.has(kind);
|
|
19
|
+
}
|
|
20
|
+
export function permissionRuleEventWordOf(kind) {
|
|
21
|
+
return kind.slice(PERMISSION_TRACE_PREFIX.length);
|
|
22
|
+
}
|
|
23
|
+
export function buildPermissionRuleEventsHelp() {
|
|
24
|
+
return ("Permission allow-layer attribution events by event — the ONE answer to \"why was I not asked this time?\" (" +
|
|
25
|
+
PERMISSION_RULE_EVENT_TABLE.map((r) => `${r.event} = ${r.meaning}`).join("; ") +
|
|
26
|
+
")");
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=permission-rule-events.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type RunRecord } from "../plugins/store-contracts.js";
|
|
2
2
|
/** 一次终局写口交给观测面的原料(store 侧装配,**未脱敏** —— 脱敏在本模块内做,见 {@link notifyRunTerminal})。 */
|
|
3
3
|
export interface RunTerminalRecord {
|
|
4
4
|
/** 这条 run 的账本 id = `GET /v1/runs/:id` 的 id(本仓 run 行主键名为 taskId,同一个值)。 */
|
|
@@ -20,8 +20,9 @@ export type RunTerminalObserver = (level: "error" | "info", fields: Record<strin
|
|
|
20
20
|
/**
|
|
21
21
|
* 终局行的级别判据。`undefined` = 不打行。
|
|
22
22
|
*
|
|
23
|
-
* `blocked`
|
|
24
|
-
* `
|
|
23
|
+
* 一句话:**非成功终局才出声**(`failed` / `blocked`),用户主动取消降 `info`。词的分类不在本模块做 ——
|
|
24
|
+
* 它的唯一属主是 `plugins/store-contracts.ts` 的 `RUN_STATUS_CLASS` 表(S-213③:本模块此前手抄了一张
|
|
25
|
+
* 穷举 switch,并在 `blocked` 那一格给出与 fleet 读面互斥的答案)。表的编译期双向钉在属主处。
|
|
25
26
|
*/
|
|
26
27
|
export declare function runTerminalLogLevel(status: RunRecord["status"], errorCode: string | undefined): "error" | "info" | undefined;
|
|
27
28
|
/** 装配点(`main.ts` 唯一)。传 `undefined` 卸载(测试隔离)。 */
|
|
@@ -52,26 +53,15 @@ export declare const REDACT_INPUT_CAP = 65536;
|
|
|
52
53
|
* 脱敏**之后**按字节封顶(占位已替换完,切在占位中间不是泄漏)并带标记。 */
|
|
53
54
|
export declare const REDACT_OUTPUT_MAX_BYTES = 65000;
|
|
54
55
|
export declare function redactErrorMessage(raw: string | undefined): string | undefined;
|
|
55
|
-
/**
|
|
56
|
-
*
|
|
57
|
-
|
|
58
|
-
*
|
|
59
|
-
* 🔴 S-136(core 7.6.0 [ref] D-8):自由文本座**搬家了** —— 失败文案与阻塞理由从顶层平面键
|
|
60
|
-
* (`errorMessage` / `blockedReason`)搬进因由 `terminal` 的两条臂(`failed.message` / `blocked.reason`)。
|
|
61
|
-
* 本函数**两种形都认**,而这不是「兼容读臂」(codex 对抗复审 r1 [high],亲读验真后**改判**;首版删掉了旧键
|
|
62
|
-
* 臂,那是错的):本函数是**读盘面**,而 `task_run.result` / `task_event(done).result` 里躺着 7.63.0 及以前
|
|
63
|
-
* 写下的**真字节**,它们本来就是平面形。写口只剩因由一条(没有任何东西会再写出旧形),所以这里不存在
|
|
64
|
-
* 「两个写者」;而不认旧形的代价是**可复现的凭据回放**:上线前落盘的原文经 `GET /v1/runs/:id/events` 重新
|
|
65
|
-
* 明文外发(读面这一层纵深正是为那批行存在的)。⇒ 规则仍只有一条:**读盘认盘上真有的形,写盘只写一种形**。
|
|
66
|
-
*
|
|
67
|
-
* 读面也走本函数,所以 `terminal` 形上取 `unknown`:旧持久 blob 与非 core 的 Runner 实现给的形不可信,
|
|
68
|
-
* 运行期逐臂判形、不抛(与 `remoteEnvFailures` / `editedFiles` 两座同款不信任)。
|
|
69
|
-
*/
|
|
56
|
+
/** {@link redactTerminalResult} 会碰的全部顶层键 = 现行形的 `"redacted"` 行 ∪ 旧形平面键;
|
|
57
|
+
* `memoRedactTerminalResult` 的记忆键由它派生(两处同源,见 `RESULT_KEY_TREATMENT` 顶注)。 */
|
|
58
|
+
export declare const REDACTABLE_RESULT_LEAVES: readonly string[];
|
|
70
59
|
export declare function redactTerminalResult<T extends {
|
|
71
60
|
terminal?: unknown;
|
|
72
|
-
/** 旧形(≤7.63.0)
|
|
61
|
+
/** 旧形(≤7.63.0)的三个顶层座;形上取 `unknown` —— 读盘面对盘上字节不作假设。 */
|
|
73
62
|
errorMessage?: unknown;
|
|
74
63
|
blockedReason?: unknown;
|
|
64
|
+
checkpointGate?: unknown;
|
|
75
65
|
remoteEnvFailures?: readonly {
|
|
76
66
|
message?: string | undefined;
|
|
77
67
|
}[] | undefined;
|
|
@@ -94,9 +84,10 @@ export declare function redactWorkerReportError<T extends {
|
|
|
94
84
|
error?: string | undefined;
|
|
95
85
|
checkpointGate?: unknown;
|
|
96
86
|
}>(report: T): T;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
*
|
|
87
|
+
export declare function redactRiskDescriptor(rd: unknown): unknown;
|
|
88
|
+
/** `CheckpointGate`(core 类型)整块上 wire —— `reason` 与 `riskDescriptor` 的叶族是自由文本
|
|
89
|
+
* ([ref] 实测 summary 带 PEM 明文;[ref] R3 登记;[ref] 把它扩到结果/账本两面)。逐键**按表**处置,
|
|
90
|
+
* 叶表属主见 {@link redactRiskDescriptor}。非对象形原样返回(同一引用);字节没变也回同一引用。 */
|
|
100
91
|
export declare function redactCheckpointGate(gate: unknown): unknown;
|
|
101
92
|
/** 工件字节(patch / *.rej 拒绝块):codex r6 [high]——拒绝块能带 `git clone https://u:TOKEN@host` 或 `_authToken=` 行,
|
|
102
93
|
* 「工件保真」不能让原凭据上元数据读面。做**原位密钥形替换**(与预览同一张规则表,字节只在密钥处变)、**不截断**;
|
|
@@ -1,24 +1,14 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { redactSecrets } from "../trace/redact.js";
|
|
3
|
+
import { isParkLedgerEventType } from "../trace/ledger-events.js";
|
|
4
|
+
import { isUnsuccessfulRunStatus } from "../plugins/store-contracts.js";
|
|
3
5
|
import { errorText } from "./err-text.js";
|
|
4
6
|
import { recordFailOpen } from "./fail-open.js";
|
|
5
7
|
const MESSAGE_CAP = 1024;
|
|
6
8
|
export function runTerminalLogLevel(status, errorCode) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
case "blocked":
|
|
11
|
-
case "completed":
|
|
12
|
-
case "running":
|
|
13
|
-
case "suspended":
|
|
14
|
-
case "needs_review":
|
|
15
|
-
return undefined;
|
|
16
|
-
default: {
|
|
17
|
-
const unhandled = status;
|
|
18
|
-
void unhandled;
|
|
19
|
-
return undefined;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
9
|
+
if (!isUnsuccessfulRunStatus(status))
|
|
10
|
+
return undefined;
|
|
11
|
+
return errorCode === "cancelled" ? "info" : "error";
|
|
22
12
|
}
|
|
23
13
|
let observer;
|
|
24
14
|
export function setRunTerminalObserver(fn) {
|
|
@@ -75,6 +65,37 @@ export function redactErrorMessage(raw) {
|
|
|
75
65
|
}
|
|
76
66
|
}
|
|
77
67
|
const MAX_EDITED_FILES = 1000;
|
|
68
|
+
const RESULT_KEY_TREATMENT = {
|
|
69
|
+
terminal: "redacted",
|
|
70
|
+
remoteEnvFailures: "redacted",
|
|
71
|
+
editedFiles: "redacted",
|
|
72
|
+
taskId: "verbatim",
|
|
73
|
+
runId: "verbatim",
|
|
74
|
+
sessionId: "verbatim",
|
|
75
|
+
model: "verbatim",
|
|
76
|
+
effectiveReasoning: "verbatim",
|
|
77
|
+
result: "verbatim",
|
|
78
|
+
salvagedOutput: "verbatim",
|
|
79
|
+
apiFailure: "verbatim",
|
|
80
|
+
retryAfterMs: "verbatim",
|
|
81
|
+
degraded: "verbatim",
|
|
82
|
+
structuredOutput: "verbatim",
|
|
83
|
+
rewindNotes: "verbatim",
|
|
84
|
+
haltedOnUserRejection: "verbatim",
|
|
85
|
+
haltedByUser: "verbatim",
|
|
86
|
+
strandedHumanAnswers: "verbatim",
|
|
87
|
+
pendingApproval: "verbatim",
|
|
88
|
+
approvalWaitedMs: "verbatim",
|
|
89
|
+
effectiveReadFace: "verbatim",
|
|
90
|
+
effectiveReadDenyPatterns: "verbatim",
|
|
91
|
+
effectiveMemoryScopes: "verbatim",
|
|
92
|
+
stats: "verbatim",
|
|
93
|
+
};
|
|
94
|
+
const LEGACY_RESULT_LEAVES = ["errorMessage", "blockedReason", "checkpointGate"];
|
|
95
|
+
export const REDACTABLE_RESULT_LEAVES = [
|
|
96
|
+
...Object.entries(RESULT_KEY_TREATMENT).filter(([, t]) => t === "redacted").map(([k]) => k),
|
|
97
|
+
...LEGACY_RESULT_LEAVES,
|
|
98
|
+
];
|
|
78
99
|
export function redactTerminalResult(result) {
|
|
79
100
|
if (result === undefined || result === null)
|
|
80
101
|
return result;
|
|
@@ -89,14 +110,41 @@ export function redactTerminalResult(result) {
|
|
|
89
110
|
out.blockedReason = redactErrorMessage(r.blockedReason);
|
|
90
111
|
changed = true;
|
|
91
112
|
}
|
|
113
|
+
if (r.checkpointGate !== undefined) {
|
|
114
|
+
const legacyGate = redactCheckpointGate(r.checkpointGate);
|
|
115
|
+
if (legacyGate !== r.checkpointGate) {
|
|
116
|
+
out.checkpointGate = legacyGate;
|
|
117
|
+
changed = true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
92
120
|
if (r.terminal !== null && typeof r.terminal === "object") {
|
|
93
121
|
const t = r.terminal;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
122
|
+
let nt;
|
|
123
|
+
if (t.kind === "failed" && typeof t.message === "string")
|
|
124
|
+
nt = { ...t, message: redactErrorMessage(t.message) };
|
|
125
|
+
else if (t.kind === "blocked" && typeof t.reason === "string")
|
|
126
|
+
nt = { ...t, reason: redactErrorMessage(t.reason) };
|
|
127
|
+
const gateOf = (cause) => {
|
|
128
|
+
if (cause === null || typeof cause !== "object" || !("gate" in cause))
|
|
129
|
+
return undefined;
|
|
130
|
+
const g = cause.gate;
|
|
131
|
+
const ng = redactCheckpointGate(g);
|
|
132
|
+
return { gate: ng, changed: ng !== g };
|
|
133
|
+
};
|
|
134
|
+
const base = nt ?? t;
|
|
135
|
+
if (t.kind === "paused") {
|
|
136
|
+
const g = gateOf(base);
|
|
137
|
+
if (g?.changed === true)
|
|
138
|
+
nt = { ...base, gate: g.gate };
|
|
97
139
|
}
|
|
98
|
-
else if (t.kind === "
|
|
99
|
-
|
|
140
|
+
else if (t.kind === "failed") {
|
|
141
|
+
const np = base.nestedPause;
|
|
142
|
+
const g = gateOf(np);
|
|
143
|
+
if (g?.changed === true)
|
|
144
|
+
nt = { ...base, nestedPause: { ...np, gate: g.gate } };
|
|
145
|
+
}
|
|
146
|
+
if (nt !== undefined) {
|
|
147
|
+
out.terminal = nt;
|
|
100
148
|
changed = true;
|
|
101
149
|
}
|
|
102
150
|
}
|
|
@@ -136,6 +184,11 @@ export function redactLedgerEventData(type, data) {
|
|
|
136
184
|
return data;
|
|
137
185
|
return { ...data, errorMessage: redactErrorMessage(msg) };
|
|
138
186
|
}
|
|
187
|
+
if (isParkLedgerEventType(type)) {
|
|
188
|
+
const gate = data.gate;
|
|
189
|
+
const next = redactCheckpointGate(gate);
|
|
190
|
+
return next === gate ? data : { ...data, gate: next };
|
|
191
|
+
}
|
|
139
192
|
return data;
|
|
140
193
|
}
|
|
141
194
|
export function redactWorkerReportError(report) {
|
|
@@ -145,38 +198,123 @@ export function redactWorkerReportError(report) {
|
|
|
145
198
|
return report;
|
|
146
199
|
return { ...report, ...(error !== report.error ? { error } : {}), ...(gate !== report.checkpointGate ? { checkpointGate: gate } : {}) };
|
|
147
200
|
}
|
|
201
|
+
const RISK_DESCRIPTOR_TREATMENT = {
|
|
202
|
+
severity: "verbatim",
|
|
203
|
+
axes: "verbatim",
|
|
204
|
+
toolName: "verbatim",
|
|
205
|
+
shellGateDoctrine: "verbatim",
|
|
206
|
+
summary: "text",
|
|
207
|
+
touchedPaths: "text[]",
|
|
208
|
+
shadowedRule: "text",
|
|
209
|
+
probeReason: "text",
|
|
210
|
+
probeCause: "probe-cause",
|
|
211
|
+
};
|
|
212
|
+
const PROBE_CAUSE_TREATMENT = {
|
|
213
|
+
code: "verbatim",
|
|
214
|
+
roots: "operands",
|
|
215
|
+
further: "operands",
|
|
216
|
+
};
|
|
217
|
+
const PROBE_CAUSE_OPERANDS_TREATMENT = {
|
|
218
|
+
shown: "text[]",
|
|
219
|
+
total: "verbatim",
|
|
220
|
+
};
|
|
221
|
+
export function redactRiskDescriptor(rd) {
|
|
222
|
+
if (rd === null || typeof rd !== "object" || Array.isArray(rd))
|
|
223
|
+
return rd;
|
|
224
|
+
const r = rd;
|
|
225
|
+
let changed = false;
|
|
226
|
+
const str = (v) => { const o = redactErrorMessage(v) ?? ""; if (o !== v)
|
|
227
|
+
changed = true; return o; };
|
|
228
|
+
const strs = (v) => (Array.isArray(v) ? v.map((x) => (typeof x === "string" ? str(x) : x)) : v);
|
|
229
|
+
const out = { ...r };
|
|
230
|
+
for (const [key, treatment] of Object.entries(RISK_DESCRIPTOR_TREATMENT)) {
|
|
231
|
+
switch (treatment) {
|
|
232
|
+
case "verbatim":
|
|
233
|
+
break;
|
|
234
|
+
case "text":
|
|
235
|
+
if (typeof r[key] === "string")
|
|
236
|
+
out[key] = str(r[key]);
|
|
237
|
+
break;
|
|
238
|
+
case "text[]":
|
|
239
|
+
if (Array.isArray(r[key]))
|
|
240
|
+
out[key] = strs(r[key]);
|
|
241
|
+
break;
|
|
242
|
+
case "probe-cause": {
|
|
243
|
+
const pc = r[key];
|
|
244
|
+
if (pc === null || typeof pc !== "object" || Array.isArray(pc))
|
|
245
|
+
break;
|
|
246
|
+
const p = pc;
|
|
247
|
+
const np = { ...p };
|
|
248
|
+
for (const [ck, ct] of Object.entries(PROBE_CAUSE_TREATMENT)) {
|
|
249
|
+
if (ct === "verbatim")
|
|
250
|
+
continue;
|
|
251
|
+
const ops = p[ck];
|
|
252
|
+
if (ops === null || typeof ops !== "object" || Array.isArray(ops))
|
|
253
|
+
continue;
|
|
254
|
+
const o = ops;
|
|
255
|
+
const no = { ...o };
|
|
256
|
+
for (const [ok, ot] of Object.entries(PROBE_CAUSE_OPERANDS_TREATMENT)) {
|
|
257
|
+
if (ot === "text[]" && Array.isArray(o[ok]))
|
|
258
|
+
no[ok] = strs(o[ok]);
|
|
259
|
+
}
|
|
260
|
+
np[ck] = no;
|
|
261
|
+
}
|
|
262
|
+
out[key] = np;
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
default: {
|
|
266
|
+
const unhandled = treatment;
|
|
267
|
+
throw new Error(`unhandled leaf treatment: ${String(unhandled)}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return changed ? out : rd;
|
|
272
|
+
}
|
|
273
|
+
const CHECKPOINT_GATE_TREATMENT = {
|
|
274
|
+
kind: "verbatim",
|
|
275
|
+
reason: "text",
|
|
276
|
+
toolName: "verbatim",
|
|
277
|
+
riskDescriptor: "risk-descriptor",
|
|
278
|
+
safetyAxis: "verbatim",
|
|
279
|
+
realApproval: "verbatim",
|
|
280
|
+
resumeAfterMs: "verbatim",
|
|
281
|
+
};
|
|
148
282
|
export function redactCheckpointGate(gate) {
|
|
149
283
|
if (gate === null || typeof gate !== "object" || Array.isArray(gate))
|
|
150
284
|
return gate;
|
|
151
285
|
const g = gate;
|
|
152
|
-
let changed = false;
|
|
153
286
|
const out = { ...g };
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
if (
|
|
175
|
-
|
|
287
|
+
let changed = false;
|
|
288
|
+
for (const [key, treatment] of Object.entries(CHECKPOINT_GATE_TREATMENT)) {
|
|
289
|
+
switch (treatment) {
|
|
290
|
+
case "verbatim":
|
|
291
|
+
break;
|
|
292
|
+
case "text": {
|
|
293
|
+
const v = g[key];
|
|
294
|
+
if (typeof v !== "string")
|
|
295
|
+
break;
|
|
296
|
+
const next = redactErrorMessage(v) ?? "";
|
|
297
|
+
if (next !== v) {
|
|
298
|
+
out[key] = next;
|
|
299
|
+
changed = true;
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
case "risk-descriptor": {
|
|
304
|
+
if (!(key in g))
|
|
305
|
+
break;
|
|
306
|
+
const next = redactRiskDescriptor(g[key]);
|
|
307
|
+
if (next !== g[key]) {
|
|
308
|
+
out[key] = next;
|
|
309
|
+
changed = true;
|
|
310
|
+
}
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
default: {
|
|
314
|
+
const unhandled = treatment;
|
|
315
|
+
throw new Error(`unhandled gate field treatment: ${String(unhandled)}`);
|
|
176
316
|
}
|
|
177
|
-
nr.probeCause = np;
|
|
178
317
|
}
|
|
179
|
-
out.riskDescriptor = nr;
|
|
180
318
|
}
|
|
181
319
|
return changed ? out : gate;
|
|
182
320
|
}
|
|
@@ -366,9 +504,11 @@ export function memoRedactTerminalResult(seat, result) {
|
|
|
366
504
|
if (result === undefined || result === null)
|
|
367
505
|
return result;
|
|
368
506
|
const r = result;
|
|
369
|
-
if (
|
|
507
|
+
if (REDACTABLE_RESULT_LEAVES.every((k) => r[k] === undefined))
|
|
370
508
|
return result;
|
|
371
|
-
const leaves = {
|
|
509
|
+
const leaves = {};
|
|
510
|
+
for (const k of REDACTABLE_RESULT_LEAVES)
|
|
511
|
+
leaves[k] = r[k];
|
|
372
512
|
const redacted = memoRedactedRead(seat, leaves, (l) => redactTerminalResult(l));
|
|
373
513
|
if (redacted === leaves)
|
|
374
514
|
return result;
|
|
@@ -14,6 +14,18 @@ import type { Metrics } from "./metrics.js";
|
|
|
14
14
|
* 🔴 BREAKING(server 7.64.0,core 7.6.0 提货):标签名 `source` → `deniedBy`,词表从七词
|
|
15
15
|
* (`policy/hook/safety/shellGate/planMode/classifier/org`)换成八词
|
|
16
16
|
* (`policy/hook/org/classifier/plan_mode/compliance/write_protection/ask_resolution`)。仪表盘/告警规则要改。
|
|
17
|
+
*
|
|
18
|
+
* 🔴 **第二个计数器 `auto_mode_classifier_unavailable_total{cause}`(server 7.70.0,core 7.12.0 [ref])**
|
|
19
|
+
* —— 分类器**停摆**的常开信号。7.12.0 之前这件事有两条可观测路:熔断跳闸时的
|
|
20
|
+
* `auto_mode_breaker_open_total`,以及「`unavailable` 的 ask 会走到人面前」(审批 inbox 里看得见)。
|
|
21
|
+
* 该版把熔断族整族退役、又把 `unavailable` 改成**当场 deny** ⇒ 两条路同时没了,而
|
|
22
|
+
* `permission_denied_total{deniedBy}` 对它是**瞎的**:分类器停摆与分类器正常拦下一次危险调用,
|
|
23
|
+
* 两者都记 `deniedBy:"classifier"`。一次持续停摆于是在默认监控上与「今天安全拦截多了点」不可区分
|
|
24
|
+
* (`TOOL_TRACE` 默认关,拿不到那半边)。core 同批把事实加在了 deny 上
|
|
25
|
+
* (`PermissionDeniedPayload.classifierUnavailable`),本仓就在**这同一只常开观察者**上消费它。
|
|
26
|
+
* 两个计数刻意**不合并成一个标签**:它们答两个问题(「谁拒的」vs「这次拒是不是因为分类器停摆、哪一类」),
|
|
27
|
+
* 而运维对停摆要立刻动手(改配置 / 等重试 / 看后端),对安全拦截不要。
|
|
28
|
+
* ⛔ 纯计量:不改任何裁决、不新开观察面、事实缺席时**不铸**(缺席 ≠ 「分类器好着呢」,只是这次拒不是它)。
|
|
17
29
|
*/
|
|
18
30
|
export declare function createPermissionDeniedMeter(metrics: Pick<Metrics, "inc">): Hooks;
|
|
19
31
|
/**
|