@sema-agent/server 7.105.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 (174) hide show
  1. package/MIGRATION.md +82 -0
  2. package/USAGE.md +134 -42
  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 +3 -9
  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 +35 -0
  75. package/dist/config.js +69 -16
  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/fleet-client.d.ts +18 -13
  82. package/dist/fleet-client.js +32 -22
  83. package/dist/fleet-lease.d.ts +72 -47
  84. package/dist/fleet-lease.js +66 -14
  85. package/dist/governance-http-status.d.ts +0 -1
  86. package/dist/governance-http-status.js +0 -1
  87. package/dist/hosted-posture.d.ts +6 -17
  88. package/dist/hosted-posture.js +11 -6
  89. package/dist/http/admission.js +14 -9
  90. package/dist/http/approval-sweeps.js +13 -1
  91. package/dist/http/dispatch.js +1 -1
  92. package/dist/http/durable-run-leg.js +2 -0
  93. package/dist/http/request-guards.d.ts +25 -0
  94. package/dist/http/request-guards.js +25 -5
  95. package/dist/http/resume-legs.js +19 -11
  96. package/dist/http/route-ctx.d.ts +9 -0
  97. package/dist/http/routes/approvals-assistant.js +0 -1
  98. package/dist/http/routes/capabilities.js +12 -11
  99. package/dist/http/routes/config-catalog.js +2 -1
  100. package/dist/http/routes/diagnostics.js +2 -0
  101. package/dist/http/routes/tasks.js +4 -1
  102. package/dist/http/server.d.ts +20 -4
  103. package/dist/http/session-inherit-env.d.ts +37 -0
  104. package/dist/http/session-inherit-env.js +6 -0
  105. package/dist/http/wire-types.d.ts +19 -28
  106. package/dist/index.d.ts +2 -1
  107. package/dist/index.js +2 -1
  108. package/dist/key-resolver.d.ts +74 -19
  109. package/dist/key-resolver.js +65 -7
  110. package/dist/leader/leader.d.ts +6 -0
  111. package/dist/leader/leader.js +2 -1
  112. package/dist/leader/repair-wire.js +2 -1
  113. package/dist/leader/wire.d.ts +9 -1
  114. package/dist/leader/wire.js +10 -6
  115. package/dist/observability/fail-open.d.ts +5 -1
  116. package/dist/observability/fail-open.js +5 -1
  117. package/dist/observability/metrics.js +2 -0
  118. package/dist/observability/permission-rule-events.d.ts +1 -1
  119. package/dist/observability/permission-rule-events.js +1 -1
  120. package/dist/observability/run-terminal.d.ts +38 -1
  121. package/dist/observability/run-terminal.js +2 -5
  122. package/dist/observability/secret-env-scrub.js +1 -1
  123. package/dist/observability/tool-trace.d.ts +2 -0
  124. package/dist/operator-ask.d.ts +29 -1
  125. package/dist/operator-ask.js +6 -0
  126. package/dist/parked-decide.d.ts +14 -1
  127. package/dist/parked-decide.js +8 -1
  128. package/dist/plugins/session-policy-store-sql.d.ts +38 -7
  129. package/dist/plugins/session-policy-store-sql.js +64 -22
  130. package/dist/plugins/sql-driver.d.ts +1 -1
  131. package/dist/plugins/store-backend.d.ts +12 -3
  132. package/dist/plugins/store-backend.js +16 -5
  133. package/dist/plugins/tidb-pool.js +1 -1
  134. package/dist/read-face-posture.d.ts +4 -4
  135. package/dist/request-key-closure.js +2 -2
  136. package/dist/request-value-closure.d.ts +17 -5
  137. package/dist/request-value-closure.js +12 -0
  138. package/dist/router/route-orchestration.d.ts +11 -0
  139. package/dist/router/route-orchestration.js +7 -6
  140. package/dist/run-local.js +4 -2
  141. package/dist/runs.d.ts +2 -1
  142. package/dist/runs.js +4 -4
  143. package/dist/runtime-caps-resolver.d.ts +4 -10
  144. package/dist/runtime-governance.d.ts +8 -11
  145. package/dist/runtime-governance.js +10 -41
  146. package/dist/sealed-key.d.ts +0 -40
  147. package/dist/sealed-key.js +0 -20
  148. package/dist/server-secret-env.d.ts +11 -12
  149. package/dist/session-policy-wire.d.ts +5 -3
  150. package/dist/session-titler.d.ts +11 -0
  151. package/dist/session-titler.js +6 -2
  152. package/dist/sql-engine-posture.d.ts +2 -2
  153. package/dist/store-posture.d.ts +53 -0
  154. package/dist/store-posture.js +32 -0
  155. package/dist/task-settings.d.ts +34 -45
  156. package/dist/task-settings.js +46 -25
  157. package/dist/tool-approval.d.ts +51 -22
  158. package/dist/tool-approval.js +44 -8
  159. package/dist/tool-declaration-http-status.d.ts +12 -0
  160. package/dist/tool-declaration-http-status.js +12 -0
  161. package/dist/trace/core-keyset-guard.d.ts +7 -7
  162. package/dist/trace/engine-notice-wire.d.ts +1 -1
  163. package/dist/trace/engine-notice-wire.js +5 -0
  164. package/dist/trace/project.d.ts +3 -2
  165. package/dist/trace/project.js +51 -3
  166. package/dist/trace/wire-projection-faces.d.ts +1 -1
  167. package/dist/trace/wire-projection-faces.js +1 -1
  168. package/dist/unattended-permission-mode.d.ts +110 -0
  169. package/dist/unattended-permission-mode.js +65 -0
  170. package/package.json +3 -3
  171. package/dist/boot/session-shell-gate-registry.d.ts +0 -39
  172. package/dist/boot/session-shell-gate-registry.js +0 -21
  173. package/dist/roster-bound-http-status.d.ts +0 -6
  174. package/dist/roster-bound-http-status.js +0 -6
@@ -3,6 +3,7 @@ import type { Pool as MySqlPool } from "mysql2/promise";
3
3
  import type { Pool as PgPool } from "pg";
4
4
  import { type JsonColumnShape } from "./sql-json-column.js";
5
5
  import { type SqlDriver } from "./sql-driver.js";
6
+ import type { Logger } from "../observability/logger.js";
6
7
  /** `session_policy.policy_key` 的**唯一铸造点**(core 复合键的 sha256)。
7
8
  *
8
9
  * 🔴 导出而不是让收编腿再抄一份(clay 宪法「schema 单一属主禁复制」):[ref] form b 的身份重绑要
@@ -32,20 +33,26 @@ export declare const policyKeyFor: (sessionId: string, principal: string | undef
32
33
  * + 恰好解一次 `decodeJsonColumn` + 折码 `foldJsonColumn`);本店只留「形判据是哪一只」与「坏体折成哪个码」。
33
34
  */
34
35
  export declare function readSessionRulesBody(body: unknown): JsonColumnShape<SessionPermissionRules>;
36
+ /** 本店唯一的日志口:抹除封键那一臂(S-891)的点名。缺席 ⇒ 退回 `console.warn`(不静默)。 */
37
+ export type SessionPolicyStoreLogger = Pick<Logger, "warn">;
35
38
  /** Dual-dialect durable SessionPolicyStore. See the file header for the dialect-delta ledger. */
