@sema-agent/server 7.51.0 → 7.52.1

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 (203) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/USAGE.md +103 -24
  4. package/dist/adoption/plan.d.ts +4 -4
  5. package/dist/adoption/plan.js +11 -0
  6. package/dist/approval-card.d.ts +26 -2
  7. package/dist/approval-card.js +9 -0
  8. package/dist/approval-hmac.d.ts +1 -1
  9. package/dist/approval-reconciler.d.ts +2 -2
  10. package/dist/approval-reconciler.js +1 -1
  11. package/dist/bench/l8/artifact.d.ts +1 -1
  12. package/dist/bench/s1/arms.d.ts +1 -1
  13. package/dist/bench/s1/live-deps.d.ts +1 -1
  14. package/dist/bench/s1/oracle.d.ts +1 -1
  15. package/dist/bench/s1/row.d.ts +1 -1
  16. package/dist/boot/budget-tracing.d.ts +1 -1
  17. package/dist/boot/budget-tracing.js +2 -5
  18. package/dist/boot/coordinators.js +3 -1
  19. package/dist/boot/deferred-sandbox-path-env.d.ts +1 -1
  20. package/dist/boot/device-lane.js +2 -0
  21. package/dist/boot/engine-lease.d.ts +1 -1
  22. package/dist/boot/execution-env.d.ts +1 -1
  23. package/dist/boot/governance-seams.d.ts +2 -2
  24. package/dist/boot/leader.d.ts +1 -1
  25. package/dist/boot/memory-boundary.d.ts +2 -2
  26. package/dist/boot/memory-consolidation.d.ts +6 -6
  27. package/dist/boot/org-memory.d.ts +1 -1
  28. package/dist/boot/parked-revive-gate.d.ts +3 -3
  29. package/dist/boot/reapers.d.ts +2 -2
  30. package/dist/boot/reapers.js +17 -4
  31. package/dist/boot/resolve-spec.d.ts +12 -0
  32. package/dist/boot/resolve-spec.js +29 -7
  33. package/dist/boot/retention-lane.d.ts +2 -2
  34. package/dist/boot/runner-deps.d.ts +17 -7
  35. package/dist/boot/runner-deps.js +4 -2
  36. package/dist/boot/runtime-caps.d.ts +23 -0
  37. package/dist/boot/runtime-caps.js +24 -2
  38. package/dist/boot/session-faces.d.ts +1 -1
  39. package/dist/boot/session-faces.js +14 -3
  40. package/dist/boot/stores.js +31 -1
  41. package/dist/boot/task-list-lane.d.ts +2 -2
  42. package/dist/brain.js +51 -31
  43. package/dist/budget.d.ts +2 -2
  44. package/dist/capabilities/center-plugins.d.ts +1 -1
  45. package/dist/capabilities/collab-workflows.d.ts +1 -1
  46. package/dist/capabilities/hands-lane.d.ts +3 -3
  47. package/dist/capabilities/scenarios.d.ts +3 -3
  48. package/dist/capabilities/skills.d.ts +1 -1
  49. package/dist/config-center/hot-keys-registry.js +5 -0
  50. package/dist/config-center/http-client.d.ts +2 -2
  51. package/dist/config-center/read-face.d.ts +1 -1
  52. package/dist/config-center/restart-signal.d.ts +1 -1
  53. package/dist/config-types.d.ts +100 -25
  54. package/dist/config.d.ts +11 -3
  55. package/dist/config.js +91 -15
  56. package/dist/deployment-governance.d.ts +1 -1
  57. package/dist/device-enrollment.d.ts +2 -2
  58. package/dist/device-store.d.ts +3 -3
  59. package/dist/device-ws-hub.d.ts +4 -2
  60. package/dist/device-ws-hub.js +9 -3
  61. package/dist/device-ws-protocol.d.ts +2 -2
  62. package/dist/env-facts.d.ts +1 -1
  63. package/dist/execution-lane-caps.d.ts +1 -1
  64. package/dist/fleet/fleet-bus.d.ts +5 -2
  65. package/dist/fleet/fleet-reconciler.d.ts +1 -1
  66. package/dist/fleet/fleet-terminal-window.d.ts +1 -1
  67. package/dist/hooks/hook-runner.d.ts +8 -1
  68. package/dist/http/active-run-conflict.d.ts +2 -2
  69. package/dist/http/principal-gate.d.ts +1 -1
  70. package/dist/http/route-ctx.d.ts +27 -5
  71. package/dist/http/routes/admin-memory-optout.d.ts +48 -0
  72. package/dist/http/routes/admin-memory-optout.js +75 -0
  73. package/dist/http/routes/agents-roster.d.ts +1 -1
  74. package/dist/http/routes/approvals-assistant.js +21 -21
  75. package/dist/http/routes/capabilities.js +20 -9
  76. package/dist/http/routes/diagnostics.d.ts +1 -1
  77. package/dist/http/routes/leader.d.ts +1 -1
  78. package/dist/http/routes/memory-bundle.d.ts +1 -1
  79. package/dist/http/routes/memory-bundle.js +2 -1
  80. package/dist/http/routes/memory-compliance.d.ts +4 -4
  81. package/dist/http/routes/memory-origin.d.ts +4 -4
  82. package/dist/http/routes/memory-origin.js +3 -1
  83. package/dist/http/routes/memory-policy.js +6 -2
  84. package/dist/http/routes/retention-ops.d.ts +1 -1
  85. package/dist/http/routes/rules.d.ts +5 -2
  86. package/dist/http/routes/rules.js +84 -5
  87. package/dist/http/routes/run-memory-capture.d.ts +34 -0
  88. package/dist/http/routes/run-memory-capture.js +97 -0
  89. package/dist/http/routes/runs.d.ts +1 -0
  90. package/dist/http/routes/runs.js +212 -6
  91. package/dist/http/routes/session-sync.d.ts +4 -6
  92. package/dist/http/routes/session-sync.js +60 -47
  93. package/dist/http/routes/sessions.js +61 -188
  94. package/dist/http/routes/shared-memory.d.ts +1 -1
  95. package/dist/http/routes/tasks.js +1 -1
  96. package/dist/http/routes/workflows.js +19 -0
  97. package/dist/http/server.d.ts +35 -13
  98. package/dist/http/server.js +110 -14
  99. package/dist/http/verify-rounds.d.ts +1 -1
  100. package/dist/http/wire-types.d.ts +41 -8
  101. package/dist/leader/endpoint.d.ts +1 -1
  102. package/dist/leader/leader.d.ts +1 -1
  103. package/dist/leader/repair-oracle.d.ts +2 -2
  104. package/dist/leader/repair-oracle.js +1 -1
  105. package/dist/main.js +14 -6
  106. package/dist/memory-bundle-engine.d.ts +1 -1
  107. package/dist/memory-operator-faces.d.ts +5 -5
  108. package/dist/memory-origin-fence.d.ts +35 -0
  109. package/dist/memory-origin-fence.js +66 -0
  110. package/dist/memory-posture.d.ts +13 -1
  111. package/dist/memory-posture.js +1 -0
  112. package/dist/memory-scope.d.ts +6 -1
  113. package/dist/memory-scope.js +3 -2
  114. package/dist/model-select.d.ts +2 -2
  115. package/dist/observability/fail-open.d.ts +17 -5
  116. package/dist/observability/fail-open.js +17 -5
  117. package/dist/orchestration/hardened-vm-runner.d.ts +2 -2
  118. package/dist/orchestration/workflow-completion-inbox.d.ts +2 -2
  119. package/dist/plugins/background-shell-support.d.ts +2 -2
  120. package/dist/plugins/blob-backend.d.ts +7 -2
  121. package/dist/plugins/blob-backend.js +22 -18
  122. package/dist/plugins/caching-session-store.d.ts +7 -3
  123. package/dist/plugins/checkpoint-store-sql.d.ts +2 -2
  124. package/dist/plugins/file-history-store-sql.d.ts +170 -0
  125. package/dist/plugins/file-history-store-sql.js +827 -0
  126. package/dist/plugins/file-run-store.d.ts +44 -5
  127. package/dist/plugins/file-run-store.js +85 -13
  128. package/dist/plugins/fork-routing-session-store.d.ts +1 -1
  129. package/dist/plugins/fork-routing-session-store.js +2 -2
  130. package/dist/plugins/leader-run-store-sql.d.ts +1 -1
  131. package/dist/plugins/local-session-store.d.ts +9 -1
  132. package/dist/plugins/memory-embedder-fingerprint.d.ts +1 -1
  133. package/dist/plugins/memory-optout-grant-store-sql.d.ts +139 -0
  134. package/dist/plugins/memory-optout-grant-store-sql.js +157 -0
  135. package/dist/plugins/memory-origin-law.d.ts +2 -2
  136. package/dist/plugins/memory-run-store.d.ts +4 -3
  137. package/dist/plugins/memory-run-store.js +2 -1
  138. package/dist/plugins/permission-rule-store-file.d.ts +16 -6
  139. package/dist/plugins/permission-rule-store-file.js +77 -13
  140. package/dist/plugins/permission-rule-store-sql.d.ts +156 -8
  141. package/dist/plugins/permission-rule-store-sql.js +123 -21
  142. package/dist/plugins/pg-pool.js +59 -0
  143. package/dist/plugins/pg-safe-json.d.ts +1 -1
  144. package/dist/plugins/remote-env-device.d.ts +1 -1
  145. package/dist/plugins/remote-env-k8s.d.ts +1 -1
  146. package/dist/plugins/retention-lane-store-sql.d.ts +3 -3
  147. package/dist/plugins/retention-store-sql.d.ts +7 -7
  148. package/dist/plugins/retention-store-sql.js +6 -0
  149. package/dist/plugins/run-store-sql.d.ts +9 -3
  150. package/dist/plugins/run-store-sql.js +2 -2
  151. package/dist/plugins/send-user-file.d.ts +1 -1
  152. package/dist/plugins/session-placement.d.ts +1 -1
  153. package/dist/plugins/sql-errors.d.ts +1 -1
  154. package/dist/plugins/store-backend.d.ts +59 -29
  155. package/dist/plugins/store-backend.js +75 -15
  156. package/dist/plugins/store-contracts.d.ts +5 -3
  157. package/dist/plugins/task-list-store-sql.d.ts +1 -1
  158. package/dist/plugins/tidb-pool.js +59 -0
  159. package/dist/plugins/web-search.d.ts +1 -1
  160. package/dist/prompts-domain-validate.js +1 -1
  161. package/dist/resource-suspend.d.ts +1 -1
  162. package/dist/resource-window.d.ts +3 -3
  163. package/dist/rules-consent.d.ts +231 -9
  164. package/dist/rules-consent.js +411 -13
  165. package/dist/run-local.js +29 -4
  166. package/dist/runs.d.ts +15 -1
  167. package/dist/runs.js +19 -0
  168. package/dist/runtime-caps-resolver.d.ts +36 -1
  169. package/dist/runtime-caps-resolver.js +25 -0
  170. package/dist/runtime-governance.d.ts +1 -1
  171. package/dist/security.d.ts +5 -5
  172. package/dist/security.js +1 -1
  173. package/dist/session-sync.d.ts +52 -37
  174. package/dist/session-sync.js +19 -16
  175. package/dist/shared-memory-scope-authorizer.d.ts +1 -1
  176. package/dist/sighup-idle.d.ts +1 -1
  177. package/dist/spec-fields.d.ts +22 -1
  178. package/dist/spec-fields.js +11 -0
  179. package/dist/store-live-probe.d.ts +2 -2
  180. package/dist/task-a2a.d.ts +1 -1
  181. package/dist/task-cwd.d.ts +1 -1
  182. package/dist/task-mcp.d.ts +3 -3
  183. package/dist/task-settings.d.ts +3 -3
  184. package/dist/task-workflow.d.ts +1 -1
  185. package/dist/text-bidi.d.ts +2 -2
  186. package/dist/tool-approval.d.ts +191 -9
  187. package/dist/tool-approval.js +248 -29
  188. package/dist/trace/core-keyset-guard.d.ts +3 -3
  189. package/dist/trace/engine-notice-wire.d.ts +6 -4
  190. package/dist/trace/engine-notice-wire.js +2 -0
  191. package/dist/trace/ledger-events.d.ts +1 -1
  192. package/dist/trace/ledger-sink.d.ts +2 -2
  193. package/dist/trace/project.d.ts +8 -2
  194. package/dist/trace/project.js +10 -0
  195. package/dist/turn-activity.d.ts +1 -1
  196. package/dist/wall-clock-jump-guard.d.ts +3 -3
  197. package/package.json +3 -3
  198. package/dist/http/tar.d.ts +0 -17
  199. package/dist/http/tar.js +0 -53
  200. package/dist/http/workspace-content.d.ts +0 -11
  201. package/dist/http/workspace-content.js +0 -20
  202. package/dist/plugins/file-snapshot-store-sql.d.ts +0 -170
  203. package/dist/plugins/file-snapshot-store-sql.js +0 -213
