@sema-agent/server 7.114.0 → 7.116.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 +237 -2
- package/USAGE.md +39 -3
- package/deploy/sema-up/cluster-up.sh +13 -1
- package/deploy/sema-up/kube-up.sh +13 -1
- package/deploy/sema-up/preflight.sh +13 -1
- package/deploy/sema-up/sema-up.sh +13 -1
- package/deploy/sema-up/smoke.sh +13 -1
- package/dist/adoption/wire.d.ts +0 -3
- package/dist/approval-ask-audit-store.d.ts +25 -6
- package/dist/approval-ask-audit-store.js +6 -1
- package/dist/approval-card.d.ts +25 -24
- package/dist/approval-card.js +14 -0
- package/dist/approval-crash-convergence.d.ts +60 -0
- package/dist/approval-crash-convergence.js +75 -0
- package/dist/approval-deny-reasons.d.ts +13 -0
- package/dist/approval-deny-reasons.js +2 -0
- package/dist/approval.js +7 -3
- package/dist/boot/coordinators.js +0 -12
- package/dist/boot/execution-env.js +2 -1
- package/dist/boot/exit-boundary.d.ts +3 -2
- package/dist/boot/exit-boundary.js +3 -0
- package/dist/boot/resolve-spec.js +5 -1
- package/dist/boot/shutdown.d.ts +19 -0
- package/dist/boot/shutdown.js +12 -1
- package/dist/boot/stage-07-capability-layer.js +21 -15
- package/dist/boot/stores.js +4 -4
- package/dist/capabilities/composite-members.d.ts +1 -0
- package/dist/capabilities/composite-members.js +1 -0
- package/dist/capabilities/hands-lane.d.ts +0 -23
- package/dist/capabilities/hands-lane.js +3 -2
- package/dist/capabilities/send-user-file-tool.d.ts +10 -22
- package/dist/capabilities/send-user-file-tool.js +1 -8
- package/dist/capability-facts.d.ts +296 -0
- package/dist/capability-facts.js +354 -0
- package/dist/config-catalog.js +2 -1
- package/dist/config-center/apply-effective.js +11 -11
- package/dist/config-provider.d.ts +16 -0
- package/dist/config-provider.js +11 -2
- package/dist/config-types.d.ts +20 -1
- package/dist/config.d.ts +55 -29
- package/dist/config.js +48 -25
- package/dist/deployment-governance.d.ts +15 -4
- package/dist/deployment-governance.js +20 -2
- package/dist/fleet/fleet-bus.d.ts +1 -22
- package/dist/hooks/hook-runner.d.ts +29 -3
- package/dist/hooks/hook-runner.js +30 -17
- package/dist/http/admission.d.ts +6 -0
- package/dist/http/admission.js +29 -7
- package/dist/http/approval-sweeps.js +20 -0
- package/dist/http/checkpoint-ctx.d.ts +68 -0
- package/dist/http/checkpoint-ctx.js +36 -0
- package/dist/http/durable-run-leg.js +2 -1
- package/dist/http/principal-gate.d.ts +3 -0
- package/dist/http/principal-gate.js +3 -0
- package/dist/http/resume-legs.js +19 -1
- package/dist/http/retired-skill-manifest.d.ts +23 -0
- package/dist/http/retired-skill-manifest.js +22 -0
- package/dist/http/route-ctx.d.ts +7 -0
- package/dist/http/route-table.d.ts +3 -2
- package/dist/http/route-table.js +5 -5
- package/dist/http/routes/a2a-serve.d.ts +1 -3
- package/dist/http/routes/a2a-serve.js +14 -23
- package/dist/http/routes/admin-config-refresh.js +7 -2
- package/dist/http/routes/admin-drain.js +7 -2
- package/dist/http/routes/admin-memory-optout.d.ts +0 -2
- package/dist/http/routes/admin-memory-optout.js +20 -7
- package/dist/http/routes/adoption.js +19 -5
- package/dist/http/routes/agents-roster.js +4 -2
- package/dist/http/routes/approvals-assistant.js +41 -18
- package/dist/http/routes/background.d.ts +0 -2
- package/dist/http/routes/background.js +4 -5
- package/dist/http/routes/capabilities.d.ts +2 -1
- package/dist/http/routes/capabilities.js +63 -65
- package/dist/http/routes/config-catalog.d.ts +4 -5
- package/dist/http/routes/config-catalog.js +16 -2
- package/dist/http/routes/devices.js +17 -11
- package/dist/http/routes/diagnostics.d.ts +5 -0
- package/dist/http/routes/diagnostics.js +8 -1
- package/dist/http/routes/fleet.js +4 -2
- package/dist/http/routes/images.js +30 -3
- package/dist/http/routes/leader.js +7 -1
- package/dist/http/routes/mcp-reconnect.js +17 -10
- package/dist/http/routes/memory-bundle.js +20 -6
- package/dist/http/routes/memory-compliance.js +19 -5
- package/dist/http/routes/memory-consolidation.d.ts +9 -12
- package/dist/http/routes/memory-consolidation.js +17 -3
- package/dist/http/routes/memory-origin.js +19 -5
- package/dist/http/routes/notify-wake.js +5 -3
- package/dist/http/routes/observability.js +19 -5
- package/dist/http/routes/retention-ops.js +6 -1
- package/dist/http/routes/rules.js +49 -21
- package/dist/http/routes/run-memory-capture.js +5 -2
- package/dist/http/routes/runs.js +90 -60
- package/dist/http/routes/session-sync.js +64 -53
- package/dist/http/routes/sessions-list.js +8 -5
- package/dist/http/routes/sessions.js +40 -25
- package/dist/http/routes/shared-memory.js +5 -3
- package/dist/http/routes/tasks.js +5 -3
- package/dist/http/routes/trace-usage.js +18 -11
- package/dist/http/routes/workflows.js +36 -13
- package/dist/http/server.js +4 -1
- package/dist/http/wire-types.d.ts +6 -0
- package/dist/index.d.ts +1 -1
- package/dist/leader/wire.js +2 -1
- package/dist/local-data-root.d.ts +1 -1
- package/dist/mcp-content-origin.d.ts +0 -8
- package/dist/model-entry-refusal.d.ts +10 -2
- package/dist/model-entry-refusal.js +19 -3
- package/dist/namespaced-rule-name.d.ts +54 -0
- package/dist/namespaced-rule-name.js +33 -0
- package/dist/observability/corrupt-read-seat.d.ts +4 -3
- package/dist/observability/corrupt-read-seat.js +4 -3
- package/dist/observability/fail-open.d.ts +10 -9
- package/dist/observability/fail-open.js +5 -1
- package/dist/observability/permission-rule-events.d.ts +3 -0
- package/dist/observability/permission-rule-events.js +4 -0
- package/dist/permission-rule-list.d.ts +81 -0
- package/dist/permission-rule-list.js +17 -0
- package/dist/plugins/file-run-store.d.ts +7 -1
- package/dist/plugins/file-run-store.js +20 -7
- package/dist/plugins/memory-run-store.js +2 -1
- package/dist/plugins/one-time-migrations.d.ts +4 -2
- package/dist/plugins/one-time-migrations.js +26 -3
- package/dist/plugins/pg-session-storage.js +1 -1
- package/dist/plugins/run-store-sql.js +4 -4
- package/dist/plugins/session-policy-store-sql.js +4 -2
- package/dist/plugins/store-backend.d.ts +6 -1
- package/dist/plugins/store-backend.js +2 -1
- package/dist/plugins/store-contracts.d.ts +27 -8
- package/dist/plugins/store-contracts.js +12 -3
- package/dist/plugins/tidb-session-store.js +1 -1
- package/dist/request-key-closure.js +1 -1
- package/dist/run-local.d.ts +37 -3
- package/dist/run-local.js +52 -6
- package/dist/runs.d.ts +0 -58
- package/dist/runs.js +15 -20
- package/dist/runtime-governance.d.ts +21 -1
- package/dist/runtime-governance.js +6 -1
- package/dist/security.js +2 -2
- package/dist/session-policy-wire.d.ts +40 -59
- package/dist/session-policy-wire.js +60 -32
- package/dist/spec-fields.d.ts +32 -0
- package/dist/spec-fields.js +31 -16
- package/dist/standing-answer-grant.d.ts +8 -0
- package/dist/standing-answer-grant.js +5 -0
- package/dist/task-settings.d.ts +17 -6
- package/dist/task-settings.js +16 -30
- package/dist/tool-approval.d.ts +32 -4
- package/dist/tool-approval.js +31 -8
- package/dist/trace/core-keyset-guard.d.ts +13 -8
- package/dist/trace/project.js +9 -0
- package/dist/trace/self-minted-wire.d.ts +42 -0
- package/dist/trace/self-minted-wire.js +13 -0
- package/dist/user-input-ledger.d.ts +8 -5
- package/package.json +6 -4
- package/scripts/drain-check-local.mjs +528 -0
- package/scripts/drain-check.mysql.sql +67 -0
- package/scripts/drain-check.pg.sql +71 -0
- package/scripts/retired-mode-seed-drain-local.mjs +0 -317
|
@@ -8,6 +8,7 @@ import { sendJson, sendError, sseHeaders, SSE_MAX_STREAM_MS, SSE_HEARTBEAT_IDLE_
|
|
|
8
8
|
import { filterFingerprint } from "../cursor-fingerprint.js";
|
|
9
9
|
import { gatedPrincipal, sendUnauthorized } from "../principal-gate.js";
|
|
10
10
|
import { assertNever } from "../route-ctx.js";
|
|
11
|
+
import * as facts from "../../capability-facts.js";
|
|
11
12
|
const TOOL_RESULT_LIMIT_CAP = 262_144;
|
|
12
13
|
const TOOL_RESULT_OFFSET_CAP = 2_147_483_646;
|
|
13
14
|
export async function handleTraceUsage(req, res, match, ctx) {
|
|
@@ -18,13 +19,15 @@ export async function handleTraceUsage(req, res, match, ctx) {
|
|
|
18
19
|
sendUnauthorized(res, "operator");
|
|
19
20
|
return;
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
+
const runs = facts.durableRuns(deps);
|
|
23
|
+
if (!runs.available) {
|
|
22
24
|
sendError(res, 501, "capability.run_store_required", "trace API requires a durable run store (DB_BACKEND=mysql|pg|local)");
|
|
23
25
|
return;
|
|
24
26
|
}
|
|
27
|
+
const runStore = runs.runStore;
|
|
25
28
|
const q = new URLSearchParams(match.search);
|
|
26
29
|
const sinceSec = Math.min(30 * 86_400, Math.max(60, Number(q.get("sinceSec") ?? 86_400) || 86_400));
|
|
27
|
-
sendJson(res, 200, { sinceSec, rows: await
|
|
30
|
+
sendJson(res, 200, { sinceSec, rows: await runStore.sourceSummary(Date.now() - sinceSec * 1000) });
|
|
28
31
|
return;
|
|
29
32
|
}
|
|
30
33
|
if (match.decl.id === "task-trace-list" ||
|
|
@@ -38,15 +41,17 @@ export async function handleTraceUsage(req, res, match, ctx) {
|
|
|
38
41
|
sendUnauthorized(res, "identity");
|
|
39
42
|
return;
|
|
40
43
|
}
|
|
41
|
-
|
|
44
|
+
const runs = facts.durableRuns(deps);
|
|
45
|
+
if (!runs.available) {
|
|
42
46
|
sendError(res, 501, "capability.run_store_required", "trace API requires a durable run store (DB_BACKEND=mysql|pg|local)");
|
|
43
47
|
return;
|
|
44
48
|
}
|
|
49
|
+
const runStore = runs.runStore;
|
|
45
50
|
const query = new URLSearchParams(match.search);
|
|
46
51
|
const taskId = match.params.taskId;
|
|
47
52
|
let ownedRun;
|
|
48
53
|
if (taskId && gateOwner !== null) {
|
|
49
|
-
const run = await
|
|
54
|
+
const run = await runStore.getRun(taskId);
|
|
50
55
|
if (!run || (run.owner !== null && run.owner !== gateOwner)) {
|
|
51
56
|
sendError(res, 404, "not_found.run", "task not found");
|
|
52
57
|
return;
|
|
@@ -55,19 +60,19 @@ export async function handleTraceUsage(req, res, match, ctx) {
|
|
|
55
60
|
}
|
|
56
61
|
switch (match.decl.id) {
|
|
57
62
|
case "task-trace-list":
|
|
58
|
-
await handleTaskList(res,
|
|
63
|
+
await handleTaskList(res, runStore, query, gateOwner);
|
|
59
64
|
return;
|
|
60
65
|
case "task-trace-turns":
|
|
61
|
-
await handleTaskTurns(res,
|
|
66
|
+
await handleTaskTurns(res, runStore, taskId, query);
|
|
62
67
|
return;
|
|
63
68
|
case "task-trace-stream":
|
|
64
|
-
await streamTaskTrace(req, res,
|
|
69
|
+
await streamTaskTrace(req, res, runStore, taskId, deps.config.runStaleSec * 1000, match.search);
|
|
65
70
|
return;
|
|
66
71
|
case "task-trace-artifacts":
|
|
67
|
-
await handleTaskArtifacts(res,
|
|
72
|
+
await handleTaskArtifacts(res, runStore, taskId);
|
|
68
73
|
return;
|
|
69
74
|
case "task-tool-result":
|
|
70
|
-
await handleToolResult(res, deps.toolResultStore,
|
|
75
|
+
await handleToolResult(res, deps.toolResultStore, runStore, taskId, ownedRun, safeDecode(match.params.ref), query);
|
|
71
76
|
return;
|
|
72
77
|
default:
|
|
73
78
|
return assertNever(match.decl.id);
|
|
@@ -78,10 +83,12 @@ export async function handleTraceUsage(req, res, match, ctx) {
|
|
|
78
83
|
{
|
|
79
84
|
const sub = match.params.sub;
|
|
80
85
|
{
|
|
81
|
-
|
|
86
|
+
const runs = facts.durableRuns(deps);
|
|
87
|
+
if (!runs.available) {
|
|
82
88
|
sendError(res, 501, "capability.run_store_required", "usage analytics requires a run store");
|
|
83
89
|
return;
|
|
84
90
|
}
|
|
91
|
+
const runStore = runs.runStore;
|
|
85
92
|
const fleetWide = isFleetWide(req);
|
|
86
93
|
const jwtOwner = fleetWide ? undefined : gatedPrincipal(req, deps.config);
|
|
87
94
|
if (!fleetWide && !jwtOwner) {
|
|
@@ -104,7 +111,7 @@ export async function handleTraceUsage(req, res, match, ctx) {
|
|
|
104
111
|
}
|
|
105
112
|
const owner = jwtOwner ?? (q.get("principal") ?? undefined);
|
|
106
113
|
try {
|
|
107
|
-
const scan = await
|
|
114
|
+
const scan = await runStore.usageScan(from, to, owner !== undefined ? { owner } : undefined);
|
|
108
115
|
const base = { from: new Date(from).toISOString(), to: new Date(to).toISOString(), ...(scan.truncated ? { truncated: true } : {}) };
|
|
109
116
|
if (sub === "summary") {
|
|
110
117
|
sendJson(res, 200, { ...base, ...usageSummary(scan.rows) });
|
|
@@ -12,6 +12,7 @@ const journalProjectionMemo = new Map();
|
|
|
12
12
|
import { sendJson, sendError, sseHeaders } from "../send.js";
|
|
13
13
|
import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
|
|
14
14
|
import { assertNever } from "../route-ctx.js";
|
|
15
|
+
import * as facts from "../../capability-facts.js";
|
|
15
16
|
export async function handleWorkflows(req, res, match, ctx) {
|
|
16
17
|
const { deps } = ctx;
|
|
17
18
|
const { runSessionAcceptOk } = ctx.helpers;
|
|
@@ -21,10 +22,21 @@ export async function handleWorkflows(req, res, match, ctx) {
|
|
|
21
22
|
sendPrincipalRefusal(res, req, deps.config);
|
|
22
23
|
return;
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
let journalStore;
|
|
26
|
+
if (match.decl.id === "workflow-journal") {
|
|
27
|
+
const journalFace = facts.workflowsJournal(deps);
|
|
28
|
+
if (!journalFace.available) {
|
|
29
|
+
sendWorkflowsUnavailable(res, journalFace.why);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
journalStore = journalFace.journal;
|
|
33
|
+
}
|
|
34
|
+
const runs = facts.workflowRuns(deps);
|
|
35
|
+
if (!runs.available) {
|
|
36
|
+
sendWorkflowsUnavailable(res, runs.why);
|
|
26
37
|
return;
|
|
27
38
|
}
|
|
39
|
+
const runStore = runs.store;
|
|
28
40
|
const scope = principal ?? "default";
|
|
29
41
|
const wfId = match.params.runId;
|
|
30
42
|
const isStream = match.decl.id === "workflow-stream";
|
|
@@ -33,7 +45,7 @@ export async function handleWorkflows(req, res, match, ctx) {
|
|
|
33
45
|
const statusFilter = q.get("status");
|
|
34
46
|
const limit = Math.min(100, Math.max(1, Number(q.get("limit") ?? 50) || 50));
|
|
35
47
|
const sessionFilter = q.get("session");
|
|
36
|
-
const sessionRuns = await
|
|
48
|
+
const sessionRuns = await runStore.listByScope(scope, {
|
|
37
49
|
...(statusFilter ? { status: statusFilter } : {}),
|
|
38
50
|
limit,
|
|
39
51
|
...(sessionFilter !== null ? { session: sessionFilter } : {}),
|
|
@@ -47,7 +59,7 @@ export async function handleWorkflows(req, res, match, ctx) {
|
|
|
47
59
|
sendJson(res, 200, { workflows: redacted });
|
|
48
60
|
return;
|
|
49
61
|
}
|
|
50
|
-
const run = await getWorkflowRun(
|
|
62
|
+
const run = await getWorkflowRun(runStore, wfId, scope);
|
|
51
63
|
if (!run || run.scope !== scope) {
|
|
52
64
|
sendError(res, 404, "not_found.workflow", "workflow not found");
|
|
53
65
|
return;
|
|
@@ -55,10 +67,8 @@ export async function handleWorkflows(req, res, match, ctx) {
|
|
|
55
67
|
if (!runSessionAcceptOk(req, res, { sessionId: run.originatingSessionId ?? null }, match.decl.id === "workflow-journal" ? "workflow.journal" : isStream ? "workflow.stream" : "workflow.detail", "workflow not found"))
|
|
56
68
|
return;
|
|
57
69
|
if (match.decl.id === "workflow-journal") {
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
70
|
+
if (journalStore === undefined)
|
|
71
|
+
throw new Error("workflow-journal reached without the workflowsJournal journal witness");
|
|
62
72
|
const jq = new URLSearchParams(match.search);
|
|
63
73
|
const jLimit = Math.min(50, Math.max(1, Number(jq.get("limit") ?? 20) || 20));
|
|
64
74
|
const jOffset = Math.max(0, Number(jq.get("offset") ?? 0) || 0);
|
|
@@ -100,7 +110,7 @@ export async function handleWorkflows(req, res, match, ctx) {
|
|
|
100
110
|
...(r.haltedOnUserRejection === true ? { haltedOnUserRejection: true } : {}),
|
|
101
111
|
};
|
|
102
112
|
};
|
|
103
|
-
const js =
|
|
113
|
+
const js = journalStore;
|
|
104
114
|
let projected;
|
|
105
115
|
if (js.loadPage) {
|
|
106
116
|
const page = await js.loadPage(wfId, scope, { offset: jOffset, limit: jLimit, maxResultBytes: MAX_ROW_BYTES });
|
|
@@ -118,7 +128,7 @@ export async function handleWorkflows(req, res, match, ctx) {
|
|
|
118
128
|
});
|
|
119
129
|
}
|
|
120
130
|
else {
|
|
121
|
-
const entries = await
|
|
131
|
+
const entries = await journalStore.load(wfId, scope);
|
|
122
132
|
projected = entries
|
|
123
133
|
.sort((a, b) => callKeyOrdinal(a.callKey) - callKeyOrdinal(b.callKey))
|
|
124
134
|
.slice(jOffset, jOffset + jLimit)
|
|
@@ -150,8 +160,9 @@ export async function handleWorkflowAgentSteer(req, res, match, ctx) {
|
|
|
150
160
|
{
|
|
151
161
|
if (rateLimited(req, res) || quotaExceeded(req, res) || (await leaseDenied(req, res)))
|
|
152
162
|
return;
|
|
153
|
-
|
|
154
|
-
|
|
163
|
+
const runs = facts.workflowRuns(deps);
|
|
164
|
+
if (!runs.available) {
|
|
165
|
+
sendWorkflowsUnavailable(res, runs.why);
|
|
155
166
|
return;
|
|
156
167
|
}
|
|
157
168
|
const principal = gatedPrincipal(req, deps.config);
|
|
@@ -183,7 +194,7 @@ export async function handleWorkflowAgentSteer(req, res, match, ctx) {
|
|
|
183
194
|
}
|
|
184
195
|
const trusted = explicitOperatorOk(principal, deps.config.operatorPrincipals);
|
|
185
196
|
const ownerScope = principal ?? "default";
|
|
186
|
-
const wfRun = await getWorkflowRun(
|
|
197
|
+
const wfRun = await getWorkflowRun(runs.store, wfRunId, ownerScope);
|
|
187
198
|
if (!trusted && !wfRun) {
|
|
188
199
|
sendError(res, 404, "not_found.workflow", "workflow not found");
|
|
189
200
|
return;
|
|
@@ -389,6 +400,18 @@ async function streamWorkflowRun(req, res, runId, scope) {
|
|
|
389
400
|
if (!res.writableEnded)
|
|
390
401
|
res.end();
|
|
391
402
|
}
|
|
403
|
+
function sendWorkflowsUnavailable(res, why) {
|
|
404
|
+
switch (why) {
|
|
405
|
+
case "workflow_run_store_absent":
|
|
406
|
+
sendError(res, 501, "capability.self_orchestration_required", "workflow runs require self-orchestration (SELF_ORCHESTRATION_ENABLED)");
|
|
407
|
+
return;
|
|
408
|
+
case "workflow_journal_store_absent":
|
|
409
|
+
sendError(res, 501, "capability.workflow_store_required", "workflow journal requires the journal store (SELF_ORCHESTRATION_ENABLED + a store backend)");
|
|
410
|
+
return;
|
|
411
|
+
default:
|
|
412
|
+
return assertNever(why);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
392
415
|
export const WORKFLOWS_ROUTES = [
|
|
393
416
|
{ id: "workflows-list", path: "/v1/workflows", methods: ["GET"] },
|
|
394
417
|
{ id: "workflow-stream", pattern: /^\/v1\/workflows\/(?<runId>[^/]+)\/stream$/, label: "/v1/workflows/:id/$sub", methods: ["GET"] },
|
package/dist/http/server.js
CHANGED
|
@@ -23,6 +23,7 @@ import { sendError, sendSessionError, httpErrorCode, msg } from "./send.js";
|
|
|
23
23
|
import { createRequestGuards } from "./request-guards.js";
|
|
24
24
|
import { createAdmission, MAX_SKILL_CONTENT_CHARS, MAX_SYSTEM_PROMPT_CHARS, MAX_OUTPUT_SCHEMA_CHARS, validateUserSkills } from "./admission.js";
|
|
25
25
|
import { createResumeLegs } from "./resume-legs.js";
|
|
26
|
+
import * as facts from "../capability-facts.js";
|
|
26
27
|
import { createDispatch, hasServiceAuth } from "./dispatch.js";
|
|
27
28
|
import { routeLabel, isBillableSubmitPath, isCredentialGatedRewrite, isMethodDispatchedSubmitPath, isProbePath } from "./route-table.js";
|
|
28
29
|
import { createApprovalSweeps } from "./approval-sweeps.js";
|
|
@@ -118,7 +119,9 @@ export function createHttpServer(rawDeps) {
|
|
|
118
119
|
sendError(res, 500, "internal.error", "internal error");
|
|
119
120
|
});
|
|
120
121
|
});
|
|
121
|
-
|
|
122
|
+
const deviceLane = facts.deviceLane(deps);
|
|
123
|
+
if (deviceLane.available)
|
|
124
|
+
deviceLane.hub.attach(server);
|
|
122
125
|
function runnerFor(spec) {
|
|
123
126
|
return deps.runnerFor(spec);
|
|
124
127
|
}
|
|
@@ -301,6 +301,12 @@ export interface TaskRequestBody {
|
|
|
301
301
|
* 续跑重放逐字重放存量体 ⇒ 同一只写点同判(坏形 defensive:只认 literal `true`)。无租户门(收窄,不跨租户)。
|
|
302
302
|
* 探测位 `GET /v1/capabilities` 的 `disableSkills`(与本键同一个词)。 */
|
|
303
303
|
disableSkills?: boolean;
|
|
304
|
+
/** core 7.41.0 #1303 —— **人用名字点过的技能**(壳从人敲的 `/name` 解析出来的,按 `SkillSpec.name`),原样交给 core 的
|
|
305
|
+
* `TaskSpec.userInvokedSkills`:这些技能的 `allowedTools` / `disallowedTools` 开跑即生效,不出 `Skill` 卡(人自己点的名就是那句表态),
|
|
306
|
+
* 只管这一次 run(steer 不终止它,下一次 run 不带)。每个名字必须是这一 run 装着的技能(场景出厂 ∪ 本请求 `skills`),否则引擎
|
|
307
|
+
* 拒启 `config.user_invoked_skill_unknown`(同步腿 400)。值形:非空串数组(受理门 400 `request.field_invalid`);续跑重放逐字重放存量体,
|
|
308
|
+
* 坏形不挂键。不投递技能正文(正文照旧由 `Skill` 工具 / 调用方负责)。 */
|
|
309
|
+
userInvokedSkills?: string[];
|
|
304
310
|
/** [ref]② (core 1.314 工具面控制批): roster 真卸载清单(schema 不上 wire,manifest 收窄)。wire 工具名;
|
|
305
311
|
* tighten-only(core 继承不变量 union 进子 spawn)。数组-of-string fail-loud at submit。 */
|
|
306
312
|
excludeTools?: string[];
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { loadConfig, type ServiceConfig } from "./config.js";
|
|
2
|
-
export { composeExecutionLane, assertExecutionLaneInvariants } from "./boot/execution-lane.js";
|
|
2
|
+
export { composeExecutionLane, assertExecutionLaneInvariants, type ExecutionLaneComposeCtx } from "./boot/execution-lane.js";
|
|
3
3
|
export { createBrain } from "./brain.js";
|
|
4
4
|
export { createSessionStore } from "./plugins/session-store.js";
|
|
5
5
|
export { createStoreBackend, type StoreBackend, type SessionCaptureRecordSource } from "./plugins/store-backend.js";
|
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 { withUnattendedLeg } from "../unattended-permission-mode.js";
|
|
9
|
+
import { buildCheckpointCtx } from "../http/checkpoint-ctx.js";
|
|
9
10
|
import { RemoteContainerExecutionEnv } from "../plugins/remote-env-e2b.js";
|
|
10
11
|
import { E2B_SANDBOX_HOME } from "../execution-lane-caps.js";
|
|
11
12
|
import { createDurableAskPolicy } from "../approval.js";
|
|
@@ -267,7 +268,7 @@ export function createLeaderRunner(cfg) {
|
|
|
267
268
|
const provisionWorker = async (sub) => {
|
|
268
269
|
const sessionId = `leader-${sub.workerId}-${Date.now()}`;
|
|
269
270
|
const durable = cfg.durable?.();
|
|
270
|
-
const resumeCtx = { body: { objective: sub.spec.objective }, unattendedLeg: true };
|
|
271
|
+
const resumeCtx = buildCheckpointCtx({ body: { objective: sub.spec.objective }, unattendedLeg: true });
|
|
271
272
|
let durableSpec = {};
|
|
272
273
|
if (durable) {
|
|
273
274
|
const d = durable;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 链(第一个非空者胜,空串视作缺席):`LOCAL_DATA_ROOT` → `CONFIG_LOCAL_DIR` → `AGENT_DATA_DIR` → `<home>/.ai-agent`。
|
|
5
5
|
*
|
|
6
6
|
* 为什么是一只**纯函数、env 与 home 都由调用方递入**:本函数有两个读者 —— 服务自己的 `loadConfig`(递 `process.env` / `os.homedir()`)与随包发的运维脚本
|
|
7
|
-
* `scripts/
|
|
7
|
+
* `scripts/drain-check-local.mjs`(经 `dist/local-data-root.js` 取同一只函数,缺省数据根不再自抄一份链:抄本只对了一半,
|
|
8
8
|
* 设了 `AGENT_DATA_DIR` 的部署不带参数跑脚本会读到另一个目录并报「干净」)。纯叶、零反向依赖 ⇒ 脚本引它不会把整个服务装载起来。
|
|
9
9
|
*
|
|
10
10
|
* ⚠️ 与**配置根**(`configLocalRoot`:`CONFIG_LOCAL_DIR` → `AGENT_DATA_DIR` → `~/.ai-agent`,不吃 `LOCAL_DATA_ROOT`)不是同一条链 —— 两根分家的那一形由
|
|
@@ -19,15 +19,8 @@
|
|
|
19
19
|
* 单向钉挡不住第二种(手滑把 `"local"` 打成 `"locale"` 只会让一个合法声明被静默拒),所以两条都要。
|
|
20
20
|
*/
|
|
21
21
|
import type { ToolContentOrigin } from "@sema-agent/core";
|
|
22
|
-
/** 编译期断言:`T` 必须收敛到 `never`(有残余 ⇒ tsc 红)。 */
|
|
23
|
-
type AssertNever<T extends never> = T;
|
|
24
22
|
/** core `ToolContentOrigin` 的执法副本 —— **唯一**一份(见模块头注)。 */
|
|
25
23
|
export declare const CONTENT_ORIGIN_WORDS: readonly ["local", "execution", "external"];
|
|
26
|
-
type ContentOriginWord = (typeof CONTENT_ORIGIN_WORDS)[number];
|
|
27
|
-
type _ContentOriginCovers = AssertNever<Exclude<ToolContentOrigin, ContentOriginWord>>;
|
|
28
|
-
type _ContentOriginNoStray = AssertNever<Exclude<ContentOriginWord, ToolContentOrigin>>;
|
|
29
|
-
/** (占位使用,防 noUnusedLocals;类型别名零运行时。) */
|
|
30
|
-
export type ContentOriginWordTableGuards = [_ContentOriginCovers, _ContentOriginNoStray];
|
|
31
24
|
/**
|
|
32
25
|
* 词表内?—— 两个平面共用的**那一只**谓词。
|
|
33
26
|
*
|
|
@@ -38,5 +31,4 @@ export type ContentOriginWordTableGuards = [_ContentOriginCovers, _ContentOrigin
|
|
|
38
31
|
export declare function isContentOriginWord(v: unknown): v is ToolContentOrigin;
|
|
39
32
|
/** 坏值的**可打印**摘要(进 400 文案 / warn 元数据)——中和 + 截长,绝不把任意长度的调用方输入原样回显。 */
|
|
40
33
|
export declare function describeBadContentOrigin(v: unknown): string;
|
|
41
|
-
export {};
|
|
42
34
|
//# sourceMappingURL=mcp-content-origin.d.ts.map
|
|
@@ -90,13 +90,21 @@ export interface ModelRefusalVerdict {
|
|
|
90
90
|
* 名 → 码,**值只码**(拒因事实里的上游 id / provider 词 / 坏字段键 / 出路句都不上 wire —— `entry_invalid` 行是没人验过的行,id / provider
|
|
91
91
|
* 可能就是坏行原样;句子是给本机运维的散文,不是契约)。**名筛在这里、只在这里**:键过 settings-schema `isReportableRefusalName`(唯一判据,
|
|
92
92
|
* import 不复制;6.3.0 起 = 标识符白名单 ∧ 无密钥形 ∧ 无 URL userinfo)—— 秘密形 / 空串 / `__proto__` / 不是标识符的串这类出不了门的名不进键、
|
|
93
|
-
*
|
|
93
|
+
* 不计数;引擎内部的判决不受影响;本机日志 / 拒因句里的写名同样先问这一只(S-805,{@link disclosableRefusalName}:不可披露的名只写码)。
|
|
94
94
|
* 逐条空且无组拒 ⇒ `undefined`(键缺席:「没有」只有一种拼法)。
|
|
95
95
|
*/
|
|
96
96
|
export declare function buildModelRefusalsSignal(v: ModelRefusalVerdict | undefined): ModelRefusalsSignal | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* 一个被拒条目名的**日志字段**:可披露 ⇒ `{ [key]: name }`;不可披露 ⇒ `{ [`${key}NameWithheld`]: true }`(缺 `key` 不是无声的:读日志的人知道是被扣下、
|
|
99
|
+
* 不是漏写)。**所有**把被拒条目名写进结构化日志字段的站点调这一只(`refusalWarnFields` / `apply-effective.ts` 的角色与档位绑定 warn、缺省悬空 warn),
|
|
100
|
+
* 不各写一份 `isReportableRefusalName` 判断。
|
|
101
|
+
*/
|
|
102
|
+
export declare function refusedNameLogField(key: string, name: string): Record<string, unknown>;
|
|
103
|
+
/** 一串被拒条目名的日志字段(`sema_registry_models` 行的 `unavailable`):只列可披露的名,被扣下的名**只数个数**(`<key>NamesWithheld`,0 个时键缺席)。 */
|
|
104
|
+
export declare function refusedNamesLogFields(key: string, names: readonly string[]): Record<string, unknown>;
|
|
97
105
|
/** 成因 → 它那一条 warn 的事件名(**穷举**)。两个名字是票面点名的既有/新事件,运维的告警规则按名挂。 */
|
|
98
106
|
export declare function refusalEventOf(reason: ModelEntryRefusalReason): "sema_registry_model_no_brain" | "sema_registry_model_entry_invalid";
|
|
99
|
-
/** warn 字段(不含值):`field` = 第一处坏字段(告警规则最常按它分组),`fields` = 全部。 */
|
|
107
|
+
/** warn 字段(不含值):`field` = 第一处坏字段(告警规则最常按它分组),`fields` = 全部。S-805:`model` 只在名可披露时写。 */
|
|
100
108
|
export declare function refusalWarnFields(name: string, r: ModelEntryRefusal): Record<string, unknown>;
|
|
101
109
|
/**
|
|
102
110
|
* 一次**显式选择**命中了不可用条目时的拒因句(HTTP `request.model_unavailable` 的 `error`、run-local 的 stderr、
|
|
@@ -94,6 +94,22 @@ export function buildModelRefusalsSignal(v) {
|
|
|
94
94
|
return undefined;
|
|
95
95
|
return v.plane === undefined ? { entries } : { entries, plane: v.plane };
|
|
96
96
|
}
|
|
97
|
+
function disclosableRefusalName(name) {
|
|
98
|
+
return isReportableRefusalName(name) ? name : undefined;
|
|
99
|
+
}
|
|
100
|
+
export function refusedNameLogField(key, name) {
|
|
101
|
+
const shown = disclosableRefusalName(name);
|
|
102
|
+
return shown !== undefined ? { [key]: shown } : { [`${key}NameWithheld`]: true };
|
|
103
|
+
}
|
|
104
|
+
export function refusedNamesLogFields(key, names) {
|
|
105
|
+
const shown = names.filter((n) => disclosableRefusalName(n) !== undefined);
|
|
106
|
+
const withheld = names.length - shown.length;
|
|
107
|
+
return { [key]: shown, ...(withheld > 0 ? { [`${key}NamesWithheld`]: withheld } : {}) };
|
|
108
|
+
}
|
|
109
|
+
function nameSlot(name) {
|
|
110
|
+
const shown = disclosableRefusalName(name);
|
|
111
|
+
return shown === undefined ? "(name withheld)" : `"${shown}"`;
|
|
112
|
+
}
|
|
97
113
|
export function refusalEventOf(reason) {
|
|
98
114
|
switch (reason) {
|
|
99
115
|
case "no_brain":
|
|
@@ -104,7 +120,7 @@ export function refusalEventOf(reason) {
|
|
|
104
120
|
}
|
|
105
121
|
export function refusalWarnFields(name, r) {
|
|
106
122
|
return {
|
|
107
|
-
model
|
|
123
|
+
...refusedNameLogField("model", name),
|
|
108
124
|
provider: r.provider,
|
|
109
125
|
code: r.code,
|
|
110
126
|
...(r.fields.length > 0 ? { field: r.fields[0], fields: [...r.fields] } : {}),
|
|
@@ -118,7 +134,7 @@ export function modelUnavailableMessage(field, requested, name, r) {
|
|
|
118
134
|
export function defaultModelRefusal(view, leg) {
|
|
119
135
|
if (Object.hasOwn(view.models, DEFAULT_MODEL_REF) && view.models[DEFAULT_MODEL_REF] !== undefined)
|
|
120
136
|
return undefined;
|
|
121
|
-
const refused = Object.entries(view.modelRefusals).map(([n, r]) =>
|
|
137
|
+
const refused = Object.entries(view.modelRefusals).map(([n, r]) => `${nameSlot(n)} [${r.code}]`);
|
|
122
138
|
const unavailable = refused.length > 0 ? ` (every enabled catalog entry is unavailable on this host: ${refused.join(", ")})` : "";
|
|
123
139
|
switch (leg) {
|
|
124
140
|
case "env":
|
|
@@ -163,7 +179,7 @@ export function refusedNameMintedRefusal(view) {
|
|
|
163
179
|
const minted = mintedRefusedNames(view);
|
|
164
180
|
if (minted.length === 0)
|
|
165
181
|
return undefined;
|
|
166
|
-
const pairs = minted.map((m) =>
|
|
182
|
+
const pairs = minted.map((m) => `${nameSlot(m.name)} [${m.code}] → ${nameSlot(m.target)} (${m.via})`);
|
|
167
183
|
return (`unavailable catalog entries would be re-minted onto other entries by the tier/alias expansion [${REFUSED_NAME_MINTED_CODE}]: ${pairs.join(", ")} — ` +
|
|
168
184
|
"an explicit pick of that name (or a role bound to it) would silently run on the other entry's endpoint and account; " +
|
|
169
185
|
"rename the unavailable entry, fix it (see its own warn), or rebind / drop the tier binding that mints the name.");
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 协议规则名(`mcp__…` / `a2a__…`)够不够得着一只挂载工具 —— **一只**判官 + **一只**句子。调它的面(点名,不是「每个面」):
|
|
3
|
+
* · 运维四表与中心下发的同名表、请求 `settings.permissions.{allow,ask,deny}`(经 `config.ts` 的 `unmatchableToolNamesRefusal`);
|
|
4
|
+
* · 会话规则写口两腿与 SQL 会话规则店的写口(经 `session-policy-wire.ts`);
|
|
5
|
+
* · 技能的两只清单 `allowedTools` / `disallowedTools`,两处受理口:顶层 `skills[]`(`http/admission.ts` 的 `validateUserSkills`)与子代
|
|
6
|
+
* `agents[].skills[]`(`spec-fields.ts` 的 `validateTaskAgents`),经 `spec-fields.ts` 的 `unreachableSkillToolListEntryOf`。
|
|
7
|
+
* **不调它的面**(如实,未接):`/v1/rules` 的持久规则写口与 `cc-import` 导入口(`http/routes/rules.ts`)、子代的工具名表 `agents[].allowTools` /
|
|
8
|
+
* `denyTools`(`spec-fields.ts` 只判形与条数)。在这两处写铸不出的服务器段仍被收下、永不命中。
|
|
9
|
+
* 规则文本里「工具名那一段」怎么取也只有一种读法:{@link ruleEntryToolName}(配置表与技能清单同读)。
|
|
10
|
+
*
|
|
11
|
+
* 规则:一条协议规则名够得着挂载工具 ⇔
|
|
12
|
+
* ① 分得出对端段 —— 三段名(`ns.parse` 认得:首段为对端、余段非空)取 `parse().peer`;两段覆盖形 `<ns>__<peer>`(core `isNamespacedCoveringRuleName`
|
|
13
|
+
* 认、`ns.parse` 不认)取前缀之后的整段;两者都不是(`mcp__` / `mcp__srv__` / `mcp____x`)= **缺段**;
|
|
14
|
+
* ② 对端段是引擎**自铸**的那一段:`mintNamespacePrefix(ns, peer) === ns.prefix + peer + "__"`。引擎铸注册名时归一对端段(字符表外换 `_`、超长截断加摘要、
|
|
15
|
+
* 折连串下划线、去尾下划线),而规则匹配对对端段逐字比(覆盖形 `namespacedRuleNameCovers`,精确名整串比)—— 铸不出的对端段一只工具都匹配不到。
|
|
16
|
+
* 服务器段里带 `*`(`mcp__*`、`mcp__git*__x`)是同一个「够不着」,但出路不同:那是在写通配,而服务器名不支持通配 —— 给一条铸名写法
|
|
17
|
+
* (`mcp__<摘要>__*`)没人照做得了 ⇒ 判词单列 `wildcard_peer`,句子说实话(写具体的服务器名)。
|
|
18
|
+
* 判据不看规则是覆盖形还是精确名:两种形的对端段命运相同。精确名的**工具段**归一不在这里判(包根没有导出工具段的铸法,不手抄)。
|
|
19
|
+
*
|
|
20
|
+
* core 下一版(7.42.0)的规则名判官会自己给这个判词;到货那天本仓这只对照整只退役(连同会话规则写口这一问的码由 `request.field_invalid` 归回
|
|
21
|
+
* `rules.legacy_tool_name`)。
|
|
22
|
+
*/
|
|
23
|
+
import { type ProtocolNamespace } from "@sema-agent/core";
|
|
24
|
+
/**
|
|
25
|
+
* 一条**规则文本**里的工具名那一段(判官与旧名判据的主语)= core 包根 `parsePermissionRule` 的 `toolName`,原样 —— 引擎的技能编译器
|
|
26
|
+
* (`compileSkillGrant`)与规则编译器读条目用的就是这一只:`Bash(npm:*)` ⇒ `Bash`;`mcp__srv(*)` / `mcp__srv()` / 带首尾空格的 `mcp__srv ` ⇒ 光杆名 `mcp__srv`。
|
|
27
|
+
* 配置表的规则编译器消费者与技能的两只清单读同一只。不在这里另写解析、不回落整串:修前用 `parseRuleText`(只认 `工具(内容)` 形,光杆名解析不出就回落整串),
|
|
28
|
+
* 整串带着 `(*)` / 尾空格进判官 ⇒ 合法条目被判「铸不出」(修复验证轮 gpt-6 腿)。条目若是引擎文法本身就拒的形(配置表上 MCP 规则带括号:
|
|
29
|
+
* `unsupported.mcp_paren`),拒由引擎的文法那一问说(配置表的判据链里它排在本判官之前),本判官不抢答。
|
|
30
|
+
*/
|
|
31
|
+
export declare function ruleEntryToolName(entry: string): string;
|
|
32
|
+
/** 一条协议规则名够不着任何挂载工具的判词(闭集)。 */
|
|
33
|
+
export type UnreachableNamespacedRuleName =
|
|
34
|
+
/** 分不出对端段:光杆前缀、对端段为空、或分隔符之后工具段为空。 */
|
|
35
|
+
{
|
|
36
|
+
readonly kind: "incomplete";
|
|
37
|
+
readonly ns: ProtocolNamespace;
|
|
38
|
+
}
|
|
39
|
+
/** 对端段不是引擎自铸的那一段;`minted` = 引擎为这样一个对端名铸出的注册名前缀(`mintNamespacePrefix` 的返回值,原样)。 */
|
|
40
|
+
| {
|
|
41
|
+
readonly kind: "unminted_peer";
|
|
42
|
+
readonly ns: ProtocolNamespace;
|
|
43
|
+
readonly minted: string;
|
|
44
|
+
}
|
|
45
|
+
/** 对端段里带 `*`:写的是通配,而服务器 / 对端名不支持通配(规则对这一段逐字比)。 */
|
|
46
|
+
| {
|
|
47
|
+
readonly kind: "wildcard_peer";
|
|
48
|
+
readonly ns: ProtocolNamespace;
|
|
49
|
+
};
|
|
50
|
+
/** 判官:不是协议名、或够得着 ⇒ `undefined`;否则给判词。纯字符串运算,不做 I/O。 */
|
|
51
|
+
export declare function unreachableNamespacedRuleNameOf(name: string): UnreachableNamespacedRuleName | undefined;
|
|
52
|
+
/** 判词 → 句子(「为什么永远匹配不到 + 怎么写」;不复述条目本身 —— 调用方的句子已点名它)。闭集穷尽:判词加一种 ⇒ 编译红。 */
|
|
53
|
+
export declare function describeUnreachableNamespacedRuleName(v: UnreachableNamespacedRuleName): string;
|
|
54
|
+
//# sourceMappingURL=namespaced-rule-name.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { isNamespacedCoveringRuleName, mintNamespacePrefix, parsePermissionRule, protocolOf } from "@sema-agent/core";
|
|
2
|
+
export function ruleEntryToolName(entry) {
|
|
3
|
+
return parsePermissionRule(entry).toolName;
|
|
4
|
+
}
|
|
5
|
+
export function unreachableNamespacedRuleNameOf(name) {
|
|
6
|
+
const ns = protocolOf(name);
|
|
7
|
+
if (ns === undefined)
|
|
8
|
+
return undefined;
|
|
9
|
+
const peer = ns.parse(name)?.peer ?? (isNamespacedCoveringRuleName(name) ? name.slice(ns.prefix.length) : undefined);
|
|
10
|
+
if (peer === undefined)
|
|
11
|
+
return { kind: "incomplete", ns };
|
|
12
|
+
if (peer.includes("*"))
|
|
13
|
+
return { kind: "wildcard_peer", ns };
|
|
14
|
+
const minted = mintNamespacePrefix(ns, peer);
|
|
15
|
+
return minted === `${ns.prefix}${peer}__` ? undefined : { kind: "unminted_peer", ns, minted };
|
|
16
|
+
}
|
|
17
|
+
export function describeUnreachableNamespacedRuleName(v) {
|
|
18
|
+
switch (v.kind) {
|
|
19
|
+
case "incomplete":
|
|
20
|
+
return `incomplete ${v.ns.id} name — the live form is ${v.ns.prefix}<server>__<tool>, or ${v.ns.prefix}<server> / ${v.ns.prefix}<server>__* to cover every tool on that server`;
|
|
21
|
+
case "wildcard_peer":
|
|
22
|
+
return `the ${v.ns.id} server segment does not take a wildcard — a rule compares the server segment literally, so the entry matches nothing; write the concrete server name: ${v.ns.prefix}<server>__* to cover every tool on that server, or ${v.ns.prefix}<server>__<tool>`;
|
|
23
|
+
case "unminted_peer":
|
|
24
|
+
return (`the engine never mints this server segment as written — a server configured under that name has its tools registered as ${v.minted}<tool>, ` +
|
|
25
|
+
`and a rule compares the server segment literally (no wildcard, no normalization), so the entry matches nothing; if that is the configured name, ` +
|
|
26
|
+
`write ${v.minted}* to cover every tool on that server, or ${v.minted}<tool>`);
|
|
27
|
+
default: {
|
|
28
|
+
const unhandled = v;
|
|
29
|
+
throw new Error(`unhandled namespaced rule name verdict: ${JSON.stringify(unhandled)}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=namespaced-rule-name.js.map
|
|
@@ -43,9 +43,10 @@ export declare function createCorruptReadFailOpenSeat(tag: FailOpenTag, leg?: (i
|
|
|
43
43
|
*
|
|
44
44
|
* ## 逐命中响亮不降级
|
|
45
45
|
* 这一座里有安全轴的脸(session-policy),所以每命中一行 warn 保留(降成「每进程一次」是降级,S-990 的裁定);**在此之上**,认出的脸另记账:
|
|
46
|
-
* · session-policy 脸 ⇒ `P-DEBT` tag(`server.session-policy.corrupt-read-fail-open`)—— 计数逐次 + 探针行 + 一次性 warn。core 7.39.2
|
|
47
|
-
*
|
|
48
|
-
*
|
|
46
|
+
* · session-policy 脸 ⇒ `P-DEBT` tag(`server.session-policy.corrupt-read-fail-open`)—— 计数逐次 + 探针行 + 一次性 warn。core 7.39.2 起按键读拒;
|
|
47
|
+
* core 7.41.0 #1309 起本会话的枚举拒、别的会话的抹除不动它、重绑带走它 —— 但只对**有归属**的坏行(行里的 `__sid`,否则同名高水位标记的 `__sid`);
|
|
48
|
+
* 无归属的坏行仍按「无规则」(会话规则只许收紧 ⇒ 丢的恰是收紧);座上分不出读法 ⇒ 这一脸每一发都计(含已拒的)。
|
|
49
|
+
* 抹除扫走无归属坏行那一形 core 是否披露、座上数不数得到,本车未亲核(未闭环,收车档登)。放行是 core 的裁定、本仓不包层(源头修复纪律),所以**只登债、不改放行**;
|
|
49
50
|
* · 其余脸 ⇒ 不计(见 {@link DurableAbsenceFace})。
|
|
50
51
|
* 记账**先于** warn:记账是总函数,warn 可能抛(core 的 guard 会吞掉那个抛),一次日志故障不许吃掉计数。
|
|
51
52
|
*
|
|
@@ -86,9 +86,10 @@ export function createDurableAbsenceSeat(warn, root, thirdFace) {
|
|
|
86
86
|
}
|
|
87
87
|
function buildDurableAbsenceNote(thirdFace) {
|
|
88
88
|
return ("a durable read hit unreadable bytes (never a plain ENOENT). " +
|
|
89
|
-
"Consequence depends on which store read it: session-policy = a keyed read (a task's session rules,
|
|
90
|
-
"
|
|
91
|
-
"
|
|
89
|
+
"Consequence depends on which store read it: session-policy = a keyed read and this session's enumeration (a task's session rules, " +
|
|
90
|
+
"GET/PUT .../policy, the session sync export) were REFUSED (code \"corrupt\"); another session's erase keeps the row and a data-root rebind carries it, " +
|
|
91
|
+
"but a damaged row nothing on disk attributes to a session (no session id in its bytes and no high-water mark beside it) is still omitted from listings, " +
|
|
92
|
+
"swept by any session's erase when it is not JSON and left behind by a rebind, i.e. read as NO RULES — fix or delete the named file (deleting it means no session rules); session repo = a listing silently " +
|
|
92
93
|
`omitted rows; ${thirdFace}. Inspect the named path before deciding.`);
|
|
93
94
|
}
|
|
94
95
|
const RULE_LANE_REMEDY = "the permission-rule lane is fail-closed until a person inspects the file (moving it aside restarts the lane with an empty log)";
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import type { Logger } from "./logger.js";
|
|
2
2
|
import type { Metrics } from "./metrics.js";
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
readonly note: string;
|
|
9
|
-
}
|
|
10
|
-
/** 闭集 tag 词表。形=`<repo>.<domain>.<site>`(跨仓同形,便于三仓遥测并表)。 */
|
|
3
|
+
/**
|
|
4
|
+
* 闭集 tag 词表。形=`<repo>.<domain>.<site>`(跨仓同形,便于三仓遥测并表)。
|
|
5
|
+
* 每项 `{ cls, note }`:`cls` 是判据表的分类(见模块头——只有这两类允许走本封装):`F`=纯体验/缓存回退,放行最坏后果只是"难看/不够好";
|
|
6
|
+
* `P-DEBT`=保护型欠账,方向明知不对、显式记债。`note` 说清这条兜底放行了什么、最坏后果是什么(写给未来读遥测的人,不写来历)。
|
|
7
|
+
*/
|
|
11
8
|
export declare const FAIL_OPEN_TAGS: {
|
|
12
9
|
readonly "server.approval-ask-journal.compaction-failed": {
|
|
13
10
|
readonly cls: "F";
|
|
@@ -371,7 +368,11 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
371
368
|
};
|
|
372
369
|
readonly "server.session-policy.corrupt-read-fail-open": {
|
|
373
370
|
readonly cls: "P-DEBT";
|
|
374
|
-
readonly note: "S-999(`observability/corrupt-read-seat.ts` 的 `createDurableAbsenceSeat`,`file_store_corrupt_read` 那只多脸座里认出的 **session-policy 脸**;local 车道,HTTP 服务的 local 腿与 run-local 同铸点):会话规则文件(数据根 `session-policy/` 下的行文件)坏了。core 7.39.2(#1299,本仓请托)起**按键读**(任务装规则、`GET` / `PUT /v1/sessions/:id/policy`)拒 —— `SessionPolicyError` 码 `corrupt
|
|
371
|
+
readonly note: "S-999(`observability/corrupt-read-seat.ts` 的 `createDurableAbsenceSeat`,`file_store_corrupt_read` 那只多脸座里认出的 **session-policy 脸**;local 车道,HTTP 服务的 local 腿与 run-local 同铸点):会话规则文件(数据根 `session-policy/` 下的行文件)坏了。core 7.39.2(#1299,本仓请托)起**按键读**(任务装规则、`GET` / `PUT /v1/sessions/:id/policy`)拒 —— `SessionPolicyError` 码 `corrupt`;core 7.41.0(#1309)起另三张脸对**有归属**的坏行(行里的 `__sid`,否则同名高水位标记的 `__sid`)也收紧了:① 本会话的 `listBySession` 拒(`corrupt`;会话同步导出 `GET /v1/sessions/:id/sync/manifest` 转 500 `corrupt`);② 别的会话的 `deleteBySession` 不动它;③ 数据根重绑带走它(新键上按键读照拒)。**仍按「无规则」的只剩无归属的坏行**(行里读不出 `__sid`、同名高水位标记也不在;core 包内 CHANGELOG 逐字「keeps its old fate」):枚举略过并披露、它「根本不是 JSON」时被任一会话的抹除扫掉(此后按键读得 ENOENT = 无规则)、重绑留在旧键下 —— 钉 `test/corrupt-read-f-seats-s990.test.ts` 的 🚧「S-1016 残余」格。会话规则**只许收紧** ⇒ 丢的恰是调用方加的收紧(部署策略 / hooks / shell 门仍在)。方向 = fail-open,**明知不对**;SQL 孪生(`plugins/session-policy-store-sql.ts`)对同形响亮抛(CENSUS 第 81 行 P 终态)。归 P 类的理由是它决定权限门读到的规则层;归 DEBT 的理由是放行是 **core 的裁定**,源头修复纪律下本仓不包层,只让债在遥测里显形:计数逐次 + 探针行(`kind` / `path` / `sessionId` / `principal` / `reason`)+ 一次性 warn;逐命中 warn(`file_store_corrupt_read`)原样保留。⚠️ **如实全记**:座上分不出是哪一只读法披露的(载荷不带读法),所以这一脸的每一发都计 —— 含已被拒的按键读与枚举(那些发本身是 fail-closed);抹除扫走无归属坏行那一形座上数不数得到未亲核。**出路 = core 把无归属行也改成拒,届时撤本 tag**(7.116.0 读不出能撤 ⇒ 照实留着)。计数非零 = 有会话的规则文件坏了:运维按探针里的 `sessionId` / `path` 去修(删文件即放弃那层规则,删前先看 `reason`)。认脸判据见 `durableAbsenceFaceOf`(披露路径落在本数据根的 `session-policy/` 之下);认不出的脸不计。7.114.0 合流时车 UY 曾以「core 已修」整只撤掉本 tag,修复轮恢复(那次删除未发版)。";
|
|
372
|
+
};
|
|
373
|
+
readonly "server.approval-crash-convergence.cause-unreadable": {
|
|
374
|
+
readonly cls: "F";
|
|
375
|
+
readonly note: "7.116.0 修复轮(`approval-crash-convergence.ts` 的 `convergeCrashedAsksAtBoot`;local 车道启动收敛):前世留下的一条 `STREAM_PENDING` 问行,它的卡信封(`card_json`)读不出 ⇒ 读不出 wire `approvalId`、无从向审计账要成因词 ⇒ 成因**回落**成 `crashed_before_park`。行照样收 VOID(决断口照样 410 —— 可决性 fail-closed,不受影响);最坏后果 = 一张其实是被有序关停截断的卡,在 410 体的 `deniedReason` 上说成「进程崩了」(`crashConverged` 读面若有它的审计组,那里的成因仍按审计账判,两处可能不同词)。计数非零 = 问账里有读不出卡信封的行(位腐 / 手改 / 版本错位),去看探针 detail 点名的 askId。读数另在启动日志 `approval_ask_audit_crash_converged` 的 `causeUnreadable` 格单列。";
|
|
375
376
|
};
|
|
376
377
|
readonly "server.outbound.connect-observer-threw": {
|
|
377
378
|
readonly cls: "F";
|
|
@@ -341,7 +341,11 @@ export const FAIL_OPEN_TAGS = {
|
|
|
341
341
|
},
|
|
342
342
|
"server.session-policy.corrupt-read-fail-open": {
|
|
343
343
|
cls: "P-DEBT",
|
|
344
|
-
note: "S-999(`observability/corrupt-read-seat.ts` 的 `createDurableAbsenceSeat`,`file_store_corrupt_read` 那只多脸座里认出的 **session-policy 脸**;local 车道,HTTP 服务的 local 腿与 run-local 同铸点):会话规则文件(数据根 `session-policy/` 下的行文件)坏了。core 7.39.2(#1299,本仓请托)起**按键读**(任务装规则、`GET` / `PUT /v1/sessions/:id/policy`)拒 —— `SessionPolicyError` 码 `corrupt
|
|
344
|
+
note: "S-999(`observability/corrupt-read-seat.ts` 的 `createDurableAbsenceSeat`,`file_store_corrupt_read` 那只多脸座里认出的 **session-policy 脸**;local 车道,HTTP 服务的 local 腿与 run-local 同铸点):会话规则文件(数据根 `session-policy/` 下的行文件)坏了。core 7.39.2(#1299,本仓请托)起**按键读**(任务装规则、`GET` / `PUT /v1/sessions/:id/policy`)拒 —— `SessionPolicyError` 码 `corrupt`;core 7.41.0(#1309)起另三张脸对**有归属**的坏行(行里的 `__sid`,否则同名高水位标记的 `__sid`)也收紧了:① 本会话的 `listBySession` 拒(`corrupt`;会话同步导出 `GET /v1/sessions/:id/sync/manifest` 转 500 `corrupt`);② 别的会话的 `deleteBySession` 不动它;③ 数据根重绑带走它(新键上按键读照拒)。**仍按「无规则」的只剩无归属的坏行**(行里读不出 `__sid`、同名高水位标记也不在;core 包内 CHANGELOG 逐字「keeps its old fate」):枚举略过并披露、它「根本不是 JSON」时被任一会话的抹除扫掉(此后按键读得 ENOENT = 无规则)、重绑留在旧键下 —— 钉 `test/corrupt-read-f-seats-s990.test.ts` 的 🚧「S-1016 残余」格。会话规则**只许收紧** ⇒ 丢的恰是调用方加的收紧(部署策略 / hooks / shell 门仍在)。方向 = fail-open,**明知不对**;SQL 孪生(`plugins/session-policy-store-sql.ts`)对同形响亮抛(CENSUS 第 81 行 P 终态)。归 P 类的理由是它决定权限门读到的规则层;归 DEBT 的理由是放行是 **core 的裁定**,源头修复纪律下本仓不包层,只让债在遥测里显形:计数逐次 + 探针行(`kind` / `path` / `sessionId` / `principal` / `reason`)+ 一次性 warn;逐命中 warn(`file_store_corrupt_read`)原样保留。⚠️ **如实全记**:座上分不出是哪一只读法披露的(载荷不带读法),所以这一脸的每一发都计 —— 含已被拒的按键读与枚举(那些发本身是 fail-closed);抹除扫走无归属坏行那一形座上数不数得到未亲核。**出路 = core 把无归属行也改成拒,届时撤本 tag**(7.116.0 读不出能撤 ⇒ 照实留着)。计数非零 = 有会话的规则文件坏了:运维按探针里的 `sessionId` / `path` 去修(删文件即放弃那层规则,删前先看 `reason`)。认脸判据见 `durableAbsenceFaceOf`(披露路径落在本数据根的 `session-policy/` 之下);认不出的脸不计。7.114.0 合流时车 UY 曾以「core 已修」整只撤掉本 tag,修复轮恢复(那次删除未发版)。",
|
|
345
|
+
},
|
|
346
|
+
"server.approval-crash-convergence.cause-unreadable": {
|
|
347
|
+
cls: "F",
|
|
348
|
+
note: "7.116.0 修复轮(`approval-crash-convergence.ts` 的 `convergeCrashedAsksAtBoot`;local 车道启动收敛):前世留下的一条 `STREAM_PENDING` 问行,它的卡信封(`card_json`)读不出 ⇒ 读不出 wire `approvalId`、无从向审计账要成因词 ⇒ 成因**回落**成 `crashed_before_park`。行照样收 VOID(决断口照样 410 —— 可决性 fail-closed,不受影响);最坏后果 = 一张其实是被有序关停截断的卡,在 410 体的 `deniedReason` 上说成「进程崩了」(`crashConverged` 读面若有它的审计组,那里的成因仍按审计账判,两处可能不同词)。计数非零 = 问账里有读不出卡信封的行(位腐 / 手改 / 版本错位),去看探针 detail 点名的 askId。读数另在启动日志 `approval_ask_audit_crash_converged` 的 `causeUnreadable` 格单列。",
|
|
345
349
|
},
|
|
346
350
|
"server.outbound.connect-observer-threw": {
|
|
347
351
|
cls: "F",
|
|
@@ -8,6 +8,9 @@ export declare const PERMISSION_RULE_EVENT_TABLE: readonly [{
|
|
|
8
8
|
}, {
|
|
9
9
|
readonly event: "read_only_allowed";
|
|
10
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
|
+
readonly event: "skill_allowed";
|
|
13
|
+
readonly meaning: "an activated skill's allowed-tools list cleared the ask for the rest of that run (core decisionReason \"skill\"; today only a skill the request itself carried can do this — this deployment's own skills carry no lists; it clears only the unstated / ordinary questions a persisted allow rule may clear, never a deny / ask rule, an operator list, a governance or shell-floor question, or a mandated one)";
|
|
11
14
|
}];
|
|
12
15
|
/** 词表里的一个词(闭集)。 */
|
|
13
16
|
export type PermissionRuleEventWord = (typeof PERMISSION_RULE_EVENT_TABLE)[number]["event"];
|
|
@@ -12,6 +12,10 @@ export const PERMISSION_RULE_EVENT_TABLE = [
|
|
|
12
12
|
event: "read_only_allowed",
|
|
13
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
14
|
},
|
|
15
|
+
{
|
|
16
|
+
event: "skill_allowed",
|
|
17
|
+
meaning: "an activated skill's allowed-tools list cleared the ask for the rest of that run (core decisionReason \"skill\"; today only a skill the request itself carried can do this — this deployment's own skills carry no lists; it clears only the unstated / ordinary questions a persisted allow rule may clear, never a deny / ask rule, an operator list, a governance or shell-floor question, or a mandated one)",
|
|
18
|
+
},
|
|
15
19
|
];
|
|
16
20
|
const PERMISSION_RULE_EVENT_KINDS = new Set(PERMISSION_RULE_EVENT_TABLE.map((r) => `${PERMISSION_TRACE_PREFIX}${r.event}`));
|
|
17
21
|
export function isPermissionRuleEventKind(kind) {
|