@sema-agent/server 7.104.0 → 7.106.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/MIGRATION.md +114 -0
  2. package/USAGE.md +193 -52
  3. package/deploy/sema-up/chart/templates/server-v4pro.yaml +2 -0
  4. package/deploy/sema-up/cluster-up.sh +15 -2
  5. package/deploy/sema-up/kube-up.sh +8 -3
  6. package/deploy/sema-up/lib/flagshape.sh +29 -0
  7. package/deploy/sema-up/lib/parse-machines.py +4 -1
  8. package/deploy/sema-up/sema-up.sh +11 -9
  9. package/deploy/sema-up/stack/sema-stack-cluster.yaml +3 -1
  10. package/dist/approval-card.d.ts +1 -1
  11. package/dist/approval.d.ts +6 -1
  12. package/dist/approval.js +4 -4
  13. package/dist/auth-bridge.d.ts +14 -0
  14. package/dist/auth-bridge.js +7 -0
  15. package/dist/auto-mode-face.d.ts +49 -40
  16. package/dist/auto-mode-face.js +6 -68
  17. package/dist/bench/s1/live-deps.d.ts +9 -2
  18. package/dist/bench/s1/live-deps.js +13 -8
  19. package/dist/boot/budget-tracing.js +3 -2
  20. package/dist/boot/config-center.d.ts +4 -0
  21. package/dist/boot/config-center.js +95 -37
  22. package/dist/boot/coordinators.d.ts +1 -0
  23. package/dist/boot/coordinators.js +3 -1
  24. package/dist/boot/device-lane.d.ts +2 -1
  25. package/dist/boot/execution-env.d.ts +11 -0
  26. package/dist/boot/execution-env.js +30 -6
  27. package/dist/boot/execution-lane.js +2 -3
  28. package/dist/boot/leader.d.ts +2 -1
  29. package/dist/boot/leader.js +2 -0
  30. package/dist/boot/parked-revive-gate.d.ts +10 -7
  31. package/dist/boot/parked-revive-gate.js +4 -11
  32. package/dist/boot/resolve-spec.d.ts +1 -4
  33. package/dist/boot/resolve-spec.js +44 -21
  34. package/dist/boot/runner-deps.d.ts +5 -8
  35. package/dist/boot/runner-deps.js +5 -10
  36. package/dist/boot/side-query-lane.d.ts +2 -2
  37. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  38. package/dist/boot/stage-03-store-backend.js +2 -2
  39. package/dist/boot/stage-04-stores.d.ts +1 -1
  40. package/dist/boot/stage-05-execution-env.d.ts +2 -1
  41. package/dist/boot/stage-05-execution-env.js +2 -2
  42. package/dist/boot/stage-06-runners.d.ts +3 -2
  43. package/dist/boot/stage-06-runners.js +2 -5
  44. package/dist/boot/stage-07-capability-layer.d.ts +2 -2
  45. package/dist/boot/stage-07-capability-layer.js +1 -2
  46. package/dist/boot/stage-08-reapers.d.ts +2 -2
  47. package/dist/boot/stage-09-leader.d.ts +2 -2
  48. package/dist/boot/stage-09-leader.js +2 -2
  49. package/dist/boot/stage-10-http-server.d.ts +2 -2
  50. package/dist/boot/stage-10-http-server.js +11 -7
  51. package/dist/boot/stores.d.ts +1 -1
  52. package/dist/boot/stores.js +6 -4
  53. package/dist/brain.d.ts +24 -2
  54. package/dist/brain.js +29 -10
  55. package/dist/budget.js +3 -0
  56. package/dist/capabilities/center-wiring.d.ts +64 -0
  57. package/dist/capabilities/center-wiring.js +24 -0
  58. package/dist/capabilities/composite-members.d.ts +89 -0
  59. package/dist/capabilities/composite-members.js +20 -0
  60. package/dist/capabilities/execution-lane.d.ts +3 -0
  61. package/dist/capabilities/execution-lane.js +5 -2
  62. package/dist/config-catalog.d.ts +30 -5
  63. package/dist/config-catalog.js +75 -26
  64. package/dist/config-center/apply-effective.d.ts +1 -1
  65. package/dist/config-center/apply-effective.js +7 -8
  66. package/dist/config-center/apply-ledger.d.ts +17 -0
  67. package/dist/config-center/apply-ledger.js +5 -0
  68. package/dist/config-center/center-request.d.ts +17 -0
  69. package/dist/config-center/center-request.js +4 -0
  70. package/dist/config-center/pull-state.d.ts +29 -0
  71. package/dist/config-center/pull-state.js +2 -0
  72. package/dist/config-types.d.ts +41 -14
  73. package/dist/config-types.js +1 -0
  74. package/dist/config.d.ts +50 -2
  75. package/dist/config.js +111 -23
  76. package/dist/cross-session-settings.d.ts +1 -1
  77. package/dist/deployment-governance.d.ts +12 -1
  78. package/dist/deployment-governance.js +2 -0
  79. package/dist/execution-lane-caps.d.ts +30 -2
  80. package/dist/execution-lane-caps.js +12 -0
  81. package/dist/file-history-disclosure.d.ts +7 -31
  82. package/dist/file-history-disclosure.js +12 -0
  83. package/dist/fleet/fleet-bus.d.ts +5 -1
  84. package/dist/fleet-client.d.ts +18 -13
  85. package/dist/fleet-client.js +32 -22
  86. package/dist/fleet-lease.d.ts +72 -47
  87. package/dist/fleet-lease.js +66 -14
  88. package/dist/governance-http-status.d.ts +0 -1
  89. package/dist/governance-http-status.js +0 -1
  90. package/dist/hosted-posture.d.ts +6 -17
  91. package/dist/hosted-posture.js +11 -6
  92. package/dist/http/admission.d.ts +1 -1
  93. package/dist/http/admission.js +14 -9
  94. package/dist/http/approval-sweeps.js +13 -1
  95. package/dist/http/dispatch.d.ts +18 -0
  96. package/dist/http/dispatch.js +18 -5
  97. package/dist/http/durable-run-leg.js +2 -0
  98. package/dist/http/request-guards.d.ts +25 -0
  99. package/dist/http/request-guards.js +25 -5
  100. package/dist/http/resume-legs.d.ts +1 -1
  101. package/dist/http/resume-legs.js +19 -11
  102. package/dist/http/route-ctx.d.ts +9 -0
  103. package/dist/http/route-table.d.ts +15 -4
  104. package/dist/http/route-table.js +6 -5
  105. package/dist/http/routes/approvals-assistant.js +0 -1
  106. package/dist/http/routes/capabilities.js +12 -11
  107. package/dist/http/routes/config-catalog.js +2 -1
  108. package/dist/http/routes/diagnostics.js +2 -0
  109. package/dist/http/routes/fleet.d.ts +3 -2
  110. package/dist/http/routes/images.js +0 -1
  111. package/dist/http/routes/runs.js +2 -8
  112. package/dist/http/routes/session-sync.js +6 -1
  113. package/dist/http/routes/sessions.js +5 -2
  114. package/dist/http/routes/side-query.js +8 -1
  115. package/dist/http/routes/tasks.js +4 -1
  116. package/dist/http/routes/trace-usage.d.ts +2 -0
  117. package/dist/http/routes/trace-usage.js +4 -9
  118. package/dist/http/server.d.ts +29 -8
  119. package/dist/http/server.js +4 -1
  120. package/dist/http/session-inherit-env.d.ts +37 -0
  121. package/dist/http/session-inherit-env.js +6 -0
  122. package/dist/http/sse-lifecycle.d.ts +1 -1
  123. package/dist/http/sse-log.d.ts +12 -9
  124. package/dist/http/sse-log.js +1 -8
  125. package/dist/http/wire-types.d.ts +19 -28
  126. package/dist/index.d.ts +2 -1
  127. package/dist/index.js +2 -1
  128. package/dist/key-resolver.d.ts +74 -19
  129. package/dist/key-resolver.js +65 -7
  130. package/dist/leader/leader.d.ts +6 -0
  131. package/dist/leader/leader.js +2 -1
  132. package/dist/leader/repair-wire.js +2 -1
  133. package/dist/leader/wire.d.ts +9 -1
  134. package/dist/leader/wire.js +10 -6
  135. package/dist/observability/fail-open.d.ts +9 -1
  136. package/dist/observability/fail-open.js +9 -1
  137. package/dist/observability/metrics.js +3 -1
  138. package/dist/observability/permission-rule-events.d.ts +1 -1
  139. package/dist/observability/permission-rule-events.js +1 -1
  140. package/dist/observability/run-terminal.d.ts +82 -9
  141. package/dist/observability/run-terminal.js +112 -9
  142. package/dist/observability/secret-env-scrub.js +1 -1
  143. package/dist/observability/tool-trace.d.ts +2 -0
  144. package/dist/operator-ask.d.ts +29 -1
  145. package/dist/operator-ask.js +6 -0
  146. package/dist/parked-decide.d.ts +14 -1
  147. package/dist/parked-decide.js +8 -1
  148. package/dist/plugins/image-bake-store-sql.d.ts +0 -2
  149. package/dist/plugins/image-bake-store-sql.js +0 -5
  150. package/dist/plugins/retention-store-sql.js +1 -0
  151. package/dist/plugins/run-store-sql.d.ts +3 -2
  152. package/dist/plugins/schema-indexes.js +1 -0
  153. package/dist/plugins/session-policy-store-sql.d.ts +38 -7
  154. package/dist/plugins/session-policy-store-sql.js +73 -18
  155. package/dist/plugins/sql-driver.d.ts +1 -1
  156. package/dist/plugins/store-backend.d.ts +17 -6
  157. package/dist/plugins/store-backend.js +16 -5
  158. package/dist/plugins/tidb-pool.js +5 -4
  159. package/dist/read-face-posture.d.ts +25 -4
  160. package/dist/read-face-posture.js +9 -0
  161. package/dist/request-key-closure.js +2 -2
  162. package/dist/request-value-closure.d.ts +17 -5
  163. package/dist/request-value-closure.js +12 -0
  164. package/dist/router/route-orchestration.d.ts +11 -0
  165. package/dist/router/route-orchestration.js +7 -6
  166. package/dist/rules-consent.js +0 -11
  167. package/dist/run-local.js +4 -2
  168. package/dist/runs.d.ts +2 -1
  169. package/dist/runs.js +4 -4
  170. package/dist/runtime-caps-resolver.d.ts +4 -10
  171. package/dist/runtime-governance.d.ts +8 -11
  172. package/dist/runtime-governance.js +10 -41
  173. package/dist/sealed-key.d.ts +0 -40
  174. package/dist/sealed-key.js +0 -20
  175. package/dist/server-secret-env.d.ts +11 -12
  176. package/dist/session-policy-wire.d.ts +100 -12
  177. package/dist/session-policy-wire.js +71 -8
  178. package/dist/session-titler.d.ts +11 -0
  179. package/dist/session-titler.js +6 -2
  180. package/dist/sql-engine-posture.d.ts +2 -2
  181. package/dist/store-posture.d.ts +53 -0
  182. package/dist/store-posture.js +32 -0
  183. package/dist/task-settings.d.ts +34 -45
  184. package/dist/task-settings.js +46 -25
  185. package/dist/tool-approval.d.ts +51 -22
  186. package/dist/tool-approval.js +44 -8
  187. package/dist/tool-declaration-http-status.d.ts +12 -0
  188. package/dist/tool-declaration-http-status.js +12 -0
  189. package/dist/trace/core-keyset-guard.d.ts +7 -7
  190. package/dist/trace/engine-notice-wire.d.ts +1 -1
  191. package/dist/trace/engine-notice-wire.js +5 -0
  192. package/dist/trace/project.d.ts +13 -7
  193. package/dist/trace/project.js +54 -6
  194. package/dist/trace/redact.d.ts +7 -0
  195. package/dist/trace/redact.js +153 -61
  196. package/dist/trace/wire-projection-faces.d.ts +1 -1
  197. package/dist/trace/wire-projection-faces.js +1 -1
  198. package/dist/unattended-permission-mode.d.ts +110 -0
  199. package/dist/unattended-permission-mode.js +65 -0
  200. package/package.json +3 -3
  201. package/dist/boot/session-shell-gate-registry.d.ts +0 -39
  202. package/dist/boot/session-shell-gate-registry.js +0 -21
  203. package/dist/plugins/memory-session-policy-store.d.ts +0 -46
  204. package/dist/plugins/memory-session-policy-store.js +0 -52
  205. package/dist/roster-bound-http-status.d.ts +0 -6
  206. package/dist/roster-bound-http-status.js +0 -6