@@ -1,6 +1,7 @@
1
1
  import { formatUserScope } from "@sema-agent/core";
2
2
  import { parseMemorySyncRequest } from "../../memory-sync.js";
3
3
  import { sendJson, sendError } from "../send.js";
4
+ import { buildOriginFence, readOriginAware } from "../../memory-origin-fence.js";
4
5
  import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
5
6
  const ORG_PREFIX = "org:";
6
7
  export const MEMORY_EXPORT_MAX_LIMIT = 500;
@@ -101,9 +102,11 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
101
102
  return;
102
103
  }
103
104
  try {
104
- const entries = await deps.memoryExport(scope);
105
+ const fence = buildOriginFence(await deps.memoryExport(scope), readOriginAware(new URL(req.url ?? "", "http://x").searchParams));
106
+ const entries = fence.entries;
107
+ const withheld = fence.withheldOriginTagged !== undefined ? { withheldOriginTagged: fence.withheldOriginTagged } : {};
105
108
  if (limitRaw === null) {
106
- sendJson(res, 200, { scope, exportedAt: new Date().toISOString(), entries });
109
+ sendJson(res, 200, { scope, exportedAt: new Date().toISOString(), entries, ...withheld });
107
110
  return;
108
111
  }
109
112
  const sorted = [...entries].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
@@ -116,6 +119,7 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
116
119
  exportedAt: new Date().toISOString(),
117
120
  entries: page,
118
121
  ...(hasMore ? { truncated: true, nextBefore: encodeExportCursor({ id: last.id }) } : {}),
122
+ ...withheld,
119
123
  });
