@sema-agent/server 7.104.0 → 7.106.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.
Files changed (206) hide show
  1. package/MIGRATION.md +114 -0
  2. package/USAGE.md +193 -52
  3. package/deploy/sema-up/chart/templates/server-v4pro.yaml +2 -0
  4. package/deploy/sema-up/cluster-up.sh +15 -2
  5. package/deploy/sema-up/kube-up.sh +8 -3
  6. package/deploy/sema-up/lib/flagshape.sh +29 -0
  7. package/deploy/sema-up/lib/parse-machines.py +4 -1
  8. package/deploy/sema-up/sema-up.sh +11 -9
  9. package/deploy/sema-up/stack/sema-stack-cluster.yaml +3 -1
  10. package/dist/approval-card.d.ts +1 -1
  11. package/dist/approval.d.ts +6 -1
  12. package/dist/approval.js +4 -4
  13. package/dist/auth-bridge.d.ts +14 -0
  14. package/dist/auth-bridge.js +7 -0
  15. package/dist/auto-mode-face.d.ts +49 -40
  16. package/dist/auto-mode-face.js +6 -68
  17. package/dist/bench/s1/live-deps.d.ts +9 -2
  18. package/dist/bench/s1/live-deps.js +13 -8
  19. package/dist/boot/budget-tracing.js +3 -2
  20. package/dist/boot/config-center.d.ts +4 -0
  21. package/dist/boot/config-center.js +95 -37
  22. package/dist/boot/coordinators.d.ts +1 -0
  23. package/dist/boot/coordinators.js +3 -1
  24. package/dist/boot/device-lane.d.ts +2 -1
  25. package/dist/boot/execution-env.d.ts +11 -0
  26. package/dist/boot/execution-env.js +30 -6
  27. package/dist/boot/execution-lane.js +2 -3
  28. package/dist/boot/leader.d.ts +2 -1
  29. package/dist/boot/leader.js +2 -0
  30. package/dist/boot/parked-revive-gate.d.ts +10 -7
  31. package/dist/boot/parked-revive-gate.js +4 -11
  32. package/dist/boot/resolve-spec.d.ts +1 -4
  33. package/dist/boot/resolve-spec.js +44 -21
  34. package/dist/boot/runner-deps.d.ts +5 -8
  35. package/dist/boot/runner-deps.js +5 -10
  36. package/dist/boot/side-query-lane.d.ts +2 -2
  37. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  38. package/dist/boot/stage-03-store-backend.js +2 -2
  39. package/dist/boot/stage-04-stores.d.ts +1 -1
  40. package/dist/boot/stage-05-execution-env.d.ts +2 -1
  41. package/dist/boot/stage-05-execution-env.js +2 -2
  42. package/dist/boot/stage-06-runners.d.ts +3 -2
  43. package/dist/boot/stage-06-runners.js +2 -5
  44. package/dist/boot/stage-07-capability-layer.d.ts +2 -2
  45. package/dist/boot/stage-07-capability-layer.js +1 -2
  46. package/dist/boot/stage-08-reapers.d.ts +2 -2
  47. package/dist/boot/stage-09-leader.d.ts +2 -2
  48. package/dist/boot/stage-09-leader.js +2 -2
  49. package/dist/boot/stage-10-http-server.d.ts +2 -2
  50. package/dist/boot/stage-10-http-server.js +11 -7
  51. package/dist/boot/stores.d.ts +1 -1
  52. package/dist/boot/stores.js +6 -4
  53. package/dist/brain.d.ts +24 -2
  54. package/dist/brain.js +29 -10
  55. package/dist/budget.js +3 -0
  56. package/dist/capabilities/center-wiring.d.ts +64 -0
  57. package/dist/capabilities/center-wiring.js +24 -0
  58. package/dist/capabilities/composite-members.d.ts +89 -0
  59. package/dist/capabilities/composite-members.js +20 -0
  60. package/dist/capabilities/execution-lane.d.ts +3 -0
  61. package/dist/capabilities/execution-lane.js +5 -2
  62. package/dist/config-catalog.d.ts +30 -5
  63. package/dist/config-catalog.js +75 -26
  64. package/dist/config-center/apply-effective.d.ts +1 -1
  65. package/dist/config-center/apply-effective.js +7 -8
  66. package/dist/config-center/apply-ledger.d.ts +17 -0
  67. package/dist/config-center/apply-ledger.js +5 -0
  68. package/dist/config-center/center-request.d.ts +17 -0
  69. package/dist/config-center/center-request.js +4 -0
  70. package/dist/config-center/pull-state.d.ts +29 -0
  71. package/dist/config-center/pull-state.js +2 -0
  72. package/dist/config-types.d.ts +41 -14
  73. package/dist/config-types.js +1 -0
  74. package/dist/config.d.ts +50 -2
  75. package/dist/config.js +111 -23
  76. package/dist/cross-session-settings.d.ts +1 -1
  77. package/dist/deployment-governance.d.ts +12 -1
  78. package/dist/deployment-governance.js +2 -0
  79. package/dist/execution-lane-caps.d.ts +30 -2
  80. package/dist/execution-lane-caps.js +12 -0
  81. package/dist/file-history-disclosure.d.ts +7 -31
  82. package/dist/file-history-disclosure.js +12 -0
  83. package/dist/fleet/fleet-bus.d.ts +5 -1
  84. package/dist/fleet-client.d.ts +18 -13
  85. package/dist/fleet-client.js +32 -22
  86. package/dist/fleet-lease.d.ts +72 -47
  87. package/dist/fleet-lease.js +66 -14
  88. package/dist/governance-http-status.d.ts +0 -1
  89. package/dist/governance-http-status.js +0 -1
  90. package/dist/hosted-posture.d.ts +6 -17
  91. package/dist/hosted-posture.js +11 -6
  92. package/dist/http/admission.d.ts +1 -1
  93. package/dist/http/admission.js +14 -9
  94. package/dist/http/approval-sweeps.js +13 -1
  95. package/dist/http/dispatch.d.ts +18 -0
  96. package/dist/http/dispatch.js +18 -5
  97. package/dist/http/durable-run-leg.js +2 -0
  98. package/dist/http/request-guards.d.ts +25 -0
  99. package/dist/http/request-guards.js +25 -5
  100. package/dist/http/resume-legs.d.ts +1 -1
  101. package/dist/http/resume-legs.js +19 -11
  102. package/dist/http/route-ctx.d.ts +9 -0
  103. package/dist/http/route-table.d.ts +15 -4
  104. package/dist/http/route-table.js +6 -5
  105. package/dist/http/routes/approvals-assistant.js +0 -1
  106. package/dist/http/routes/capabilities.js +12 -11
  107. package/dist/http/routes/config-catalog.js +2 -1
  108. package/dist/http/routes/diagnostics.js +2 -0
  109. package/dist/http/routes/fleet.d.ts +3 -2
  110. package/dist/http/routes/images.js +0 -1
  111. package/dist/http/routes/runs.js +2 -8
  112. package/dist/http/routes/session-sync.js +6 -1
  113. package/dist/http/routes/sessions.js +5 -2
  114. package/dist/http/routes/side-query.js +8 -1
  115. package/dist/http/routes/tasks.js +4 -1
  116. package/dist/http/routes/trace-usage.d.ts +2 -0
  117. package/dist/http/routes/trace-usage.js +4 -9
  118. package/dist/http/server.d.ts +29 -8
  119. package/dist/http/server.js +4 -1
  120. package/dist/http/session-inherit-env.d.ts +37 -0
  121. package/dist/http/session-inherit-env.js +6 -0
  122. package/dist/http/sse-lifecycle.d.ts +1 -1
  123. package/dist/http/sse-log.d.ts +12 -9
  124. package/dist/http/sse-log.js +1 -8
  125. package/dist/http/wire-types.d.ts +19 -28
  126. package/dist/index.d.ts +2 -1
  127. package/dist/index.js +2 -1
  128. package/dist/key-resolver.d.ts +74 -19
  129. package/dist/key-resolver.js +65 -7
  130. package/dist/leader/leader.d.ts +6 -0
  131. package/dist/leader/leader.js +2 -1
  132. package/dist/leader/repair-wire.js +2 -1
  133. package/dist/leader/wire.d.ts +9 -1
  134. package/dist/leader/wire.js +10 -6
  135. package/dist/observability/fail-open.d.ts +9 -1
  136. package/dist/observability/fail-open.js +9 -1
  137. package/dist/observability/metrics.js +3 -1
  138. package/dist/observability/permission-rule-events.d.ts +1 -1
  139. package/dist/observability/permission-rule-events.js +1 -1
  140. package/dist/observability/run-terminal.d.ts +82 -9
  141. package/dist/observability/run-terminal.js +112 -9
  142. package/dist/observability/secret-env-scrub.js +1 -1
  143. package/dist/observability/tool-trace.d.ts +2 -0
  144. package/dist/operator-ask.d.ts +29 -1
  145. package/dist/operator-ask.js +6 -0
  146. package/dist/parked-decide.d.ts +14 -1
  147. package/dist/parked-decide.js +8 -1
  148. package/dist/plugins/image-bake-store-sql.d.ts +0 -2
  149. package/dist/plugins/image-bake-store-sql.js +0 -5
  150. package/dist/plugins/retention-store-sql.js +1 -0
  151. package/dist/plugins/run-store-sql.d.ts +3 -2
  152. package/dist/plugins/schema-indexes.js +1 -0
  153. package/dist/plugins/session-policy-store-sql.d.ts +38 -7
  154. package/dist/plugins/session-policy-store-sql.js +73 -18
  155. package/dist/plugins/sql-driver.d.ts +1 -1
  156. package/dist/plugins/store-backend.d.ts +17 -6
  157. package/dist/plugins/store-backend.js +16 -5
  158. package/dist/plugins/tidb-pool.js +5 -4
  159. package/dist/read-face-posture.d.ts +25 -4
  160. package/dist/read-face-posture.js +9 -0
  161. package/dist/request-key-closure.js +2 -2
  162. package/dist/request-value-closure.d.ts +17 -5
  163. package/dist/request-value-closure.js +12 -0
  164. package/dist/router/route-orchestration.d.ts +11 -0
  165. package/dist/router/route-orchestration.js +7 -6
  166. package/dist/rules-consent.js +0 -11
  167. package/dist/run-local.js +4 -2
  168. package/dist/runs.d.ts +2 -1
  169. package/dist/runs.js +4 -4
  170. package/dist/runtime-caps-resolver.d.ts +4 -10
  171. package/dist/runtime-governance.d.ts +8 -11
  172. package/dist/runtime-governance.js +10 -41
  173. package/dist/sealed-key.d.ts +0 -40
  174. package/dist/sealed-key.js +0 -20
  175. package/dist/server-secret-env.d.ts +11 -12
  176. package/dist/session-policy-wire.d.ts +100 -12
  177. package/dist/session-policy-wire.js +71 -8
  178. package/dist/session-titler.d.ts +11 -0
  179. package/dist/session-titler.js +6 -2
  180. package/dist/sql-engine-posture.d.ts +2 -2
  181. package/dist/store-posture.d.ts +53 -0
  182. package/dist/store-posture.js +32 -0
  183. package/dist/task-settings.d.ts +34 -45
  184. package/dist/task-settings.js +46 -25
  185. package/dist/tool-approval.d.ts +51 -22
  186. package/dist/tool-approval.js +44 -8
  187. package/dist/tool-declaration-http-status.d.ts +12 -0
  188. package/dist/tool-declaration-http-status.js +12 -0
  189. package/dist/trace/core-keyset-guard.d.ts +7 -7
  190. package/dist/trace/engine-notice-wire.d.ts +1 -1
  191. package/dist/trace/engine-notice-wire.js +5 -0
  192. package/dist/trace/project.d.ts +13 -7
  193. package/dist/trace/project.js +54 -6
  194. package/dist/trace/redact.d.ts +7 -0
  195. package/dist/trace/redact.js +153 -61
  196. package/dist/trace/wire-projection-faces.d.ts +1 -1
  197. package/dist/trace/wire-projection-faces.js +1 -1
  198. package/dist/unattended-permission-mode.d.ts +110 -0
  199. package/dist/unattended-permission-mode.js +65 -0
  200. package/package.json +3 -3
  201. package/dist/boot/session-shell-gate-registry.d.ts +0 -39
  202. package/dist/boot/session-shell-gate-registry.js +0 -21
  203. package/dist/plugins/memory-session-policy-store.d.ts +0 -46
  204. package/dist/plugins/memory-session-policy-store.js +0 -52
  205. package/dist/roster-bound-http-status.d.ts +0 -6
  206. package/dist/roster-bound-http-status.js +0 -6
