@sema-agent/server 7.76.1 → 7.76.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/USAGE.md +47 -21
  2. package/dist/boot/engine-lease.d.ts +89 -11
  3. package/dist/boot/engine-lease.js +26 -11
  4. package/dist/boot/exit-last.d.ts +24 -0
  5. package/dist/boot/exit-last.js +46 -0
  6. package/dist/boot/shutdown.js +28 -4
  7. package/dist/config-catalog.js +3 -2
  8. package/dist/config-types.d.ts +23 -11
  9. package/dist/config.js +8 -8
  10. package/dist/http/principal-gate.d.ts +24 -1
  11. package/dist/http/principal-gate.js +16 -1
  12. package/dist/http/routes/a2a-serve.js +2 -2
  13. package/dist/http/routes/admin-config-refresh.js +2 -2
  14. package/dist/http/routes/admin-drain.js +2 -2
  15. package/dist/http/routes/admin-memory-optout.js +2 -2
  16. package/dist/http/routes/adoption.js +2 -2
  17. package/dist/http/routes/approvals-assistant.js +4 -4
  18. package/dist/http/routes/attachments.js +2 -2
  19. package/dist/http/routes/config-catalog.js +2 -2
  20. package/dist/http/routes/devices.js +2 -2
  21. package/dist/http/routes/fleet.js +2 -2
  22. package/dist/http/routes/leader.js +2 -2
  23. package/dist/http/routes/memory-bundle.js +2 -2
  24. package/dist/http/routes/memory-compliance.js +2 -2
  25. package/dist/http/routes/memory-consolidation.js +2 -2
  26. package/dist/http/routes/memory-origin.js +2 -2
  27. package/dist/http/routes/memory-policy.js +2 -2
  28. package/dist/http/routes/notify-wake.js +3 -3
  29. package/dist/http/routes/observability.js +2 -2
  30. package/dist/http/routes/retention-ops.js +2 -2
  31. package/dist/http/routes/run-memory-capture.js +2 -2
  32. package/dist/http/routes/runs.js +13 -13
  33. package/dist/http/routes/sessions.js +7 -7
  34. package/dist/http/routes/side-query.js +2 -2
  35. package/dist/http/routes/workflows.js +3 -3
  36. package/dist/http/server.js +2 -2
  37. package/dist/parent-watch.d.ts +32 -18
  38. package/dist/parent-watch.js +12 -10
  39. package/dist/tool-approval.js +2 -2
  40. package/dist/trace/redact.d.ts +23 -1
  41. package/dist/trace/redact.js +45 -11
  42. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import type { IncomingMessage } from "node:http";
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  import type { ServiceConfig } from "../config-types.js";
3
3
  /** Coerce a possibly-repeated request header to a single string (or undefined). */
4
4
  export declare function headerStr(v: string | string[] | undefined): string | undefined;
@@ -21,6 +21,29 @@ export declare function isLoopbackAddress(addr: string | undefined): boolean;
21
21
  */
22
22
  export declare function verifiedIdentitySource(req: IncomingMessage, config: ServiceConfig): "sso" | "direct_door_jwt" | "service_token" | undefined;
23
23
  export declare function gatedPrincipal(req: IncomingMessage, config: ServiceConfig): string | undefined;