120
124
  }
121
125
  catch (err) {
@@ -24,7 +24,7 @@
24
24
  * `checkAdoptionWidths` 同一条判据):一次静默截断会让 hold 落在一个**别的**域上。
25
25
  *
26
26
  * ── 审计行为什么放置/解除都要写 ────────────────────────────────────────────────────────────
27
- * hold 本身就是一次留存治理动作,不留痕的 hold 是审计漏洞(设计稿 §5 逐字)。行**每次成功调用都写**,
27
+ * hold 本身就是一次留存治理动作,不留痕的 hold 是审计缺陷(设计稿 §5 逐字)。行**每次成功调用都写**,
28
28
  * 而不是"只在状态真的翻转时写":审计表记的是**发生过的动作**(operator 在某时刻按了这个按钮),不是
29
29
  * 状态机的边。一次对已冻结域的重复 PUT 同样是一次需要留痕的操作。
30
30
  *
@@ -11,11 +11,11 @@
11
11
  * 因此**导入两口**只认已验明的 principal,连 `explicitOperatorOk` 的旁路都不给:一个 operator 想给
12
12
  * 自己导规则,用他自己的 principal 走同一条路即可(那时他就是租户)。
13
13
  *
14
- * 🔴 **撤销面两口多一条 operator 越权域,而这不是上面那句话的例外,是它的对偶**(design/203 §2 设计题
14
+ * 🔴 **撤销面两口多一条 operator 超范围域,而这不是上面那句话的例外,是它的对偶**(design/203 §2 设计题
15
15
  * 一裁定):`?principal=` / body 里的 `principal` 让 operator 读**或收回**任一租户的规则。方向决定授权:
16
16
  * 收回是**收紧**(core 的 `removePersistedRule` 头注逐字:「a host may narrow on a user's behalf, it may
17
17
  * not widen」),而多租户管理员能回收权限正是撤销面的本义。读那一半同样只开给 operator ——
18
- * 规则文本里含命令样式,跨 principal 读是信息面泄露,所以**不开第三方 principal 相互读**。
18
+ * 规则文本里含命令样式,跨 principal 读是信息面外泄,所以**不开第三方 principal 相互读**。
19
19
  * 判据用 `explicitOperatorOk`(空名单 = 全拒),**绝不**从 service token 推断身份:一个共享的部署凭证
20
20
  * 不是一个人。
21
21
  *
@@ -29,6 +29,9 @@ import type { IncomingMessage, ServerResponse } from "node:http";
29
29
  import type { RouteCtx } from "../route-ctx.js";
30
30
  export declare const RULES_CC_IMPORT_PREPARE_PATH = "/v1/rules/cc-import/prepare";
31
31
  export declare const RULES_CC_IMPORT_REDEEM_PATH = "/v1/rules/cc-import/redeem";
32
+ /** #265 A7:File 规则直通票口两条(与 cc-import 两口并列,票语义分家见 `rules-consent.ts` 的 A7 段)。 */
33
+ export declare const RULES_LOCAL_IMPORT_PREPARE_PATH = "/v1/rules/local-import/prepare";
34
+ export declare const RULES_LOCAL_IMPORT_REDEEM_PATH = "/v1/rules/local-import/redeem";
32
35
  /** 撤销面两口共用的字面路径(GET 列举 / DELETE 撤销)。 */
33
36
  export declare const RULES_PATH = "/v1/rules";
34
37
  export declare function handleRules(req: IncomingMessage, res: ServerResponse, url: string, ctx: RouteCtx): Promise<boolean>;
@@ -1,11 +1,16 @@
1
1
  import { z } from "zod";
2
- import { sendJson, sendError } from "../send.js";
2
+ import { sendJson, sendError, httpErrorCode } from "../send.js";
3
3
  import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
4
4
  import { createLogger } from "../../observability/logger.js";
5
5
  import { RULE_IMPORT_RETRY_AFTER_SEC, MAX_RULE_SCOPE_CHARS, parseRuleScope, serializeRuleScope } from "../../rules-consent.js";
6
+ import { CONTROL_AND_BIDI_CHARS } from "../../text-bidi.js";
7
+ import { HttpError } from "../../security.js";
8
+ import { MAX_RULE_TEXT_CHARS } from "@sema-agent/core";
6
9
  const logger = createLogger();
7
10
  export const RULES_CC_IMPORT_PREPARE_PATH = "/v1/rules/cc-import/prepare";
8
11
  export const RULES_CC_IMPORT_REDEEM_PATH = "/v1/rules/cc-import/redeem";
12
+ export const RULES_LOCAL_IMPORT_PREPARE_PATH = "/v1/rules/local-import/prepare";
13
+ export const RULES_LOCAL_IMPORT_REDEEM_PATH = "/v1/rules/local-import/redeem";
9
14
  export const RULES_PATH = "/v1/rules";
10
15
  const RULES_PAGE_DEFAULT = 50;
11
16
  const RULES_PAGE_MAX = 200;
@@ -26,7 +31,30 @@ const CcImportPrepareBodySchema = z
26
31
  .max(MAX_LAYERS),
27
32
  })