@@ -2,9 +2,9 @@ import { isThinkingLevel, expandTiers } from "@sema-agent/core";
2
2
  import { isModelAllowlisted, matchCatalogModel, refusedCatalogEntry, selectableCatalog } from "../model-select.js";
3
3
  import { modelUnavailableMessage } from "../model-entry-refusal.js";
4
4
  import { HttpError } from "../security.js";
5
- import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_PERMISSION_RULES, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS, SETTINGS_PERMISSION_MODE_WORDS, isSettingsPermissionMode, parseTaskSettings, skillListingBudgetFractionOk, skillListingMaxDescOk, taskSettingsKeyIssue } from "../task-settings.js";
5
+ import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS, SETTINGS_PERMISSION_MODE_WORDS, isSettingsPermissionMode, parseTaskSettings, settingsPermissionBucketIssue, skillListingBudgetFractionOk, skillListingMaxDescOk, taskSettingsKeyIssue } from "../task-settings.js";
6
6
  import { taskBodyKeyIssue } from "../request-key-closure.js";
7
- import { FILE_HISTORY_DISABLED_NO_RESTORE, fileHistoryRestoreConflict, singleMemberOffFieldIssue } from "../request-value-closure.js";
7
+ import { FILE_HISTORY_DISABLED_NO_RESTORE, fileHistoryRestoreConflict, inheritEnvFieldIssue, singleMemberOffFieldIssue } from "../request-value-closure.js";
8
8
  import { selfOrchestrationDenial } from "../task-workflow.js";
9
9
  import { centerEntitlementSourceWired } from "../runtime-caps-resolver.js";
10
10
  import { parseHooksConfig } from "../hooks/hook-runner.js";
@@ -157,13 +157,11 @@ export function createAdmission(ctx) {
157
157
  sendError(res, 400, "request.field_invalid", "settings.permissions must be an object");
158
158
  return null;
159
159
  }