24
+ /**
25
+ * 「**出示了凭证但验不过** ≠ **没出示**」在 **direct-door principal JWT** 面上的形 —— 本仓
26
+ * {@link bearerPresentedButUnverified} 早就是同一条律在 Bearer 面的形,这里只是把它推到另一面。
27
+ *
28
+ * 判据三段,缺一不成立:①这台机器是直连门(BFF/gated 上 `principalHeader` 由上游验过,本函数恒 false ⇒
29
+ * 那条部署形上零行为变化);②`X-Approval-Principal-Token` **在场**(缺席 = 真的没出示,那是
30
+ * `auth.principal_required` 的语义,不许借本码);③它**验不过**({@link verifiedPrincipal} 回 undefined)。
31
+ */
32
+ export declare function principalTokenPresentedButUnverified(req: IncomingMessage, config: ServiceConfig): boolean;
33
+ /**
34
+ * 🔴 **`requirePrincipal` 部署上「这次请求没有可用身份」的唯一 401 铸点**(S-305,7.76.2)。
35
+ *
36
+ * 此前 45 个路由各自手抄同一行 `sendError(res, 401, "auth.principal_required", …)`,于是直连门上
37
+ * 「JWT 出示了但过期/签不上」与「什么头都没带」在 wire 上**同形**(黑盒实测:plan_review 带过期 JWT
38
+ * 拿到 `auth.principal_required`,而契约与路由层的 `verifyDirectDoorProof` 说的是 `principal_unverified`)。
39
+ * 两件事的处置完全不同 —— 前者「重签一张再来」,后者「你压根没接上身份面」。
40
+ *
41
+ * 一条规则、一处落点:{@link principalTokenPresentedButUnverified} 为真 ⇒ `401 principal_unverified`
42
+ * (既有码,文案与 `principal-jwt.ts` 的验签层同句);否则 `401 auth.principal_required`(原样)。
43
+ * 路由层 `verifyDirectDoorProof` 自己那条 `principal_unverified` 臂保留 —— 它守的是 decide/plan_review
44
+ * 调用链上的**证明三件套**,与本门(身份面)是两道门。
45
+ */
46
+ export declare function sendPrincipalRefusal(res: ServerResponse, req: IncomingMessage, config: ServiceConfig): void;
24
47
  /** The EXPLICIT operator gate (IMAGE-API-DESIGN.md §P2.4a MUST-FIX) — NOT the bare `isOperator` (whose empty-list
25
48
  * "true-for-all" is an unrestricted-write exec door the instant OPERATOR_PRINCIPALS is empty). Requires a non-empty
26
49
  * operator set AND a present principal IN it. The same form the preempt/resume endpoints already use.
@@ -1,5 +1,6 @@
1
1
  import { timingSafeEqual } from "node:crypto";
2
- import { verifiedPrincipal, ssoVerifiedPrincipal } from "../security.js";
2
+ import { PRINCIPAL_TOKEN_HEADER, verifiedPrincipal, ssoVerifiedPrincipal } from "../security.js";
3
+ import { sendError } from "./send.js";
3
4
  export function headerStr(v) {
4
5
  return Array.isArray(v) ? v[0] : v;
5
6
  }
@@ -50,6 +51,20 @@ export function gatedPrincipal(req, config) {
50
51
  return undefined;
51
52
  return verifiedPrincipal(req, config);
52
53
  }
54
+ export function principalTokenPresentedButUnverified(req, config) {
55
+ if (!config.directDoorActive)
56
+ return false;
57
+ if (headerStr(req.headers[PRINCIPAL_TOKEN_HEADER]) === undefined)
58
+ return false;
59
+ return verifiedPrincipal(req, config) === undefined;
60
+ }
61
+ export function sendPrincipalRefusal(res, req, config) {
62
+ if (principalTokenPresentedButUnverified(req, config)) {
63
+ sendError(res, 401, "principal_unverified", "principal token failed verification");
64
+ return;
65
+ }
66
+ sendError(res, 401, "auth.principal_required", `missing principal header '${config.principalHeader}'`);
67
+ }
53
68
  export function explicitOperatorOk(principal, operatorPrincipals) {
54
69
  return operatorPrincipals.length > 0 && principal !== undefined && operatorPrincipals.includes(principal);
55
70
  }
@@ -6,7 +6,7 @@ import { isParkedRunStatus, isTerminalRunStatus } from "../../plugins/store-cont
6
6
  import { HttpError } from "../../security.js";
7
7
  import { scopedIdempotencyKey } from "../idempotency.js";
8
8
  import { sendJson, sendError } from "../send.js";
9
- import { gatedPrincipal } from "../principal-gate.js";
9
+ import { gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
10
10
  import { createDurableRun } from "./runs.js";
11
11
  export const A2A_PROTOCOL_VERSION = "1.0";
12
12
  export const A2A_VERSION_HEADER = "a2a-version";
@@ -344,7 +344,7 @@ async function handleA2aRpcBody(req, res, url, ctx, miss) {
344
344
  res.setHeader(A2A_VERSION_HEADER, A2A_PROTOCOL_VERSION);
345
345
  if (deps.config.requirePrincipal && !gatedPrincipal(req, deps.config)) {
346
346
  deps.metrics?.inc("a2a_serve_rejected_total", { reason: "principal_required" });
347
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
347
+ sendPrincipalRefusal(res, req, deps.config);
348
348
  return;
349
349
  }
350
350
  let payload;
@@ -1,5 +1,5 @@
1
1
  import { sendJson, sendError } from "../send.js";
2
- import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
2
+ import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
3
3
  export const ADMIN_CONFIG_REFRESH_PATH = "/v1/admin/config/refresh";
4
4
  const OPERATOR_ONLY_MESSAGE = "config refresh is operator-only: the caller principal is not in OPERATOR_PRINCIPALS (an empty list means NO one is an operator — configure it to enable this endpoint)";
5
5
  export async function handleAdminConfigRefresh(req, res, url, ctx) {
@@ -16,7 +16,7 @@ async function handleAdminConfigRefreshBody(req, res, url, ctx, miss) {
16
16
  }
17
17
  const principal = gatedPrincipal(req, deps.config);
18
18
  if (deps.config.requirePrincipal && !principal) {
19
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
19
+ sendPrincipalRefusal(res, req, deps.config);
20
20
  return;
21
21
  }
22
22
  if (!explicitOperatorOk(principal, deps.config.operatorPrincipals)) {
@@ -1,5 +1,5 @@
1
1
  import { sendJson, sendError } from "../send.js";
2
- import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
2
+ import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
3
3
  export const ADMIN_DRAIN_PATH = "/v1/admin/drain";
4
4
  export const MAX_DRAIN_REASON_CHARS = 256;
5
5
  const OPERATOR_ONLY_MESSAGE = "drain declaration is operator-only: the caller principal is not in OPERATOR_PRINCIPALS (an empty list means NO one is an operator — configure it to enable this endpoint)";
@@ -17,7 +17,7 @@ async function handleAdminDrainBody(req, res, url, ctx, miss) {
17
17
  }
18
18
  const principal = gatedPrincipal(req, deps.config);
19
19
  if (deps.config.requirePrincipal && !principal) {
20
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
20
+ sendPrincipalRefusal(res, req, deps.config);
21
21
  return;
22
22
  }
23
23
  if (!explicitOperatorOk(principal, deps.config.operatorPrincipals)) {
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { sendJson, sendError } from "../send.js";
3
- import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
3
+ import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
4
4
  import { memoryOptOutPrincipalShapeProblem } from "../../plugins/memory-optout-grant-store-sql.js";
5
5
  export const ADMIN_MEMORY_OPTOUT_PATH = "/v1/admin/memory-optout";
6
6
  export const ADMIN_MEMORY_OPTOUT_OVERRIDE_RE = /^\/v1\/admin\/memory-optout\/([^/]+)$/;
@@ -38,7 +38,7 @@ async function handleBody(req, res, url, ctx, miss) {
38
38
  }
39
39
  const principal = gatedPrincipal(req, deps.config);
40
40
  if (deps.config.requirePrincipal && !principal) {
41
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
41
+ sendPrincipalRefusal(res, req, deps.config);
42
42
  return;
43
43
  }
44
44
  if (!explicitOperatorOk(principal, deps.config.operatorPrincipals)) {
@@ -2,7 +2,7 @@ import { createAdoptionRunner } from "../../adoption/runner.js";
2
2
  import { AdoptionRequestSchema } from "../../adoption/wire.js";
3
3
  import { checkAdoptionPrincipalShape, checkAdoptionWidths } from "../../adoption/plan.js";
4
4
  import { sendJson, sendError } from "../send.js";
5
- import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
5
+ import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
6
6
  const ADOPTION_ID_RE = /^\/v1\/adoption\/([^/]+)$/;
7
7
  function sendOutcome(res, out) {
8
8
  switch (out.kind) {
@@ -54,7 +54,7 @@ async function handleAdoptionBody(req, res, url, ctx, miss) {
54
54
  }
55
55
  const principal = gatedPrincipal(req, deps.config);
56
56
  if (deps.config.requirePrincipal && !principal) {
57
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
57
+ sendPrincipalRefusal(res, req, deps.config);
58
58
  return;
59
59
  }
60
60
  if (!explicitOperatorOk(principal, deps.config.operatorPrincipals)) {
@@ -14,7 +14,7 @@ import { fleetRunLabels } from "../../fleet/fleet-bus.js";
14
14
  import { sleep } from "../sse-log.js";
15
15
  import { bindSseLifecycle } from "../sse-lifecycle.js";
16
16
  import { sendJson, sendError, sseHeaders, SSE_MAX_STREAM_MS, SSE_HEARTBEAT_IDLE_MS } from "../send.js";
17
- import { gatedPrincipal, explicitOperatorOk, isOperator } from "../principal-gate.js";
17
+ import { gatedPrincipal, explicitOperatorOk, isOperator, sendPrincipalRefusal } from "../principal-gate.js";
18
18
  import { governanceOriginOf } from "../active-run-conflict.js";
19
19
  import { sendResumeOutcome } from "../route-ctx.js";
20
20
  export const ASSISTANT_PREEMPT_RE = /^\/v1\/assistant\/tasks\/([^/]+)\/preempt$/;
@@ -246,7 +246,7 @@ async function handleApprovalsAssistantBody(req, res, url, ctx, miss) {
246
246
  return;
247
247
  }
248
248
  if (deps.config.requirePrincipal && principal === undefined) {
249
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
249
+ sendPrincipalRefusal(res, req, deps.config);
250
250
  return;
251
251
  }
252
252
  if (!deps.config.resourceSuspend || !deps.checkpointStore) {
@@ -310,7 +310,7 @@ async function handleApprovalsAssistantBody(req, res, url, ctx, miss) {
310
310
  return;
311
311
  }
312
312
  if (deps.config.requirePrincipal && principal === undefined) {
313
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
313
+ sendPrincipalRefusal(res, req, deps.config);
314
314
  return;
315
315
  }
316
316
  const taskId = resumeMatch[1];
@@ -338,7 +338,7 @@ async function handleApprovalsAssistantBody(req, res, url, ctx, miss) {
338
338
  return;
339
339
  }
340
340
  if (deps.config.requirePrincipal && principal === undefined) {
341
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
341
+ sendPrincipalRefusal(res, req, deps.config);
342
342
  return;
343
343
  }
344
344
  const taskId = planReviewMatch[1];
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { uuidv7, sanitizePathComponent } from "@sema-agent/core";
3
3
  import { HttpError } from "../../security.js";
4
4
  import { sendJson, sendError } from "../send.js";
5
- import { gatedPrincipal } from "../principal-gate.js";
5
+ import { gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
6
6
  export async function handleAttachments(req, res, url, ctx) {
7
7
  const miss = { fell: false };
8
8
  await handleAttachmentsBody(req, res, url, ctx, miss);
@@ -23,7 +23,7 @@ async function handleAttachmentsBody(req, res, url, ctx, miss) {
23
23
  }
24
24
  const principal = gatedPrincipal(req, deps.config);
25
25
  if (deps.config.requirePrincipal && principal === undefined) {
26
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
26
+ sendPrincipalRefusal(res, req, deps.config);
27
27
  return;
28
28
  }
29
29
  const owner = principal ?? "default";
@@ -1,6 +1,6 @@
1
1
  import { DOMAINS } from "@sema-agent/settings-schema";
2
2
  import { sendJson, sendError } from "../send.js";
3
- import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
3
+ import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
4
4
  import { configKnobTable } from "../../config.js";
5
5
  import { buildConfigCatalog } from "../../config-catalog.js";
6
6
  export const CONFIG_CATALOG_PATH = "/v1/config/catalog";
@@ -23,7 +23,7 @@ function handleConfigCatalogBody(req, res, url, ctx, miss) {
23
23
  }
24
24
  const principal = gatedPrincipal(req, deps.config);
25
25
  if (deps.config.requirePrincipal && !principal) {
26
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
26
+ sendPrincipalRefusal(res, req, deps.config);
27
27
  return;
28
28
  }
29
29
  if (!explicitOperatorOk(principal, deps.config.operatorPrincipals)) {
@@ -1,5 +1,5 @@
1
1
  import { sendError, sendJson } from "../send.js";
2
- import { explicitOperatorOk, gatedPrincipal } from "../principal-gate.js";
2
+ import { explicitOperatorOk, gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
3
3
  import { ssoVerifiedScope } from "../../security.js";
4
4
  import { assertDeviceIdShape, assertRootSessionIdShape, sameDeviceOwner, } from "../../device-store.js";
5
5
  const DEVICE_NOT_FOUND_MESSAGE = "device not found";
@@ -60,7 +60,7 @@ async function handleDevicesBody(req, res, url, ctx, miss) {
60
60
  }
61
61
  const principal = gatedPrincipal(req, deps.config);
62
62
  if (deps.config.requirePrincipal && principal === undefined) {
63
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
63
+ sendPrincipalRefusal(res, req, deps.config);
64
64
  return;
65
65
  }
66
66
  const store = deps.deviceStore;
@@ -2,7 +2,7 @@ import { hookNoticeWire } from "../../fleet/fleet-bus.js";
2
2
  import { FLEET_SNAPSHOT_TERMINAL_MAX_ROWS, isTerminalWorkflowRowStatus, recentTerminalWorkflowRows } from "../../fleet/fleet-terminal-window.js";
3
3
  import { taskNotificationFoldKey } from "../../orchestration/workflow-completion-inbox.js";
4
4
  import { sendJson, sendError, sseHeaders } from "../send.js";
5
- import { gatedPrincipal } from "../principal-gate.js";
5
+ import { gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
6
6
  export async function handleFleet(req, res, url, ctx) {
7
7
  const miss = { fell: false };
8
8
  await handleFleetBody(req, res, url, ctx, miss);
@@ -20,7 +20,7 @@ async function handleFleetBody(req, res, url, ctx, miss) {
20
20
  const { fleetWide } = sessionOwnerScope(req);
21
21
  const principal = gatedPrincipal(req, deps.config);
22
22
  if (deps.config.requirePrincipal && !principal && !fleetWide) {
23
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
23
+ sendPrincipalRefusal(res, req, deps.config);
24
24
  return;
25
25
  }
26
26
  const fleetParams = new URL(req.url ?? "", "http://x").searchParams;
@@ -1,5 +1,5 @@
1
1
  import { sendJson, sendError } from "../send.js";
2
- import { gatedPrincipal, headerStr } from "../principal-gate.js";
2
+ import { gatedPrincipal, headerStr, sendPrincipalRefusal } from "../principal-gate.js";
3
3
  export async function handleLeader(req, res, url, ctx) {
4
4
  const miss = { fell: false };
5
5
  await handleLeaderBody(req, res, url, ctx, miss);
@@ -13,7 +13,7 @@ async function handleLeaderBody(req, res, url, ctx, miss) {
13
13
  const isLeaderGet = req.method === "GET" && /^\/v1\/leader\/[^/]+$/.test(url);
14
14
  if (isLeaderPost || isLeaderGet) {
15
15
  if (deps.config.requirePrincipal && !gatedPrincipal(req, deps.config)) {
16
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
16
+ sendPrincipalRefusal(res, req, deps.config);
17
17
  return;
18
18
  }
19
19
  const requester = gatedPrincipal(req, deps.config) ?? null;
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { sendJson, sendError } from "../send.js";
3
3
  import { buildOriginFencedBundle, readOriginAware } from "../../memory-origin-fence.js";
4
- import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
4
+ import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
5
5
  export const MEMORY_BUNDLE_EXPORT_PATH = "/v1/memory/export";
6
6
  export const MEMORY_BUNDLE_IMPORT_PATH = "/v1/memory/import";
7
7
  const ExportRequestSchema = z.object({ scopes: z.array(z.string().min(1)).min(1) });
@@ -72,7 +72,7 @@ async function handleMemoryBundleBody(req, res, url, ctx, miss) {
72
72
  }
73
73
  const principal = gatedPrincipal(req, deps.config);
74
74
  if (deps.config.requirePrincipal && !principal) {
75
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
75
+ sendPrincipalRefusal(res, req, deps.config);
76
76
  return;
77
77
  }
78
78
  if (!explicitOperatorOk(principal, deps.config.operatorPrincipals)) {
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { ControlPlaneCorruptError } from "@sema-agent/core";
3
3
  import { erasureInputSmugglesPrototypeKey } from "../../memory-operator-faces.js";
4
4
  import { sendJson, sendError } from "../send.js";
5
- import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
5
+ import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
6
6
  export const MEMORY_ERASE_PATH = "/v1/memory/erase";
7
7
  export const MEMORY_PROVENANCE_RE = /^\/v1\/memory\/entries\/([^/]+)\/provenance$/;
8
8
  const EraseRequestSchema = z.object({
@@ -61,7 +61,7 @@ async function handleMemoryComplianceBody(req, res, url, ctx, miss) {
61
61
  }
62
62
  const principal = gatedPrincipal(req, deps.config);
63
63
  if (deps.config.requirePrincipal && !principal) {
64
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
64
+ sendPrincipalRefusal(res, req, deps.config);
65
65
  return;
66
66
  }
67
67
  if (!explicitOperatorOk(principal, deps.config.operatorPrincipals)) {
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { ControlPlaneCorruptError } from "@sema-agent/core";
3
3
  import { sendJson, sendError } from "../send.js";
4
- import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
4
+ import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
5
5
  export const MEMORY_CONSOLIDATION_STATUS_PATH = "/v1/admin/memory/consolidation";
6
6
  export const MEMORY_CONSOLIDATION_RUN_PATH = "/v1/admin/memory/consolidation/run";
7
7
  const RunRequestSchema = z.object({ scope: z.string().optional() }).strict();
@@ -86,7 +86,7 @@ async function handleMemoryConsolidationBody(req, res, url, ctx, miss) {
86
86
  }
87
87
  const principal = gatedPrincipal(req, deps.config);
88
88
  if (deps.config.requirePrincipal && !principal) {
89
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
89
+ sendPrincipalRefusal(res, req, deps.config);
90
90
  return;
91
91
  }
92
92
  if (!explicitOperatorOk(principal, deps.config.operatorPrincipals)) {
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { ControlPlaneCorruptError } from "@sema-agent/core";
3
3
  import { sendJson, sendError } from "../send.js";
4
4
  import { buildOriginFence, readOriginAware } from "../../memory-origin-fence.js";
5
- import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
5
+ import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
6
6
  export const MEMORY_ORIGIN_EXTERNAL_PATH = "/v1/memory/origin/external";
7
7
  export const MEMORY_ORIGIN_CLEARANCES_PATH = "/v1/memory/origin/clearances";
8
8
  export const MEMORY_ORIGIN_CLEAR_RE = /^\/v1\/memory\/origin\/entries\/([^/]+)\/clear$/;
@@ -123,7 +123,7 @@ async function handleMemoryOriginBody(req, res, url, ctx, miss) {
123
123
  }
124
124
  const principal = gatedPrincipal(req, deps.config);
125
125
  if (deps.config.requirePrincipal && !principal) {
126
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
126
+ sendPrincipalRefusal(res, req, deps.config);
127
127
  return;
128
128
  }
129
129
  if (!explicitOperatorOk(principal, deps.config.operatorPrincipals)) {
@@ -2,7 +2,7 @@ import { formatUserScope } from "@sema-agent/core";
2
2
  import { parseMemorySyncRequest } from "../../memory-sync.js";
3
3
  import { sendJson, sendError } from "../send.js";
4
4
  import { buildOriginFence, readOriginAware } from "../../memory-origin-fence.js";
5
- import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
5
+ import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
6
6
  const ORG_PREFIX = "org:";
7
7
  export const MEMORY_EXPORT_MAX_LIMIT = 500;
8
8
  function encodeExportCursor(c) {
@@ -171,7 +171,7 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
171
171
  if (url === "/v1/policy" && req.method === "GET") {
172
172
  const principal = gatedPrincipal(req, deps.config);
173
173
  if (deps.config.requirePrincipal && !principal) {
174
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
174
+ sendPrincipalRefusal(res, req, deps.config);
175
175
  return;
176
176
  }
177
177
  sendJson(res, 200, {
@@ -1,6 +1,6 @@
1
1
  import { taskNotificationInboxEntry } from "../../orchestration/workflow-completion-inbox.js";
2
2
  import { sendJson, sendError } from "../send.js";
3
- import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
3
+ import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
4
4
  import { sendResumeOutcome } from "../route-ctx.js";
5
5
  export const SESSION_WAKE_RE = /^\/v1\/sessions\/[^/]+\/wake$/;
6
6
  export async function handleNotifyWake(req, res, url, ctx) {
@@ -18,7 +18,7 @@ async function handleNotifyWakeBody(req, res, url, ctx, miss) {
18
18
  return;
19
19
  const principal = gatedPrincipal(req, deps.config);
20
20
  if (deps.config.requirePrincipal && principal === undefined) {
21
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
21
+ sendPrincipalRefusal(res, req, deps.config);
22
22
  return;
23
23
  }
24
24
  const notifySession = decodeURIComponent(url.split("/")[3]);
@@ -112,7 +112,7 @@ async function handleNotifyWakeBody(req, res, url, ctx, miss) {
112
112
  return;
113
113
  const principal = gatedPrincipal(req, deps.config);
114
114
  if (deps.config.requirePrincipal && principal === undefined) {
115
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
115
+ sendPrincipalRefusal(res, req, deps.config);
116
116
  return;
117
117
  }
118
118
  const wakeSession = decodeURIComponent(url.split("/")[3]);
@@ -1,5 +1,5 @@
1
1
  import { sendJson, sendError } from "../send.js";
2
- import { gatedPrincipal, isOperator, explicitOperatorOk } from "../principal-gate.js";
2
+ import { gatedPrincipal, isOperator, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
3
3
  export async function handleObservability(req, res, url, ctx) {
4
4
  const miss = { fell: false };
5
5
  await handleObservabilityBody(req, res, url, ctx, miss);
@@ -34,7 +34,7 @@ async function handleObservabilityBody(req, res, url, ctx, miss) {
34
34
  if (url === "/v1/usage" && req.method === "GET") {
35
35
  const principal = gatedPrincipal(req, deps.config);
36
36
  if (!principal) {
37
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
37
+ sendPrincipalRefusal(res, req, deps.config);
38
38
  return;
39
39
  }
40
40
  const windowSec = deps.config.costQuotaWindowSec;
@@ -1,5 +1,5 @@
1
1
  import { sendJson, sendError } from "../send.js";
2
- import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
2
+ import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
3
3
  import { RETENTION_AUDIT_DEFAULT_LIMIT, RETENTION_AUDIT_MAX_LIMIT } from "../../plugins/retention-lane-store-sql.js";
4
4
  const HOLD_RE = /^\/v1\/ops\/retention\/holds\/([^/]*)$/;
5
5
  const AUDIT_PATH = "/v1/ops/retention/audit";
@@ -40,7 +40,7 @@ async function handleRetentionBody(req, res, url, ctx, miss) {
40
40
  }
41
41
  const principal = gatedPrincipal(req, deps.config);
42
42
  if (deps.config.requirePrincipal && !principal) {
43
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
43
+ sendPrincipalRefusal(res, req, deps.config);
44
44
  return;
45
45
  }
46
46
  if (!explicitOperatorOk(principal, deps.config.operatorPrincipals)) {
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { isParkedRunStatus, isTerminalRunStatus } from "../../plugins/store-contracts.js";
3
3
  import { sendJson, sendError } from "../send.js";
4
- import { gatedPrincipal } from "../principal-gate.js";
4
+ import { gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
5
5
  import { RUN_NOT_FOUND_MESSAGE, runNotFoundMessage } from "../route-ctx.js";
6
6
  export const RUN_MEMORY_CAPTURE_OPTOUT_RE = /^\/v1\/runs\/([^/]+)\/memory\/capture-optout$/;
7
7
  const BodySchema = z.object({ reason: z.string().min(1).max(500).optional() }).strict();
@@ -25,7 +25,7 @@ async function handleBody(req, res, url, ctx, miss) {
25
25
  }
26
26
  const principal = gatedPrincipal(req, deps.config);
27
27
  if (deps.config.requirePrincipal && principal === undefined) {
28
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
28
+ sendPrincipalRefusal(res, req, deps.config);
29
29
  return;
30
30
  }
31
31
  const taskId = m[1];
@@ -23,7 +23,7 @@ import { sendJson, sendError, httpErrorCode, sseHeaders } from "../send.js";
23
23
  import { readTurnActivityMs } from "../../turn-activity.js";
24
24
  import { mintCancelledResult } from "../../run-cancel-context.js";
25
25
  import { buildCrossSliceUsage, readResourceWindow } from "../../resource-window.js";
26
- import { headerStr, gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
26
+ import { headerStr, gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
27
27
  import { ActorAssertionWire } from "@sema-agent/settings-schema";
28
28
  import { mintAndDriveDurableRun } from "../durable-run-leg.js";
29
29
  import { RUN_NOT_FOUND_MESSAGE, runNotFoundMessage } from "../route-ctx.js";
@@ -82,7 +82,7 @@ export const RUN_TASK_STOP_RE = /^\/v1\/runs\/([^/]+)\/tasks\/([^/]+)\/stop$/;
82
82
  async function subagentReadGate(req, res, deps, runId) {
83
83
  const principal = gatedPrincipal(req, deps.config);
84
84
  if (deps.config.requirePrincipal && principal === undefined) {
85
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
85
+ sendPrincipalRefusal(res, req, deps.config);
86
86
  return null;
87
87
  }
88
88
  const trusted = explicitOperatorOk(principal, deps.config.operatorPrincipals);
@@ -197,7 +197,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
197
197
  }
198
198
  const taskId = runMatch[1];
199
199
  if (deps.config.requirePrincipal && !gatedPrincipal(req, deps.config)) {
200
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
200
+ sendPrincipalRefusal(res, req, deps.config);
201
201
  return;
202
202
  }
203
203
  const run = await deps.runStore.getRun(taskId);
@@ -279,7 +279,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
279
279
  return;
280
280
  }
281
281
  if (deps.config.requirePrincipal && !gatedPrincipal(req, deps.config)) {
282
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
282
+ sendPrincipalRefusal(res, req, deps.config);
283
283
  return;
284
284
  }
285
285
  const taskId = cancelMatch[1];
@@ -407,7 +407,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
407
407
  }
408
408
  const principal = gatedPrincipal(req, deps.config);
409
409
  if (deps.config.requirePrincipal && principal === undefined) {
410
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
410
+ sendPrincipalRefusal(res, req, deps.config);
411
411
  return;
412
412
  }
413
413
  const taskId = steerMatch[1];
@@ -686,7 +686,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
686
686
  }
687
687
  const principal = gatedPrincipal(req, deps.config);
688
688
  if (deps.config.requirePrincipal && principal === undefined) {
689
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
689
+ sendPrincipalRefusal(res, req, deps.config);
690
690
  return;
691
691
  }
692
692
  const taskId = interruptMatch[1];
@@ -832,7 +832,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
832
832
  }
833
833
  const principal = gatedPrincipal(req, deps.config);
834
834
  if (deps.config.requirePrincipal && principal === undefined) {
835
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
835
+ sendPrincipalRefusal(res, req, deps.config);
836
836
  return;
837
837
  }
838
838
  const taskId = compactMatch[1];
@@ -899,7 +899,7 @@ async function handleRunsBody(req, res, url, ctx, miss) {
899
899
  }
900
900
  const principal = gatedPrincipal(req, deps.config);
901
901
  if (deps.config.requirePrincipal && principal === undefined) {
902
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
902
+ sendPrincipalRefusal(res, req, deps.config);
903
903
  return;
904
904
  }
905
905
  const taskId = detachMatch[1];
@@ -1117,7 +1117,7 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
1117
1117
  }
1118
1118
  const principal = gatedPrincipal(req, deps.config);
1119
1119
  if (deps.config.requirePrincipal && principal === undefined) {
1120
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
1120
+ sendPrincipalRefusal(res, req, deps.config);
1121
1121
  return;
1122
1122
  }
1123
1123
  const runId = safeDecode(subVerbMatch[1]);
@@ -1214,7 +1214,7 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
1214
1214
  }
1215
1215
  const principal = gatedPrincipal(req, deps.config);
1216
1216
  if (deps.config.requirePrincipal && principal === undefined) {
1217
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
1217
+ sendPrincipalRefusal(res, req, deps.config);
1218
1218
  return;
1219
1219
  }
1220
1220
  const id = elicitMatch[1];
@@ -1240,7 +1240,7 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
1240
1240
  }
1241
1241
  const principal = gatedPrincipal(req, deps.config);
1242
1242
  if (deps.config.requirePrincipal && principal === undefined) {
1243
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
1243
+ sendPrincipalRefusal(res, req, deps.config);
1244
1244
  return;
1245
1245
  }
1246
1246
  const id = questionMatch[1];
@@ -1266,7 +1266,7 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
1266
1266
  }
1267
1267
  const principal = gatedPrincipal(req, deps.config);
1268
1268
  if (deps.config.requirePrincipal && principal === undefined) {
1269
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
1269
+ sendPrincipalRefusal(res, req, deps.config);
1270
1270
  return;
1271
1271
  }
1272
1272
  const id = approvalMatch[1];
@@ -1303,7 +1303,7 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
1303
1303
  }
1304
1304
  const principal = gatedPrincipal(req, deps.config);
1305
1305
  if (deps.config.requirePrincipal && principal === undefined) {
1306
- sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
1306
+ sendPrincipalRefusal(res, req, deps.config);
1307
1307
  return;
1308
1308
  }
1309
1309
  let rawBody;