28
33
  .strict();
29
- const CcImportRedeemBodySchema = z.object({ ticket: z.string().min(1).max(190) }).strict();
34
+ const TICKET_CONTROL_CHARS_RE = new RegExp(`[${CONTROL_AND_BIDI_CHARS}]`);
35
+ const TicketStringSchema = z.string().min(1).max(190).refine((t) => !TICKET_CONTROL_CHARS_RE.test(t));
36
+ const CcImportRedeemBodySchema = z.object({ ticket: TicketStringSchema }).strict();
37
+ const LocalImportAddSchema = z
38
+ .object({
39
+ dot: z.object({ actor: z.string().min(1).max(190), counter: z.number().int().nonnegative().safe() }).strict(),
40
+ origin: z.enum(["user", "imported-cc", "starter"]),
41
+ createdAt: z.string().min(1).max(64),
42
+ })
43
+ .strict();
44
+ const LocalImportRowSchema = z
45
+ .object({
46
+ rule: z.string().min(1).max(MAX_RULE_TEXT_CHARS),
47
+ scope: z.string().min(1).max(MAX_RULE_SCOPE_CHARS),
48
+ tool: z.literal("Bash"),
49
+ match: z.enum(["exact", "prefix"]),
50
+ command: z.string().min(1).max(MAX_RULE_TEXT_CHARS),
51
+ adds: z.array(LocalImportAddSchema).max(64),
52
+ })
53
+ .strict();
54
+ const LOCAL_IMPORT_ROW_SHAPE_PIN = true;
55
+ void LOCAL_IMPORT_ROW_SHAPE_PIN;
56
+ const LocalImportPrepareBodySchema = z.object({ rows: z.array(LocalImportRowSchema).min(1) }).strict();
57
+ const LocalImportRedeemBodySchema = z.object({ ticket: TicketStringSchema }).strict();
30
58
  const RuleRevokeBodySchema = z