160
- if (perms !== null && typeof perms === "object" && !Array.isArray(perms)) {
161
- for (const k of ["allow", "deny", "ask"]) {
162
- const arr = perms[k];
163
- if (Array.isArray(arr) && arr.length > MAX_SETTINGS_PERMISSION_RULES) {
164
- sendError(res, 400, "request.field_invalid", `settings.permissions.${k} must have at most ${MAX_SETTINGS_PERMISSION_RULES} entries`);
165
- return null;
166
- }
160
+ {
161
+ const bucketIssue = settingsPermissionBucketIssue(perms);
162
+ if (bucketIssue) {
163
+ sendError(res, 400, "request.field_invalid", bucketIssue.message, bucketIssue.fields);
164
+ return null;
167
165
  }
168
166
  }
169
167
  const envRaw = st.env;
@@ -333,6 +331,13 @@ export function createAdmission(ctx) {
333
331
  return null;
334
332
  }
335
333
  }
334
+ {
335
+ const inheritEnvIssue = inheritEnvFieldIssue(body);
336
+ if (inheritEnvIssue) {
337
+ sendError(res, 400, "request.field_invalid", inheritEnvIssue);
338
+ return null;
339
+ }
340
+ }
336
341
  if (fileHistoryRestoreConflict(body)) {
337
342
  sendError(res, 400, "request.field_conflict", FILE_HISTORY_DISABLED_NO_RESTORE);
338
343
  return null;
@@ -7,6 +7,18 @@ import { createWallClockJumpGuard } from "../wall-clock-jump-guard.js";
7
7
  import { hasServiceAuth } from "./dispatch.js";
8
8
  import { gatedPrincipal } from "./principal-gate.js";
9
9
  import { readinessOf, billableRefusalOf } from "./readiness.js";
10
+ const BLOCKED_SCALAR_KEYS = ["errorCode", "blockedBy", "field", "reason", "count", "cap", "index"];
11
+ function blockedScalars(body) {
12
+ const out = {};
13
+ if (body === null || typeof body !== "object")
14
+ return out;
15
+ for (const k of BLOCKED_SCALAR_KEYS) {
16
+ const v = body[k];
17
+ if (typeof v === "string" || (typeof v === "number" && Number.isFinite(v)))
18
+ out[k] = v;
19
+ }
20
+ return out;
21
+ }
10
22
  export function createApprovalSweeps(ctx) {
11
23
  const { deps, resumeCheckpoint, mayDiscloseRowError } = ctx;
12
24
  const denySweepClockGuard = createWallClockJumpGuard({
@@ -59,7 +71,7 @@ export function createApprovalSweeps(ctx) {
59
71
  continue;
60
72
  }
61
73
  await resumeCheckpoint(sessionId, "deny", "approval SLA expired — auto-denied", { decidedBy: "sla_timeout" }).then((r) => { if (r.status >= 400)
62
- deps.logger?.warn?.("deny_sweep_resume_blocked", { sessionId, status: r.status, errorCode: r.body?.errorCode }); }, (e) => deps.logger?.warn?.("deny_sweep_resume_failed", { sessionId, err: redactHead(e instanceof Error ? e.message : String(e), 2000) }));
74
+ deps.logger?.warn?.("deny_sweep_resume_blocked", { sessionId, status: r.status, ...blockedScalars(r.body) }); }, (e) => deps.logger?.warn?.("deny_sweep_resume_failed", { sessionId, err: redactHead(e instanceof Error ? e.message : String(e), 2000) }));
63
75
  }
64
76
  if (parkedSkipped > 0)
65
77
  deps.logger?.info?.("deny_sweep_parked_skipped", { count: parkedSkipped });
@@ -22,6 +22,24 @@ import type { FlatServiceDeps } from "./server.js";
22
22
  * 凭证门、[ref] 的 parked 赎回席、以及 [ref] 的 consolidation 阀门启动期告警读同一句(手抄迟早
23
23
  * 只改一处,而这条判据决定的是「无凭证部署上写口开不开」)。导出仅为最后一个消费点(main.ts)。 */
24
24
  export declare function hasServiceAuth(config: ServiceConfig): boolean;
25
+ /**
26
+ * 🔴 S-672(test T-72 [ref];主会话裁 ①,分诊 [ref])—— 全局服务凭据门**之前**那几族只读路
27
+ * (`route-table.ts` 里 `position: "read-credential"` 的行)的**唯一**鉴权谓词:「服务凭据 ∨ 已验 principal」。
28
+ *
29
+ * 病:这几族(trace-usage / workflows / fleet)排在全局门之前,理由只有一条 —— 只读 `TRACE_TOKEN` 单凭据的调用方
30
+ * (registry 用量 / 轨迹中继)在全局门会被 401。可排在门前之后,每一族只剩**块内**自己那道门:fleet / workflows
31
+ * 只在 `REQUIRE_PRINCIPAL` 时要身份,trace 认 `gatedPrincipal`(BFF 形上就是调用方自己写的 principal 头)。于是
32
+ * 配了 service token、没开 REQUIRE_PRINCIPAL 的部署上,`POST /v1/tasks` 吃凭据,这三族读路对端口全开。
33
+ *
34
+ * 规则(一条,三族同读;fail-closed):
35
+ * · 部署**没配**任何 service 凭据(`hasServiceAuth` 假)⇒ 放行 —— 与全局门对 `/v1/tasks` 同判(run-local 形);
36
+ * · 否则要么出示了**服务凭据**(service token 单 / 多,或只读 `TRACE_TOKEN` —— 后者正是这几族在门前的理由),
37
+ * 要么带着**验过的** principal(registry 登录令牌 / direct-door 签名 principal 令牌;判据 = `verifiedIdentitySource`,
38
+ * 与 off-box operator 读面同一只)。调用方自己写的 principal 头不是凭据。
39
+ * 过了本门,各族块内的门(属主 scope / fleet-wide 上限 / `REQUIRE_PRINCIPAL` 的身份门)照旧 —— 本谓词只回答
40
+ * 「这个请求带没带这台部署认的凭据」,不回答「它能看谁的行」。
41
+ */
42
+ export declare function readCredentialOk(req: IncomingMessage, config: ServiceConfig): boolean;
25
43
  export declare function createDispatch(ctx: {
26
44
  deps: FlatServiceDeps;
27
45
  noteActivity: (url: string) => void;
@@ -3,14 +3,21 @@ import { setSsoPrincipal, ssoVerifiedPrincipal, setSsoScope, isDestructiveSessio
3
3
  import { publicStoreProbeError } from "../store-live-probe.js";
4
4
  import { looksLikeJwt } from "@sema-agent/settings-schema/api/auth-bridge";
5
5
  import { ROUTE_METHOD_ORDER } from "./route-ctx.js";
6
- import { PRE_CREDENTIAL_ROUTES, POST_CREDENTIAL_ROUTES, routeLabel, isBillableSubmitPath, isCredentialGatedRewrite, isMethodDispatchedSubmitPath, lookupRoute, isProbePath } from "./route-table.js";
6
+ import { READ_CREDENTIAL_ROUTES, PUBLIC_ROUTES, POST_CREDENTIAL_ROUTES, routeLabel, isBillableSubmitPath, isCredentialGatedRewrite, isMethodDispatchedSubmitPath, lookupRoute, isProbePath } from "./route-table.js";
7
7
  import { sendJson, sendError, isSseResponse } from "./send.js";
8
8
  import { readinessOf, healthReadinessKeys, sendReadyz, billableRefusalOf, sendBillableRefusal } from "./readiness.js";
9
- import { authorized, systemFor, gatedPrincipal, sendPrincipalRefusal, sendUnauthorized } from "./principal-gate.js";
9
+ import { authorized, systemFor, gatedPrincipal, sendPrincipalRefusal, sendUnauthorized, verifiedIdentitySource } from "./principal-gate.js";
10
10
  const PROCESS_STARTED_AT_MS = Date.now() - Math.round(process.uptime() * 1000);
11
11
  export function hasServiceAuth(config) {
12
12
  return Boolean(config.authToken) || Object.keys(config.authTokens ?? {}).length > 0;
13
13
  }
14
+ export function readCredentialOk(req, config) {
15
+ if (!hasServiceAuth(config))
16
+ return true;
17
+ if (verifiedIdentitySource(req, config) !== undefined)
18
+ return true;
19
+ return config.traceToken !== undefined && config.traceToken !== "" && authorized(req, config.traceToken);
20
+ }
14
21
  function cors(res, req, origins, principalHeader) {
15
22
  if (!origins || origins.length === 0)
16
23
  return;
@@ -44,7 +51,7 @@ export function createDispatch(ctx) {
44
51
  };
45
52
  deps.drainState.lastAttachEndedAt = () => lastAttachEndedAt;
46
53
  }
47
- async function dispatchSegment(rows, req, res, url, search, method, allow, ctx) {
54
+ async function dispatchSegment(rows, req, res, url, search, method, allow, ctx, admit) {
48
55
  for (const row of rows) {
49
56
  if (row.enabled !== undefined && !row.enabled(deps))
50
57
  continue;
@@ -54,6 +61,10 @@ export function createDispatch(ctx) {
54
61
  allow.push(m);
55
62
  if (found.decl === null)
56
63
  continue;
64
+ if (admit !== undefined && !admit(req)) {
65
+ sendUnauthorized(res, "credential");
66
+ return true;
67
+ }
57
68
  await row.handler(req, res, { decl: found.decl, params: found.params, search }, ctx);
58
69
  return true;
59
70
  }
@@ -112,7 +123,7 @@ export function createDispatch(ctx) {
112
123
  ...(typeof deps.capabilities?.version === "string" ? { version: deps.capabilities.version } : {}),
113
124
  ...(deps.drainState?.draining ? { draining: true, ...(deps.drainState.since ? { drainingSince: deps.drainState.since } : {}), ...(deps.drainState.reason ? { drainReason: deps.drainState.reason } : {}) } : {}),
114
125
  ...(deps.drainState?.inflight ? { inflight: deps.drainState.inflight() } : {}),
115
- ...(deps.storeDegraded ? { durable: false, storeDegraded: true } : {}),
126
+ ...(deps.storePosture?.degradedReason !== undefined ? { durable: false, storeDegraded: true } : {}),
116
127
  ...healthReadinessKeys(readinessOf(deps)),
117
128
  model: deps.config.model.id,
118
129
  sessionBackend: deps.sessionStoreLabel ?? deps.config.sessionBackend,
@@ -204,7 +215,9 @@ export function createDispatch(ctx) {
204
215
  }
205
216
  }
206
217
  }
207
- if (await dispatchSegment(PRE_CREDENTIAL_ROUTES, req, res, url, search, method, allow.pre, ctx))
218
+ if (await dispatchSegment(READ_CREDENTIAL_ROUTES, req, res, url, search, method, allow.pre, ctx, (r) => readCredentialOk(r, deps.config)))
219
+ return;
220
+ if (await dispatchSegment(PUBLIC_ROUTES, req, res, url, search, method, allow.pre, ctx))
208
221
  return;
209
222
  let source = null;
210
223
  const anyServiceAuth = hasServiceAuth(deps.config);
@@ -5,6 +5,7 @@ import { cascadeConfig, runMeta } from "./run-meta.js";
5
5
  import { resolveStreamApprovalGate } from "../tool-approval.js";
6
6
  import { buildActiveRunConflict } from "./active-run-conflict.js";
7
7
  import { commitPlacement, devicePlacementErrorBody } from "./device-placement.js";
8
+ import { commitSessionInheritEnv } from "./session-inherit-env.js";
8
9
  import { clearTurnActivity, readTurnActivityMs } from "../turn-activity.js";
9
10
  import { recordResourceWindow } from "../resource-window.js";
10
11
  export async function mintAndDriveDurableRun(ctx, runStore, args) {
@@ -28,6 +29,7 @@ export async function mintAndDriveDurableRun(ctx, runStore, args) {
28
29
  const placementReject = await commitPlacement(deps, runStore, taskId, args.prepared.bindingIntent);
29
30
  if (placementReject)
30
31
  return { status: placementReject.status, body: devicePlacementErrorBody(placementReject) };
32
+ commitSessionInheritEnv(deps, args.prepared.spec.sessionId, args.prepared.body);
31
33
  if (deps.checkpointStore) {
32
34
  await deps.checkpointStore.putCtx(sessionId, { body: args.prepared.body, memoryScope: args.prepared.auth?.memoryScope });
33
35
  }
@@ -5,6 +5,31 @@
5
5
  */
6
6
  import type { IncomingMessage, ServerResponse } from "node:http";
7
7
  import type { FlatServiceDeps } from "./server.js";
8
+ import type { LeaseRefusalAdmission } from "../fleet-lease.js";
9
+ /**
10
+ * S-866 ③ —— 租约拒绝的应答形({@link leaseRefusalOf} 的产物;`{status, body}` 交回形 + 镜像头用的 `retryAfterSec`)。
11
+ * 体键序固定 `error, errorCode, …`(冻结门 `FROZEN_RESULT_BODY` 按这个字面形锚)。
12
+ */
13
+ export interface LeaseRefusal {
14
+ readonly status: 429 | 503;
15
+ readonly body: {
16
+ readonly error: string;
17
+ readonly errorCode: string;
18
+ readonly retryAfterSec: number;
19
+ readonly [extra: string]: unknown;
20
+ };
21
+ readonly retryAfterSec: number;
22
+ }
23
+ /**
24
+ * S-866 ③ —— 一次被拒的租约准入 → HTTP 应答的**唯一**映射(三只门 {@link createRequestGuards} 里的 `leaseDenied` /
25
+ * `ownerLeaseDenied` / `ownerFleetLeaseDenied` 同调它;12 个调用点零改)。按拒因穷举(加一臂 = 编译红):
26
+ * · `budget_exhausted` ⇒ **429 `quota_exhausted`**(字节同 7.105.0 的路由发送形:`retryAfterSec` + 中心 E4 结构化字段逐字透传);
27
+ * · `lease_unavailable` ⇒ **503 `fleet.lease_unavailable`**(只在 `FLEET_LEASE_FAIL_CLOSED` 生效时出现):体 `{reason, retryAfterSec}`,
28
+ * `reason` ∈ `auth-rejected | unreachable`(中心拒了本引擎的凭证 / 凭证读不出 ;中心不可达 / 5xx / 超时 / 坏体)。可重试。
29
+ * 7.105.0 及以前三只门各写一份 `!adm.ok ⇒ 429 quota_exhausted`,且 resume 族那份不带 E4 字段(两形分歧,当年如实登记);
30
+ * 一只映射之后两形同体(resume 族的 429 体多出中心的 E4 字段,additive)。
31
+ */
32
+ export declare function leaseRefusalOf(adm: LeaseRefusalAdmission): LeaseRefusal;
8
33
  export declare function createRequestGuards(ctx: {
9
34
  deps: FlatServiceDeps;
10
35
  }): {
@@ -4,6 +4,27 @@ import { RUN_NOT_FOUND_MESSAGE } from "./route-ctx.js";
4
4
  import { sendError } from "./send.js";
5
5
  import { authorized, systemFor, gatedPrincipal, explicitOperatorOk, runOwnerReadable, sendPrincipalRefusal } from "./principal-gate.js";
6
6
  const MAX_BODY = 8 * 1024 * 1024;
7
+ export function leaseRefusalOf(adm) {
8
+ switch (adm.reason) {
9
+ case "budget_exhausted":
10
+ return { status: 429, body: { error: "budget exhausted (quota lease)", errorCode: "quota_exhausted", retryAfterSec: adm.retryAfterSec, ...(adm.detail ?? {}) }, retryAfterSec: adm.retryAfterSec };
11
+ case "lease_unavailable":
12
+ return {
13
+ status: 503,
14
+ body: { error: "quota lease unavailable — this deployment enforces budgets fail-closed and could not confirm this caller's budget with the quota center, so nothing was run; retry after retryAfterSec seconds", errorCode: "fleet.lease_unavailable", reason: adm.cause, retryAfterSec: adm.retryAfterSec },
15
+ retryAfterSec: adm.retryAfterSec,
16
+ };
17
+ default: {
18
+ const unhandled = adm;
19
+ throw new Error(`lease refusal without an HTTP answer: ${JSON.stringify(unhandled)}`);
20
+ }
21
+ }
22
+ }
23
+ function sendLeaseRefusal(res, refusal) {
24
+ const { error, errorCode, ...extra } = refusal.body;
25
+ res.setHeader("retry-after", String(refusal.retryAfterSec));
26
+ sendError(res, refusal.status, errorCode, error, extra);
27
+ }
7
28
  export function createRequestGuards(ctx) {
8
29
  const { deps } = ctx;
9
30
  function quotaExceeded(req, res) {
@@ -36,7 +57,8 @@ export function createRequestGuards(ctx) {
36
57
  if (adm.ok)
37
58
  return undefined;
38
59
  deps.metrics?.inc("fleet_lease_rejected_total");
39
- return { status: 429, body: { error: "budget exhausted (quota lease)", errorCode: "quota_exhausted", retryAfterSec: adm.retryAfterSec } };
60
+ const refusal = leaseRefusalOf(adm);
61
+ return { status: refusal.status, body: refusal.body };
40
62
  }
41
63
  async function ownerLeaseDenied(res, owner) {
42
64
  if (!deps.fleetLease || !owner)
@@ -45,8 +67,7 @@ export function createRequestGuards(ctx) {
45
67
  if (adm.ok)
46
68
  return false;
47
69
  deps.metrics?.inc("fleet_lease_rejected_total");
48
- res.setHeader("retry-after", String(adm.retryAfterSec));
49
- sendError(res, 429, "quota_exhausted", "budget exhausted (quota lease)", { retryAfterSec: adm.retryAfterSec, ...(adm.detail ?? {}) });
70
+ sendLeaseRefusal(res, leaseRefusalOf(adm));
50
71
  return true;
51
72
  }
52
73
  function ownerQuotaDenied(res, owner) {
@@ -68,8 +89,7 @@ export function createRequestGuards(ctx) {
68
89
  if (adm.ok)
69
90
  return false;
70
91
  deps.metrics?.inc("fleet_lease_rejected_total");
71
- res.setHeader("retry-after", String(adm.retryAfterSec));
72
- sendError(res, 429, "quota_exhausted", "budget exhausted (quota lease)", { retryAfterSec: adm.retryAfterSec, ...(adm.detail ?? {}) });
92
+ sendLeaseRefusal(res, leaseRefusalOf(adm));
73
93
  return true;
74
94
  }
75
95
  async function usageWindowDenialFor(key) {
@@ -24,7 +24,7 @@ import type { createRequestGuards } from "./request-guards.js";
24
24
  import type { FlatServiceDeps } from "./server.js";
25
25
  export declare function createResumeLegs(ctx: {
26
26
  deps: FlatServiceDeps;
27
- runnerFor: NonNullable<FlatServiceDeps["runnerFor"]>;
27
+ runnerFor: FlatServiceDeps["runnerFor"];
28
28
  } & RunRegistry & RouteHelpers & Pick<RouteLegs, "prepareSpec" | "finalizeTaskResult"> & Pick<ReturnType<typeof createRequestGuards>, "ownerCostQuotaDenied" | "ownerFleetLeaseDenied" | "usageWindowDenialFor">): {
29
29
  routeCtxBase: RouteCtxBase;
30
30
  resumeCheckpoint: (sessionId: string, decision: "approve" | "deny", reason: string | undefined, hostDecision: HostDecision, req?: IncomingMessage, answer?: QuestionAnswer, binding?: DecideBinding, onResumeCommitted?: (grant: {
@@ -37,6 +37,7 @@ import { createCapabilitiesLocal } from "./routes/capabilities.js";
37
37
  import { gatedPrincipal, explicitOperatorOk } from "./principal-gate.js";
38
38
  import { buildActiveRunConflict, resumeEntryForGate } from "./active-run-conflict.js";
39
39
  import { commitPlacement, devicePlacementErrorBody } from "./device-placement.js";
40
+ import { commitSessionInheritEnv } from "./session-inherit-env.js";
40
41
  import { ASK_USER_QUESTION_TOOL_NAME } from "../approval-content-kind.js";
41
42
  import { readResumeCtx, resumeCtxCorruptAnswer } from "./resume-ctx-read.js";
42
43
  import { httpErrorCode } from "./send.js";
@@ -265,6 +266,7 @@ export function createResumeLegs(ctx) {
265
266
  sessionFaceOf: deps.sessionStorage?.sessionFaceOf ? deps.sessionStorage.sessionFaceOf.bind(deps.sessionStorage) : sessionFaceUnavailable,
266
267
  ...(deps.parkedKnownAgentTypes ? { knownAgentTypes: deps.parkedKnownAgentTypes } : {}),
267
268
  ...(deps.parkedReviveInheritedGate ? { rebuildInheritedGate: deps.parkedReviveInheritedGate } : {}),
269
+ ...(deps.toolApproval ? { approverSeatFor: deps.toolApproval.boundUnattendedAsk } : {}),
268
270
  ...(deps.logger ? { warn: parkWarn } : {}),
269
271
  }, parkedReq, { handle: origin.handle, row: origin.row }));
270
272
  void ctxPromise.catch(() => { });
@@ -297,7 +299,7 @@ export function createResumeLegs(ctx) {
297
299
  if (!ctx)
298
300
  return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
299
301
  const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
300
- const resolved = await resolveResumeSpecOrBlocked(ctx.body, req, auth);
302
+ const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
301
303
  if (!resolved.ok)
302
304
  return resolved.answer;
303
305
  const { spec, engineNotices } = resolved;
@@ -329,14 +331,18 @@ export function createResumeLegs(ctx) {
329
331
  }
330
332
  const ONE_SHOT_HISTORY_KEYS = ["resumeAt", "restoreFiles", "acceptPartialRestore", "rewindFilesTo", "rewindFiles"];
331
333
  const ONE_SHOT_HISTORY_STRIP = Object.fromEntries(ONE_SHOT_HISTORY_KEYS.map((k) => [k, undefined]));
332
- const resolveResumeSpec = async (body, req, auth) => {
334
+ const resolveResumeSpec = async (ctx, req, auth) => {
333
335
  const engineNotices = [];
334
- const spec = await deps.resolveSpec({ ...body, ...ONE_SHOT_HISTORY_STRIP }, req, auth, { leg: "resume", onNotice: (n) => engineNotices.push(n) });
336
+ const spec = await deps.resolveSpec({ ...ctx.body, ...ONE_SHOT_HISTORY_STRIP }, req, auth, {
337
+ leg: "resume",
338
+ onNotice: (n) => engineNotices.push(n),
339
+ ...(ctx.unattendedLeg === true ? { unattendedLeg: true } : {}),
340
+ });
335
341
  return { spec, engineNotices };
336
342
  };
337
- const resolveResumeSpecOrBlocked = async (body, req, auth) => {
343
+ const resolveResumeSpecOrBlocked = async (ctx, req, auth) => {
338
344
  try {
339
- const { spec, engineNotices } = await resolveResumeSpec(body, req, auth);
345
+ const { spec, engineNotices } = await resolveResumeSpec(ctx, req, auth);
340
346
  return { ok: true, spec, engineNotices };
341
347
  }
342
348
  catch (e) {
@@ -427,7 +433,7 @@ export function createResumeLegs(ctx) {
427
433
  if (deps.toolApproval && taskId) {
428
434
  const toolApproval = deps.toolApproval;
429
435
  resumeTaskConfig.onAsk = approvalLeg.windowZero
430
- ? (askReq) => Promise.resolve(toolApproval.unattendedAskOutcome({ taskId, owner: principal ?? null, toolName: askReq.toolName }))
436
+ ? toolApproval.boundUnattendedAsk({ owner: principal ?? null, taskId, sessionId })
431
437
  : toolApproval.boundAsk({
432
438
  owner: principal ?? null,
433
439
  taskId,
@@ -491,6 +497,7 @@ export function createResumeLegs(ctx) {
491
497
  deps.modelUsage?.register(taskId);
492
498
  resumeManifestLease = deps.promptManifests?.register(taskId) ?? 0;
493
499
  }
500
+ commitSessionInheritEnv(deps, sessionId, resumeCtx.ctx.body);
494
501
  if (args.onResumeCommitted)
495
502
  await args.onResumeCommitted();
496
503
  let unregisterEngineNotice;
@@ -1061,7 +1068,7 @@ export function createResumeLegs(ctx) {
1061
1068
  return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
1062
1069
  const ctx = ctxRead.ctx;
1063
1070
  const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
1064
- const resolved = await resolveResumeSpecOrBlocked(ctx.body, req, auth);
1071
+ const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
1065
1072
  if (!resolved.ok)
1066
1073
  return resolved.answer;
1067
1074
  const { spec, engineNotices } = resolved;
@@ -1093,7 +1100,7 @@ export function createResumeLegs(ctx) {
1093
1100
  if (windowDenial)
1094
1101
  return { status: windowDenial.status, body: windowDenial.body };
1095
1102
  const auth = { sessionId, principal, memoryScope: ctx.memoryScope };
1096
- const resolved = await resolveResumeSpecOrBlocked(ctx.body, req, auth);
1103
+ const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
1097
1104
  if (!resolved.ok)
1098
1105
  return resolved.answer;
1099
1106
  const { spec, engineNotices } = resolved;
@@ -1167,7 +1174,7 @@ export function createResumeLegs(ctx) {
1167
1174
  return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
1168
1175
  const ctx = ctxRead.ctx;
1169
1176
  const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
1170
- const resolved = await resolveResumeSpecOrBlocked(ctx.body, req, auth);
1177
+ const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
1171
1178
  if (!resolved.ok)
1172
1179
  return resolved.answer;
1173
1180
  const { spec, engineNotices } = resolved;
@@ -1267,7 +1274,8 @@ export function createResumeLegs(ctx) {
1267
1274
  }
1268
1275
  const approvedMode = decision === "approve" && effMode === "plan" ? (permissionModeAfter ?? "default") : undefined;
1269
1276
  const resumeBody = approvedMode === undefined ? ctx.body : { ...ctx.body, permissionMode: approvedMode };
1270
- const resolved = await resolveResumeSpecOrBlocked(resumeBody, req, auth);
1277
+ const rebuildCtx = { ...ctx, body: resumeBody };
1278
+ const resolved = await resolveResumeSpecOrBlocked(rebuildCtx, req, auth);
1271
1279
  if (!resolved.ok)
1272
1280
  return resolved.answer;
1273
1281
  const { spec, engineNotices } = resolved;
@@ -1279,7 +1287,7 @@ export function createResumeLegs(ctx) {
1279
1287
  ...(editedPlan !== undefined ? { editedPlan } : {}),
1280
1288
  ...(reason ? { reason } : {}),
1281
1289
  };
1282
- const out = await driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, verifyRounds, resumeCtx: { sessionId, ctx: { ...ctx, body: resumeBody } }, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
1290
+ const out = await driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, verifyRounds, resumeCtx: { sessionId, ctx: rebuildCtx }, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
1283
1291
  return out;
1284
1292
  }
1285
1293
  const routeCtxBase = {
@@ -147,6 +147,15 @@ export interface CheckpointCtx {
147
147
  body: TaskRequestBody;
148
148
  /** 提交时这条会话绑定的记忆域(重建 `RequestAuth` 用);缺席 = 没有记忆面。 */
149
149
  memoryScope?: string;
150
+ /**
151
+ * 🔴 S-877 候裁 18(codex r3 F1)—— 这条会话的 run 是**无人值守腿**铸的(今天唯一的写者 = leader worker 的 durable 块,
152
+ * `leader/wire.ts`),**服务端写、不来自请求体**(`body` 是提交体;本位是它的兄弟键,客户端够不着)。
153
+ * 续跑家族据此请 `resolveSpec` 带 `unattendedLeg: true`,由它按续跑 Runner 的同名座经 `createUnattendedLeg` / `withUnattendedLeg`
154
+ * 重挂整条腿(模式 = 旋钮、写面 roots、模式释放写保护时的写保护拒层)—— 拒层是活闭包、进不了 checkpoint,而 core 在 spec 不说
155
+ * 模式时取 checkpoint 记下的那一个(`seat ?? recorded`),不重挂 = 续跑落 bypass 而拒层缺席。写回口(`DriveResumeArgs.resumeCtx`)
156
+ * 传原 ctx / 其展开 ⇒ 标随行。缺席 = 不是无人值守腿(HTTP 提交腿;7.106.0 前落盘的行 —— 那时这些腿不铸模式,checkpoint 里也没有)。
157
+ */
158
+ unattendedLeg?: true;
150
159
  }
151
160
  /** B4①(命名化):`driveResumeIntoRunLog` 的入参形(resume 家族共用)——原为 9 成员内联匿名对象。 */
152
161
  export interface DriveResumeArgs {
@@ -55,7 +55,17 @@ import type { FlatServiceDeps } from "./server.js";
55
55
  import type { IncomingMessage, ServerResponse } from "node:http";
56
56
  import type { RouteDecl, RouteDispatchDecl, RouteMatch, RouteCtx, RouteMethod } from "./route-ctx.js";
57
57
  /** 一只域 handler 相对**全局服务凭据门**的位置。门前 = 未鉴权可达(每一只都要单独论证),门后 = 先过凭据。 */
58
- export type RoutePosition = "pre-credential" | "post-credential";
58
+ /**
59
+ * 一行路由在全局服务凭据门的哪一侧、吃哪道门(闭集,三员;S-672 起由两员拆成三员):
60
+ * · `"read-credential"` —— 门**之前**,但过**一只**读路谓词(`dispatch.ts` 的 `readCredentialOk`:部署配了 service
61
+ * 凭据时要「服务凭据(含只读 TRACE_TOKEN)∨ 已验 principal」,没配时与全局门同样放行)。只读路才能坐这一段:
62
+ * 它们在门前只为收只读 TRACE_TOKEN 单凭据的调用方,不是为了对端口全开;
63
+ * · `"public"` —— 门之前、**不吃**任何凭据门:按裁定匿名可达的面(今天只有 A2A agent card,R-11「公网匿名可发现」,
64
+ * 零存量披露);
65
+ * · `"post-credential"` —— 全局服务凭据门(+ 无凭据写门 / 排空 503 / roster-pending 503 / 破坏性会话写门)之后。
66
+ * 修前是 `"pre-credential" | "post-credential"` 两员:门前四族一律不吃任何门,三族只读路因此对端口全开(T-72)。
67
+ */
68
+ export type RoutePosition = "read-credential" | "public" | "post-credential";
59
69
  export interface RouteRow {
60
70
  /** 域入口(`routes/*.ts` 的 `handleX`)。签名一致性是编译期钉:入口漂了形 = 编译红,不是运行时静默 404。
61
71
  *
@@ -79,9 +89,10 @@ export interface RouteRow {
79
89
  readonly enabled?: (deps: FlatServiceDeps) => boolean;
80
90
  }
81
91
  export declare const ROUTES: readonly RouteRow[];
82
- /** 两段(`dispatch.ts` 的分派循环各跑一段)。段内序 = 本表行序;
83
- * 「门前行不许出现在门后行之后」由 `test/route-order-freeze.test.ts` 钉住。 */
84
- export declare const PRE_CREDENTIAL_ROUTES: readonly RouteRow[];
92
+ /** 三段(`dispatch.ts` 的分派循环按 read-credential → public → 全局门 → post-credential 各跑一段)。段内序 = 本表行序;
93
+ * 「三段各自连续、按这个次序排」由 `test/route-order-freeze.test.ts` 钉住。 */
94
+ export declare const READ_CREDENTIAL_ROUTES: readonly RouteRow[];
95
+ export declare const PUBLIC_ROUTES: readonly RouteRow[];
85
96
  export declare const POST_CREDENTIAL_ROUTES: readonly RouteRow[];
86
97
  /**
87
98
  * **探针路径**(k8s / 编排器 / 抓取器的常态高频采样):不计 admission inflight、不推活性时钟(`http/server.ts`
@@ -38,10 +38,10 @@ function row(spec) {
38
38
  return spec;
39
39
  }
40
40
  export const ROUTES = [
41
- row({ handler: handleTraceUsage, decls: TRACE_USAGE_ROUTES, position: "pre-credential", label: "trace-usage" }),
42
- row({ handler: handleWorkflows, decls: WORKFLOWS_ROUTES, position: "pre-credential", label: "workflows" }),
43
- row({ handler: handleFleet, decls: FLEET_ROUTES, position: "pre-credential", label: "fleet" }),
44
- row({ handler: handleA2aServe, decls: A2A_SERVE_ROUTES, position: "pre-credential", label: "a2a-serve" }),
41
+ row({ handler: handleTraceUsage, decls: TRACE_USAGE_ROUTES, position: "read-credential", label: "trace-usage" }),
42
+ row({ handler: handleWorkflows, decls: WORKFLOWS_ROUTES, position: "read-credential", label: "workflows" }),
43
+ row({ handler: handleFleet, decls: FLEET_ROUTES, position: "read-credential", label: "fleet" }),
44
+ row({ handler: handleA2aServe, decls: A2A_SERVE_ROUTES, position: "public", label: "a2a-serve" }),
45
45
  row({ handler: handleCapabilities, decls: CAPABILITIES_ROUTES, position: "post-credential", label: "capabilities" }),
46
46
  row({ handler: handleSideQuery, decls: SIDE_QUERY_ROUTES, position: "post-credential", label: "side-query" }),
47
47
  row({ handler: handleTasks, decls: TASKS_ROUTES, position: "post-credential", label: "tasks" }),
@@ -78,7 +78,8 @@ export const ROUTES = [
78
78
  row({ handler: handleAttachments, decls: ATTACHMENTS_ROUTES, position: "post-credential", label: "attachments" }),
79
79
  row({ handler: handleNotifyWake, decls: NOTIFY_WAKE_ROUTES, position: "post-credential", label: "notify-wake" }),
80
80
  ];
81
- export const PRE_CREDENTIAL_ROUTES = ROUTES.filter((r) => r.position === "pre-credential");
81
+ export const READ_CREDENTIAL_ROUTES = ROUTES.filter((r) => r.position === "read-credential");
82
+ export const PUBLIC_ROUTES = ROUTES.filter((r) => r.position === "public");
82
83
  export const POST_CREDENTIAL_ROUTES = ROUTES.filter((r) => r.position === "post-credential");
83
84
  const PIPELINE_ROUTES = [
84
85
  { id: "health", path: "/health", methods: ["GET"] },
@@ -347,7 +347,6 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
347
347
  ...(s.mandate !== undefined ? { mandate: s.mandate } : {}),
348
348
  ...(s.denialLimitFallback !== undefined ? { denialLimitFallback: s.denialLimitFallback } : {}),
349
349
  ...(s.origin !== undefined ? { origin: s.origin } : {}),
350
- ...(s.ruleStoreUnreadable !== undefined ? { ruleStoreUnreadable: s.ruleStoreUnreadable } : {}),
351
350
  objective,
352
351
  input,
353
352
  };
@@ -1,9 +1,9 @@
1
1
  import { expandTiers, reasoningEffortLevelsOf, routePairingStatus } from "@sema-agent/core";
2
2
  import { isModelAllowlisted, selectableCatalog } from "../../model-select.js";
3
- import { resolveModelApiKey } from "../../key-resolver.js";
4
- import { isSealedKeyPoison, projectSealedKeyCapability } from "../../sealed-key.js";
3
+ import { modelKeyPoisonOf, resolveModelApiKey } from "../../key-resolver.js";
4
+ import { projectSealedKeyCapability } from "../../sealed-key.js";
5
5
  import { cwdHonored, deviceCwdHonored } from "../../task-cwd.js";
6
- import { taskApproverPostureHonored, taskWriteFaceRefusal } from "../../hosted-posture.js";
6
+ import { taskWriteFaceRefusal } from "../../hosted-posture.js";
7
7
  import { autonomyOverrides } from "../../runtime-governance.js";
8
8
  import { DEVICE_PROTOCOL_VERSION, DEVICE_WS_PATH } from "../../device-ws-protocol.js";
9
9
  import { a2aInjectionHonored } from "../../task-a2a.js";
@@ -16,9 +16,10 @@ import { fileHistoryCaptureOf } from "../../file-history-capture.js";
16
16
  import { resolveStreamApprovalGate } from "../../tool-approval.js";
17
17
  import { projectMemoryEngineCapability } from "../../memory-posture.js";
18
18
  import { projectWriteProtectionCapability } from "../../write-protection.js";
19
- import { projectSqlEngineCapability } from "../../sql-engine-posture.js";
19
+ import { projectStoreCapability } from "../../store-posture.js";
20
20
  import { projectWebSearchCapability } from "../../plugins/web-search.js";
21
21
  import { projectExecutionLaneCapability } from "../../capabilities/execution-lane.js";
22
+ import { gateCompositeMembers } from "../../capabilities/composite-members.js";
22
23
  import { projectApprovalsStreamLiveCapability } from "../../capabilities/approvals-push.js";
23
24
  import { workflowModelAllowlistFor, selfOrchestrationDenial } from "../../task-workflow.js";
24
25
  import { centerEntitlementSourceWired } from "../../runtime-caps-resolver.js";
@@ -27,7 +28,7 @@ import { coercePermissionModeStrict, SETTINGS_PERMISSION_MODE_WORDS } from "../.
27
28
  import { gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
28
29
  import { sessionBackgroundFacesAvailable } from "./background.js";
29
30
  function routePairingOf(deps, name, m) {
30
- if (isSealedKeyPoison(deps.config.modelApiKeys?.[name]))
31
+ if (modelKeyPoisonOf(name, deps.config.modelApiKeyEnv ?? {}, process.env, deps.config.modelApiKeys ?? {}) !== undefined)
31
32
  return "unknown";
32
33
  const key = resolveModelApiKey(name, deps.config.modelApiKeyEnv ?? {}, process.env, deps.config.modelApiKeys ?? {});
33
34
  return routePairingStatus(deps.routeJudgeBrain ?? {}, m, key !== undefined ? { apiKey: key } : undefined);
@@ -65,11 +66,11 @@ export async function handleCapabilities(req, res, match, ctx) {
65
66
  return;
66
67
  }
67
68
  const entitlementSource = centerEntitlementSourceWired(deps.config);
68
- const { fault: autoModeFault, ...autoMode } = await judgeAutoModeArming(deps.autoModeFace, { requestedMode, principal: gatedPrincipal(req, deps.config), entitlementSource, localDeny: deps.config.permissionsDisableAutoMode });
69
+ const { fault: autoModeFault, ...autoMode } = await judgeAutoModeArming(deps.autoModeFace, { requestedMode, principal: gatedPrincipal(req, deps.config), localDeny: deps.config.permissionsDisableAutoMode });
69
70
  if (autoModeFault !== undefined)
70
71
  deps.logger?.warn("auto_mode_face_resolver_fault", { fault: autoModeFault });
71
72
  const rulesLaneReachable = Boolean(deps.ruleConsent) && gatedPrincipal(req, deps.config) !== undefined;
72
- sendJson(res, 200, {
73
+ sendJson(res, 200, gateCompositeMembers({
73
74
  ...deps.capabilities,
74
75
  asyncRuns: Boolean(deps.runStore),
75
76
  artifacts: Boolean(deps.runStore),
@@ -88,7 +89,8 @@ export async function handleCapabilities(req, res, match, ctx) {
88
89
  memory: false,
89
90
  memoryWrite: false,
90
91
  memoryEngine: projectMemoryEngineCapability(deps.memoryPosture),
91
- sql: projectSqlEngineCapability(deps.sqlEngineFacts?.()),
92
+ store: projectStoreCapability(deps.storePosture ?? { declared: deps.config.dbBackend, backend: "memory" }, deps.sqlEngineFacts?.()),
93
+ centerWiring: deps.centerWiring?.(gatedPrincipal(req, deps.config)),
92
94
  webSearch: projectWebSearchCapability(deps.webSearchProvider),
93
95
  sessionBackgroundable: Boolean(deps.runStore),
94
96
  runMemoryCaptureOptOut: Boolean(deps.runStore),
@@ -125,7 +127,7 @@ export async function handleCapabilities(req, res, match, ctx) {
125
127
  accepted: true,
126
128
  classifierSeat: deps.autoModeFace?.seatMounted === true,
127
129
  entitlementSource,
128
- intentArming: deps.autoModeFace?.engineArmsOnIntent === true,
130
+ intentArming: true,
129
131
  ...autoMode,
130
132
  },
131
133
  readFace: deps.config.readFace ?? null,
@@ -141,7 +143,6 @@ export async function handleCapabilities(req, res, match, ctx) {
141
143
  projectContext: cwdHonored(deps.config),
142
144
  callerCwd: cwdHonored(deps.config) || deviceCwdHonored(deps.config),
143
145
  taskWriteFaceOpen: taskWriteFaceRefusal(deps.config) === undefined && autonomyOverrides(deps.config.autonomy).writeFace !== "roots",
144
- taskApproverPosture: taskApproverPostureHonored(deps.config),
145
146
  executionLane: projectExecutionLaneCapability(deps.config),
146
147
  mcpInjection: mcpInjectionHonored(deps.config),
147
148
  a2a: true,
@@ -200,7 +201,7 @@ export async function handleCapabilities(req, res, match, ctx) {
200
201
  manualCompact: Boolean(deps.runStore),
201
202
  configCatalog: deps.config.configCatalogEnabled === true && deps.config.operatorPrincipals.length > 0,
202
203
  sealedKey: projectSealedKeyCapability(deps.sealedKeyPublic),
203
- });
204
+ }));
204
205
  return;
205
206
  }
206
207
  case "capability-mcp": {
@@ -2,7 +2,7 @@ import { DOMAINS } from "@sema-agent/settings-schema";
2
2
  import { sendJson, sendError } from "../send.js";
3
3
  import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
4
4
  import { assertNever } from "../route-ctx.js";
5
- import { configKnobTable } from "../../config.js";
5
+ import { buildRetiredEnvWire, configKnobTable } from "../../config.js";
6
6
  import { buildConfigCatalog } from "../../config-catalog.js";
7
7
  export const CONFIG_CATALOG_PATH = "/v1/config/catalog";
8
8
  const OPERATOR_ONLY_MESSAGE = "the config catalog 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)";
@@ -27,6 +27,7 @@ export async function handleConfigCatalog(req, res, match, ctx) {
27
27
  knobs: configKnobTable(),
28
28
  centerDomains: DOMAINS,
29
29
  centerManagedKeys: deps.configCenterManagedKeys?.(),
30
+ retired: buildRetiredEnvWire(),
30
31
  }));
31
32
  return;
32
33
  }
@@ -1,5 +1,6 @@
1
1
  import { buildPostureKnobReading } from "../../posture-source.js";
2
2
  import { buildReadFaceWiring } from "../../read-face-posture.js";
3
+ import { projectExecutionLaneCapability } from "../../capabilities/execution-lane.js";
3
4
  import { resolveStreamApprovalGate } from "../../tool-approval.js";
4
5
  import { sendJson, sendError } from "../send.js";
5
6
  import { gatedPrincipal, explicitOperatorOk, isLoopbackAddress, verifiedIdentitySource } from "../principal-gate.js";
@@ -104,6 +105,7 @@ export async function handleDiagnostics(req, res, match, ctx) {
104
105
  sqlEngine: deps.sqlEngineFacts?.() ?? null,
105
106
  writeProtection: deps.writeProtection ?? null,
106
107
  readFace: buildReadFaceWiring(deps.config),
108
+ executionLane: projectExecutionLaneCapability(deps.config),
107
109
  configApply: deps.configApplyState?.() ?? null,
108
110
  mcpRevocations: deps.mcpRevocationState?.() ?? null,
109
111
  serverGates: buildServerWiringGates({
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * [ref] A9 域模块 —— MF-Fleet 实时舰队流。
3
3
  *
4
- * 路由:`GET /v1/fleet/stream`(SSE PUSH:连上先快照,之后每条 per-row delta)。跑在服务凭据门**之前**
5
- * (按 principal 自门,与 workflow 只读路由同列)。本域零可变状态。
4
+ * 路由:`GET /v1/fleet/stream`(SSE PUSH:连上先快照,之后每条 per-row delta)。跑在服务凭据门**之前**的
5
+ * 读路谓词段(S-672:先过 `dispatch.ts` 的 `readCredentialOk`「服务凭据 ∨ 已验 principal」,再按 principal 自门,
6
+ * 与 workflow 只读路由同段)。本域零可变状态。
6
7
  */
7
8
  import type { IncomingMessage, ServerResponse } from "node:http";
8
9
  import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
@@ -420,7 +420,6 @@ async function streamBakeEvents(req, res, bakes, bakeId, staleMs) {
420
420
  return bake ? { status: bake.status, updatedAt: bake.updatedAt } : undefined;
421
421
  },
422
422
  getEvents: (id, after) => bakes.getEvents(id, after),
423
- retainedFrom: (id) => bakes.retainedFrom(id),
424
423
  formatEvent: (ev) => ({
425
424
  id: ev.seq,
426
425
  event: ev.kind,