36
39
  export declare class SqlSessionPolicyStore implements SessionPolicyStore {
37
40
  private readonly db;
38
- constructor(db: SqlDriver);
41
+ private readonly logger?;
42
+ constructor(db: SqlDriver, logger?: SessionPolicyStoreLogger | undefined);
39
43
  /** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */
40
44
  private q;
41
45
  /** 判据属主 = `sql-errors.ts`([ref] P1-①;本站点旧形已是并集形,收编后语义不变)。 */
42
46
  private isDupKey;
43
- /** Read the stored rules for (session, principal-owner); null when none (parity with InMemory). */
47
+ /** Read the stored rules for (session, principal-owner); null when none (parity with InMemory).
48
+ * 🔴 S-891(codex r1 [high] ①):同一条语句带上这把键的**封键记号** —— 封着 ⇒ `corrupt`,不答「没有规则」(见 {@link sealedKeyError})。 */
44
49
  getRules(sessionId: string, principal?: string): Promise<StoredSessionRules | null>;
45
50
  /** 2c session-sync: every (principal, rules) record for a session across ALL principals — getRules is
46
51
  * per-(session,principal), but a cross-backend session EXPORT must bundle the WHOLE session's policy (the importer
47
52
  * replays each via putRules). principal is read from the DENORMALIZED column (the sha256 policy_key is one-way);
48
- * NULL → undefined = the session-wide row (parity with core's InMemory `principal ?? undefined`). Rules carry rev. */
53
+ * NULL → undefined = the session-wide row (parity with core's InMemory `principal ?? undefined`). Rules carry rev.
54
+ * 🔴 S-891:会话里有**封着的键** ⇒ 整只枚举 `corrupt`(与一行读不出形时整只枚举拒同一口径 —— 导出一份缺了那把键的规则,
55
+ * 导入方就会把「状态未知」当成「这个 principal 没有规则」)。 */
49
56
  listBySession(sessionId: string): Promise<SessionRulesRecord[]>;
50
57
  /** Operator/owner write: CAS + tighten-only (unless operator) + rev bump, atomic in one txn; first-write race retried. */
51
58
  putRules(sessionId: string, principal: string | undefined, rules: SessionPermissionRules, opts?: PutRulesOptions): Promise<StoredSessionRules>;
@@ -91,7 +98,8 @@ export declare class SqlSessionPolicyStore implements SessionPolicyStore {
91
98
  * against a CURRENT read (PG is pinned to READ COMMITTED, TiDB's DML is a current read), so a row for a
92
99
  * DIFFERENT principal that a concurrent putRules commits BETWEEN ① and ③ would be deleted by a session-wide
93
100
  * DELETE while ② never marked it — a silently reset rev line, exactly the defect this batch exists to close.
94
- * Scoped to the key set, the invariant is exact and checkable: **every row this seam deletes leaves a mark**.
101
+ * Scoped to the key set, the invariant is exact and checkable: **every row this seam deletes leaves a mark** —
102
+ * a readable row's mark continues its rev line and ends its lineage; an unreadable row's mark SEALS its key (S-891, below).
95
103
  * A row born after ① is simply not part of this erase (same as one born a microsecond after ③ — the caller
96
104
  * that needs "and nothing came back" re-issues; the seam is idempotent).
97
105
  * No rows ⇒ return early: an erase of a session that never had rules writes NO mark (no line, nothing to keep),
@@ -99,8 +107,31 @@ export declare class SqlSessionPolicyStore implements SessionPolicyStore {
99
107
  * Why the mark is a separate table and not a tombstone row: see the DDL comment in tidb-pool.ts — a tombstone
100
108
  * would keep `session_id` + the DENORMALIZED plaintext `principal` in the table after an E21 purge, i.e. the
101
109
  * right-to-delete would degrade to buy rev ordering. The mark keeps only the one-way hash, the session id and
102
- * an integer. */
110
+ * an integer.
111
+ *
112
+ * 🔴 **S-891(7.106.0)—— 抹除恒删行;计数读不出的行留「封键记号」**。一行的计数读不出(三个读点的同一只判词
113
+ * {@link readRowCounters} 判坏:`rev` 不是 ≥ 1 的安全整数 / `gen` 不是 ≥ 0 的安全整数;或下一代的号 `gen + 1` 已出安全整数)
114
+ * ⇒ 这一行**照删**,它的记号元组写 `(键, 会话, {@link SEALED_MARK_REV}, 0, 时刻)`、走**同一句** `GREATEST` 合并:rev 落上界,
115
+ * 世代 = `GREATEST(旧记号.gen + 1, 0)`(有旧记号照抬,没有就是 0)。于是这把键的下一次 `putRules` 算出 `上界 + 1`,被写前那道
116
+ * **同一只** {@link isStoredCounter} 拒(`corrupt`,零写);读面同答 `corrupt`({@link sealedKeyError})—— **封键**,直到运营方
117
+ * 显式解封(USAGE §9.11:原地把记号的 `rev` 归 0、`gen` + 1;不删记号,删了会交回世代)。
118
+ * 两条都不许丢,所以是这个形:
119
+ * · **删得掉** —— 抹除是运营方清坏行的出路(E21 `DELETE /v1/sessions/:id`、2c overwrite-dst 都经这里)。7.105.0 的「拒,一条写
120
+ * 都不发」让一行坏计数把整个会话的删除半程停住、重试恒败;
121
+ * · **不许同世代** —— 修复车 QS 的「照删、不写记号」被 codex 真复现:这把键的血统没在本次抹除上结束,重建行从旧记号起号、
122
+ * 与持有**旧快照**的活读者同世代 ⇒ 读者把重建的宽松行读成延续(deny → allow)。读不出的号不能拿来结束血统,
123
+ * 那就不让这把键再有下一行,直到有人看过。
124
+ * 响亮面:提交**之后**一条 warn(`session_policy_erase_sealed_keys`,点名会话与被封的键 + 两个计数的原文;回滚了就什么都没封,
125
+ * 死锁重试也不重复报)。这**不是** fail-open(没有放行任何东西,方向是 fail-closed),所以不走 `recordFailOpen`;普查登记 P 类终态。
126
+ * 残余(如实):记号里**已有**的世代若被列外改到 BIGINT 上界,冲突臂的 `记号.gen + 1` 在库里溢出 ⇒ 这次抹除响亮失败
127
+ * (驱动错误;那只记号本来就读不回,`putRules` 同样拒)—— 抹除不读记号,这一形不在本判词射程内。 */
103
128
  deleteBySession(sessionId: string): Promise<void>;
129
+ /**
130
+ * S-891 封键的响亮面:一次抹除至多一条 warn。日志座**抛** ⇒ 退回 `console.warn` 带原句 —— 走到这里删除已经提交,
131
+ * 一次日志故障不许把它报成失败(E21 会因此 500、overwrite-dst 会因此中止;codex 7.105.0 修复验证轮 [medium] 同形)。
132
+ * 缺日志座(直接 `new` 本店的工具 / 测试)⇒ `console.warn`,不静默。
133
+ */
134
+ private discloseSealed;
104
135
  /**
105
136
  * The store's one read-check-write transaction. Its load-bearing read is line ~106's
106
137
  * `SELECT rules, rev … FOR UPDATE` — a LOCKING read, therefore a CURRENT read on both engines, which is
@@ -114,10 +145,10 @@ export declare class SqlSessionPolicyStore implements SessionPolicyStore {
114
145
  }
115
146
  /** MySQL-protocol (TiDB) binding — historical class name + ctor shape preserved. */
116
147
  export declare class TiDBSessionPolicyStore extends SqlSessionPolicyStore {
117
- constructor(pool: MySqlPool);
148
+ constructor(pool: MySqlPool, logger?: SessionPolicyStoreLogger);
118
149
  }
119
150
  /** PostgreSQL binding — historical class name + ctor shape preserved. */
120
151
  export declare class PgSessionPolicyStore extends SqlSessionPolicyStore {
121
- constructor(pool: PgPool);
152
+ constructor(pool: PgPool, logger?: SessionPolicyStoreLogger);
122
153
  }
123
154
  //# sourceMappingURL=session-policy-store-sql.d.ts.map
@@ -26,9 +26,15 @@ function isStoredCounter(n, floor) {
26
26
  return Number.isSafeInteger(n) && n >= floor;
27
27
  }
28
28
  function rowToStored(r, where) {
29
+ const counters = readRowCounters(r, where);
30
+ if ("problem" in counters)
31
+ throw new SessionPolicyError("corrupt", counters.problem);
32
+ return { ...readRulesBody(r.rules, `session_policy.rules ${where}`), rev: counters.rev, gen: counters.gen };
33
+ }
34
+ function readRowCounters(r, where) {
29
35
  const rev = Number(r.rev);
30
36
  if (!isStoredCounter(rev, 1)) {
31
- throw new SessionPolicyError("corrupt", `session rules row ${where} carries rev ${String(r.rev)} — a stored row's rev is always a whole number ≥ 1 within the safe-integer range (the stamp is floor+1), and rev 0 is the live reader's synthetic empty seed; refusing rather than serve it as rules`);
37
+ return { problem: `session rules row ${where} carries rev ${String(r.rev)} — a stored row's rev is always a whole number ≥ 1 within the safe-integer range (the stamp is floor+1), and rev 0 is the live reader's synthetic empty seed; refusing rather than serve it as rules` };
32
38
  }
33
39
  if (r.gen === undefined || r.gen === null) {
34
40
  throw new Error(`session_policy row ${where} came back without a \`gen\` column — a read leg did not project it (the column is BIGINT NOT NULL on both dialects). ` +
@@ -36,14 +42,22 @@ function rowToStored(r, where) {
36
42
  }
37
43
  const gen = Number(r.gen);
38
44
  if (!isStoredCounter(gen, 0)) {
39
- throw new SessionPolicyError("corrupt", `session rules row ${where} carries lineage gen ${String(r.gen)} — a generation is a whole number ≥ 0 within the safe-integer range; refusing rather than let a reader judge lineage by a value nothing minted`);
45
+ return { problem: `session rules row ${where} carries lineage gen ${String(r.gen)} — a generation is a whole number ≥ 0 within the safe-integer range; refusing rather than let a reader judge lineage by a value nothing minted` };
40
46
  }
41
- return { ...readRulesBody(r.rules, `session_policy.rules ${where}`), rev, gen };
47
+ return { rev, gen };
48
+ }
49
+ const SEALED_MARK_REV = Number.MAX_SAFE_INTEGER;
50
+ const SEALED_EVENT = "session_policy_erase_sealed_keys";
51
+ const NO_SEALED_KEYS = [];
52
+ function sealedKeyError(sessionId, what) {
53
+ return new SessionPolicyError("corrupt", `session rules for session ${JSON.stringify(sessionId)}: ${what} is sealed — an erase removed a row whose counters were unreadable (session_policy_rev_mark.rev = ${SEALED_MARK_REV}), so its rule state is unknown; refusing rather than serve it as "no rules" until an operator unseals it (USAGE §9.11)`);
42
54
  }
43
55
  export class SqlSessionPolicyStore {
44
56
  db;
45
- constructor(db) {
57
+ logger;
58
+ constructor(db, logger) {
46
59
  this.db = db;
60
+ this.logger = logger;
47
61
  }
48
62
  q(tidb, pg) {
49
63
  return this.db.dialect === "tidb" ? tidb : pg;
@@ -52,13 +66,23 @@ export class SqlSessionPolicyStore {
52
66
  return isDupKeyError(this.db.dialect, e);
53
67
  }
54
68
  async getRules(sessionId, principal) {
55
- const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("tidb", "rules")}, rev, gen FROM session_policy WHERE policy_key = ?`, `SELECT ${jsonColumnText("pg", "rules")}, rev, gen FROM session_policy WHERE policy_key = $1`), [policyKey(sessionId, principal)]);
56
- if (!rows[0])
69
+ const key = policyKey(sessionId, principal);
70
+ const { rows } = await this.db.query(this.q(`SELECT 'row' AS src, ${jsonColumnText("tidb", "rules")}, rev, gen FROM session_policy WHERE policy_key = ?` +
71
+ " UNION ALL SELECT 'seal', NULL, rev, gen FROM session_policy_rev_mark WHERE policy_key = ? AND rev = ?", `SELECT 'row' AS src, ${jsonColumnText("pg", "rules")}, rev, gen FROM session_policy WHERE policy_key = $1` +
72
+ " UNION ALL SELECT 'seal', NULL, rev, gen FROM session_policy_rev_mark WHERE policy_key = $2 AND rev = $3"), [key, key, SEALED_MARK_REV]);
73
+ if (rows.some((r) => r.src === "seal"))
74
+ throw sealedKeyError(sessionId, `key ${key}`);
75
+ const row = rows.find((r) => r.src === "row");
76
+ if (row === undefined)
57
77
  return null;
58
- return rowToStored(rows[0], `for session ${JSON.stringify(sessionId)}`);
78
+ return rowToStored(row, `for session ${JSON.stringify(sessionId)}`);
59
79
  }
60
80
  async listBySession(sessionId) {
61
- const { rows } = await this.db.query(this.q(`SELECT principal, ${jsonColumnText("tidb", "rules")}, rev, gen FROM session_policy WHERE session_id = ?`, `SELECT principal, ${jsonColumnText("pg", "rules")}, rev, gen FROM session_policy WHERE session_id = $1`), [sessionId]);
81
+ const { rows } = await this.db.query(this.q(`SELECT 'row' AS src, principal, ${jsonColumnText("tidb", "rules")}, rev, gen FROM session_policy WHERE session_id = ?` +
82
+ " UNION ALL SELECT 'seal', NULL, NULL, rev, gen FROM session_policy_rev_mark WHERE session_id = ? AND rev = ?", `SELECT 'row' AS src, principal, ${jsonColumnText("pg", "rules")}, rev, gen FROM session_policy WHERE session_id = $1` +
83
+ " UNION ALL SELECT 'seal', NULL, NULL, rev, gen FROM session_policy_rev_mark WHERE session_id = $2 AND rev = $3"), [sessionId, sessionId, SEALED_MARK_REV]);
84
+ if (rows.some((r) => r.src === "seal"))
85
+ throw sealedKeyError(sessionId, "one of its keys");
62
86
  return rows.map((r) => ({
63
87
  principal: r.principal == null ? undefined : String(r.principal),
64
88
  rules: rowToStored(r, `for session ${JSON.stringify(sessionId)} (enumeration)`),
@@ -120,22 +144,20 @@ export class SqlSessionPolicyStore {
120
144
  }
121
145
  }
122
146
  async deleteBySession(sessionId) {
123
- await this.retrying(() => this.tx(async (conn) => {
147
+ const sealed = await this.retrying(() => this.tx(async (conn) => {
124
148
  const { rows } = await conn.query(this.q("SELECT policy_key, rev, gen FROM session_policy WHERE session_id = ? ORDER BY policy_key FOR UPDATE", "SELECT policy_key, rev, gen FROM session_policy WHERE session_id = $1 ORDER BY policy_key FOR UPDATE"), [sessionId]);
125
149
  if (rows.length === 0)
126
- return;
150
+ return NO_SEALED_KEYS;
127
151
  const now = new Date();
128
152
  const keys = rows.map((r) => String(r.policy_key));
129
153
  const params = [];
130
- for (const r of rows) {
131
- const rev = Number(r.rev);
132
- const nextGen = Number(r.gen ?? 0) + 1;
133
- if (!isStoredCounter(rev, 1) || !isStoredCounter(nextGen, 0)) {
134
- throw new SessionPolicyError("corrupt", `session rules row for session ${JSON.stringify(sessionId)} carries rev ${String(r.rev)} / gen ${String(r.gen)} — the erase would leave a mark outside the safe-integer range; refusing before writing it`);
135
- }
136
- }
154
+ const sealedKeys = [];
137
155
  const tuples = rows.map((r) => {
138
- params.push(String(r.policy_key), sessionId, Number(r.rev), Number(r.gen ?? 0) + 1, now);
156
+ const counters = readRowCounters(r, `for session ${JSON.stringify(sessionId)} (erase)`);
157
+ const readable = !("problem" in counters) && isStoredCounter(counters.gen + 1, 0);
158
+ if (!readable)
159
+ sealedKeys.push({ policyKey: String(r.policy_key), rev: String(r.rev), gen: String(r.gen) });
160
+ params.push(String(r.policy_key), sessionId, readable ? counters.rev : SEALED_MARK_REV, readable ? counters.gen + 1 : 0, now);
139
161
  const base = params.length - 5;
140
162
  return this.db.dialect === "tidb" ? "(?,?,?,?,?)" : `($${base + 1},$${base + 2},$${base + 3},$${base + 4},$${base + 5})`;
141
163
  });
@@ -144,7 +166,27 @@ export class SqlSessionPolicyStore {
144
166
  " ON CONFLICT (policy_key) DO UPDATE SET rev = GREATEST(session_policy_rev_mark.rev, EXCLUDED.rev), gen = GREATEST(session_policy_rev_mark.gen + 1, EXCLUDED.gen), erased_at = EXCLUDED.erased_at"), params);
145
167
  const holes = keys.map((_, i) => (this.db.dialect === "tidb" ? "?" : `$${i + 1}`)).join(",");
146
168
  await conn.query(`DELETE FROM session_policy WHERE policy_key IN (${holes})`, keys);
169
+ return sealedKeys;
147
170
  }));
171
+ if (sealed.length > 0)
172
+ this.discloseSealed(sessionId, sealed);
173
+ }
174
+ discloseSealed(sessionId, keys) {
175
+ const fields = {
176
+ sessionId,
177
+ keys,
178
+ note: "these rows' counters were unreadable, so the erase could not end their lineage: each key now carries a sealed rev mark and answers every read and write with SessionPolicyError corrupt until an operator unseals its session_policy_rev_mark row in place (USAGE §9.11)",
179
+ };
180
+ if (this.logger === undefined) {
181
+ console.warn(`[session-policy] ${SEALED_EVENT}`, fields);
182
+ return;
183
+ }
184
+ try {
185
+ this.logger.warn(SEALED_EVENT, fields);
186
+ }
187
+ catch (err) {
188
+ console.warn(`[session-policy] ${SEALED_EVENT} — the injected logger threw (${err instanceof Error ? err.message : String(err)})`, fields);
189
+ }
148
190
  }
149
191
  async tx(fn) {
150
192
  const conn = await this.db.connect();
@@ -168,13 +210,13 @@ export class SqlSessionPolicyStore {
168
210
  }
169
211
  }
170
212
  export class TiDBSessionPolicyStore extends SqlSessionPolicyStore {
171
- constructor(pool) {
172
- super(mysqlDriver(pool));
213
+ constructor(pool, logger) {
214
+ super(mysqlDriver(pool), logger);
173
215
  }
174
216
  }
175
217
  export class PgSessionPolicyStore extends SqlSessionPolicyStore {
176
- constructor(pool) {
177
- super(pgDriver(pool));
218
+ constructor(pool, logger) {
219
+ super(pgDriver(pool), logger);
178
220
  }
179
221
  }
180
222
  //# sourceMappingURL=session-policy-store-sql.js.map
@@ -59,7 +59,7 @@
59
59
  * `INTRA_TXN_PLAIN_READS` 每条带理由);真值神谕:`test/sql-txn-semantics-integration.test.ts`(三引擎)。
60
60
  *
61
61
  * **④ 引擎事实不进店** —— `SqlDriver.facts` 是**运维读面**(`GET /v1/diagnostics/wiring` 的 `sqlEngine`
62
- * 段与 `GET /v1/capabilities` 的 `sql` 位)的料。店里不许出现 `if (engine === …)`:引擎判别在全仓
62
+ * 段与 `GET /v1/capabilities` 的 `store.sql` 位(7.106.0 起;此前顶层 `sql`,S-866 ①))的料。店里不许出现 `if (engine === …)`:引擎判别在全仓
63
63
  * 只有连接初始化这一处,机器门 G-A 守着。`SqlDialect`(SQL **文本**方言,两值)与 `engine`(运行期
64
64
  * 引擎事实,三值)是两条轴,刻意不合成一个三值。
65
65
  *
@@ -13,6 +13,7 @@ import { type ApprovalAskStore as ApprovalAskStoreType } from "./approval-ask-st
13
13
  import { type MemoryOptOutGrantStore } from "./memory-optout-grant-store-sql.js";
14
14
  import { type SendFileLedger } from "./send-file-ledger.js";
15
15
  import { TiDBResumeAnchorStore, PgResumeAnchorStore } from "./resume-anchor-store-sql.js";
16
+ import { type SessionPolicyStoreLogger } from "./session-policy-store-sql.js";
16
17
  import type { ServiceConfig } from "../config-types.js";
17
18
  import { TiDBRunStore, PgRunStore } from "./run-store-sql.js";
18
19
  import { TiDBCheckpointStore, PgCheckpointStore } from "./checkpoint-store-sql.js";
@@ -31,6 +32,7 @@ import { type AdoptionLogStore } from "./adoption-log-sql.js";
31
32
  import { type SqlRetentionStore, type SqlRetentionStoreOptions } from "./retention-store-sql.js";
32
33
  import { type RetentionLaneStore } from "./retention-lane-store-sql.js";
33
34
  import type { PermissionRuleStoreBundle } from "../rules-consent.js";
35
+ import { type StoreDegradedReason } from "../store-posture.js";
34
36
  /** boot 期的一条日志座(级别由**座位**决定,不由参数携带):`createStoreBackend` 的 `onWarn` / `onError`
35
37
  * 两参与 `LocalBackend` 的同名字段共用此形。装配层把它们分别接到 `logger.warn` / `logger.error` 上。 */
36
38
  export type BootLogSink = (msg: string, meta?: Record<string, unknown>) => void;
@@ -194,8 +196,9 @@ export interface StoreBackend {
194
196
  * mapping from URLs). REQUIRED on all backends (works local — one JSONL, like approvalExemption). */
195
197
  sendFileLedger(): SendFileLedger;
196
198
  /** E6 durable SessionPolicyStore (operator-tightened per-session tool rules). REQUIRED on all backends — works local
197
- * (local = core's FileSessionPolicyStore), like resumeAnchor. Backs core's RunnerDeps.sessionPolicyStore. */
198
- sessionPolicy(): ServiceSessionPolicyStore;
199
+ * (local = core's FileSessionPolicyStore), like resumeAnchor. Backs core's RunnerDeps.sessionPolicyStore.
200
+ * `logger`:SQL 孪生抹除封键那一臂(S-891)的点名口(缺席 ⇒ 店退回 `console.warn`);local(core 的 File 店)不收。 */
201
+ sessionPolicy(logger?: SessionPolicyStoreLogger): ServiceSessionPolicyStore;
199
202
  /** [ref] per-edited-file rewind FileHistoryStore (trackEdit/annulTrack/snapshot/restore over the tracked set;
200
203
  * also the 2c history-transfer store). REQUIRED on all backends (local = core's file-backed
201
204
  * `FileFileHistoryStore` under the data root). Backs core's RunnerDeps.fileHistoryStore. */
@@ -394,6 +397,12 @@ export declare function openStoreBackendWithFallback(config: ServiceConfig, logg
394
397
  error?: (msg: string, meta?: Record<string, unknown>) => void;
395
398
  }): Promise<{
396
399
  backend: StoreBackend | undefined;
397
- degraded: boolean;
400
+ degradedReason: StoreDegradedReason | undefined;
398
401
  }>;
402
+ /**
403
+ * S-866 ⑤c / G4 —— SQL 族「显式要了持久而店打不开」的拒启句(唯一铸点)。点名 `DB_BACKEND=<引擎>`,给三条出路:修坐标 / 连通性;
404
+ * `SESSION_BACKEND=auto`(尽力而为:店不可达时以内存起服,什么都不持久);`DB_BACKEND=memory`(显式内存部署)。驱动原文只取 message
405
+ * (与修前 `fatalExit` 打出的同一句),凭据不在其中(驱动不回显口令)。
406
+ */
407
+ export declare function buildSqlStoreOpenRefusal(engine: "mysql" | "pg", err: unknown): string;
399
408
  //# sourceMappingURL=store-backend.d.ts.map
@@ -52,6 +52,7 @@ import { TiDBRetentionLaneStore, PgRetentionLaneStore } from "./retention-lane-s
52
52
  import { createSqlPermissionRuleStores } from "./permission-rule-store-sql.js";
53
53
  import { createFilePermissionRuleStores } from "./permission-rule-store-file.js";
54
54
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
55
+ import { storeDegradedReasonFor } from "../store-posture.js";
55
56
  function historyBlobBackend(config, dialect, pool) {
56
57
  const s = config.snapshotBlobStore;
57
58
  if (!s)
@@ -122,7 +123,7 @@ class TiDBBackend {
122
123
  approvalAsk() { return new TiDBApprovalAskStore(this.pool); }
123
124
  permissionRule() { return createSqlPermissionRuleStores(mysqlDriver(this.pool)); }
124
125
  sendFileLedger() { return new TiDBSendFileLedger(this.pool); }
125
- sessionPolicy() { return new TiDBSessionPolicyStore(this.pool); }
126
+ sessionPolicy(logger) { return new TiDBSessionPolicyStore(this.pool, logger); }
126
127
  fileHistory() { return new TiDBFileHistoryStore(this.pool, historyBlobBackend(this.config, "tidb", this.pool), this.config.fileHistory); }
127
128
  memoryOptOutGrant() { return new TiDBMemoryOptOutGrantStore(this.pool); }
128
129
  sessionCaptureRecords() { return () => (this.captureRecordsInst ??= new TiDBSessionCaptureRecordStore(this.pool)); }
@@ -170,7 +171,7 @@ class PgBackend {
170
171
  approvalAsk() { return new PgApprovalAskStore(this.pool); }
171
172
  permissionRule() { return createSqlPermissionRuleStores(pgDriver(this.pool)); }
172
173
  sendFileLedger() { return new PgSendFileLedger(this.pool); }
173
- sessionPolicy() { return new PgSessionPolicyStore(this.pool); }
174
+ sessionPolicy(logger) { return new PgSessionPolicyStore(this.pool, logger); }
174
175
  fileHistory() { return new PgFileHistoryStore(this.pool, historyBlobBackend(this.config, "pg", this.pool), this.config.fileHistory); }
175
176
  memoryOptOutGrant() { return new PgMemoryOptOutGrantStore(this.pool); }
176
177
  sessionCaptureRecords() { return () => (this.captureRecordsInst ??= new PgSessionCaptureRecordStore(this.pool)); }
@@ -346,15 +347,25 @@ export async function openStoreBackendWithFallback(config, logger) {
346
347
  throw err;
347
348
  if (err instanceof StoreBootRefusalError)
348
349
  throw err;
349
- if (!mayFallback)
350
+ if (!mayFallback) {
351
+ if (config.dbBackend === "mysql" || config.dbBackend === "pg")
352
+ throw new Error(buildSqlStoreOpenRefusal(config.dbBackend, err), { cause: err });
350
353
  throw err;
354
+ }
351
355
  logger.warn("store_db_unreachable_fallback_memory", {
352
356
  backend: backend?.kind ?? config.dbBackend,
353
357
  error: err instanceof Error ? err.message : String(err),
354
358
  });
355
359
  await backend?.close().catch(() => undefined);
356
- return { backend: undefined, degraded: true };
360
+ return { backend: undefined, degradedReason: storeDegradedReasonFor(config.dbBackend) };
357
361
  }
358
- return { backend, degraded: false };
362
+ return { backend, degradedReason: undefined };
363
+ }
364
+ export function buildSqlStoreOpenRefusal(engine, err) {
365
+ const coords = engine === "pg" ? "PG_*" : "MYSQL_*";
366
+ const detail = err instanceof Error ? err.message : String(err);
367
+ return (`DB_BACKEND=${engine}: cannot open the configured ${engine === "pg" ? "PostgreSQL" : "MySQL-protocol"} store (${detail}) — refusing to boot, because DB_BACKEND asks for a durable store and SESSION_BACKEND does not allow running without it. ` +
368
+ `Next: fix the ${coords} coordinates or the store's reachability; or set SESSION_BACKEND=auto to boot in memory while the store is unreachable (best effort — nothing persists, and /health reports storeDegraded); ` +
369
+ `or set DB_BACKEND=memory for an explicitly in-memory deployment.`);
359
370
  }
360
371
  //# sourceMappingURL=store-backend.js.map
@@ -390,7 +390,7 @@ export const SCHEMA_STATEMENTS = [
390
390
  -- 被删会话(**明文**会话 id —— 所以它必须随会话一起走,见上)。记号的读写按 policy_key 单键;托管留存的整树
391
391
  -- 清扫按本列删(S-546 A9,索引 idx_session_policy_rev_mark_session)。
392
392
  session_id VARCHAR(64) NOT NULL,
393
- -- 抹除时刻该键带过的最高 rev(GREATEST 合并:记号只升不降)。
393
+ -- 抹除时刻该键带过的最高 rev(GREATEST 合并:记号只升不降)。= 安全整数上界 ⇒ 封键(S-891:被抹行的计数读不出;运营方原地解封,见 USAGE §9.11)。
394
394
  rev BIGINT NOT NULL,
395
395
  -- 这把键的**下一行从第几代开始**(每次抹除 +1;与 core File 侧车 <stem>.hwm.json 的 __gen 同义)。
396
396
  -- 血统必须比它终结的那一行活得久,理由与 rev 完全一样:留在行里就等于跟着行一起消失。
@@ -25,10 +25,10 @@
25
25
  * 本模块**只**管 `face` 一档。内建读拒表(`readDenyPatterns` / `readDenyBuiltinTiers` /
26
26
  * `readDenyBuiltinExclude`)一个字节不动 —— 放开容纳面不等于放开那几档 deny,两者在 core 里是**并列
27
27
  * 机制**(有活行的 deny 表在 open 面上照样拦)。
28
- * ⚠️ **但别把这句话读成「凭证/shell 历史/钱包那几档一直在岗」**:core 7.22.0([ref])起内建表的**缺省
29
- * 选择是空** —— 一台没写 `READ_DENY_BUILTIN_TIERS` 的部署上,那张表**一行都不生效**(要老姿态就显式写
30
- * `credentials,browser,wallet,agent-config`;运维面全文见 `docs/DEPLOY-PREREQS.md` 的 core 7.22.0 段)。
31
- * 本模块的定界不变,变的是「并列的那一侧默认开着」这个**前提**。core 的 `config.read_face_deployment_clamped`
28
+ * ⚠️ **但别把这句话读成「每一档都一直在岗」**:内建表的缺省选择是 core 的 `READ_DENY_DEFAULT_TIERS` —— core 7.34.0 R-2 起
29
+ * = `credentials` + 家目录锚定的 `agent-config` 两档(7.22.0–7.33.x 是空选择 OFF;7.106.0 合并复审 X10-L2 改真)。一台没写
30
+ * `READ_DENY_BUILTIN_TIERS` 的部署上只有这两档生效(shell 历史 / 浏览器 / 钱包要显式写,`none` = 一行都不生效)。
31
+ * 本模块的定界不变:并列的那一侧开着哪几档,是那张表自己的缺省说了算。core 的 `config.read_face_deployment_clamped`
32
32
  * (只读挂载钳制)同样不受本件影响。
33
33
  */
34
34
  import { type PostureSource } from "./posture-source.js";
@@ -19,10 +19,10 @@ export function keyClosureClause(names, label) {
19
19
  }
20
20
  const TASK_BODY_DECLARED_KEYS = [
21
21
  "a2aPeers", "acceptPartialRestore", "additionalDirectories", "additionalReadDirectories", "agents",
22
- "appendSystemPrompt", "approverPosture", "attachmentIds", "attachments", "capabilitiesNeeded", "cascade", "clientContext",
22
+ "appendSystemPrompt", "attachmentIds", "attachments", "capabilitiesNeeded", "cascade", "clientContext",
23
23
  "compaction", "compactionModel", "council", "cwd", "debate", "deferTools", "deviceId", "enableFork",
24
24
  "excludeAllTools", "excludeTools", "fileHistory",
25
- "finalVerification", "forwardSubagentEvents", "images", "interactiveTools", "jobId", "limits", "maxCostUsd",
25
+ "finalVerification", "forwardSubagentEvents", "images", "inheritEnv", "interactiveTools", "jobId", "limits", "maxCostUsd",
26
26
  "maxTokens", "mcpServers", "memory", "memoryCapture", "memoryWrite", "model", "objective", "oneShot", "outputRetries",
27
27
  "outputSchema", "permissionMode", "projectId", "promptProfile", "reasoningEffort", "requireExistingSession",
28
28
  "resilience", "restoreFiles", "resumeAt", "resumeAtMode", "retainBackgroundProcesses", "retainSubagentSessions",
@@ -10,8 +10,8 @@
10
10
  * 一个敲错的隐私/收紧请求被静默丢掉(用户以为关了,其实没关),或者一个敲错的键把功能静默关掉。
11
11
  *
12
12
  * 🔴 **判别是「值域恰好是一个关断词」,不是「值域恰好是一个字面量」**(S-724 加 `fileHistory` 时定形):
13
- * `approverPosture` 的值域也只有一个词(`"bypass"`),但它是一句**姿态声明**、闭集属主在 core、本仓刻意**零判据**
14
- * (`http/wire-types.ts` 那一段逐字成文);把判别放宽成「单字面量」会把它也拖进本表 = 同一语义面第二个判官。
13
+ * 当年的反例是 `approverPosture`(值域也只有一个词 `"bypass"`,但它是一句**姿态声明**、闭集属主在 core,本仓刻意零判据);
14
+ * 那个请求键已随 core 7.34.0 退役(S-877,[ref] 裁 ③),判别式的理由不变 —— 一个非关断的单字面量键不属于本表。
15
15
  * 关断词全集 {@link OffSwitchWord} 是 core 的两种关断拼法,新增第三种拼法 = 加一个词(并逐键重判)。
16
16
  *
17
17
  * ## 为什么是一只判据函数,不是每处一抄
@@ -53,10 +53,14 @@ export declare const FILE_HISTORY_OFF_SPELLING_MESSAGE = "fileHistory must be th
53
53
  /**
54
54
  * S-724 b′(主会话裁,车PW codex r1 [high] 复现后)—— `fileHistory: "disabled"` 与还原**同体**的拒句(`request.field_conflict`)。
55
55
  * 与 `tool-approval.ts` 的 `SETTLED_BY_DENY_ONLY` / `READ_ROOT_GRANT_ALLOW_ONLY` **同一族**:「只看体就能判的矛盾,在裁决之前响亮说」。
56
- * 这一对是调用方自己写出的矛盾(关掉这条 run 的文件历史,又要从文件历史还原);core 也拒(`rewind.capture_disabled`),但它的拒
57
- * 落在 prepare 的**会话分叉之后**(`prepare-acquire-reconcile` 先按 `resumeAt` 把活动分支移过去,`prepare-file-history` 才判),
58
- * 且拒后不回滚 —— 拒传不回来。所以本层在任何副作用之前拒。**这不是替 core 再判一次「捕获关了能不能还原」**:判的是体里两个键
56
+ * 这一对是调用方自己写出的矛盾(关掉这条 run 的文件历史,又要从文件历史还原);core 也拒(`rewind.capture_disabled`)。立门时(7.104.0)
57
+ * core 的拒落在 prepare 的**会话分叉之后**(`prepare-acquire-reconcile` 先按 `resumeAt` 把活动分支移过去,`prepare-file-history` 才判),
58
+ * 且拒后不回滚 —— 所以本层在任何副作用之前拒。**这不是替 core 再判一次「捕获关了能不能还原」**:判的是体里两个键
59
59
  * 能不能共存,不看部署、不看店。
60
+ * ⚠️ S-865 / S-877(core 7.35.0 #1195):core 起把只看请求体就判得出的拒因(本码在内)挪到**会话 acquire 之前**的请求门
61
+ * (`runner/file-history-request.js`),被拒的请求会话日志逐字节不动 —— 本门当年防的那个副作用在引擎侧已不存在。本门的 400
62
+ * 照旧有效(core wire 单:「the at-accept 400 stays valid」),退不退(退则 fresh 腿改落 core 的 422 `rewind.capture_disabled`)
63
+ * 列 7.106.0 收车档候裁。
60
64
  * 判据:{@link fileHistoryRestoreConflict}。**只在 fresh 受理门**(`http/admission.ts`);续跑腿不过这道门(RESUME 重放不过受理门,
61
65
  * 同律 —— 且续跑腿重建时整族剥掉一次性历史动作,矛盾在盘上的体里本就留不下来)。wire 可见、字节冻结。
62
66
  */
@@ -96,5 +100,13 @@ export interface SingleMemberOffFieldIssue {
96
100
  * 全部合法或全部缺席 ⇒ `null`。**两腿(fresh admission / resume 重放)共用这一只**。
97
101
  */
98
102
  export declare function singleMemberOffFieldIssue(raw: unknown): SingleMemberOffFieldIssue | null;
103
+ /** `inheritEnv` 的受理词表(闭集;**顺序 = 拒句里的列举顺序**)。 */
104
+ export declare const INHERIT_ENV_WORDS: readonly ["all", "scrub"];
105
+ /** 一条请求能声明的继承策略(wire 形,见 `TaskRequestBody.inheritEnv`)。 */
106
+ export type TaskInheritEnv = (typeof INHERIT_ENV_WORDS)[number];
107
+ /** 词表外的拒句(wire 可见;点名两词与各自的意思 —— 拿错的人要在同一条回执里读到该发什么)。 */
108
+ export declare const INHERIT_ENV_VALUE_MESSAGE: string;
109
+ /** `inheritEnv` 在场且不在词表里 ⇒ 拒句;缺席或合法 ⇒ `null`。**两腿(fresh admission / resume 重放)共用这一只**。 */
110
+ export declare function inheritEnvFieldIssue(raw: unknown): string | null;
99
111
  export {};
100
112
  //# sourceMappingURL=request-value-closure.d.ts.map
@@ -23,4 +23,16 @@ export function singleMemberOffFieldIssue(raw) {
23
23
  }
24
24
  return null;
25
25
  }
26
+ export const INHERIT_ENV_WORDS = ["all", "scrub"];
27
+ export const INHERIT_ENV_VALUE_MESSAGE = `inheritEnv must be one of ${INHERIT_ENV_WORDS.map((w) => JSON.stringify(w)).join(" | ")} when present ` +
28
+ `("all" = the model-driven host shell inherits this machine's whole environment, honored only on a single-user host-lane deployment; ` +
29
+ `"scrub" = the default scrub of credential-shaped variables, accepted on every lane; absent = "scrub") — a name list is not a request value`;
30
+ export function inheritEnvFieldIssue(raw) {
31
+ if (raw === null || raw === undefined || typeof raw !== "object" || Array.isArray(raw))
32
+ return null;
33
+ const value = raw.inheritEnv;
34
+ if (value === undefined)
35
+ return null;
36
+ return INHERIT_ENV_WORDS.includes(value) ? null : INHERIT_ENV_VALUE_MESSAGE;
37
+ }
26
38
  //# sourceMappingURL=request-value-closure.js.map
@@ -104,6 +104,17 @@ export declare function isIsolatedExecEnv(provider?: string, runtimeClass?: stri
104
104
  * - `toolPolicy` — ALSO ask on the direct write hand tools `Edit`/`Write` (review HIGH: shellGate alone
105
105
  * leaves a non-isolated SUP task able to mutate the real fs via write tools ungated). The worker's existing
106
106
  * approval machinery (durable suspend / poll) enforces the `ask` — same precondition as `shellGate:"always"`.
107
+ *
108
+ * 🔴 S-897(7.106.0,[ref]):这些 ask 是**部署**(运维开的 `ROUTER_ENABLED` 路由)写下的门 ⇒ 盖 core 的部署出身位
109
+ * `authoredBy:"deployment"`,经**唯一**盖位函数(`operator-ask.ts` 的 `withDeploymentAuthorship`,经 `operatorAlwaysHumanAsk`
110
+ * 取用 —— 治理层 ask、`APPROVAL_NEVER_AUTO` / 预算耗尽 ask 同一只)。修前:写工具的 ask 只自报 `matchedAskRule`(非强制卡),
111
+ * shellGate always 的 shell ask 是 core 自铸的、没有出身位 ⇒ 卡上只剩单个强制词(`operator_always`;读边界探针命中时是
112
+ * `probe_mandate`,它把 always 藏在后面)⇒ bypass 下壳按那一个词放行(cli L-758 的规则面)。盖了之后 core 判 `mandated`、
113
+ * 不铸 `mandate` 词(表外的门)⇒ 活卡与 park 行两腿都读成「另有门在」,壳出卡。
114
+ * shell 那一问的出身只能由**策略的** ask 带进去(core 的 shell 座铸的 ask 没有出身位的写口)⇒ 策略对 shell 工具也问一次,
115
+ * core 把它与 shellGate always 折成同一只 ask(不是两问)。
116
+ * ⚠️ 连带(产品面,如实):写工具的 ask 从此也是强制卡 ⇒ 用户的「本会话放行」不再退役 SUP 路由的写门(shell 那一问修前就退不掉)。
117
+ * 不改路由判据本身(`routeServiceTask` 一字不动)。
107
118
  */
108
119
  export declare function supPostureOverrides(): Partial<TaskSpec>;
109
120
  //# sourceMappingURL=route-orchestration.d.ts.map
@@ -1,5 +1,5 @@
1
- import {} from "@sema-agent/core";
2
- import { operatorAsk } from "../operator-ask.js";
1
+ import { COARSE_SHELL_TOOLS } from "@sema-agent/core";
2
+ import { operatorAlwaysHumanAsk } from "../operator-ask.js";
3
3
  export function routeOrchestration(s) {
4
4
  if (s.explicitMode)
5
5
  return { mode: s.explicitMode, reason: `explicit:${s.explicitMode}` };
@@ -40,11 +40,12 @@ export function isIsolatedExecEnv(provider, runtimeClass) {
40
40
  return false;
41
41
  }
42
42
  const WRITE_HAND_TOOLS = new Set(["Edit", "Write", "NotebookEdit"]);
43
+ const SHELL_TOOLS = new Set(COARSE_SHELL_TOOLS);
43
44
  export function supPostureOverrides() {
44
- const ask = (toolName) => operatorAsk(`supervisor routing(capability-danger:${toolName})`, `${toolName} gated by supervisor routing (capability-danger task)`);
45
- const writeGate = {
46
- check: (req) => (WRITE_HAND_TOOLS.has(req.toolName) ? ask(req.toolName) : { action: "allow" }),
45
+ const ask = (toolName) => operatorAlwaysHumanAsk(`supervisor routing(capability-danger:${toolName})`, `${toolName} gated by supervisor routing (capability-danger task)`);
46
+ const gate = {
47
+ check: (req) => (WRITE_HAND_TOOLS.has(req.toolName) || SHELL_TOOLS.has(req.toolName) ? ask(req.toolName) : { action: "allow" }),
47
48
  };
48
- return { shellGate: "always", toolPolicy: writeGate };
49
+ return { shellGate: "always", toolPolicy: gate };
49
50
  }
50
51
  //# sourceMappingURL=route-orchestration.js.map
package/dist/run-local.js CHANGED
@@ -9,6 +9,7 @@ import { createInterface } from "node:readline/promises";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { Runner, uuidv7, FileStorageBackend, NodeExecutionEnv, TtlSessionStore, CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore, defaultTaskRegistry } from "@sema-agent/core";
11
11
  import { singleUserAutoAcceptBaselineApplies } from "./approval.js";
12
+ import { createUnattendedLeg, unattendedPermissionMode, withUnattendedLeg } from "./unattended-permission-mode.js";
12
13
  import { createBrain } from "./brain.js";
13
14
  import { assertGuardPatternsUsable, buildOnlySensitiveBaselineWarning, createApprovalBaselinePolicy, createDeploymentGovernanceInputs, } from "./deployment-governance.js";
14
15
  import { applyRuntimeGovernance } from "./runtime-governance.js";
@@ -567,7 +568,8 @@ export async function runLocal(argv, deps = {}) {
567
568
  ...(maxCostUsd !== undefined ? { maxCostUsd } : {}),
568
569
  ...(maxTokens !== undefined ? { maxTokens } : {}),
569
570
  };
570
- const spec = applyLocalGovernance({
571
+ const unattendedLeg = createUnattendedLeg(unattendedPermissionMode(config), runnerDeps);
572
+ const spec = withUnattendedLeg(applyLocalGovernance({
571
573
  objective: mention.cleanedObjective,
572
574
  sessionId,
573
575
  backgroundScope: "session",
@@ -583,7 +585,7 @@ export async function runLocal(argv, deps = {}) {
583
585
  ...(mcp ? { mcp } : {}),
584
586
  ...(a2a ? { a2a } : {}),
585
587
  ...(Object.keys(limits).length > 0 ? { limits } : {}),
586
- }, config, workspaceDir, memoryEngine?.root);
588
+ }, config, workspaceDir, memoryEngine?.root), unattendedLeg);
587
589
  logger.info("run_local_start", { scenario: scenarioName, model: spec.model, sessionId, exec: executionLaneOf(config.remoteExec?.provider) });
588
590
  const settleSessionBackground = async () => {
589
591
  const reaped = defaultTaskRegistry.reapSessionBackground(sessionId, scope);
package/dist/runs.d.ts CHANGED
@@ -150,7 +150,8 @@ export declare function isSessionConflictResult(result: {
150
150
  * 逐码显式登记为债务,并由那道门看住不许陈腐。
151
151
  *
152
152
  * S-724(core 7.31.0 #1048 / #1050):`rewind.capture_disabled` → **422**(关断词与还原同体 = 自相矛盾;本服务的请求面上
153
- * 走不到它 —— fresh 受理门先 400 `request.field_conflict`(core 这一拒落在会话分叉之后、不回滚);映射是分诊面完备性)、`rewind.store_unconfigured`
153
+ * 走不到它 —— fresh 受理门先 400 `request.field_conflict`(立门理由:core 这一拒落在会话分叉之后、不回滚;S-865 / core 7.35.0
154
+ * #1195 起 core 在分叉之前拒);映射是分诊面完备性)、`rewind.store_unconfigured`
154
155
  * → **409**(部署没有历史店;`FILE_HISTORY_CAPTURE=false` 自本版不再摘店 ⇒ 可达面只剩没有后端的部署形,那里
155
156
  * `capabilities.rewindFiles` 恒报 false —— 三问在 CHANGELOG 7.104.0)。
156
157
  */
package/dist/runs.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { runWithVerification, runCascade, defaultTaskRegistry, taskNotificationDedupKey, DURABLE_AGENT_HANDLE_RE } from "@sema-agent/core";
2
2
  import { failureCodeOf, sealResultForExit, outwardPlane } from "./terminal.js";
3
3
  import { GOVERNANCE_HTTP_STATUS } from "./governance-http-status.js";
4
- import { ROSTER_BOUND_HTTP_STATUS } from "./roster-bound-http-status.js";
4
+ import { TOOL_DECLARATION_HTTP_STATUS } from "./tool-declaration-http-status.js";
5
5
  import { isSessionErrorCode, sessionErrorHttp } from "./session-error-wire.js";
6
6
  import { redactErrorMessage } from "./observability/run-terminal.js";
7
7
  import { withPrincipal } from "./observability/principal-context.js";
@@ -76,8 +76,8 @@ export function resumeAtHttpStatus(result) {
76
76
  if (Object.hasOwn(GOVERNANCE_HTTP_STATUS, code)) {
77
77
  return GOVERNANCE_HTTP_STATUS[code] ?? undefined;
78
78
  }
79
- if (Object.hasOwn(ROSTER_BOUND_HTTP_STATUS, code)) {
80
- return ROSTER_BOUND_HTTP_STATUS[code];
79
+ if (Object.hasOwn(TOOL_DECLARATION_HTTP_STATUS, code)) {
80
+ return TOOL_DECLARATION_HTTP_STATUS[code] ?? undefined;
81
81
  }
82
82
  if (isSessionErrorCode(code)) {
83
83
  const wire = sessionErrorHttp(code, "open");
@@ -208,7 +208,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
208
208
  if (approval) {
209
209
  const coordinator = approval.coordinator;
210
210
  spec.onAsk = approvalLeg.windowZero
211
- ? (askReq) => Promise.resolve(coordinator.unattendedAskOutcome({ taskId, owner: elicitOwner ?? owner, toolName: askReq.toolName }))
211
+ ? coordinator.boundUnattendedAsk({ owner: elicitOwner ?? owner, taskId, ...(spec.sessionId ? { sessionId: spec.sessionId } : {}) })
212
212
  : coordinator.boundAsk({
213
213
  owner: elicitOwner ?? owner,
214
214
  taskId,
@@ -113,18 +113,12 @@ export interface EntitlementsResolverOpts {
113
113
  *
114
114
  * `autoMode`(core 1.276.0 `RuntimeCaps.autoMode`;**[ref] / core [ref] 起三态**,黑板 [ref]/[ref]):
115
115
  * `undefined`(center 缺席/未答)= 不阻;`true` = 不阻;`false` = **org deny**(CC `permissions.disableAutoMode`
116
- * 镜像)。core 武装式 = `spec.autoModeRequested === true ∧ deps.autoMode !== undefined ∧ runtimeCaps?.autoMode !== false`
116
+ * 镜像)。core 武装式 = `spec.permissionMode === "auto" ∧ deps.autoMode !== undefined ∧ runtimeCaps?.autoMode !== false`(core 7.34.0 起;此前意图座是退役的 `autoModeRequested`)
117
117
  * ——「用户开 + org 拒」极性(此前是 `=== true` 的「org 授予」极性,无 center 的本地部署零授予路径 ⇒ auto 永不
118
118
  * 武装,真实用户诊断的病根)。
119
- * 🔴 **上面那条武装式是 [ref] 的目标形,不是「本仓现在装的那版 core 的形」——引用它之前先看装的是哪版**
120
- * ([ref]① 随 codex r2-[high] 补:我方在 WIRE 附录 F 照抄这一句,写成了「7.2.0 的行为」,而 npm **7.2.0
121
- * 不含 [ref]** —— 它的 `dist/core/runner/prepare-task.js` 逐字仍是 `runtimeCaps?.autoMode === true &&
122
- * deps.autoMode !== undefined`,即旧「org 授予」极性、**缺键 ⇒ 不武装**)。判「现在是哪套极性」的唯一判据 =
123
- * 安装包探针 `CORE_ARMS_AUTO_ON_INTENT`(`src/auto-mode-face.ts`;实测本树 7.2.0 ⇒ **false** = 旧式臂),
124
- * 代码侧已按它分臂,失实的只是散落各处的散文。⚠️ 同形措辞在本文件 :209 / :362-364 / :548 与
125
- * `src/boot/runtime-caps.ts:182` 还各有一处(均 [ref] 既有文字,本批未逐条改 —— 登记在 handoff 未闭环项)。
126
- * 所以**缺键=不写键**在这条轴上是行为承重的(在新式 core 上):补一个 false 就是替 org 说了一句
127
- * 它没说的 deny;在旧式 core 上两者同为「不武装」,差别不显形。[ref]B-6a:registry-core 0.13.0 已把 autoMode 收进 EntitlementRuntimeCaps——类型直读。 */
119
+ * ([ref]① 的历史:npm 7.2.0 不含 [ref]、是旧「org 授予」极性,本仓曾用安装包探针分臂;S-877 / core 7.34.0 起引擎只剩
120
+ * 上面这一种极性,探针随 7.106.0 退役 —— 本仓精确钉的 core 恒是它。)
121
+ * 所以**缺键=不写键**在这条轴上是行为承重的:补一个 false 就是替 org 说了一句它没说的 deny。[ref]B-6a:registry-core 0.13.0 已把 autoMode 收进 EntitlementRuntimeCaps——类型直读。 */
128
122
  export declare function toCoreRuntimeCaps(ent: EntitlementRuntimeCaps | null | undefined): RuntimeCaps | undefined;
129
123
  /**
130
124
  * #A5([ref] 件2)—— 「这台 worker 上**存在** per-principal entitlement 源吗」的**单一谓词**。