@@ -22,25 +22,42 @@ function readRulesBody(text, where) {
22
22
  throw new SessionPolicyError("corrupt", `${e.message} — an unreadable rules body served as an EMPTY rule set would silently drop every tightening this session carries`);
23
23
  }
24
24
  }
25
+ function isStoredCounter(n, floor) {
26
+ return Number.isSafeInteger(n) && n >= floor;
27
+ }
25
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) {
26
35
  const rev = Number(r.rev);
27
- if (!Number.isFinite(rev) || !Number.isInteger(rev) || rev < 1) {
28
- throw new SessionPolicyError("corrupt", `session rules row ${where} carries rev ${String(r.rev)} — a stored row's rev is always ≥ 1 (the stamp is floor+1), and rev 0 is the live reader's synthetic empty seed; refusing rather than serve it as rules`);
36
+ if (!isStoredCounter(rev, 1)) {
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` };
29
38
  }
30
39
  if (r.gen === undefined || r.gen === null) {
31
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). ` +
32
41
  "Refusing rather than judge this row's lineage as generation 0, which is a value nothing minted.");
33
42
  }
34
43
  const gen = Number(r.gen);
35
- if (!Number.isFinite(gen) || !Number.isInteger(gen) || gen < 0) {
36
- throw new SessionPolicyError("corrupt", `session rules row ${where} carries lineage gen ${String(r.gen)} — a generation is a whole number ≥ 0; refusing rather than let a reader judge lineage by a value nothing minted`);
44
+ if (!isStoredCounter(gen, 0)) {
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` };
37
46
  }
38
- 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)`);
39
54
  }
40
55
  export class SqlSessionPolicyStore {
41
56
  db;
42
- constructor(db) {
57
+ logger;
58
+ constructor(db, logger) {
43
59
  this.db = db;
60
+ this.logger = logger;
44
61
  }
45
62
  q(tidb, pg) {
46
63
  return this.db.dialect === "tidb" ? tidb : pg;
@@ -49,13 +66,23 @@ export class SqlSessionPolicyStore {
49
66
  return isDupKeyError(this.db.dialect, e);
50
67
  }
51
68
  async getRules(sessionId, principal) {
52
- 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)]);
53
- 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)
54
77
  return null;
55
- return rowToStored(rows[0], `for session ${JSON.stringify(sessionId)}`);
78
+ return rowToStored(row, `for session ${JSON.stringify(sessionId)}`);
56
79
  }