31
59
  .object({
32
60
  rule: z.string().min(1).max(1024),
@@ -60,9 +88,11 @@ async function handleRulesBody(req, res, url, ctx, miss) {
60
88
  const { readJson, rateLimited } = ctx.helpers;
61
89
  const isPrepare = req.method === "POST" && url === RULES_CC_IMPORT_PREPARE_PATH;
62
90
  const isRedeem = req.method === "POST" && url === RULES_CC_IMPORT_REDEEM_PATH;
91
+ const isLocalPrepare = req.method === "POST" && url === "/v1/rules/local-import/prepare";
92
+ const isLocalRedeem = req.method === "POST" && url === "/v1/rules/local-import/redeem";
63
93
  const isList = req.method === "GET" && url === "/v1/rules";
64
94
  const isRevoke = req.method === "DELETE" && url === "/v1/rules";
65
- if (!isPrepare && !isRedeem && !isList && !isRevoke) {
95
+ if (!isPrepare && !isRedeem && !isList && !isRevoke && !isLocalPrepare && !isLocalRedeem) {
66
96
  miss.fell = true;
67
97
  return;
68
98
  }
@@ -86,7 +116,11 @@ async function handleRulesBody(req, res, url, ctx, miss) {
86
116
  try {
87
117
  rawBody = await readJson(req);
88
118
  }
89
- catch {
119
+ catch (err) {
120
+ if (err instanceof HttpError && err.status !== 400) {
121
+ sendError(res, err.status, httpErrorCode(err.status, err.code), err.message);
122
+ return;
123
+ }
90
124
  sendError(res, 400, "request.body_shape", "body must be valid JSON");
91
125
  return;
92
126
  }
@@ -94,6 +128,46 @@ async function handleRulesBody(req, res, url, ctx, miss) {
94
128
  await handleRuleRevoke(res, rawBody, principal, lane, deps.config.operatorPrincipals);
95
129
  return;
96
130
  }
131
+ if (isLocalPrepare) {
132
+ const parsed = LocalImportPrepareBodySchema.safeParse(rawBody);
133
+ if (!parsed.success) {
134
+ sendError(res, 400, "request.body_shape", "body must be {rows:[{rule,scope,tool,match,command,adds}]}");
135
+ return;
136
+ }
137
+ const prepared = await lane.prepareLocalImport(principal, parsed.data.rows);
138
+ if (!prepared.ok) {
139
+ logger.info("local rule import batch refused", prepared.reason === "too-many-rows" ? { reason: prepared.reason, rows: prepared.rows } : { reason: prepared.reason, bytes: prepared.bytes });
140
+ sendError(res, 413, "request.payload_too_large", "too many rule rows in this batch — split it and import again");
141
+ return;
142
+ }
143
+ sendJson(res, 200, { preview: prepared.preview, ...(prepared.ticket !== undefined ? { ticket: prepared.ticket, expiresAtMs: prepared.expiresAtMs } : {}) });
144
+ return;
145
+ }
146
+ if (isLocalRedeem) {
147
+ const parsed = LocalImportRedeemBodySchema.safeParse(rawBody);
148
+ if (!parsed.success) {
149
+ sendError(res, 400, "request.body_shape", "body must be {ticket}");
150
+ return;
151
+ }
152
+ const redeemed = await lane.redeemLocalImport(principal, parsed.data.ticket);
153
+ if (redeemed.ok) {
154
+ sendJson(res, 200, { members: redeemed.outcome.members, rev: redeemed.outcome.rev });
155
+ return;
156
+ }
157
+ logger.info("local rule import ticket refused", { reason: redeemed.reason, detail: redeemed.detail, retryable: redeemed.reason !== "ticket-unusable" && redeemed.reason !== "in-flight" && redeemed.retryable === true });
158
+ if (redeemed.reason === "in-flight") {
159
+ res.setHeader("retry-after", String(RULE_IMPORT_RETRY_AFTER_SEC));
160
+ sendError(res, 409, "conflict.rule_import_in_flight", "another redemption of this import ticket is still in flight — retry with the same ticket", { retryAfterSec: RULE_IMPORT_RETRY_AFTER_SEC });
161
+ return;
162
+ }
163
+ if (redeemed.reason !== "ticket-unusable" && redeemed.retryable === true) {
164
+ res.setHeader("retry-after", String(RULE_IMPORT_RETRY_AFTER_SEC));
165
+ sendError(res, 503, "state.rule_import_retry", "the rule import could not be completed right now; the ticket is still usable — retry", { retryAfterSec: RULE_IMPORT_RETRY_AFTER_SEC });
166
+ return;
167
+ }
168
+ sendError(res, 404, "not_found.rule_ticket", "no such import ticket");
169
+ return;
170
+ }
97
171
  if (isPrepare) {
98
172
  const parsed = CcImportPrepareBodySchema.safeParse(rawBody);
99
173
  if (!parsed.success) {
@@ -115,7 +189,12 @@ async function handleRulesBody(req, res, url, ctx, miss) {
115
189
  }
116
190
  const redeemed = await lane.redeemImport(principal, parsed.data.ticket);
117
191
  if (!redeemed.ok) {
118
- logger.info("rule import ticket refused", { reason: redeemed.reason, detail: redeemed.detail, retryable: redeemed.retryable === true });
192
+ logger.info("rule import ticket refused", { reason: redeemed.reason, detail: redeemed.detail, retryable: redeemed.reason !== "state-unreadable" && redeemed.retryable === true });
193
+ if (redeemed.reason === "state-unreadable") {
194
+ res.setHeader("retry-after", String(RULE_IMPORT_RETRY_AFTER_SEC));
195
+ sendError(res, 503, "state.rule_ticket_unreadable", "the import ticket's state could not be read right now — retry with the same ticket, and re-run the import if it then reports not found", { retryAfterSec: RULE_IMPORT_RETRY_AFTER_SEC });
196
+ return;
197
+ }
119
198
  if (redeemed.retryable === true) {
120
199
  res.setHeader("retry-after", String(RULE_IMPORT_RETRY_AFTER_SEC));
121
200
  sendError(res, 503, "state.rule_import_retry", "the rule import could not be completed right now; the ticket is still usable — retry", { retryAfterSec: RULE_IMPORT_RETRY_AFTER_SEC });
@@ -0,0 +1,34 @@
1
+ /**
2
+ * design/383 §2.1 / §3.4 S-8①(server S-49;[5596] 认领)—— **中途翻转 verb** 的 wire 面:
3
+ *
4
+ * `POST /v1/runs/:id/memory/capture-optout` body `{ reason?: string }`
5
+ * → 200 `{ taskId, sessionId, outcome: "created" | "existed" }`
6
+ *
7
+ * 语义 = core `TaskStream.optOutMemoryCapture()`(「聊着聊着发现不该记」):从这一刻起本会话**零提交**长期记忆、
8
+ * 已提交贡献退出整理候选(回溯 A2 臂)、写根残留边界隔离清扫。**单向**(无反向动词;重复调用幂等 `existed`),
9
+ * 回到采集只能开新会话。
10
+ *
11
+ * ── 权限法条(steer 车道逐字)────────────────────────────────────────────────────────────────────
12
+ * core 不认证:持有活流句柄即能力。**本面拥有 verified-principal / 会话所有权判定**:裸 sessionId 中继会让任何人
13
+ * 关掉任何人的记忆(可用性攻击)。所以:身份来自 `gatedPrincipal`(验证过的身份,不是可自造的头),run 属主不符
14
+ * ⇒ 404 `not_found.run` **同串反枚举**(与 steer/interrupt 同判)。core 在翻转时刻**重新裁决** per-principal
15
+ * entitlement(`RuntimeCaps.allowMemoryOptOut` under `memoryCapturePolicy`,fresh resolve)—— 被拒 ⇒ typed
16
+ * `memory.capture_optout_denied` + 零记录残留;server 不复刻那条裁决。
17
+ *
18
+ * ── 四臂(steer 车道同形,但**没有 park 腿**)──────────────────────────────────────────────────────
19
+ * (a) 本副本活流在场 ⇒ 调 verb,200 `{outcome}`;core 的 typed 拒闭集逐码转发(见 switch);
20
+ * (b) 非 live(parked / 他副本 / 无活流 / 终态)⇒ 409 `steering.not_running` + 指路**声明形**:opt-out 的持久
21
+ * 形是 `TaskSpec.memory.capture:"off"`(wire `memoryCapture:"off"`),下一次 resume/新会话提交时带上即可 ——
22
+ * core 明写声明与 verb 是同一条记录的两个 ingress,**不存在**「park 一个翻转等 resume 再翻」的形(记录是
23
+ * 一次性单向的,resume 时带声明就是幂等重写),所以本面刻意不铸 park 腿。
24
+ *
25
+ * ── 计费/门 ─────────────────────────────────────────────────────────────────────────────────────
26
+ * 零模型工作(不进 billable;不吃 drain / roster-pending);mutating ⇒ `rateLimited`。**不进** `isCredentialGatedRewrite`:
27
+ * 有属主门兜底(run.owner),爆炸半径 = 本人会话。能力位 `runMemoryCaptureOptOut`(= 持久 run 账面在场,与
28
+ * `sessionBackgroundable` 同谓词):老 worker 键缺席 ⇒ 壳不渲入口(假 affordance 纪律)。
29
+ */
30
+ import type { IncomingMessage, ServerResponse } from "node:http";
31
+ import { type RouteCtx } from "../route-ctx.js";
32
+ export declare const RUN_MEMORY_CAPTURE_OPTOUT_RE: RegExp;
33
+ export declare function handleRunMemoryCapture(req: IncomingMessage, res: ServerResponse, url: string, ctx: RouteCtx): Promise<boolean>;
34
+ //# sourceMappingURL=run-memory-capture.d.ts.map
@@ -0,0 +1,97 @@
1
+ import { z } from "zod";
2
+ import { isParkedRunStatus, isTerminalRunStatus } from "../../plugins/store-contracts.js";
3
+ import { sendJson, sendError } from "../send.js";
4
+ import { gatedPrincipal } from "../principal-gate.js";
5
+ import { RUN_NOT_FOUND_MESSAGE, runNotFoundMessage } from "../route-ctx.js";
6
+ export const RUN_MEMORY_CAPTURE_OPTOUT_RE = /^\/v1\/runs\/([^/]+)\/memory\/capture-optout$/;
7
+ const BodySchema = z.object({ reason: z.string().min(1).max(500).optional() }).strict();
8
+ export async function handleRunMemoryCapture(req, res, url, ctx) {
9
+ const miss = { fell: false };
10
+ await handleBody(req, res, url, ctx, miss);
11
+ return !miss.fell;
12
+ }
13
+ async function handleBody(req, res, url, ctx, miss) {
14
+ const { deps } = ctx;
15
+ const m = req.method === "POST" ? RUN_MEMORY_CAPTURE_OPTOUT_RE.exec(url) : null;
16
+ if (m === null) {
17
+ miss.fell = true;
18
+ return;
19
+ }
20
+ if (ctx.helpers.rateLimited(req, res))
21
+ return;
22
+ if (!deps.runStore) {
23
+ sendError(res, 501, "capability.run_store_required", "async runs require a durable run store (DB_BACKEND=mysql|pg|local)");
24
+ return;
25
+ }
26
+ const principal = gatedPrincipal(req, deps.config);
27
+ if (deps.config.requirePrincipal && principal === undefined) {
28
+ sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
29
+ return;
30
+ }
31
+ const taskId = m[1];
32
+ const parsed = BodySchema.safeParse((await ctx.helpers.readJson(req)) ?? {});
33
+ if (!parsed.success) {
34
+ sendError(res, 400, "request.body_shape", "body must be {} or { reason: string (non-empty, at most 500 chars) } — the session memory-capture opt-out verb takes no other keys");
35
+ return;
36
+ }
37
+ const run = await deps.runStore.getRun(taskId);
38
+ if (!run) {
39
+ sendError(res, 404, "not_found.run", runNotFoundMessage(taskId));
40
+ return;
41
+ }
42
+ if (run.owner !== null && run.owner !== principal) {
43
+ sendError(res, 404, "not_found.run", RUN_NOT_FOUND_MESSAGE);
44
+ return;
45
+ }
46
+ const live = ctx.registry.steerableRuns.get(taskId);
47
+ if (live) {
48
+ try {
49
+ const { outcome } = await live.optOutMemoryCapture(parsed.data.reason !== undefined ? { reason: parsed.data.reason } : {});
50
+ sendJson(res, 200, { taskId, sessionId: run.sessionId, outcome });
51
+ return;
52
+ }
53
+ catch (e) {
54
+ const code = e.code;
55
+ const msg = e instanceof Error ? e.message : String(e);
56
+ switch (code) {
57
+ case "memory.capture_optout_denied":
58
+ sendError(res, 403, "memory.capture_optout_denied", msg);
59
+ return;
60
+ case "memory.capture_optout_unpersisted":
61
+ sendError(res, 503, "memory.capture_optout_unpersisted", msg);
62
+ return;
63
+ case "memory.capture_optout_unavailable":
64
+ sendError(res, 409, "memory.capture_optout_unavailable", msg);
65
+ return;
66
+ case "memory.capture_optout_sweep_failed":
67
+ sendError(res, 409, "memory.capture_optout_sweep_failed", msg);
68
+ return;
69
+ case "config.memory_capture_unsupported":
70
+ sendError(res, 409, "config.memory_capture_unsupported", msg);
71
+ return;
72
+ case "steering.invalid_content":
73
+ sendError(res, 422, "steering.invalid_content", msg);
74
+ return;
75
+ case "steering.not_running":
76
+ sendError(res, 409, "steering.not_running", "run just finished — the memory-capture opt-out verb needs a live run; declare it on the next submit instead (memoryCapture: 'off')");
77
+ return;
78
+ default: throw e;
79
+ }
80
+ }
81
+ }
82
+ if (isParkedRunStatus(run.status)) {
83
+ sendError(res, 409, "steering.not_running", "run is parked on a pending decision — the memory-capture opt-out verb needs a live turn, and a resumed continuation rebuilds its spec from the persisted body (it cannot take a new declaration); the opt-out becomes effective on this session's next NEW submit (POST /v1/tasks with this sessionId and memoryCapture: 'off') — content captured until then stays captured");
84
+ return;
85
+ }
86
+ if (isTerminalRunStatus(run.status)) {
87
+ sendError(res, 409, "steering.not_running", "run already ended — the memory-capture opt-out verb needs a live run; submit the next turn of this session with memoryCapture: 'off' (the declaration writes the same one-way session record)");
88
+ return;
89
+ }
90
+ const provenElsewhere = typeof run.instanceId === "string" && run.instanceId.length > 0 && deps.instanceId !== undefined && run.instanceId !== deps.instanceId;
91
+ if (provenElsewhere) {
92
+ sendError(res, 409, "steering.not_running", "run is live on another replica — the memory-capture opt-out verb must reach the replica holding the stream; resend it there, or declare memoryCapture: 'off' on the next submit");
93
+ return;
94
+ }
95
+ sendError(res, 409, "steering.not_running", "no live steerable face for this run on this replica — verify/cascade execution lanes expose none; declare memoryCapture: 'off' on the next submit of this session instead");
96
+ }
97
+ //# sourceMappingURL=run-memory-capture.js.map
@@ -20,6 +20,7 @@ export declare const RUN_CANCEL_RE: RegExp;
20
20
  export declare const RUN_STEER_RE: RegExp;
21
21
  export declare const RUN_DETACH_RE: RegExp;
22
22
  export declare const RUN_COMPACT_RE: RegExp;
23
+ export declare const RUN_INTERRUPT_RE: RegExp;
23
24
  /** PAIR-REVIEW F-10:runs 面 404 文案统一富形——a\* 形 id(background-agent registry 句柄,`a`+hex 短形)
24
25
  * 是「结构性不在 run 店」的常客([1347]⑤ 记档裁量:不改路由,只给引导)。识别句法形即补引导;
25
26
  * 非 a\* 形保通用富文案(cancel 腿 [888]① 同款)。 */