@sema-agent/server 7.136.0 → 7.137.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 (214) hide show
  1. package/MIGRATION.md +173 -0
  2. package/USAGE.md +84 -16
  3. package/deploy/sema-up/README.md +14 -0
  4. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +7 -1
  5. package/deploy/sema-up/stack/sema-stack-cluster.yaml +7 -1
  6. package/dist/approval-content-kind.d.ts +6 -0
  7. package/dist/approval-content-kind.js +7 -0
  8. package/dist/approval-gate-currency.d.ts +48 -6
  9. package/dist/approval-gate-currency.js +16 -9
  10. package/dist/approval-hmac.d.ts +13 -1
  11. package/dist/approval-hmac.js +1 -1
  12. package/dist/approval.js +3 -3
  13. package/dist/auth-bridge.js +2 -2
  14. package/dist/boot/execution-env.d.ts +24 -1
  15. package/dist/boot/execution-env.js +69 -6
  16. package/dist/boot/reapers.d.ts +3 -0
  17. package/dist/boot/reapers.js +15 -1
  18. package/dist/boot/resolve-spec.d.ts +4 -0
  19. package/dist/boot/resolve-spec.js +16 -17
  20. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  21. package/dist/boot/stage-04-stores.d.ts +1 -1
  22. package/dist/boot/stage-05-execution-env.d.ts +3 -1
  23. package/dist/boot/stage-05-execution-env.js +11 -2
  24. package/dist/boot/stage-06-runners.d.ts +3 -1
  25. package/dist/boot/stage-06-runners.js +5 -1
  26. package/dist/boot/stage-07-capability-layer.d.ts +4 -1
  27. package/dist/boot/stage-07-capability-layer.js +1 -1
  28. package/dist/boot/stage-08-reapers.d.ts +4 -1
  29. package/dist/boot/stage-08-reapers.js +2 -1
  30. package/dist/boot/stage-09-leader.d.ts +4 -1
  31. package/dist/boot/stage-10-http-server.d.ts +4 -1
  32. package/dist/boot/stage-10-http-server.js +3 -2
  33. package/dist/boot/stores.d.ts +1 -1
  34. package/dist/broker/broker.d.ts +89 -0
  35. package/dist/broker/broker.js +287 -0
  36. package/dist/broker/constants.d.ts +39 -0
  37. package/dist/broker/constants.js +15 -0
  38. package/dist/broker/http1.d.ts +111 -0
  39. package/dist/broker/http1.js +256 -0
  40. package/dist/broker/lease-book.d.ts +87 -0
  41. package/dist/broker/lease-book.js +142 -0
  42. package/dist/broker/lease-file.d.ts +60 -0
  43. package/dist/broker/lease-file.js +127 -0
  44. package/dist/broker/main.d.ts +2 -0
  45. package/dist/broker/main.js +20 -0
  46. package/dist/capabilities/composite-members.d.ts +2 -1
  47. package/dist/capabilities/composite-members.js +2 -1
  48. package/dist/capabilities/engine-capabilities.js +1 -0
  49. package/dist/capabilities/scenarios.d.ts +23 -8
  50. package/dist/capabilities/scenarios.js +13 -8
  51. package/dist/capabilities/skills.d.ts +28 -5
  52. package/dist/capabilities/skills.js +8 -0
  53. package/dist/center-credential-renewer.js +5 -1
  54. package/dist/checkpoint-ctx-seal-m28.d.ts +3 -10
  55. package/dist/checkpoint-ctx-seal-m28.js +1 -1
  56. package/dist/checkpoint-identity.d.ts +3 -3
  57. package/dist/config-catalog.js +9 -6
  58. package/dist/config-types.d.ts +9 -0
  59. package/dist/config.d.ts +1 -1
  60. package/dist/config.js +34 -8
  61. package/dist/deployment-governance.d.ts +2 -2
  62. package/dist/deployment-governance.js +7 -9
  63. package/dist/device-ws-protocol.d.ts +1 -0
  64. package/dist/device-ws-protocol.js +2 -1
  65. package/dist/file-history-disclosure.d.ts +4 -2
  66. package/dist/file-history-disclosure.js +2 -0
  67. package/dist/hooks/classic-stations.d.ts +11 -54
  68. package/dist/http/admission.js +10 -7
  69. package/dist/http/approval-sweeps.js +4 -3
  70. package/dist/http/caller-trust.d.ts +2 -1
  71. package/dist/http/caller-trust.js +1 -3
  72. package/dist/http/checkpoint-ctx.d.ts +48 -7
  73. package/dist/http/checkpoint-ctx.js +40 -0
  74. package/dist/http/dispatch.js +15 -10
  75. package/dist/http/durable-run-leg.d.ts +5 -1
  76. package/dist/http/durable-run-leg.js +1 -1
  77. package/dist/http/health-self-report.d.ts +2 -2
  78. package/dist/http/mcp-remote-text.d.ts +5 -0
  79. package/dist/http/mcp-remote-text.js +7 -0
  80. package/dist/http/principal-gate.d.ts +15 -10
  81. package/dist/http/principal-gate.js +48 -9
  82. package/dist/http/resume-legs.d.ts +2 -20
  83. package/dist/http/resume-legs.js +52 -38
  84. package/dist/http/route-ctx.d.ts +8 -2
  85. package/dist/http/routes/approvals-assistant.js +18 -4
  86. package/dist/http/routes/capabilities.js +11 -1
  87. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  88. package/dist/http/routes/mcp-reconnect.js +5 -7
  89. package/dist/http/routes/runs.js +2 -1
  90. package/dist/http/routes/session-sync.js +42 -6
  91. package/dist/http/routes/sessions.js +2 -1
  92. package/dist/http/routes/tasks.js +8 -6
  93. package/dist/http/server.d.ts +4 -0
  94. package/dist/http/stored-body-replay.d.ts +3 -3
  95. package/dist/http/stored-body-replay.js +6 -1
  96. package/dist/http/wire-types.d.ts +52 -27
  97. package/dist/identity-source.d.ts +65 -0
  98. package/dist/identity-source.js +45 -0
  99. package/dist/memory-store-refusal.d.ts +3 -3
  100. package/dist/model-entry-refusal.d.ts +2 -0
  101. package/dist/model-entry-refusal.js +11 -0
  102. package/dist/model-select.d.ts +6 -17
  103. package/dist/model-select.js +0 -3
  104. package/dist/namespaced-rule-name.d.ts +1 -1
  105. package/dist/observability/fail-open.d.ts +13 -1
  106. package/dist/observability/fail-open.js +13 -1
  107. package/dist/observability/metrics.js +1 -1
  108. package/dist/observability/run-terminal.js +1 -0
  109. package/dist/operator-ask.d.ts +2 -2
  110. package/dist/org-delivery-gate.js +1 -1
  111. package/dist/org-memory-admission.d.ts +2 -2
  112. package/dist/org-memory-admission.js +39 -2
  113. package/dist/parked-decide.js +3 -2
  114. package/dist/peer-directory.js +2 -0
  115. package/dist/permission-rule-list.d.ts +2 -2
  116. package/dist/plugins/checkpoint-store-sql.d.ts +19 -9
  117. package/dist/plugins/checkpoint-store-sql.js +6 -6
  118. package/dist/plugins/file-history-store-sql.js +7 -14
  119. package/dist/plugins/file-run-store.js +8 -2
  120. package/dist/plugins/k8s-exec-protocol.d.ts +69 -13
  121. package/dist/plugins/k8s-exec-protocol.js +116 -28
  122. package/dist/plugins/k8s-pod-delete-failure.d.ts +25 -0
  123. package/dist/plugins/k8s-pod-delete-failure.js +17 -0
  124. package/dist/plugins/k8s-pod-reaper.d.ts +112 -0
  125. package/dist/plugins/k8s-pod-reaper.js +122 -0
  126. package/dist/plugins/local-checkpoint-store.d.ts +3 -6
  127. package/dist/plugins/local-checkpoint-store.js +3 -3
  128. package/dist/plugins/memory-run-store.js +2 -0
  129. package/dist/plugins/one-time-migrations.d.ts +28 -9
  130. package/dist/plugins/one-time-migrations.js +48 -5
  131. package/dist/plugins/permission-rule-store-file.js +2 -2
  132. package/dist/plugins/permission-rule-store-sql.d.ts +17 -17
  133. package/dist/plugins/permission-rule-store-sql.js +16 -17
  134. package/dist/plugins/pg-pool.js +4 -0
  135. package/dist/plugins/pg-session-storage.js +2 -2
  136. package/dist/plugins/remote-env-adb.d.ts +3 -0
  137. package/dist/plugins/remote-env-adb.js +1 -0
  138. package/dist/plugins/remote-env-device.d.ts +3 -0
  139. package/dist/plugins/remote-env-device.js +4 -0
  140. package/dist/plugins/remote-env-e2b.d.ts +3 -0
  141. package/dist/plugins/remote-env-e2b.js +1 -0
  142. package/dist/plugins/remote-env-file-error.d.ts +8 -1
  143. package/dist/plugins/remote-env-file-error.js +7 -1
  144. package/dist/plugins/remote-env-host.d.ts +7 -1
  145. package/dist/plugins/remote-env-host.js +7 -1
  146. package/dist/plugins/remote-env-k8s.d.ts +137 -10
  147. package/dist/plugins/remote-env-k8s.js +276 -14
  148. package/dist/plugins/remote-env-local-docker.d.ts +3 -0
  149. package/dist/plugins/remote-env-local-docker.js +1 -0
  150. package/dist/plugins/remote-env-ssh.d.ts +3 -0
  151. package/dist/plugins/remote-env-ssh.js +1 -0
  152. package/dist/plugins/run-store-sql.js +10 -4
  153. package/dist/plugins/session-ownership.d.ts +17 -3
  154. package/dist/plugins/session-ownership.js +19 -5
  155. package/dist/plugins/sql-errors.d.ts +23 -2
  156. package/dist/plugins/sql-errors.js +15 -0
  157. package/dist/plugins/store-contracts.d.ts +10 -1
  158. package/dist/plugins/store-fault.d.ts +2 -2
  159. package/dist/plugins/store-fault.js +4 -2
  160. package/dist/plugins/tidb-pool.js +7 -0
  161. package/dist/plugins/tidb-session-store.js +2 -2
  162. package/dist/request-key-closure.js +1 -1
  163. package/dist/request-value-closure.d.ts +31 -2
  164. package/dist/request-value-closure.js +7 -1
  165. package/dist/rule-entry-list.d.ts +3 -2
  166. package/dist/rule-entry-list.js +2 -2
  167. package/dist/rules-consent.js +7 -4
  168. package/dist/run-local.d.ts +2 -2
  169. package/dist/runs.js +2 -1
  170. package/dist/runtime-governance.d.ts +5 -5
  171. package/dist/sandbox-git-lease.d.ts +49 -0
  172. package/dist/sandbox-git-lease.js +60 -0
  173. package/dist/sandbox-git-plane.d.ts +84 -0
  174. package/dist/sandbox-git-plane.js +105 -0
  175. package/dist/security.d.ts +16 -4
  176. package/dist/security.js +8 -3
  177. package/dist/server-secret-env.d.ts +6 -0
  178. package/dist/server-secret-env.js +14 -0
  179. package/dist/session-error-wire.d.ts +14 -6
  180. package/dist/session-error-wire.js +2 -1
  181. package/dist/session-policy-wire.d.ts +1 -1
  182. package/dist/session-policy-wire.js +2 -2
  183. package/dist/session-sync.d.ts +1 -1
  184. package/dist/spec-fields.d.ts +43 -13
  185. package/dist/spec-fields.js +33 -12
  186. package/dist/stored-request-prompt-profile-m30.d.ts +49 -0
  187. package/dist/stored-request-prompt-profile-m30.js +25 -0
  188. package/dist/task-mcp.d.ts +1 -1
  189. package/dist/task-settings.d.ts +2 -2
  190. package/dist/tool-approval.d.ts +12 -2
  191. package/dist/tool-approval.js +8 -3
  192. package/dist/tool-rule-names.d.ts +5 -0
  193. package/dist/trace/core-keyset-guard.d.ts +4 -4
  194. package/dist/trace/engine-notice-wire.d.ts +1 -1
  195. package/dist/trace/engine-notice-wire.js +2 -0
  196. package/dist/trace/exit-seat.js +2 -2
  197. package/dist/trace/ledger-sink.d.ts +4 -5
  198. package/dist/trace/ledger-sink.js +3 -7
  199. package/dist/trace/project.d.ts +37 -3
  200. package/dist/trace/project.js +20 -6
  201. package/dist/trace/redact.d.ts +18 -6
  202. package/dist/trace/redact.js +118 -53
  203. package/dist/trace/self-minted-wire.d.ts +377 -3
  204. package/dist/trace/self-minted-wire.js +122 -0
  205. package/dist/trace/wire-projection-faces.d.ts +5 -3
  206. package/dist/trace/wire-projection-faces.js +7 -3
  207. package/dist/web-admin-client/apply-effective.js +0 -1
  208. package/dist/web-admin-client/center-request.d.ts +20 -2
  209. package/dist/web-admin-client/center-request.js +40 -5
  210. package/dist/web-admin-client/http-client.js +6 -6
  211. package/dist/web-admin-client/types.d.ts +5 -5
  212. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +11 -5
  213. package/package.json +2 -2
  214. package/wire-changes/server@7.137.0.json +825 -0