57
80
  async listBySession(sessionId) {
58
- 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");
59
86
  return rows.map((r) => ({
60
87
  principal: r.principal == null ? undefined : String(r.principal),
61
88
  rules: rowToStored(r, `for session ${JSON.stringify(sessionId)} (enumeration)`),
@@ -73,7 +100,7 @@ export class SqlSessionPolicyStore {
73
100
  const { rows: markRows } = await conn.query(this.q("SELECT rev, gen FROM session_policy_rev_mark WHERE policy_key = ? FOR UPDATE", "SELECT rev, gen FROM session_policy_rev_mark WHERE policy_key = $1 FOR UPDATE"), [key]);
74
101
  const markRev = markRows[0] ? Number(markRows[0].rev) : 0;
75
102
  const markGen = markRows[0] ? Number(markRows[0].gen) : 0;
76
- if (markRows[0] !== undefined && (!Number.isInteger(markGen) || markGen < 0 || !Number.isFinite(markRev) || markRev < 0)) {
103
+ if (markRows[0] !== undefined && (!isStoredCounter(markGen, 0) || !isStoredCounter(markRev, 0))) {
77
104
  throw new SessionPolicyError("corrupt", `the session rules rev mark for session ${JSON.stringify(sessionId)} is unreadable (rev ${String(markRows[0].rev)}, gen ${String(markRows[0].gen)}) — refusing rather than stamp a rev/lineage nothing proves`);
78
105
  }
79
106
  const clean = normalizeRules(stripRev(rules));
@@ -88,6 +115,9 @@ export class SqlSessionPolicyStore {
88
115
  }
89
116
  const nextRev = Math.max(priorRev, markRev) + 1;
90
117
  const nextGen = prior?.gen ?? markGen;
118
+ if (!isStoredCounter(nextRev, 1) || !isStoredCounter(nextGen, 0)) {
119
+ throw new SessionPolicyError("corrupt", `session rules for session ${JSON.stringify(sessionId)}: the next stamp (rev ${nextRev}, gen ${nextGen}) would leave the safe-integer range this store reads back — refusing before writing a row nothing could read`);
120
+ }
91
121
  const json = JSON.stringify(clean);
92
122
  const now = new Date();
93
123
  if (prior) {
@@ -114,15 +144,20 @@ export class SqlSessionPolicyStore {
114
144
  }
115
145
  }
116
146
  async deleteBySession(sessionId) {
117
- await this.retrying(() => this.tx(async (conn) => {
147
+ const sealed = await this.retrying(() => this.tx(async (conn) => {
118
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]);
119
149
  if (rows.length === 0)
120
- return;
150
+ return NO_SEALED_KEYS;
121
151
  const now = new Date();
122
152
  const keys = rows.map((r) => String(r.policy_key));
123
153
  const params = [];
154
+ const sealedKeys = [];
124
155
  const tuples = rows.map((r) => {
125
- 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);
126
161
  const base = params.length - 5;
127
162
  return this.db.dialect === "tidb" ? "(?,?,?,?,?)" : `($${base + 1},$${base + 2},$${base + 3},$${base + 4},$${base + 5})`;
128
163
  });
@@ -131,7 +166,27 @@ export class SqlSessionPolicyStore {
131
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);
132
167
  const holes = keys.map((_, i) => (this.db.dialect === "tidb" ? "?" : `$${i + 1}`)).join(",");
133
168
  await conn.query(`DELETE FROM session_policy WHERE policy_key IN (${holes})`, keys);
169
+ return sealedKeys;
134
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
+ }
135
190
  }
136
191
  async tx(fn) {
137
192
  const conn = await this.db.connect();
@@ -155,13 +210,13 @@ export class SqlSessionPolicyStore {
155
210
  }
156
211
  }
157
212
  export class TiDBSessionPolicyStore extends SqlSessionPolicyStore {
158
- constructor(pool) {
159
- super(mysqlDriver(pool));
213
+ constructor(pool, logger) {
214
+ super(mysqlDriver(pool), logger);
160
215
  }
161
216
  }
162
217
  export class PgSessionPolicyStore extends SqlSessionPolicyStore {
163
- constructor(pool) {
164
- super(pgDriver(pool));
218
+ constructor(pool, logger) {
219
+ super(pgDriver(pool), logger);
165
220
  }
166
221
  }
167
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;
@@ -44,9 +46,11 @@ export type { ApprovalNonceStore } from "./approval-nonce-store.js";
44
46
  /** [ref]([ref] v3.1 §3.0-§3.2 流内审批协议持久层):`ApprovalAskStore` 接口 + 行/补丁类型。
45
47
  * 车3 刀 3a 起 `StoreBackend.approvalAsk()` 已在(见接口体的那条注)。 */
46
48
  export type { ApprovalAskStore, AskRow, NewAskRow, AskTransitionPatch, DecideAskInput, DecideResult, ExpireResult, BindGateInput, BatchRow, AskDecision } from "./approval-ask-store-sql.js";
47
- /** E6 SessionPolicyStore — core's interface PLUS the service `deleteBySession` (E21 purge; only the durable twins carry
48
- * it, core's InMemory omits it → optional). tidb/pg = durable twins; local = core's InMemorySessionPolicyStore (works
49
- * local — rules need no cloud-only infra). The shared env-gated equivalence suite keeps the twins byte-matched to InMemory. */
49
+ /** E6 SessionPolicyStore — core's interface, incl. its optional `deleteBySession` / `listBySession` seams (E21 purge / 2c
50
+ * export; core's File and InMemory stores carry both since core 7.25.0 — S-546 A7 retired the service's own in-memory
51
+ * twin that existed only because they once did not). tidb/pg = the SQL twin; local = core's FileSessionPolicyStore
52
+ * (works local — rules need no cloud-only infra). The rev-line contract kit runs the SQL twin against core's InMemory
53
+ * as oracle (`test/helpers/session-policy-rev-line-contract.ts`). */
50
54
  export type ServiceSessionPolicyStore = SessionPolicyStore;
51
55
  /** [ref] per-edited-file rewind FileHistoryStore — core's interface PLUS the service extras only the SQL twins
52
56
  * carry (optional so the local lane's core `FileFileHistoryStore` still fits). Replaces the retired E19
@@ -192,8 +196,9 @@ export interface StoreBackend {
192
196
  * mapping from URLs). REQUIRED on all backends (works local — one JSONL, like approvalExemption). */
193
197
  sendFileLedger(): SendFileLedger;
194
198
  /** E6 durable SessionPolicyStore (operator-tightened per-session tool rules). REQUIRED on all backends — works local
195
- * (local = core's InMemorySessionPolicyStore), like resumeAnchor. Backs core's RunnerDeps.sessionPolicyStore. */
196
- 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;
197
202
  /** [ref] per-edited-file rewind FileHistoryStore (trackEdit/annulTrack/snapshot/restore over the tracked set;
198
203
  * also the 2c history-transfer store). REQUIRED on all backends (local = core's file-backed
199
204
  * `FileFileHistoryStore` under the data root). Backs core's RunnerDeps.fileHistoryStore. */
@@ -392,6 +397,12 @@ export declare function openStoreBackendWithFallback(config: ServiceConfig, logg
392
397
  error?: (msg: string, meta?: Record<string, unknown>) => void;
393
398
  }): Promise<{
394
399
  backend: StoreBackend | undefined;
395
- degraded: boolean;
400
+ degradedReason: StoreDegradedReason | undefined;
396
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;
397
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
@@ -387,17 +387,18 @@ export const SCHEMA_STATEMENTS = [
387
387
  ) COLLATE utf8mb4_bin`,
388
388
  `CREATE TABLE IF NOT EXISTS session_policy_rev_mark (
389
389
  policy_key CHAR(64) NOT NULL,
390
- -- 被删会话。记号的读写一律按 policy_key 单键;这一列是**运维可读性 + 未来按会话/按龄回收**的那条腿
391
- -- 的抓手(今天没有任何生产查询按它选行 —— 留存腿与收编腿都不碰本表,理由见上面那段)。
390
+ -- 被删会话(**明文**会话 id —— 所以它必须随会话一起走,见上)。记号的读写按 policy_key 单键;托管留存的整树
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 完全一样:留在行里就等于跟着行一起消失。
397
397
  -- DEFAULT 0 = 本列到货前落下的记号(那些键的行也都是第 0 代 ⇒ 下一次抹除把它们抬到 1,前后自洽)。
398
398
  gen BIGINT NOT NULL DEFAULT 0,
399
399
  erased_at DATETIME(3) NOT NULL,
400
- PRIMARY KEY (policy_key)
400
+ PRIMARY KEY (policy_key),
401
+ KEY idx_session_policy_rev_mark_session (session_id)
401
402
  ) COLLATE utf8mb4_bin`,
402
403
  `CREATE TABLE IF NOT EXISTS snapshot_blob (
403
404
  blob_hash CHAR(64) NOT NULL,
@@ -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";
@@ -65,4 +65,25 @@ export declare function buildReadFaceWiring(facts: ReadFacePostureFacts): {
65
65
  source: ReadFaceSource;
66
66
  note: string;
67
67
  };
68
+ /**
69
+ * S-176 —— posture 派生的档位在 **boot 期推送面**的那一行(`config.ts` 经既有 `CONFIG_NOTICES` 以 info 级排空)。
70
+ *
71
+ * 为什么要有:S-167 的补偿项是「升级即放开,得让运维看得见并知道怎么钉回」,可此前唯一看得见的是**拉取**面
72
+ * (`GET /v1/diagnostics/wiring`)—— 只对会去拉的人成立。单机 boot 日志里零字。
73
+ * 为什么只在 `posture` 一词上说:另外三词都不是「没人表态、服务替它选了一个更宽的档」——`env` 是运维自己写的,
74
+ * `engine-default` 是本服务没铸(引擎默认接管),`center` 在 boot 这一拍还没到货(它有自己的换席通告)。
75
+ * 为什么是 info 不是 warn:每一台照常配置的单机每次启动都成立,是常态事实不是异常(与执行车道落点同级)。
76
+ *
77
+ * 字段与极性表的 posture 行同名(`knob` / `value` / `source`),另带指路句;三格由 {@link buildReadFaceWiring} 投影出 ——
78
+ * 拉取面与推送面**同一只函数**,不各算一次。`value` 是字符串:只有 `face` 在场的那一形才会走到这里。
79
+ */
80
+ export declare function buildReadFacePostureNotice(facts: ReadFacePostureFacts): {
81
+ event: "config_knob_posture_derived";
82
+ fields: {
83
+ knob: "READ_FACE";
84
+ value: string;
85
+ source: ReadFaceSource;
86
+ note: string;
87
+ };
88
+ } | undefined;
68
89
  //# sourceMappingURL=read-face-posture.d.ts.map
@@ -14,4 +14,13 @@ export function readFaceSourceNote(source) {
14
14
  export function buildReadFaceWiring(facts) {
15
15
  return { face: facts.readFace ?? null, source: facts.readFaceSource, note: readFaceSourceNote(facts.readFaceSource) };
16
16
  }
17
+ export function buildReadFacePostureNotice(facts) {
18
+ if (facts.readFaceSource !== "posture")
19
+ return undefined;
20
+ const wiring = buildReadFaceWiring(facts);
21
+ if (wiring.face === null) {
22
+ throw new Error("read-face posture notice: source is `posture` but no face was minted — readFaceForPosture's invariant broke");
23
+ }
24
+ return { event: "config_knob_posture_derived", fields: { knob: "READ_FACE", value: wiring.face, source: wiring.source, note: wiring.note } };
25
+ }
17
26
  //# sourceMappingURL=read-face-posture.js.map
@@ -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
@@ -736,17 +736,6 @@ export function createRuleConsentLane(stores, opts) {
736
736
  adds: [],
737
737
  };
738
738
  const rowOf = (rows) => rows.find((r) => sameWireRuleIdentity(r, { behavior: input.behavior, rule: written.rule.rule, scope: row.scope }));
739
- let before;
740
- try {
741
- before = await lane.listRules(input.principal);
742
- }
743
- catch (e) {
744
- localImportLogger.warn("direct_rule_write_pre_read_failed", { behavior: input.behavior, err: e instanceof Error ? e.message : String(e) });
745
- return { status: "failed", detail: `the rule store could not be read before writing: ${e instanceof Error ? e.message : String(e)}`, committed: "no" };
746
- }
747
- const standing = rowOf(before.rows);
748
- if (standing !== undefined)
749
- return { status: "no-op", rev: before.rev, stillLive: "yes", row: standing };
750
739
  const screened = screenLocalImportRow(row);
751
740
  if ("reason" in screened)
752
741
  return { status: "rejected", reason: screened.reason, detail: screened.detail };
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 源吗」的**单一谓词**。