@@ -0,0 +1,105 @@
1
+ import { WEB_ADMIN_ENV } from "@sema-agent/settings-schema";
2
+ import { DEPLOYMENT_SHAPE_PREDICATES } from "./deployment-shape.js";
3
+ import { SANDBOX_SECRET_SEAT_VERBS } from "./plugins/k8s-exec-protocol.js";
4
+ export const SANDBOX_GIT_PLANE_OFF_REASONS = [
5
+ "single_user",
6
+ "lane_without_boundary",
7
+ "no_caller_credential",
8
+ "center_not_https",
9
+ "secret_seat_unverified",
10
+ "secret_seat_denied",
11
+ "pod_patch_reaches_broker",
12
+ "broker_image_unset",
13
+ "no_origins",
14
+ ];
15
+ export const SANDBOX_GIT_PLANE_OFF_TEXT = {
16
+ single_user: "this is a single-user deployment (REQUIRE_PRINCIPAL is not true): there are no members to keep apart",
17
+ lane_without_boundary: "the execution lane is not the k8s pod sandbox (REMOTE_EXEC=k8s): only a k8s sandbox pod can carry the credential broker beside the agent",
18
+ no_caller_credential: "this worker holds no credential the console's vault admits for credential resolution",
19
+ center_not_https: `the console address (${WEB_ADMIN_ENV.url}) is not https — credentials are never fetched over plain http`,
20
+ secret_seat_unverified: `the boot could not confirm the sandbox Role's secrets verbs for the lease Secret (${SANDBOX_SECRET_SEAT_VERBS.lease.join(" / ")}) in the sandbox namespace`,
21
+ secret_seat_denied: `the sandbox Role does not allow secrets ${SANDBOX_SECRET_SEAT_VERBS.lease.join(" / ")} (the lease Secret) in the sandbox namespace`,
22
+ pod_patch_reaches_broker: "the deployment's pod-spec patch could reach the broker container (a podSecurityContext, or a field outside the broker white-list)",
23
+ broker_image_unset: "K8S_SANDBOX_BROKER_IMAGE is not set (the broker container's image; this engine's own image is the recommended value)",
24
+ no_origins: "the effective configuration routes no git origins to the plane (sandboxGit.origins)",
25
+ };
26
+ const isHttps = (url) => url !== undefined && URL.canParse(url) && new URL(url).protocol === "https:";
27
+ export function judgeSandboxGitPlane(p) {
28
+ const off = (reason) => ({ state: "off", reason });
29
+ if (!p.multiTenant)
30
+ return off("single_user");
31
+ if (!p.k8sLane)
32
+ return off("lane_without_boundary");
33
+ if (!p.callerCredential)
34
+ return off("no_caller_credential");
35
+ if (!isHttps(p.centerUrl))
36
+ return off("center_not_https");
37
+ const seat = p.secretSeat;
38
+ if (seat === undefined)
39
+ return off("secret_seat_unverified");
40
+ switch (seat.state) {
41
+ case "unverified":
42
+ return off("secret_seat_unverified");
43
+ case "denied":
44
+ return off("secret_seat_denied");
45
+ case "granted":
46
+ break;
47
+ default: {
48
+ const unreachable = seat;
49
+ throw new Error(`unhandled sandbox secret seat reading: ${String(unreachable.state)}`);
50
+ }
51
+ }
52
+ if (p.patchRefusal !== undefined)
53
+ return off("pod_patch_reaches_broker");
54
+ if (p.brokerImage === undefined || p.brokerImage === "")
55
+ return off("broker_image_unset");
56
+ if (p.origins === null || p.origins.length === 0)
57
+ return off("no_origins");
58
+ return {
59
+ state: "on",
60
+ origins: p.origins.map((origin) => (origin.startsWith("https://") ? { origin, delivers: true } : { origin, delivers: false, reason: "insecure_transport" })),
61
+ };
62
+ }
63
+ export function sandboxGitPlaneAsksSecretSeat(p) {
64
+ return p.multiTenant && p.k8sLane && p.callerCredential && isHttps(p.centerUrl);
65
+ }
66
+ export function sandboxGitPlanePremisesOf(config, readings) {
67
+ const lane = config.remoteExec;
68
+ return {
69
+ multiTenant: DEPLOYMENT_SHAPE_PREDICATES.multiTenant.holds(config),
70
+ k8sLane: lane?.provider === "k8s",
71
+ callerCredential: readSandboxGitCallerCredential(config),
72
+ centerUrl: config.webAdmin?.baseUrl,
73
+ secretSeat: readings.secretSeat,
74
+ patchRefusal: readings.patchRefusal,
75
+ brokerImage: lane?.provider === "k8s" ? lane.brokerImage : undefined,
76
+ origins: readSandboxGitConfig(config)?.origins ?? null,
77
+ };
78
+ }
79
+ export function readSandboxGitConfig(_config) {
80
+ return null;
81
+ }
82
+ export function readSandboxGitCallerCredential(_config) {
83
+ return false;
84
+ }
85
+ export async function runIdentitySourceOf(runId, runs, logger) {
86
+ if (runId === undefined || runs === undefined)
87
+ return undefined;
88
+ try {
89
+ return (await runs.getRun(runId))?.identity?.source;
90
+ }
91
+ catch (e) {
92
+ logger?.warn("sandbox_broker_identity_unread", {
93
+ run: runId,
94
+ error: e instanceof Error ? e.message : String(e),
95
+ effect: "this run's sandbox pod carries no member-credential broker (its identity could not be read from the run row)",
96
+ });
97
+ return undefined;
98
+ }
99
+ }
100
+ export function sandboxBrokerSeatOf(plane, brokerImage, identitySource) {
101
+ if (plane.state !== "on" || brokerImage === undefined || brokerImage === "" || identitySource !== "sso")
102
+ return undefined;
103
+ return { image: brokerImage, origins: plane.origins.map((o) => o.origin) };
104
+ }
105
+ //# sourceMappingURL=sandbox-git-plane.js.map
@@ -316,6 +316,13 @@ export declare function createAuthorizer(config: ServiceConfig, sessionStore: Ow
316
316
  /** F1/F3 (codex scope-ledger audit 2026-07-14) — the auth-boundary principal SHAPE gate, applied to every
317
317
  * verified identity source (trusted header, SSO-bridge sub, direct-door JWT sub) so no downstream consumer
318
318
  * ever sees an ambiguous or over-wide principal:
319
+ * - a string that is NOT A PRINCIPAL NAME is REJECTED (7.137.0): the engine's one judge `principalNameOf` (core 7.56.0 [ref] —
320
+ * visible content, no control / line-or-paragraph separator / format character other than the two joiners; kept verbatim, never
321
+ * normalized). The session stores' first ownership write and fork and the durable rule buckets read the same judge
322
+ * (`plugins/session-ownership.ts`, `permission-rule-store-sql.ts`); before this arm the boundary admitted what the store then refused,
323
+ * and the request fell into the generic 500 with its code lost. Typical source: a proxy writing raw UTF-8 bytes into the principal
324
+ * header — Node reads header bytes one per character (latin1), and a CJK character's continuation bytes often land on 0x80–0x9F,
325
+ * the C1 controls;
319
326
  * - RESERVED internal identifiers are REJECTED (修1, 三路复审 A 组 HIGH — see {@link RESERVED_PRINCIPALS} /
320
327
  * {@link RESERVED_PRINCIPAL_PREFIXES} below): a REAL tenant whose principal string-equals an internal scope
321
328
  * sentinel would pass the plain string-compare owner/scope gates downstream and merge with internal rows —
@@ -328,8 +335,8 @@ export declare function createAuthorizer(config: ServiceConfig, sessionStore: Ow
328
335
  * - length > 190 is REJECTED: every scope/owner column the principal lands in VERBATIM is VARCHAR(190)
329
336
  * (sendfile_link.scope, approval.owner/scope, memory-engine scope, rate-limit keys, …) — an over-long
330
337
  * principal would truncate/500 at the INSERT and split one identity into two.
331
- * Throws a typed 400 `HttpError` (the HTTP layer maps it); the message names the rule (a reserved identifier /
332
- * the length cap), never a secret and never the internal scope's semantics. */
338
+ * Throws a typed 400 `HttpError` (the HTTP layer maps it): `principal_not_a_name` / `principal_reserved` / `principal_too_long`; the message
339
+ * names the rule (not a name / a reserved identifier / the length cap), never a secret and never the internal scope's semantics. */
333
340
  export declare const PRINCIPAL_MAX_LENGTH = 190;
334
341
  /**
335
342
  * 一个被受理的主体串最多占多少个 UTF-8 字节 —— 按**字节**计宽的身份列(TiDB `VARBINARY(n)`)的列宽**唯一来源**(派生,不手抄第二个数)。
@@ -354,8 +361,13 @@ export declare const RESERVED_PRINCIPAL_PREFIXES: readonly string[];
354
361
  /** The principal-shape rule as a VERDICT (the one judge; {@link assertPrincipalShape} is its throwing reader). `undefined` = a
355
362
  * principal may carry this string; otherwise which rule it breaks. A reader that only needs "can this string be anyone?" (the
356
363
  * organization delivery gate's recipient: a string that breaks the rule is nobody, refused with the same bytes as an unknown
357
- * principal) reads the verdict instead of catching the throw. */
358
- export type PrincipalShapeProblem = "reserved" | "too_long";
364
+ * principal) reads the verdict instead of catching the throw.
365
+ *
366
+ * Order: "is it a name at all" first — the other two rules presuppose a name (a reserved identifier / a name too wide for the
367
+ * column), and the `reserved` sentence echoes the value while the `not_a_name` sentence never does (a string that names nobody may be a
368
+ * pasted credential). An empty string is `not_a_name` here (the engine's judge says so); a header reader that treats a blank header as
369
+ * "no principal" decides that before asking. */
370
+ export type PrincipalShapeProblem = "not_a_name" | "reserved" | "too_long";
359
371
  export declare function principalShapeProblem(principal: string): PrincipalShapeProblem | undefined;
360
372
  export declare function assertPrincipalShape(principal: string): void;
361
373
  /** The checkpoint `scope` column's single-user/anonymous sentinel — the same "_" documented above
package/dist/security.js CHANGED
@@ -1,10 +1,11 @@
1
- import { uuidv7, PROJECT_ID_REGEX } from "@sema-agent/core";
1
+ import { uuidv7, PROJECT_ID_REGEX, principalNameOf } from "@sema-agent/core";
2
2
  import { DEPLOYMENT_SHAPE_PREDICATES } from "./deployment-shape.js";
3
3
  import { headReadOf } from "./session-incarnation.js";
4
4
  import { memoryScopeFor } from "./memory-scope.js";
5
5
  import { verifyPrincipalJwt } from "./principal-jwt.js";
6
6
  import { fieldConflictFacts } from "./field-conflict.js";
7
7
  import { ORPHAN_SESSION_OWNER_PREFIX } from "./delegated-session-owner.js";
8
+ import { NOT_A_PRINCIPAL_NAME } from "./plugins/session-ownership.js";
8
9
  export function isDestructiveSessionWrite(method, url) {
9
10
  if (method === "DELETE")
10
11
  return /^\/v1\/sessions\/[^/]+$/.test(url);
@@ -134,6 +135,8 @@ export const PRINCIPAL_MAX_UTF8_BYTES = PRINCIPAL_MAX_LENGTH * 3;
134
135
  export const RESERVED_PRINCIPALS = ["_", "__none__", "_leader"];
135
136
  export const RESERVED_PRINCIPAL_PREFIXES = ["_leader-", ORPHAN_SESSION_OWNER_PREFIX];
136
137
  export function principalShapeProblem(principal) {
138
+ if (principalNameOf(principal) === undefined)
139
+ return "not_a_name";
137
140
  if (RESERVED_PRINCIPALS.includes(principal) || RESERVED_PRINCIPAL_PREFIXES.some((p) => principal.startsWith(p)))
138
141
  return "reserved";
139
142
  if (principal.length > PRINCIPAL_MAX_LENGTH)
@@ -145,10 +148,12 @@ export function assertPrincipalShape(principal) {
145
148
  switch (problem) {
146
149
  case undefined:
147
150
  return;
151
+ case "not_a_name":
152
+ throw createCodedHttpError(400, "principal_not_a_name", `principal is not a principal name: got ${NOT_A_PRINCIPAL_NAME} — send the identity as visible characters only (a proxy that writes raw UTF-8 bytes into the principal header is read one byte per character, which can produce control characters: encode it upstream as ASCII or another form without them)`);
148
153
  case "reserved":
149
- throw new HttpError(400, `principal ${JSON.stringify(principal)} is reserved (an internal scope sentinel) — authenticate with a real tenant identifier`, { code: "principal_reserved" });
154
+ throw createCodedHttpError(400, "principal_reserved", `principal ${JSON.stringify(principal)} is reserved (an internal scope sentinel) — authenticate with a real tenant identifier`);
150
155
  case "too_long":
151
- throw new HttpError(400, `principal exceeds ${PRINCIPAL_MAX_LENGTH} characters (the scope-column width every store pins, e.g. sendfile_link.scope VARCHAR(190)); got ${principal.length}`, { code: "principal_too_long" });
156
+ throw createCodedHttpError(400, "principal_too_long", `principal exceeds ${PRINCIPAL_MAX_LENGTH} characters (the scope-column width every store pins, e.g. sendfile_link.scope VARCHAR(190)); got ${principal.length}`);
152
157
  default: {
153
158
  const unknownProblem = problem;
154
159
  throw new Error(`assertPrincipalShape: unknown problem ${String(unknownProblem)}`);
@@ -124,6 +124,12 @@ export declare function hostSubprocessEnv(o: HostSubprocessEnvRequest): NodeJS.P
124
124
  * 既不另写一份组合(第二份会漂),也不把一次没发生的 spawn 记进剥除计数。`findings` 是组合点自己的收集器(引擎把形状臂与自有凭据臂的每一条扣下都推进去)。
125
125
  */
126
126
  export declare function composeHostSubprocessEnv(o: Omit<HostSubprocessEnvRequest, "site">, findings?: SecretEnvFinding[]): NodeJS.ProcessEnv;
127
+ /**
128
+ * core 7.56.0 [ref] `ExecutionEnv.withheldEnv` 的**宿主子进程**读法:{@link composeHostSubprocessEnv} 那只组合扣下的发现(名 + 规则,从不带值),
129
+ * 减去适配器显式授予重供的键(`regranted` 的自有键:子进程收得到那个名,不是损失)—— 引擎契约的原句「the findings of that composition less what its
130
+ * explicit grants re-supply」。同一个名字被两条规则各扣一次 ⇒ 按「名 + 规则」各记一笔(与披露同一去重)。只读组合:不披露、不计数(没有 spawn 发生)。
131
+ */
132
+ export declare function withheldFromHostSubprocess(o: Omit<HostSubprocessEnvRequest, "site">, regranted: Readonly<Record<string, string>> | undefined): SecretEnvFinding[];
127
133
  /** git 子进程腿的现场(闭集里名字带 `.git-` 的那几只 —— 编译期筛;它们都没有点名放行面)。 */
128
134
  export type GitSubprocessSite = Extract<SecretEnvScrubSite, `${string}.git-${string}`>;
129
135
  /**
@@ -86,6 +86,20 @@ export function composeHostSubprocessEnv(o, findings = []) {
86
86
  const putBack = o.allow.length > 0 ? inheritedHostEnv([...o.allow], findings) : {};
87
87
  return { ...inheritedHostEnv(o.policy ?? "scrub", findings), ...putBack };
88
88
  }
89
+ export function withheldFromHostSubprocess(o, regranted) {
90
+ const findings = [];
91
+ composeHostSubprocessEnv(o, findings);
92
+ const withheld = [];
93
+ const seen = new Set();
94
+ for (const f of findings) {
95
+ const k = `${f.key}=${f.kind}`;
96
+ if (seen.has(k) || (regranted !== undefined && Object.hasOwn(regranted, f.key)))
97
+ continue;
98
+ seen.add(k);
99
+ withheld.push(f);
100
+ }
101
+ return withheld;
102
+ }
89
103
  export function gitSubprocessEnv(o) {
90
104
  return {
91
105
  ...hostSubprocessEnv({ site: o.site, allow: [] }),
@@ -10,11 +10,10 @@
10
10
  *
11
11
  * ## 为什么多一个 `face` 参数(而不是单参)
12
12
  * core 的 `invalid_session` 是**同词两义**(亲读 dist:`engine/session/import-validate.js` 的导入门拒调用方交来的
13
- * 日志,`engine/session/storage-base.js` 的建树 / 走根拒**已存**日志里指向缺席条目的叶子或祖先 —— 后者即 core [ref])。
14
- * 本仓对它的成文义是前者(调用方的日志没过校验 ⇒ 422),而后者今天在打开会话的各口上答的是通用 500 / 同步口
15
- * 200 + failed([ref] 的 3b 负控格钉住这一形,[ref] 落地后按 core 的新码再对一次)。单参签名装不下这件事:要么
16
- * 打开面的每个消费点都写一句 `code === "corrupt_session"` 特判,要么把 [ref] 形从今天的应答改成「你交的日志坏了」。
17
- * ⇒ 面是映射的一维,写进属主,不散在消费点。
13
+ * 日志,`engine/session/storage-base.js` / `parent-chain-guard.js` 的建树 / 走根拒**已存**日志的结构 —— 叶子或父不在日志里、
14
+ * 条目是自己的祖先〔自指 / 成环〕)。前者是调用方的日志没过校验(import 面 422),后者是这只会话**存着的**日志坏了(open 面 500,
15
+ * 调用方没做错任何事)。单参签名装不下这件事:要么打开面的每个消费点都写一句 `code === "invalid_session"` 特判,要么把已存日志坏了
16
+ * 说成「你交的日志坏了」。⇒ 面是映射的一维,写进属主,不散在消费点。
18
17
  * · `"open"` —— 为某只会话**打开它已存的日志**(提交各腿的引擎 prepare、鉴权门的归属探针、直接读会话的口);
19
18
  * · `"import"` —— 校验 / 写入**调用方交来的**日志(2c session-sync 的暂存导入)。
20
19
  *
@@ -24,7 +23,16 @@
24
23
  * 原样上 wire,契约既有立场:不为上游码铸孪生码)。三种会话后端同词:文件店由 core 铸,两只 SQL 店由
25
24
  * `plugins/session-event-payload.ts` 铸(坏的 `session_event.payload` 行)。
26
25
  * · `invalid_session` —— `import` 面 **422 `invalid_entries`**(修前 session-sync 的那一行,码位 / 状态逐字不变);
27
- * `open` 面留白([ref] 形,见上)。
26
+ * `open` 面 **500 `invalid_session`**:这只会话已存的日志结构坏了(悬空父 / 自指 / 成环 / 叶子缺席),打得开存档、走不通分支。
27
+ * 句子发 core 的原句(点名条目 id,不含盘上路径),过既有脱敏。
28
+ * · **为什么是 500、不是 4xx / 503**:坏的是存着的数据,调用方没做错任何事;重试改不了判(修复在运维手里)—— 与 `corrupt_session` 同一档同一理由。
29
+ * · **为什么码是 `invalid_session`、不是 `corrupt_session`**:码不另铸、也不把一个 core 词改名成另一个 core 词。两个词说的是两件事
30
+ * (`corrupt_session` = 存档读不出;`invalid_session` = 读得出、结构走不通),而 run 终局上它本来就是引擎词(`terminal.code`,同步口 4xx / 5xx 体的
31
+ * `errorCode` 取终局码原样):读口答 `corrupt_session`、提交口答 `invalid_session` 就是同一件事两个名字。所以映射只有一格,
32
+ * 读口与提交口的 run 终局分诊({@link sessionErrorHttp} 的 open 面,`resumeAtHttpStatus` 读它)同一只、同码同状态。
33
+ * · **没有 `reason` 机读位**:core 的 `SessionError` 只带 `code` 与句子;悬空父与叶子缺席是同一句 `Entry <id> not found`,自指与成环是同一句
34
+ * `is its own ancestor` —— 本仓分出 {parent_missing, parent_self, parent_cycle} 只有解析句子或再写一只判官两条路(会话视图自己的走链器已在 core 7.54.0 提货时退役:
35
+ * 视图读的就是引擎读的那条分支,见 `plugins/local-session-window.ts` 头注)。要分,请求 core 把种类放进错误(同 `placement_kind` 的先例),不在这里猜。
28
36
  * · `not_found` —— 留白:每一面各有自己的「不存在」答法(404 `not_found.session` / 同步口的 `resume.session_not_found` /
29
37
  * 列表里的缺席),码位各不相同,不由本表统一。
30
38
  * · `placement_refused` —— `import` 面 **409 `conflict.session_placed`**(机读位 `detail.placement_kind`,固定句);`open` 面留白。
@@ -3,6 +3,7 @@ export const PLACED_SESSION_SENTENCE = "this session holds a delegated agent's t
3
3
  const PLACEMENT_KIND = "subagent";
4
4
  export const STAGED_IMPORT_OWNER_CHANGED_SENTENCE = "session owner changed while the import streamed; nothing was imported — start the sync again";
5
5
  const CORRUPT_SESSION = { status: 500, errorCode: "corrupt_session" };
6
+ const STORED_LOG_INVALID = { status: 500, errorCode: "invalid_session" };
6
7
  const INVALID_ENTRIES = { status: 422, errorCode: "invalid_entries", sentence: STAGED_IMPORT_INVALID_SENTENCE };
7
8
  const SESSION_PLACED = { status: 409, errorCode: "conflict.session_placed", sentence: PLACED_SESSION_SENTENCE, detail: { placement_kind: PLACEMENT_KIND } };
8
9
  const STAGED_IMPORT_OWNER_CHANGED = { status: 409, errorCode: "conflict", sentence: STAGED_IMPORT_OWNER_CHANGED_SENTENCE };
@@ -11,7 +12,7 @@ export function sessionErrorHttp(code, face) {
11
12
  case "corrupt_session":
12
13
  return CORRUPT_SESSION;
13
14
  case "invalid_session":
14
- return face === "import" ? INVALID_ENTRIES : undefined;
15
+ return face === "import" ? INVALID_ENTRIES : STORED_LOG_INVALID;
15
16
  case "placement_refused":
16
17
  return face === "import" ? SESSION_PLACED : undefined;
17
18
  case "conflict":
@@ -52,7 +52,7 @@ export declare function namespaceCoveringForms(): readonly {
52
52
  }[];
53
53
  /**
54
54
  * 能力位 `sessionRulesNamespaceForms` 的**唯一**判据:会话规则记录里协议命名空间的覆盖形,引擎认成「一组工具」**且**写口收。
55
- * 对 `PROTOCOL_TABLE` 的每一员:① 它的三只覆盖形,引擎的匹配器(`namespacedRuleNameCovers`,会话层与其余规则面同一只)都判得出覆盖那一员的真工具名
55
+ * 对 `PROTOCOL_TABLE` 的每一员:① 它的三只覆盖形,引擎的匹配器(`ruleNameCovers`,会话层与其余规则面同一只)都判得出覆盖那一员的真工具名
56
56
  * (只当精确名收的写口不算 ——「收」与「认成一组」是两问);② 三形放进 `toolAllow` 与 `toolDeny` 两桶,两条写入腿共用的写口读者
57
57
  * {@link readWritableSessionPermissionRules}(= 引擎写口判官)不拒。表非空 ∧ 每员都过 ⇒ `true`。
58
58
  *
@@ -1,4 +1,4 @@
1
- import { PROTOCOL_TABLE, SessionPolicyError, judgeSessionRulesWrite, namespacedRuleNameCovers } from "@sema-agent/core";
1
+ import { PROTOCOL_TABLE, SessionPolicyError, judgeSessionRulesWrite, ruleNameCovers } from "@sema-agent/core";
2
2
  import { displayListEntry } from "./list-entry-display.js";
3
3
  function isSessionRulesRefusal(e) {
4
4
  return e instanceof SessionPolicyError && (e.code === "invalid_shape" || e.code === "inert_entry");
@@ -25,7 +25,7 @@ export function namespaceCoveringForms() {
25
25
  export function sessionRulesNamespaceFormsHonored() {
26
26
  const probes = namespaceCoveringForms();
27
27
  return (probes.length > 0 &&
28
- probes.every(({ forms, tool }) => forms.every((form) => namespacedRuleNameCovers(form, tool)) && readWritableSessionPermissionRules({ toolAllow: [...forms], toolDeny: [...forms] }).refusal === undefined));
28
+ probes.every(({ forms, tool }) => forms.every((form) => ruleNameCovers(form, tool)) && readWritableSessionPermissionRules({ toolAllow: [...forms], toolDeny: [...forms] }).refusal === undefined));
29
29
  }
30
30
  export function readSessionRulesRecords(raw) {
31
31
  if (!Array.isArray(raw))
@@ -50,7 +50,7 @@ import type { ServiceFileHistoryStore, ServiceSessionPolicyStore, ResumeAnchorSt
50
50
  /**
51
51
  * core 7.55.0 [ref] T2([ref]):一只会话的文件历史**读不全**(撕裂 / 读不出的边界、读不出的跟踪集 —— 店的 `exportHistory` 答 `{ ok: false }`,`adopt_failed`
52
52
  * 带店判的 `permanence`)。它**不是**「没有历史」:读成 `null` 的同步会把这只会话的全部 rewind 历史丢在路上、还答成功。会话同步的两只拉取口
53
- * (manifest、history blob)对它答同一句 503 `state.file_history_unreadable`(路由 `sendPullUnreadable`;连接类故障在拉取口同码同体键答 `permanence: "transient"`)。
53
+ * (manifest、history blob)对它答同一句 503 `state.file_history_unreadable`(路由 `sendPullUnreadable`;连接类故障在拉取口同码同体键答 `permanence: "transient"`,部署错配类故障〔缺表 / 权限拒〕答 `permanence: "terminal"`)。
54
54
  */
55
55
  export declare class FileHistoryUnreadableError extends Error {
56
56
  readonly sessionId: string;
@@ -21,6 +21,32 @@ export declare function normalizeSuggestNextPrompts(v: unknown): TaskSpec["sugge
21
21
  * undefined (never an empty object — keeps the spec byte-identical for non-users).
22
22
  */
23
23
  export declare function normalizeResilience(v: unknown, operator: boolean): TaskSpec["resilience"];
24
+ /**
25
+ * 请求体 `attachments` 的子键**只有这一张表**(键 → 受理类别)。三处读它,不再各抄一份键名:
26
+ * · 导出类型 `TaskRequestAttachments`(`http/wire-types.ts`)—— 与本表互为差集门(下面两只 `_GuardAttachmentKeys*`:类型加 / 删一个键而本表没跟 ⇒ tsc 红);
27
+ * · 受理门(`http/admission.ts`)—— 非对象那一句列的键、布尔类键的逐键 `must be a boolean` 检查都从这里取;
28
+ * · 重放翻译口 {@link normalizeAttachments} —— 入参形与逐键赋值的键集都是本表(`parts` 记录对 {@link AttachmentKey} 穷举,少一个键 ⇒ tsc 红)。
29
+ * 类别只回答受理门一个问题:`boolean` = 值必须是布尔(literal-true 键与布尔透传键的差别在翻译口,不在门);`mode` = `todoReminderMode` 的枚举;
30
+ * `changedFiles` = 布尔或 `{ maxFiles }`。后两类各有自己的门。键的**顺序**就是受理门那一句里的顺序(句子字面另由错误文案冻结钉守)。
31
+ */
32
+ export type AttachmentKeyKind = "boolean" | "mode" | "changedFiles";
33
+ export declare const ATTACHMENT_KEY_KINDS: {
34
+ readonly todoReminder: "boolean";
35
+ readonly todoReminderMode: "mode";
36
+ readonly changedFiles: "changedFiles";
37
+ readonly budgetUsd: "boolean";
38
+ readonly backgroundTasks: "boolean";
39
+ readonly toolsDelta: "boolean";
40
+ readonly agentListing: "boolean";
41
+ readonly skillsListing: "boolean";
42
+ readonly mcpInstructions: "boolean";
43
+ readonly mcpUnavailable: "boolean";
44
+ };
45
+ export type AttachmentKey = keyof typeof ATTACHMENT_KEY_KINDS;
46
+ /** 表的键,原序。 */
47
+ export declare const ATTACHMENT_KEYS: readonly AttachmentKey[];
48
+ /** 受理门要验「值必须是布尔」的键(= 类别 `boolean`),原序。 */
49
+ export declare const ATTACHMENT_BOOLEAN_KEYS: readonly AttachmentKey[];
24
50
  /**
25
51
  * [ref] (core 1.251) — normalize `body.attachments` → `TaskSpec.attachments`. Core's contract is
26
52
  * literal-true unions ("关闭=删 key,勿传 false"): only `true` (or, for changedFiles, an object with a sane
@@ -163,21 +189,25 @@ export declare function toolNameListFromBody(raw: unknown): string[] | undefined
163
189
  export declare function allowToolsSpecFragment(raw: unknown): {
164
190
  allowTools?: string[];
165
191
  };
166
- /** [ref]/[ref] — body.promptProfile → `TaskSpec.promptProfile`(core 1.328 R2 主件,CC LT(model_id)
167
- * 同构双形轴)。语义:纯呈现轴不碰策略(simple=CC 212 短形面缺省;classic=长形面,可按任务/模型试分)
168
- * ⇒ 无租户门(deferTools 同姿势);全委托树继承由 core 管。缺省不挂键(引擎缺省 simple,byte-compat)。
192
+ /** core 7.56.0 [ref] — body.promptLean → `TaskSpec.promptLean`(提示词形的唯一任务开关:`true` 加 `lean_prompt` 特征、`false` 去掉它、
193
+ * 缺省 = 模型声明)。纯呈现轴不碰策略 ⇒ 无租户门(deferTools 同姿势);全委托树继承由 core 管。缺省不挂键(模型目录条目的声明说话)。
169
194
  *
170
- * ⚠️ **这不是一条 defer 注**([ref] 件⑤ 对表结论,core [ref] 同判):本段说的是 `promptProfile` 这根
171
- * **呈现**轴,它与 `deferMode` / `deferTools` 的延迟披露轴**无耦合** —— 上一行提 deferTools 只是在借它
172
- * 的「无租户门」姿势作类比。「引擎缺省 simple」这句本身在 core 5.40.0 上仍逐字为真
173
- * (`core/types.d.ts`:default "simple"),本批亲验未变。
174
- * 真正的 defer 轴对表(core 5.40.0 起 `deferMode:"auto"` 不再 sweep 内建;7.15.0 起它不再是第二道门,
175
- * 只是把候选集拓宽后走同一道阈值门)记在
176
- * `src/capabilities/tool-defer.ts` 的头注 —— 别把两根轴的结论写到一处。
177
- * DEFENSIVE(resume 重放不过 HTTP 400 门):非法值 ⇒ undefined;提交面另有 fail-loud 400(邻居姿势)。 */
178
- export declare function promptProfileFromBody(raw: unknown): "simple" | "classic" | undefined;
195
+ * ⚠️ **这不是一条 defer 注**:本段说的是提示词形这根**呈现**轴,它与 `deferMode` / `deferTools` 的延迟披露轴**无耦合** —— 上一行提
196
+ * deferTools 只是在借它的「无租户门」姿势作类比。真正的 defer 轴对表记在 `src/capabilities/tool-defer.ts` 的头注 —— 别把两根轴的结论写到一处。
197
+ * DEFENSIVE(resume 重放不过 HTTP 400 门):非布尔 ⇒ undefined;提交面另有 fail-loud 400(邻居姿势)。 */
198
+ export declare function promptLeanFromBody(raw: unknown): boolean | undefined;
199
+ /**
200
+ * `TaskSpec.promptLean` 的座位片段:体里的 `promptLean`(布尔才算,见 {@link promptLeanFromBody})是唯一读数;缺席 ⇒ 不挂键。两条腿同一读法:
201
+ * 退役键 `promptProfile` 不读 —— 新提交带它在键闭集门 400;7.136.x 存体里的它由一次性迁移 M30 在起服时改写成 `promptLean`(`stored-request-prompt-profile-m30.ts`),
202
+ * 改写不到的残留行在续跑判据上拒(`http/stored-body-replay.ts` 第四问),到不了这里。
203
+ */
204
+ export declare function promptLeanSpecFragment(body: {
205
+ readonly promptLean?: unknown;
206
+ }): {
207
+ promptLean?: boolean;
208
+ };
179
209
  /** [ref]②(core 5.14.0 `TaskSpec.toolMaterializeStrategy`):deferred 工具激活后的 schema 供给策略。
180
- * 与 {@link promptProfileFromBody} 同姿势的**防御性归一**——提交面的 fail-loud 400 在 HTTP 门,这里
210
+ * 与 {@link promptLeanFromBody} 同姿势的**防御性归一**——提交面的 fail-loud 400 在 HTTP 门,这里
181
211
  * 只负责 resume 重放存量 body 时把不认识的值静默 DROP(回落 core 的 env/缺省链),而不是把它带上 spec。 */
182
212
  export declare function toolMaterializeStrategyFromBody(raw: unknown): "static" | "swap" | undefined;
183
213
  /** [ref]④ — 每键的运营方上限旋钮(env,缺省不设=不封顶)。 */
@@ -25,6 +25,20 @@ export function normalizeResilience(v, operator) {
25
25
  };
26
26
  return Object.keys(out).length > 0 ? out : undefined;
27
27
  }
28
+ export const ATTACHMENT_KEY_KINDS = {
29
+ todoReminder: "boolean",
30
+ todoReminderMode: "mode",
31
+ changedFiles: "changedFiles",
32
+ budgetUsd: "boolean",
33
+ backgroundTasks: "boolean",
34
+ toolsDelta: "boolean",
35
+ agentListing: "boolean",
36
+ skillsListing: "boolean",
37
+ mcpInstructions: "boolean",
38
+ mcpUnavailable: "boolean",
39
+ };
40
+ export const ATTACHMENT_KEYS = Object.keys(ATTACHMENT_KEY_KINDS);
41
+ export const ATTACHMENT_BOOLEAN_KEYS = ATTACHMENT_KEYS.filter((k) => ATTACHMENT_KEY_KINDS[k] === "boolean");
28
42
  export function normalizeAttachments(v) {
29
43
  if (v == null || typeof v !== "object" || Array.isArray(v))
30
44
  return undefined;
@@ -37,17 +51,19 @@ export function normalizeAttachments(v) {
37
51
  changedFiles =
38
52
  typeof mf === "number" && Number.isFinite(mf) && mf >= 1 ? { maxFiles: Math.min(100, Math.floor(mf)) } : true;
39
53
  }
40
- const out = {
41
- ...(o.todoReminder === true ? { todoReminder: true } : {}),
42
- ...(changedFiles !== undefined ? { changedFiles } : {}),
43
- ...(typeof o.backgroundTasks === "boolean" ? { backgroundTasks: o.backgroundTasks } : {}),
44
- ...(o.toolsDelta === true ? { toolsDelta: true } : {}),
45
- ...(o.todoReminderMode === "baseline" || o.todoReminderMode === "off" ? { todoReminderMode: o.todoReminderMode } : {}),
46
- ...(o.budgetUsd === true ? { budgetUsd: true } : {}),
47
- ...(o.mcpInstructions === true ? { mcpInstructions: true } : {}),
48
- ...(typeof o.agentListing === "boolean" ? { agentListing: o.agentListing } : {}),
49
- ...(typeof o.skillsListing === "boolean" ? { skillsListing: o.skillsListing } : {}),
54
+ const parts = {
55
+ todoReminder: o.todoReminder === true ? { todoReminder: true } : {},
56
+ todoReminderMode: o.todoReminderMode === "baseline" || o.todoReminderMode === "off" ? { todoReminderMode: o.todoReminderMode } : {},
57
+ changedFiles: changedFiles !== undefined ? { changedFiles } : {},
58
+ budgetUsd: o.budgetUsd === true ? { budgetUsd: true } : {},
59
+ backgroundTasks: typeof o.backgroundTasks === "boolean" ? { backgroundTasks: o.backgroundTasks } : {},
60
+ toolsDelta: o.toolsDelta === true ? { toolsDelta: true } : {},
61
+ agentListing: typeof o.agentListing === "boolean" ? { agentListing: o.agentListing } : {},
62
+ skillsListing: typeof o.skillsListing === "boolean" ? { skillsListing: o.skillsListing } : {},
63
+ mcpInstructions: o.mcpInstructions === true ? { mcpInstructions: true } : {},
64
+ mcpUnavailable: typeof o.mcpUnavailable === "boolean" ? { mcpUnavailable: o.mcpUnavailable } : {},
50
65
  };
66
+ const out = Object.assign({}, ...ATTACHMENT_KEYS.map((k) => parts[k]));
51
67
  return Object.keys(out).length > 0 ? out : undefined;
52
68
  }
53
69
  export function normalizeResumeAtMode(v, resumeAtResolved) {
@@ -90,6 +106,7 @@ const SKILL_FIELD_SHAPES = {
90
106
  disallowedTools: { required: false, ok: skillToolListOk },
91
107
  files: { required: false, ok: skillFilesOk },
92
108
  baseDir: { required: false, ok: isAbsoluteHostPath },
109
+ mods: { required: false, ok: () => false },
93
110
  };
94
111
  const skillOk = (s) => {
95
112
  if (!plainObject(s))
@@ -301,8 +318,12 @@ export function toolNameListFromBody(raw) {
301
318
  export function allowToolsSpecFragment(raw) {
302
319
  return raw === undefined ? {} : { allowTools: raw };
303
320
  }
304
- export function promptProfileFromBody(raw) {
305
- return raw === "simple" || raw === "classic" ? raw : undefined;
321
+ export function promptLeanFromBody(raw) {
322
+ return typeof raw === "boolean" ? raw : undefined;
323
+ }
324
+ export function promptLeanSpecFragment(body) {
325
+ const lean = promptLeanFromBody(body.promptLean);
326
+ return lean !== undefined ? { promptLean: lean } : {};
306
327
  }
307
328
  export function toolMaterializeStrategyFromBody(raw) {
308
329
  return raw === "static" || raw === "swap" ? raw : undefined;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * 一次性迁移 M30 —— 升级之前停驻的行(`checkpoint_ctx`)里请求体的退役键 `promptProfile`,起服**原地改写**成 `promptLean`。
3
+ * 🔴 M30 · 窗口见 `plugins/one-time-migrations.ts` 的 ONE_TIME_MIGRATIONS 登记表(窗、删法、删后由谁接住只写在那一行)。
4
+ *
5
+ * ## 为什么要它(持久数据面)
6
+ * 引擎 7.56.0 把提示词形收成一个布尔(`TaskSpec.promptLean`),双形词 `promptProfile`(`"simple" | "classic"`)整键退役;新提交带它在键闭集门 400。
7
+ * 7.136.x 及以前停驻的任务,`checkpoint_ctx` 存着的请求体带的是旧键 —— 续跑腿重放它,没有读者 ⇒ `"classic"`(要长形)静默回到模型声明的短形。
8
+ * 持久数据面 = 一次性迁移把旧表示消灭(不是续跑时按日历再读一遍:那样存体从不改,窗内续跑过一次、再停驻、窗关后再续跑,意图照样静默丢)。
9
+ *
10
+ * ## 法(一条映射,与 MIGRATION 写给调用方的同一张)
11
+ * 窗内每次起服扫一遍 `checkpoint_ctx`(两孪生共有的 ctx 行改写店腿 `rewriteCtxRows`,与 M28 同一只;SQL 按原文 CAS、`updated_at_ms` 不动;local 信封的 `updatedAt` 不动),
12
+ * 体里带自有键 `promptProfile` 的行:删掉它;`"classic"` 且体里没有布尔 `promptLean` ⇒ 写 `promptLean: false`(长形的意图);`"simple"`(当年的缺省形)/ 词表外的值 ⇒
13
+ * 只删键;体里已有布尔 `promptLean`(它说了算)⇒ 只删键。体的其余键、行上的读法记号与凭据位原样随行(行改写 = `http/checkpoint-ctx.ts` `rewriteStoredBodyKeys`)。
14
+ * 已改写 / 不带旧键的行零写;读不出的行计数、不动(留给读口的响亮臂)。读数一行 info `checkpoint_ctx_prompt_profile_rewritten`(行数,不带值),不进
15
+ * `OneTimeMigrationReport`(登记行 `reportKeys` 空,与 M28 同形)。窗外不扫。
16
+ *
17
+ * ## 改写不到的行(响亮拒,不是第二种读法)
18
+ * 续跑腿不读旧键(`resolveSpec` 只读布尔 `promptLean`)。存体里仍带 `promptProfile` 的行 —— 窗内从没起过本版的部署,或滚动升级里旧副本在本版起服扫描之后才写下的行 ——
19
+ * 在五个续跑问点的同一只判据上拒(`http/stored-body-replay.ts` 第四问,409 `checkpoint.unsupported_version`);窗内再起一次本版即改写它。
20
+ *
21
+ * ## 为什么不在 `one-time-migrations.ts` 的迁移段里
22
+ * 那一段跑在店层(起服第 3 段),checkpoint 店还没建;本条与 M28 同在起服第 6 段 checkpoint 店建成之后、任何路由装配之前调一次。
23
+ */
24
+ import { type SealedCheckpointCtx } from "./http/checkpoint-ctx.js";
25
+ import { type OneTimeMigrationWindow } from "./plugins/one-time-migrations.js";
26
+ import type { CheckpointCtxRowsRewriter } from "./plugins/checkpoint-store-sql.js";
27
+ /** M30 一次起服的读数(起服 info 行逐键打出;测试按它断言)。`window: "closed"` ⇒ 一行都没读(其余全 0)。`rowsLongFormKept` = 改写成 `promptLean: false` 的行。 */
28
+ export interface PromptProfileRetireM30Report {
29
+ readonly window: "open" | "closed";
30
+ readonly rowsScanned: number;
31
+ readonly rowsRewritten: number;
32
+ readonly rowsLongFormKept: number;
33
+ readonly rowsUnreadable: number;
34
+ readonly rowsTooLarge: number;
35
+ }
36
+ /**
37
+ * 一行存量 ctx 的 M30 改写(纯函数;店腿逐行调):体里没有自有键 `promptProfile`(或体不是对象)⇒ `undefined`(不写);否则交回改写后的落盘形与改动数
38
+ * (写了 `promptLean: false` 记 1,只删键记 0)。导出给两孪生的真库钉(`checkpoint-ctx-credential-seal-db-integration.test.ts`)直接喂店腿。
39
+ */
40
+ export declare function retirePromptProfileRowM30(stored: object): {
41
+ readonly ctx: SealedCheckpointCtx;
42
+ readonly values: number;
43
+ } | undefined;
44
+ /** 跑一次 M30(`today` = UTC 日历日 YYYY-MM-DD;`window` 缺省 = 登记表 M30 那一行 —— 只有测试递别的窗)。窗关 ⇒ 不扫。 */
45
+ export declare function retirePromptProfileM30(store: CheckpointCtxRowsRewriter, opts: {
46
+ readonly today: string;
47
+ readonly window?: Pick<OneTimeMigrationWindow, "removableAfter">;
48
+ }): Promise<PromptProfileRetireM30Report>;
49
+ //# sourceMappingURL=stored-request-prompt-profile-m30.d.ts.map
@@ -0,0 +1,25 @@
1
+ import { rewriteStoredBodyKeys } from "./http/checkpoint-ctx.js";
2
+ import { ONE_TIME_MIGRATIONS, migrationWindowOpen } from "./plugins/one-time-migrations.js";
3
+ const RETIRED_KEY = "promptProfile";
4
+ export function retirePromptProfileRowM30(stored) {
5
+ const body = stored.body;
6
+ if (body === null || typeof body !== "object" || Array.isArray(body) || !Object.hasOwn(body, RETIRED_KEY))
7
+ return undefined;
8
+ const fields = body;
9
+ const keepsLongForm = fields[RETIRED_KEY] === "classic" && typeof fields["promptLean"] !== "boolean";
10
+ return { ctx: rewriteStoredBodyKeys(stored, { drop: [RETIRED_KEY], ...(keepsLongForm ? { set: { promptLean: false } } : {}) }), values: keepsLongForm ? 1 : 0 };
11
+ }
12
+ function m30Window() {
13
+ const row = ONE_TIME_MIGRATIONS.find((w) => w.id === "M30");
14
+ if (row === undefined)
15
+ throw new Error("stored-request-prompt-profile-m30: the one-time migration registry has no M30 row — this migration outlived its registry row; delete it with that row");
16
+ return row;
17
+ }
18
+ export async function retirePromptProfileM30(store, opts) {
19
+ if (!migrationWindowOpen(opts.window ?? m30Window(), opts.today)) {
20
+ return { window: "closed", rowsScanned: 0, rowsRewritten: 0, rowsLongFormKept: 0, rowsUnreadable: 0, rowsTooLarge: 0 };
21
+ }
22
+ const r = await store.rewriteCtxRows((_sessionId, ctx) => retirePromptProfileRowM30(ctx));
23
+ return { window: "open", rowsScanned: r.scanned, rowsRewritten: r.rewritten, rowsLongFormKept: r.valuesRewritten, rowsUnreadable: r.unreadable, rowsTooLarge: r.tooLarge };
24
+ }
25
+ //# sourceMappingURL=stored-request-prompt-profile-m30.js.map
@@ -190,7 +190,7 @@ export declare function assertRequestMcpEntries(bodyMcp: unknown): void;
190
190
  * A caller can ADD servers but can never SHADOW or REFILL a deployment-declared one: a center entry that was skipped
191
191
  * (disabled / missing env ref / malformed `toolFaces`) still occupies its name, otherwise "skipped" would silently
192
192
  * mean "free for the caller to remount without the operator's `allowTools` / `toolFaces`" (车CF codex r2 [high]).
193
- * Same posture as `mergeUserSkills`.
193
+ * Same posture as `buildRunSkills`.
194
194
  */
195
195
  export declare function deploymentClosedMcpNames(baseline: McpServerSpec[] | undefined, reservedNames: readonly string[]): ReadonlySet<string>;
196
196
  /**
@@ -73,7 +73,7 @@ export declare const PERMISSION_MODE_WORDS: string;
73
73
  * through verbatim (`dontAsk` included: folding the auto-deny mode to `default`, the asking mode, would turn a refusal
74
74
  * into a card). An UNKNOWN string coerces to `default` (fail-safe: the most-asking mode).
75
75
  * [ref]-②: fresh submits can no longer REACH the unknown-word arm — the HTTP gate 400s a non-member value
76
- * (admission.ts, sibling of promptProfile) and the wire type is a closed enum. This lenient fold stays for the one
76
+ * (admission.ts, sibling of toolMaterializeStrategy) and the wire type is a closed enum. This lenient fold stays for the one
77
77
  * population the gate deliberately does not cover: RESUME replay of a persisted pre-gate body (outputStyle/model
78
78
  * two-layer posture — bricking a resume over a value that was legal at submit time is worse than the quiet fold).
79
79
  */
@@ -477,7 +477,7 @@ export declare const REQUEST_SETTINGS_REFUSAL_PREFIX = "request settings cannot
477
477
  /**
478
478
  * Build the per-request permission ToolPolicy from the parsed settings: the two **rule lists** (`deny` / `ask`) through ONE compiler
479
479
  * ({@link createRuleListPolicy} → core `createPermissionRulePolicy`) — a `deny` rule hard-blocks, an `ask` rule forces an approval
480
- * gate, `deny` > `ask` on overlap. 两表的 **covering 语义同源**([ref]):底下是 core 的同一只 `namespacedRuleNameCovers`,本仓不持第二套 glob。
480
+ * gate, `deny` > `ask` on overlap. 两表的 **covering 语义同源**([ref]):底下是 core 的同一只 `ruleNameCovers`,本仓不持第二套 glob。
481
481
  * The `allow` bucket is not here: a policy says deny / ask only (core 7.42.0); the list rides into `TaskSpec.allowedTools` at
482
482
  * {@link applyTaskSettings}, the one write point.
483
483
  *
@@ -213,11 +213,14 @@ export declare function carriesDeploymentListQuestion(deploymentListAsks: boolea
213
213
  * 本仓**策略层自己交出去的一问**的 {@link StandingAnswerFacts}。读者两个:部署名单门(`approval.ts`,`deploymentListQuestion: true`)与 workflow 问句门
214
214
  * (`task-settings.ts`,`false`)—— 它们在交出这一问之前先读按工具名记的耐久豁免店(「本会话不再询问」在本层兑现,继承链上的同一问才够得着;审批席依约不退役
215
215
  * 继承问),而**能不能读**与审批席 / 回决写侧是同一只判据({@link standingAnswerDurablyRecordable}):记得下的才代答,记不下的(带授予绑定的问 ……)不代答,
216
- * 存量行也不例外 —— 一事一读。事实取自**这一问本身**:类集经 core 包根唯一的类派生 `classifyAsk`({@link NO_GATE_FACTS});`requiresRealApproval` /
216
+ * 存量行也不例外 —— 一事一读。事实取自**这一问本身**:类集经 core 包根唯一的类派生 `classifyAsk`({@link noGateFacts},调用坐标取 `req`);`requiresRealApproval` /
217
217
  * `mandated` 读这一问自报的成员;治理位恒假(本层不是治理层);授予绑定读**调用**(`callGrantBindingOf` —— 本层手里还没有引擎铸的卡,core 7.51.0 不在
218
218
  * `ToolCallRequest` 上盖绑定词,[ref] 载体问题见 `standing-answer-grant.ts` 头注)。
219
219
  */
220
- export declare function policyAskStandingAnswerFacts(req: StandingAnswerCall, ask: Extract<PermissionResult, {
220
+ export declare function policyAskStandingAnswerFacts(req: StandingAnswerCall & {
221
+ readonly toolCallId: string;
222
+ readonly toolName: string;
223
+ }, ask: Extract<PermissionResult, {
221
224
  action: "ask";
222
225
  }>, deploymentListQuestion: boolean): StandingAnswerFacts;
223
226
  /**
@@ -1027,6 +1030,13 @@ export type UnattendedAskOutcome = Extract<AskOutcome, "unavailable"> | Unattend
1027
1030
  /** [ref] 候裁 16:耐久会话授权店(`src/plugins/approval-exemption-store.ts` 的同一只店):审批席读它(引擎的问),活卡上部署名单那一问的
1028
1031
  * 「本会话放行」写它(名单门在出问之前读同一行)。 */
1029
1032
  export type SessionGrantStore = Pick<ApprovalExemptionStore, "has" | "grant">;
1033
+ /** redactDeep + size-bound the UNTRUSTED tool args before they are streamed. Over the cap / unserializable ⇒
1034
+ * undefined (frame rides argsOmitted — see MAX_APPROVAL_ARGS_BYTES). */
1035
+ /**
1036
+ * 给审批人看的一份入参 —— 卡上 `args` 的**同一种读法**({@link boundArgs}:脱敏 + 字节帽;超帽 / 序列化不出 ⇒ `undefined`)。
1037
+ * 读者:卡帧 / 卡面(经 `boundArgs` 本体)与续跑拒体的 `acceptableForm`(core 7.56.0 [ref]:编辑复判只收改写后的那一形,人在决定口看到它、批准它)。
1038
+ */
1039
+ export declare function approverFacingArgs(args: unknown): unknown;
1030
1040
  /**
1031
1041
  * [ref] 车3 刀 3b —— 「流内审批协议到底上不上场」的**单一谓词**(设计稿 §2.3 注入 / §2.4 能力面 /
1032
1042
  * §8.4 park 设施自检,三处同一份判据)。