@sema-agent/server 7.91.2 → 7.92.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +1 -1
  2. package/USAGE.md +8 -0
  3. package/dist/approval-ask-machine.d.ts +1 -1
  4. package/dist/approval-reconciler.d.ts +1 -1
  5. package/dist/approval.d.ts +1 -1
  6. package/dist/bench/s1/repair-oracle-adapter.d.ts +1 -1
  7. package/dist/bench/s1/runner-ctx.d.ts +1 -1
  8. package/dist/boot/runner-deps.d.ts +9 -9
  9. package/dist/capabilities/memory-notice.d.ts +27 -10
  10. package/dist/capabilities/memory-notice.js +21 -2
  11. package/dist/config-catalog.js +3 -1
  12. package/dist/config-center/types.d.ts +1 -1
  13. package/dist/config.d.ts +1 -1
  14. package/dist/env-facts.d.ts +1 -1
  15. package/dist/execution-lane-caps.d.ts +1 -1
  16. package/dist/file-history-disclosure.d.ts +2 -2
  17. package/dist/fleet/fleet-bus.d.ts +7 -1
  18. package/dist/fleet/fleet-bus.js +5 -3
  19. package/dist/http/admission.d.ts +1 -1
  20. package/dist/http/idempotency.d.ts +27 -0
  21. package/dist/http/route-ctx.d.ts +2 -5
  22. package/dist/http/routes/memory-compliance.d.ts +1 -1
  23. package/dist/http/routes/memory-origin.d.ts +2 -2
  24. package/dist/http/routes/rules.js +3 -1
  25. package/dist/http/routes/runs.js +3 -6
  26. package/dist/http/routes/session-sync.js +3 -2
  27. package/dist/http/routes/sessions.js +3 -2
  28. package/dist/http/routes/tasks.js +35 -19
  29. package/dist/http/wire-types.d.ts +29 -2
  30. package/dist/leader/wire.d.ts +1 -1
  31. package/dist/lsp/manager.d.ts +2 -2
  32. package/dist/memory-operator-faces.d.ts +13 -13
  33. package/dist/memory-scope.d.ts +2 -2
  34. package/dist/observability/fail-open.d.ts +1 -1
  35. package/dist/observability/fail-open.js +1 -1
  36. package/dist/observability/metrics.js +2 -1
  37. package/dist/orchestration/workflow-agent-session-index.d.ts +2 -2
  38. package/dist/peer-directory.d.ts +3 -2
  39. package/dist/permission-rule-vocab.d.ts +16 -1
  40. package/dist/plugins/file-run-store.d.ts +1 -1
  41. package/dist/plugins/local-session-store.d.ts +1 -1
  42. package/dist/plugins/memory-session-policy-store.d.ts +22 -3
  43. package/dist/plugins/memory-session-policy-store.js +11 -4
  44. package/dist/plugins/permission-rule-store-sql.js +16 -7
  45. package/dist/plugins/pg-pool.js +11 -0
  46. package/dist/plugins/remote-env-host.d.ts +65 -0
  47. package/dist/plugins/remote-env-host.js +155 -100
  48. package/dist/plugins/session-policy-store-sql.d.ts +51 -3
  49. package/dist/plugins/session-policy-store-sql.js +77 -38
  50. package/dist/plugins/sql-errors.d.ts +25 -0
  51. package/dist/plugins/sql-errors.js +10 -0
  52. package/dist/plugins/store-backend.d.ts +5 -5
  53. package/dist/plugins/store-contracts.d.ts +2 -2
  54. package/dist/plugins/tidb-pool.d.ts +1 -1
  55. package/dist/plugins/tidb-pool.js +19 -0
  56. package/dist/rules-consent.d.ts +16 -9
  57. package/dist/rules-consent.js +14 -35
  58. package/dist/run-cancel-context.d.ts +1 -1
  59. package/dist/run-local.d.ts +1 -1
  60. package/dist/runtime-caps-resolver.d.ts +10 -10
  61. package/dist/runtime-governance.d.ts +2 -2
  62. package/dist/session-policy-wire.d.ts +25 -1
  63. package/dist/session-policy-wire.js +11 -0
  64. package/dist/spec-fields.d.ts +4 -4
  65. package/dist/task-cwd.d.ts +1 -1
  66. package/dist/task-settings.d.ts +2 -2
  67. package/dist/tool-approval.d.ts +1 -1
  68. package/dist/trace/core-keyset-guard.d.ts +1 -1
  69. package/dist/trace/project.d.ts +25 -1
  70. package/dist/trace/project.js +4 -0
  71. package/dist/trace/redact.d.ts +1 -1
  72. package/dist/trace/redact.js +8 -3
  73. package/package.json +3 -3
@@ -68,4 +68,29 @@ export declare const MYSQL_ER_CHECK_VIOLATED_CODE = "ER_CHECK_CONSTRAINT_VIOLATE
68
68
  * 它守的是一句**指向手工 DDL 的**错误指路,认不出的一律不是 CHECK 拒(原始错误原样上抛)。
69
69
  */
70
70
  export declare function isCheckViolationError(err: unknown, dialect: SqlDialect): boolean;
71
+ /** MySQL/TiDB `ER_LOCK_DEADLOCK`(引擎挑了本事务当牺牲者)的数字 errno。 */
72
+ export declare const MYSQL_ER_LOCK_DEADLOCK_ERRNO = 1213;
73
+ /** MySQL/TiDB `ER_LOCK_DEADLOCK` 的字符串错误码。 */
74
+ export declare const MYSQL_ER_LOCK_DEADLOCK_CODE = "ER_LOCK_DEADLOCK";
75
+ /** PostgreSQL `deadlock_detected` 的 SQLSTATE。 */
76
+ export declare const PG_DEADLOCK_DETECTED_SQLSTATE = "40P01";
77
+ /**
78
+ * 「这个错误**是**『引擎判了死锁、本事务被挑成牺牲者』吗」——唯一的**可重试**锁故障判据。
79
+ *
80
+ * 🔴 **只认死锁,不认锁等待超时**(`ER_LOCK_WAIT_TIMEOUT` / 语句超时**刻意不在**识别集里):死锁是
81
+ * 引擎的**仲裁产物** —— 被挑中的一方已经整事务回滚、锁全放,立刻重来通常就过去了,而**谁**当牺牲者
82
+ * 是引擎的任意选择,把它抛给调用方等于把一次内部仲裁当成用户的错。锁等待超时是**另一件事**:它说的是
83
+ * 「有人把锁握了太久」,那是一个延迟信号,重试只会把一次已经很长的等待乘上重试次数(InnoDB 缺省 50s),
84
+ * 所以它必须响亮上抛。
85
+ *
86
+ * ⚠️ 重试的**前提**在调用方,不在这里:只有整事务重来后语义等价(读-查-写全部重做)的腿才可以用它;
87
+ * 它**不是**「锁序可以随便写」的许可证 —— 锁序的属主仍是店(真值神谕 =
88
+ * `test/sql-lock-shape-innodb-integration.test.ts` 三引擎并发套)。
89
+ *
90
+ * 引擎事实(2026-09-20 真 InnoDB 实测,S-529 取证):同一把缺行主键上 N=8 路并发「`FOR UPDATE` 缺行读
91
+ * → `INSERT`」,InnoDB(REPEATABLE READ,gap lock + insert-intention)恒定 1 成 7 死锁;TiDB(悲观、无
92
+ * gap lock)与 PG(READ COMMITTED,缺行 `FOR UPDATE` 不取锁)各自零死锁。⇒ 这条谓词实际的受众是
93
+ * **MySQL 协议腿**,两方言都认只是为了让店不必自己判引擎。
94
+ */
95
+ export declare function isDeadlockError(dialect: SqlDialect, err: unknown): boolean;
71
96
  //# sourceMappingURL=sql-errors.d.ts.map
@@ -35,4 +35,14 @@ export function isCheckViolationError(err, dialect) {
35
35
  return keyOf(err, "code") === PG_CHECK_VIOLATION_SQLSTATE;
36
36
  return keyOf(err, "errno") === MYSQL_ER_CHECK_VIOLATED_ERRNO || keyOf(err, "code") === MYSQL_ER_CHECK_VIOLATED_CODE;
37
37
  }
38
+ export const MYSQL_ER_LOCK_DEADLOCK_ERRNO = 1213;
39
+ export const MYSQL_ER_LOCK_DEADLOCK_CODE = "ER_LOCK_DEADLOCK";
40
+ export const PG_DEADLOCK_DETECTED_SQLSTATE = "40P01";
41
+ export function isDeadlockError(dialect, err) {
42
+ if (err === null || typeof err !== "object")
43
+ return false;
44
+ if (dialect === "pg")
45
+ return keyOf(err, "code") === PG_DEADLOCK_DETECTED_SQLSTATE;
46
+ return keyOf(err, "errno") === MYSQL_ER_LOCK_DEADLOCK_ERRNO || keyOf(err, "code") === MYSQL_ER_LOCK_DEADLOCK_CODE;
47
+ }
38
48
  //# sourceMappingURL=sql-errors.js.map
@@ -125,7 +125,7 @@ export type RateLimiterStore = TiDBRateLimiter | PgRateLimiter;
125
125
  * 多一个**不指名平面**的缺省臂:`src()` = 「本部署记忆引擎自己那一只平面」。
126
126
  *
127
127
  * 🔴 为什么是工厂而不是一只实例(S-215 codex r1 [high] 验真后改形):core 的记忆平面**不止一个**
128
- * (`prepare-memory.js:162` 的 personal 平面自带 `derivePersonalControlDir(engineRoot)`,与 project 平面的
128
+ * (`prepare-memory.js` 的 personal 平面自带 `derivePersonalControlDir(engineRoot)`,与 project 平面的
129
129
  * controlDir 不是同一只)。返回单只实例 ⇒ server 把所有平面折到一只目录上,于是升级前落在 personal
130
130
  * 控制面的旧 opt-out 记录在升级后读作**缺席**=采集恢复(隐私面的静默 fail-open)。按平面取用后,
131
131
  * file 载体与 core 的缺省**逐字节同址**,唯一的变化是这个席位现在**被声明了**(core 的能力信号要的就是它)。
@@ -224,8 +224,8 @@ export interface StoreBackend {
224
224
  *
225
225
  * 🔴 修前 local 恒 `undefined` 且注称「缺席是一个真答案」—— 那句话在**事实层面**成立(单机的正确载体
226
226
  * 确实就是文件三腿)、在**接线层面**是假的:core 的 `captureCarrierUnsupported`
227
- * (core 7.13.0 `prepare-task.js:497`)判的是「这个席位在不在」而不是「文件形能不能用」,叠上
228
- * `isRemoteExecutionEnv` 的接口形鸭子判(`remote-env.js:21`)把本仓 host lane 一并算作 remote,
227
+ * (core 7.13.0 `prepare-task.js`)判的是「这个席位在不在」而不是「文件形能不能用」,叠上
228
+ * `isRemoteExecutionEnv` 的接口形鸭子判(`remote-env.js`)把本仓 host lane 一并算作 remote,
229
229
  * 于是单机默认部署上 `memoryCapture:"off"` 两条 ingress 结构性不可达(B-085)。
230
230
  * 刻意仍不给 local 一个**内存** twin:内存形会在重启时把一条隐私记录静默丢掉(比文件三腿严格更差)。 */
231
231
  sessionCaptureRecords(): SessionCaptureRecordSource | undefined;
@@ -246,8 +246,8 @@ export interface StoreBackend {
246
246
  *
247
247
  * ✅ WIRED (was "NOT YET WIRABLE, as of core 1.144.0" — that claim went STALE and sat here misleading readers
248
248
  * until a 2026-07-25 doc-rot sweep caught it). core 1.145.0 added the seam this comment was waiting for:
249
- * `RunnerDeps.workflowJournalStore` (now `@sema-agent/core` `dist/core/types.d.ts:708`) + `journalStore` on the
250
- * run_workflow tool deps (`dist/orchestration/run-workflow-tool.d.ts:65`), and the boot chain consumes it
249
+ * `RunnerDeps.workflowJournalStore` (now `@sema-agent/core` `dist/core/types.d.ts`) + `journalStore` on the
250
+ * run_workflow tool deps (`dist/orchestration/run-workflow-tool.d.ts`), and the boot chain consumes it
251
251
  * end-to-end: built in `boot/workflow-orchestration.ts` (`const workflowJournalStore = …backend.workflowJournal()`),
252
252
  * threaded into RunnerDeps (`boot/runner-deps.ts` 的 `workflowJournalStore`), reaped on the maintenance tick
253
253
  * (`boot/reapers.ts` 同名字段), and read by `GET /v1/workflows/:id/journal` (`http/routes/workflows.ts`, [ref]). */
@@ -258,11 +258,11 @@ export declare function isParkedRunStatus(status: RunRecord["status"]): boolean;
258
258
  * · 读不懂的词(滚动升级里新副本写入的新状态词经裸 cast 读到本进程)同判 —— 与
259
259
  * {@link isTerminalRunStatus} 族的保守方向一致。
260
260
  * · **缺席怎么渲染是 core 的辖域,本仓一个字都不复述**(S-514-JU:此处原写着「core 对缺席渲染成 `idle`」
261
- * 并抄了当时 `list-agents-tool.js:58` 的旧三元 —— core 7.24.0 [ref] 把那一臂改成了一张 own-key 表 +
261
+ * 并抄了当时 `list-agents-tool.js` 的旧三元 —— core 7.24.0 [ref] 把那一臂改成了一张 own-key 表 +
262
262
  * `unknown` 兜底,于是那句复述当天就成了假话,而它在本仓被抄了六处)。本表只负责**铸不铸键**;
263
263
  * 渲染词由**跑真 core 的黑盒**钉住(`test/peer-lane-e2e.test.ts` 的 G1 格),core 哪天再改那张表,
264
264
  * 红的是那一格而不是一句注释。承重的不变量只有一条,且它与渲染词无关:**缺席绝不铸 `dead`** ——
265
- * `liveness` 才是 core 会拿来**整行隐藏**的那一位(7.24.2 dist 的 `list-agents-tool.js:60/62`),而任何 tempo 词
265
+ * `liveness` 才是 core 会拿来**整行隐藏**的那一位(7.24.2 dist 的 `list-agents-tool.js`),而任何 tempo 词
266
266
  * (含缺席)都照样列出。
267
267
  */
268
268
  export declare const SESSION_TEMPO_BY_RUN_CLASS: {
@@ -14,7 +14,7 @@ export declare function tidbPoolOptions(tidb: NonNullable<ServiceConfig["tidb"]>
14
14
  /** ── [ref] 同查结论:mysql2 与 pg-pool **不同病**,本池不需要(也不应)挂进程保命监听 ────────────────
15
15
  * pg 侧的病是「idle client 错误 emit 到 Pool 且无监听 ⇒ 未捕获 ⇒ 整进程 exit 1」(修在 pg-pool.ts
16
16
  * createPgPool 头注)。mysql2@3.22.6 亲读(不信文档,以实现为准):
17
- * · `lib/pool_connection.js:14-16` —— PoolConnection 构造器**恒挂** `once('error', () =>
17
+ * · `lib/pool_connection.js` —— PoolConnection 构造器**恒挂** `once('error', () =>
18
18
  * this._removeFromPool())`:每条池连接天生有 error 监听(**借出期同在**——不像 pg-pool 借出时
19
19
  * 摘监听,mysql2 的 checked-out 连接也无裸奔窗),EventEmitter 不会把 idle 死转 throw,
20
20
  * 坏连接就地淘汰;
@@ -364,10 +364,29 @@ export const SCHEMA_STATEMENTS = [
364
364
  principal VARCHAR(190) NULL,
365
365
  rules JSON NOT NULL,
366
366
  rev BIGINT NOT NULL,
367
+ -- gen (core 7.25.0 StoredSessionRules.gen, S-529 二期):这一行的**血统**。rev 只在一条血统内计数,
368
+ -- deleteBySession 终结血统并把下一代的号留在 session_policy_rev_mark.gen 上,于是活规则读者能把
369
+ -- 「被抹掉又重建的行」与「它正在执行的那一行的延续」分开 —— 无论重建行的 rev 因高水位爬到多大。
370
+ -- DEFAULT 0 = **第 0 代**(本列到货前写下的行,以及从没被抹除过的键):core 契约逐字「Absent = generation 0」。
371
+ gen BIGINT NOT NULL DEFAULT 0,
367
372
  created_at DATETIME(3) NOT NULL,
368
373
  updated_at DATETIME(3) NOT NULL,
369
374
  PRIMARY KEY (policy_key),
370
375
  KEY idx_session_policy_session (session_id)
376
+ ) COLLATE utf8mb4_bin`,
377
+ `CREATE TABLE IF NOT EXISTS session_policy_rev_mark (
378
+ policy_key CHAR(64) NOT NULL,
379
+ -- 被删会话。记号的读写一律按 policy_key 单键;这一列是**运维可读性 + 未来按会话/按龄回收**的那条腿
380
+ -- 的抓手(今天没有任何生产查询按它选行 —— 留存腿与收编腿都不碰本表,理由见上面那段)。
381
+ session_id VARCHAR(64) NOT NULL,
382
+ -- 抹除时刻该键带过的最高 rev(GREATEST 合并:记号只升不降)。
383
+ rev BIGINT NOT NULL,
384
+ -- 这把键的**下一行从第几代开始**(每次抹除 +1;与 core File 侧车 <stem>.hwm.json 的 __gen 同义)。
385
+ -- 血统必须比它终结的那一行活得久,理由与 rev 完全一样:留在行里就等于跟着行一起消失。
386
+ -- DEFAULT 0 = 本列到货前落下的记号(那些键的行也都是第 0 代 ⇒ 下一次抹除把它们抬到 1,前后自洽)。
387
+ gen BIGINT NOT NULL DEFAULT 0,
388
+ erased_at DATETIME(3) NOT NULL,
389
+ PRIMARY KEY (policy_key)
371
390
  ) COLLATE utf8mb4_bin`,
372
391
  `CREATE TABLE IF NOT EXISTS snapshot_blob (
373
392
  blob_hash CHAR(64) NOT NULL,
@@ -513,7 +513,7 @@ export declare function parseRuleScope(text: string): DurableRuleScope | undefin
513
513
  * 🔴 **三元组,不是 `(rule, scope)` 二元组**(异源复核 [medium],验真后修):core 的 `sameRuleIdentity`
514
514
  * 头注逐字 ——「a comparison that forgot the behavior member would let a delete aimed at an allow take the
515
515
  * same-text deny」。漏掉 behavior 的比较在 wire 上的表现不是「删错行」,是**报错行的死活**:撤一条从来
516
- * 没有过的 `allow` 会因为看见同文本的兄弟 `deny` 而答 `stillLive: true`,那句话说的是另一条规则。
516
+ * 没有过的 `allow` 会因为看见同文本的兄弟 `deny` 而答 `stillLive: "yes"`,那句话说的是另一条规则。
517
517
  *
518
518
  * 凡「这份清单里有没有这条规则」一律走这一只(撤销口的存活回读 / 单步写口的写前问与写后读回)——
519
519
  * 同一个问题有两个说话人,迟早会给两个答案。
@@ -590,10 +590,12 @@ export type RuleWritten =
590
590
  /** `persisted` = 这一次**真的往店里写了**;`no-op` = 这条身份**此前就站在店里**,本次调用**一个字都没写**
591
591
  * (幂等重写)。两支都带 `row` —— 它是**从店里读出来的**那一行,不是我方按入参拼出来的回声。
592
592
  *
593
- * 🔴 两个词的判别位是「**这次调用有没有写**」,不是 core 的 `deduped`(codex 对抗复审 F1,验真后修)。
594
- * core 的 `deduped` 只说「等价规则此前已站」,它**仍然落了一枚新的 dot**(契约逐字:`deduped` counts
595
- * as LANDED;亲读 dist 的成员循环:`res.dot` 无条件进 members)。把它当 `no-op` 上 wire 是一句假话:
596
- * 一个照常重试的客户端会让 `adds` 台账无界增长、桶的 rev 逐次推进(在翻页的治理面全体重列)。 */
593
+ * 🔴 两个词的判别位是「**这次调用有没有写**」(codex 对抗复审 F1,验真后修)。
594
+ * S-511 时这条口借的是导入面的兑付腿,而那侧的 `deduped` 只说「等价规则此前已站」、**仍然落了一枚新的 dot**
595
+ * ⇒ 那一格必须如实报 `persisted`,否则一个照常重试的客户端会让 `adds` 台账无界增长、桶的 rev 逐次推进
596
+ * (在翻页的治理面全体重列)。**S-525 换成 core 的直写接缝之后这条歧义没了**:`addPersistedRule` 的 `no-op`
597
+ * 契约逐字是「等价规则已经在这只桶里活着,**没有铸第二枚 dot**」——与本口 `no-op` 承诺的那句话逐字同一件事,
598
+ * 所以现在两个词与 core 的两个词一一对应,不再需要翻译。 */
597
599
  {
598
600
  status: "persisted" | "no-op";
599
601
  rev: number;
@@ -649,8 +651,11 @@ export interface RuleConsentLane {
649
651
  * 是**无墓碑硬删**:sync 的 join 会让它复活,而运维以为自己收回了权限。
650
652
  *
651
653
  * 返回值逐字是 core 的 `RemoveResult` 三态(removed / no-op / failed),**不塌**:
652
- * `removed.stillLive:true` 意思是「墓碑落了,但本次调用期间又落了一次新的批准,规则按 add-wins
653
- * 仍然活着」——那是一个**有名字的真结果**,不是异常,更不是「撤销完成」。
654
+ * `removed.stillLive` 自 core 7.25.0 起是**三词**(`"yes"` / `"no"` / `"unknown"`)而不是布尔 ——
655
+ * `"yes"` 意思是「墓碑落了,但本次调用期间又落了一次新的批准,规则按 add-wins 仍然活着」(一个**有名字的
656
+ * 真结果**,不是异常,更不是「撤销完成」);`"unknown"` 意思是**回答这一问的那次读回没做成**,它与 `"no"`
657
+ * 的区别正是这个域唯一不肯说的那句话:「我们看不见规则」永远不等于「没有规则」。本车道**原样**交出去、
658
+ * 不折叠(折 `unknown` 进 `no` 就是把「没看见」渲染成「不在了」)。
654
659
  *
655
660
  * `principal` 裸串 = principal 简写(既有调用方逐字不变);{@link RuleBucketRef} 的结构形让
656
661
  * local-owner 桶**真可撤**(core 5.24.0 起 `removePersistedRule` 的入参 union 扩,[ref] 收官件)。
@@ -669,8 +674,10 @@ export interface RuleConsentLane {
669
674
  * 方向在类型上就到不了这里:`behavior` 收窄成 {@link DirectWritableRuleBehavior}。
670
675
  *
671
676
  * 🔴 **一行都不自造**:行的 tool/match/command 三格是规则文本的**派生事实**,由 core 的
672
- * `parseRuleText` 铸;落店走的是导入面那条**同一条**兑付腿(逐行判据 → core 的记录铸造口 → confirm
673
- * → 批兑付),于是本口与导入口对「一条规则能不能进店」永远给同一个答案。
677
+ * `parseRuleText` 铸;**内容判据**与导入面走**同一个函数**({@link screenLocalImportRow}),于是本口与
678
+ * 导入口对「一条规则能不能进店」永远给同一个答案。**落店**(S-525 / core 7.25.0)走 core 的**记录级
679
+ * 直写接缝** `addPersistedRule` —— 一步、无票、core 自己铸 `origin:"user"`、`allow` 在类型上不可拼;
680
+ * 此前借的是导入面的两跳票仪式,代价是出处词恒 `imported-cc` + 每次成功的写白落两行台账。
674
681
  *
675
682
  * `principal` 是裸串(与 prepare/redeem 两腿同形);local-owner 桶**不在本口**上 —— 那只桶没有
676
683
  * 「谁在写」这个主体,而本口的授权基础就是已验明的 principal。
@@ -5,7 +5,7 @@ import { createLogger } from "./observability/logger.js";
5
5
  import { MAX_CWD_CHARS } from "./task-cwd.js";
6
6
  import { isDirectWritableRuleBehavior } from "./permission-rule-vocab.js";
7
7
  import { CONTROL_AND_BIDI_CHARS, hasBidiControls } from "./text-bidi.js";
8
- import { confirmRuleApproval, RULE_BEHAVIORS, parseRuleText, ruleReachesProgramRun, ruleToolGrammarOf, precheckEditedRuleText, prepareCardApproval, prepareCcImport, redeemRuleBatch, redeemRuleTicket, removePersistedRule, effectiveOrThrow, readRowBehavior, } from "@sema-agent/core";
8
+ import { confirmRuleApproval, RULE_BEHAVIORS, parseRuleText, ruleReachesProgramRun, ruleToolGrammarOf, precheckEditedRuleText, prepareCardApproval, prepareCcImport, redeemRuleBatch, redeemRuleTicket, addPersistedRule, removePersistedRule, effectiveOrThrow, readRowBehavior, } from "@sema-agent/core";
9
9
  import { buildRulePayloadHash } from "./plugins/permission-rule-store-sql.js";
10
10
  export const RULE_IMPORT_TICKET_TTL_MS = 10 * 60_000;
11
11
  export const RULE_IMPORT_RETRY_AFTER_SEC = 2;
@@ -740,45 +740,24 @@ export function createRuleConsentLane(stores, opts) {
740
740
  const standing = rowOf(before.rows);
741
741
  if (standing !== undefined)
742
742
  return { status: "no-op", rev: before.rev, row: standing };
743
- const prepared = await lane.prepareLocalImport(input.principal, [row]);
744
- if (!prepared.ok) {
745
- throw new Error(`a single-rule write hit the batch ceiling (${prepared.reason}) — the write door and the batch caps are out of step`);
746
- }
747
- if (prepared.ticket === undefined) {
748
- const member = prepared.preview.members[0];
749
- if (member === undefined || member.admitted) {
750
- throw new Error("the rule screening reported neither an admitted row nor a refusal for this write — refusing to guess which of the two happened");
751
- }
752
- return { status: "rejected", reason: member.reason, detail: member.detail };
753
- }
754
- const redeemed = await lane.redeemLocalImport(input.principal, prepared.ticket);
755
- if (!redeemed.ok) {
756
- localImportLogger.warn("direct_rule_write_ticket_refused", {
757
- reason: redeemed.reason,
758
- detail: redeemed.detail,
759
- note: "a self-minted, immediately redeemed ticket was refused — the outcome of this write is not known",
760
- });
761
- return { status: "indeterminate", detail: `${redeemed.reason}: ${redeemed.detail}` };
762
- }
763
- const member = redeemed.outcome.members[0];
764
- if (member === undefined) {
765
- throw new Error("the redemption of a single-rule write came back with no member — refusing to report an outcome nobody computed");
766
- }
767
- switch (member.outcome) {
768
- case "persisted":
743
+ const screened = screenLocalImportRow(row);
744
+ if ("reason" in screened)
745
+ return { status: "rejected", reason: screened.reason, detail: screened.detail };
746
+ const added = await addPersistedRule({ behavior: input.behavior, rule: written.rule.rule, scope: input.scope, principal: input.principal, provider: stores.provider });
747
+ switch (added.status) {
748
+ case "added":
769
749
  break;
770
750
  case "no-op":
771
751
  break;
772
- case "indeterminate":
773
- return { status: "indeterminate", detail: member.detail ?? member.reason ?? "the store did not say whether this rule landed" };
774
- case "refused":
775
- return { status: "indeterminate", detail: member.detail ?? "the store refused this rule without a machine-readable reason" };
752
+ case "failed":
753
+ return { status: "indeterminate", detail: added.error };
776
754
  default: {
777
- const unknown = member.outcome;
778
- throw new Error(`unknown single-rule write outcome: ${JSON.stringify(unknown)}`);
755
+ const unknown = added;
756
+ throw new Error(`unknown addPersistedRule outcome: ${JSON.stringify(unknown)}`);
779
757
  }
780
758
  }
781
- const landed = rowOf((await lane.listRules(input.principal)).rows);
759
+ const readBack = await lane.listRules(input.principal);
760
+ const landed = rowOf(readBack.rows);
782
761
  if (landed === undefined) {
783
762
  localImportLogger.warn("direct_rule_write_vanished_before_read_back", {
784
763
  behavior: input.behavior,
@@ -787,7 +766,7 @@ export function createRuleConsentLane(stores, opts) {
787
766
  });
788
767
  return { status: "indeterminate", detail: "this rule landed and was already gone when it was read back — a revocation landed in between" };
789
768
  }
790
- return { status: "persisted", rev: redeemed.outcome.rev, row: landed };
769
+ return { status: added.status === "no-op" ? "no-op" : "persisted", rev: added.rev, row: landed };
791
770
  },
792
771
  };
793
772
  return lane;
@@ -31,7 +31,7 @@ export declare const CANCELLED_CODE = "cancelled";
31
31
  * 发 `done`,`terminal` 是一条**不带码**的 `failed`。这里在**它**之上补标,不另造一份结果(另造会把
32
32
  * 引擎已经算出的 `stats` / `result` / `editedFiles` 全丢掉)。
33
33
  * · **缺席**:根本没读到 done —— 客户端断连后迭代器早收,或**开跑之前**就已经立起的旗
34
- * (亲核 core 7.11.2 `dist/core/runner/runtask.js:623-626`:`runLocked` 入口对已 aborted 的 signal
34
+ * (亲核 core 7.11.2 `dist/core/runner/runtask.js`:`runLocked` 入口对已 aborted 的 signal
35
35
  * 抛 `AbortError`)⇒ 合成一条诚实的取消终局。
36
36
  *
37
37
  * ## 「只补空座」纪律(与 core 的 `amendTerminal` 同规),而且**补码与补快照同进同退**
@@ -113,7 +113,7 @@ export interface LocalApproverIo {
113
113
  * 复刻一份 = 同源谎(两处判据日后各改各的)。裁决归 core,座位归这里。
114
114
  *
115
115
  * 🔴 非 TTY 臂答 `false`(真 deny),**绝不答 `"unavailable"`**:后者被 core 读成「席位够不到」并**改道
116
- * `suspendAsk`**(dist `core/hooks.js:296` 的 `approverUnavailable` 重投)。今天本腿无店 ⇒ 那条改道无处
116
+ * `suspendAsk`**(dist `core/hooks.js` 的 `approverUnavailable` 重投)。今天本腿无店 ⇒ 那条改道无处
117
117
  * 可去(fail-closed 拒),但这一条仍是本席的契约:答案是「不批」,不是「找不到人」。
118
118
  */
119
119
  export declare function createLocalApprover(io: LocalApproverIo): OnAsk;
@@ -9,23 +9,23 @@
9
9
  * (engine-can ∧ center-may ∧ shell-show): core calls it with `spec.principal`.
10
10
  *
11
11
  * ⚠️ **调用次数不是「每任务一次」**([ref]① 随 codex r1-[medium] 亲验后纠:旧句写「ONCE per task at
12
- * prepare time」):prepare 期解析一次(prepare-task.js:1826),**并且** capture-opt-out 的 flip 动词每被打一次
13
- * 就再解析一次(prepare-memory.js:572 的 `flip` → :538 `resolveCaptureEntitlementFresh` → :542 本 resolver)。
12
+ * prepare time」):prepare 期解析一次(prepare-task.js),**并且** capture-opt-out 的 flip 动词每被打一次
13
+ * 就再解析一次(prepare-memory.js 的 `flip` → 同文件 `resolveCaptureEntitlementFresh` → 本 resolver)。
14
14
  * 所以本 resolver 必须是**可重入、可多次调用**的,缓存的 TTL 语义对 flip 腿同样生效;把它当「每任务一次的
15
15
  * 快照」来实现(例如在里面攒 per-task 状态)会在 flip 腿上出错。
16
16
  *
17
17
  * ⚠️ [ref]① 纠失实:核心那句曾写作「引擎只执法它能执法的**两**个 cap」—— 早已不属实。core 7.2.0
18
18
  * 装树 dist **亲读**的消费点是**六**个(本仓 node_modules 实测;下列坐标除注明外均在 `dist/core/runner/` 下,
19
19
  * `agents/subagent.js` 那一条已就地标出 —— 说「均在 runner 下」是 r1 抓到的第二处过头话):
20
- * · `allowWorkflows` — prepare-task.js:1917 `runtimeCaps?.allowWorkflows !== false`(deny 自编排;:1918-1919 另发 onError)
21
- * · `allowFork` — prepare-task.js:1867 `forkGovernanceDenial(spec.enableFork, runtimeCaps?.allowFork)`(+:2418 拒由,agents/subagent.js:1575 拒词)
22
- * · `allowObservers` — prepare-task.js:1868 `=== true`(**极性与 allow* 相反**:显式 opt-in,默认 OFF)
23
- * · `forceDurableGate`— prepare-task.js:1644/1646 的 durable/content mandate、:2337 活 content-ask 抑制、
24
- * :2365 durable question face(该行合取 `resolveCheckpointStore(spec, deps) !== undefined`
20
+ * · `allowWorkflows` — prepare-task.js `runtimeCaps?.allowWorkflows !== false`(deny 自编排;同处另发一条 onError)
21
+ * · `allowFork` — prepare-task.js `forkGovernanceDenial(spec.enableFork, runtimeCaps?.allowFork)`(拒由同处铸出,拒词在 agents/subagent.js)
22
+ * · `allowObservers` — prepare-task.js `=== true`(**极性与 allow* 相反**:显式 opt-in,默认 OFF)
23
+ * · `forceDurableGate`— prepare-task.js 的 durable/content mandate、活 content-ask 抑制、
24
+ * durable question face(该处合取 `resolveCheckpointStore(spec, deps) !== undefined`
25
25
  * ⇒ **无 checkpoint 店的部署上这一面本就不成立**,该键在那里 inert)
26
- * · `autoMode` — prepare-task.js:1869 `=== true` **∧** `deps.autoMode !== undefined`(部署需先具备分类器;
26
+ * · `autoMode` — prepare-task.js `=== true` **∧** `deps.autoMode !== undefined`(部署需先具备分类器;
27
27
  * 显式 opt-in 极性,且 [ref] 起本侧传的是三态 org deny 位,见 toCoreRuntimeCaps 头注)
28
- * · `allowMemoryOptOut` — prepare-memory.js:542-543 `resolveCaptureEntitlementFresh`(+prepare-task.js:2474-2475
28
+ * · `allowMemoryOptOut` — prepare-memory.js `resolveCaptureEntitlementFresh`(+prepare-task.js
29
29
  * 的 value/faulted 投影);**极性反转,但只在 `open` 姿态下**——判决全表在
30
30
  * `dist/core/runner/memory-capture-optout.js` 的 `adjudicateCaptureOptOut`,按
31
31
  * `RunnerDeps.memoryCapturePolicy` 三姿态分叉(codex r1-[medium] 亲验后纠:旧句无条件
@@ -112,7 +112,7 @@ export interface EntitlementsResolverOpts {
112
112
  * 武装,真实用户诊断的病根)。
113
113
  * 🔴 **上面那条武装式是 [ref] 的目标形,不是「本仓现在装的那版 core 的形」——引用它之前先看装的是哪版**
114
114
  * ([ref]① 随 codex r2-[high] 补:我方在 WIRE 附录 F 照抄这一句,写成了「7.2.0 的行为」,而 npm **7.2.0
115
- * 不含 [ref]** —— 它的 `dist/core/runner/prepare-task.js:1869` 逐字仍是 `runtimeCaps?.autoMode === true &&
115
+ * 不含 [ref]** —— 它的 `dist/core/runner/prepare-task.js` 逐字仍是 `runtimeCaps?.autoMode === true &&
116
116
  * deps.autoMode !== undefined`,即旧「org 授予」极性、**缺键 ⇒ 不武装**)。判「现在是哪套极性」的唯一判据 =
117
117
  * 安装包探针 `CORE_ARMS_AUTO_ON_INTENT`(`src/auto-mode-face.ts`;实测本树 7.2.0 ⇒ **false** = 旧式臂),
118
118
  * 代码侧已按它分臂,失实的只是散落各处的散文。⚠️ 同形措辞在本文件 :209 / :362-364 / :548 与
@@ -42,8 +42,8 @@ export declare function isDelegationToolName(name: string): boolean;
42
42
  * tool at all.
43
43
  *
44
44
  * 🔴 原始理由已被上游收编(2026-08-05 亲验安装包 core 5.13.x):子任务 spec 现在**继承** `handsReadOnly`
45
- * (`dist/agents/subagent.js:1501` 的 `...(ctx.handsReadOnly === true ? { handsReadOnly: true } : {})`,resume
46
- * 腿 `:405` 同款)与 `shellGate`(`dist/core/runner/prepare-task.js` 的 `inheritedGateForChildren`),所以
45
+ * (`dist/agents/subagent.js` 的 `...(ctx.handsReadOnly === true ? { handsReadOnly: true } : {})`,resume
46
+ * resume 腿同款)与 `shellGate`(`dist/core/runner/prepare-task.js` 的 `inheritedGateForChildren`),所以
47
47
  * 「子任务会拿到可写的手」这个当年的缺口在 core 侧已经补上。本 strip 因此是**同向的第二层**(更严,不更松):
48
48
  * 它保证 read-only 一档下连委派工具面都不铸,与继承是否存在无关。拆掉它是一次行为变更(read-only 任务将
49
49
  * 重新能委派),需要单独决定——不要因为「上游修好了」就顺手删。
@@ -27,7 +27,7 @@
27
27
  * 「常量锚」):句子收进本文件**单一属主**,冻结职责搬到冻结门的**常量锚格** —— 那一格直接断言这两只
28
28
  * 渲染器对每一种判词的输出字节,且对判词闭集穷尽(加一种判词 ⇒ 那一格编译红)。文案一个字节未改。
29
29
  */
30
- import type { SessionPermissionRules, SessionRulesRecord } from "@sema-agent/core";
30
+ import type { SessionPermissionRules, SessionPolicyError, SessionRulesRecord } from "@sema-agent/core";
31
31
  /** core `SessionPermissionRules` 的桶名全集(派生,不是手抄)。 */
32
32
  export declare const SESSION_PERMISSION_RULE_FIELDS: readonly (keyof SessionPermissionRules)[];
33
33
  /** 一条规则体的形问题。判词而非文案(见文件头注的「文案属主」一段)。 */
@@ -100,4 +100,28 @@ export declare function readSessionRulesRecords(raw: unknown): SessionRulesRecor
100
100
  export declare function describeSessionPermissionRulesProblem(p: SessionPermissionRulesProblem): string;
101
101
  /** `policy[]` 判词 → 人话(sync/import 腿在它前面拼 `policy: `)。闭集穷尽同上。 */
102
102
  export declare function describeSessionRulesRecordProblem(p: SessionRulesRecordProblem): string;
103
+ /**
104
+ * 🔴 **`SessionPolicyError` 的闭集 → HTTP 的**唯一**映射(core 7.25.0 提货批新建)。**
105
+ *
106
+ * 此前这张映射在两条路由上各写了一个**二元三元表达式**:`sessions.ts` 写 `conflict ? 409 : 403`、
107
+ * `session-sync.ts` 写 `loosen_forbidden ? 403 : 409`。两句话在两个词的世界里都对,而 core 这一版给闭集
108
+ * 加了第三个词 `corrupt`(店读不出自己写的记号/行 ⇒ 拒写,而不是按 0 续号)—— 于是同一个新词在两条路由上
109
+ * 拿到**两个不同的、都不对的**答案:一边 403「你没权限」,一边 409「有人先改了」,而它其实是一次
110
+ * **数据完整性故障**。这正是 [ref] 词表纪律说的那一形:闭集必须穷尽 `switch`,未知词是**编译**错,
111
+ * 不是运行期落进某个恰好存在的臂。
112
+ *
113
+ * ⇒ 映射搬到这里(本文件已是 policy wire 形的单一读者/属主,S-496 先例),两条路由同调它。
114
+ * 新增一个词 ⇒ 本 switch 编译红,由人裁定它的状态码与码位,而不是让它静默继承邻居的。
115
+ *
116
+ * 逐词:
117
+ * · `conflict`(409)—— CAS 输了,重读 `rev` 再来。**码位与状态逐字不变**。
118
+ * · `loosen_forbidden`(403)—— 非 operator 想放松。**码位与状态逐字不变**。
119
+ * · `corrupt`(**500**)—— 店读不出它自己该读的那份字节(记号/行被列外篡改或撕裂)。
120
+ * 不是 4xx:调用方没做错任何事,它也改不了这件事;不是 503:重试改不了判(core 成文的修复路径是
121
+ * **人**去改回或删掉那一行,而删记号要交出它握着的那个号)。
122
+ */
123
+ export declare function sessionPolicyErrorHttp(code: SessionPolicyError["code"]): {
124
+ status: number;
125
+ errorCode: string;
126
+ };
103
127
  //# sourceMappingURL=session-policy-wire.d.ts.map
@@ -63,4 +63,15 @@ export function describeSessionRulesRecordProblem(p) {
63
63
  }
64
64
  }
65
65
  }
66
+ export function sessionPolicyErrorHttp(code) {
67
+ switch (code) {
68
+ case "conflict": return { status: 409, errorCode: "conflict" };
69
+ case "loosen_forbidden": return { status: 403, errorCode: "loosen_forbidden" };
70
+ case "corrupt": return { status: 500, errorCode: "corrupt" };
71
+ default: {
72
+ const unhandled = code;
73
+ throw new Error(`unhandled SessionPolicyError code: ${JSON.stringify(unhandled)}`);
74
+ }
75
+ }
76
+ }
66
77
  //# sourceMappingURL=session-policy-wire.js.map
@@ -65,15 +65,15 @@ export declare function taskAgentsFromBody(raw: unknown, requirePrincipal: boole
65
65
  /** [ref]②/[ref](cli 认领消费)—— fresh-submit 的 agents[].model **目录成员资格**门(形状门
66
66
  * {@link validateTaskAgents} 通过之后跑;调用方铸 400 `request.unknown_reference` + `available` 枚举)。
67
67
  * 病灶=报错太晚太远:server 持目录却不查,错到 core prepare 才炸 `config.agent.unknown_model`
68
- * (dist/agents/subagent.js:1258),以「run failed」形出现而非 400;cli resolver 只挡词法形,目录
68
+ * (dist/agents/subagent.js),以「run failed」形出现而非 400;cli resolver 只挡词法形,目录
69
69
  * 成员资格只有 server 持有。
70
70
  * 三条判定纪律(全部 core dist 亲读):
71
- * - **只查 string 形**:core 只对 `typeof a.model === "string"` 走 `resolveModel`(subagent.js:1253);
71
+ * - **只查 string 形**:core 只对 `typeof a.model === "string"` 走 `resolveModel`(subagent.js);
72
72
  * Model 对象形 core 原样信任不查目录([ref]④ 契约:自带完整 Model 配置,其 id 本就不必在目录)——
73
73
  * 对象形没有「晚失败」病灶,这里同拒会是射程外的行为收紧,不做。
74
74
  * - **`catalog` 必须是 tier 展开形**(调用方传 `expandTiers(config.models, config.tiers) ?? config.models`,
75
75
  * resolve-spec.ts wireCatalog 同源纪律):core Runner 构造期把 deps.models 换成展开目录
76
- * (runtask.js:1472),agentCatalog.models 即它 —— 裸 config.models 会把合法档位词/CC 别名误杀 400。
76
+ * (runtask.js),agentCatalog.models 即它 —— 裸 config.models 会把合法档位词/CC 别名误杀 400。
77
77
  * - **判定=`Object.hasOwn` 纯键查**,刻意不用 `matchCatalogModel`:那是 body.model 门的四形判
78
78
  * (name/tier/alias/**id**),而 core 的 `resolveModel` 是纯键查(roles.js:`models?.[ref]`)——
79
79
  * 多认 id 形会放行一个 core prepare 必炸的值(门假绿,病灶照旧)。hasOwn 顺带挡原型链键。
@@ -111,7 +111,7 @@ export declare function toolNameListFromBody(raw: unknown): string[] | undefined
111
111
  * ⚠️ **这不是一条 defer 注**([ref] 件⑤ 对表结论,core [ref] 同判):本段说的是 `promptProfile` 这根
112
112
  * **呈现**轴,它与 `deferMode` / `deferTools` 的延迟披露轴**无耦合** —— 上一行提 deferTools 只是在借它
113
113
  * 的「无租户门」姿势作类比。「引擎缺省 simple」这句本身在 core 5.40.0 上仍逐字为真
114
- * (`core/types.d.ts:133`:default "simple"),本批亲验未变。
114
+ * (`core/types.d.ts`:default "simple"),本批亲验未变。
115
115
  * 真正的 defer 轴对表(core 5.40.0 起 `deferMode:"auto"` 不再 sweep 内建;7.15.0 起它不再是第二道门,
116
116
  * 只是把候选集拓宽后走同一道阈值门)记在
117
117
  * `src/capabilities/tool-defer.ts` 的头注 —— 别把两根轴的结论写到一处。
@@ -35,7 +35,7 @@ export declare function normalizeAbsolutePathLexically(p: string): string;
35
35
  * **词法**判域:`dir` 是不是 `target` 本身或它的祖先(= `target` ⊆ `dir`,「等于 ∪ 后代」)。
36
36
  *
37
37
  * 这是 core `assertExemptDirsDoNotCoverRoot` 那条律的本仓同形(亲读安装树
38
- * `dist/core/fs-write-gate-policy.js:11-28` → `swallowedFenceRoot` → `isWithin(root, p)`:
38
+ * `dist/core/fs-write-gate-policy.js` → `swallowedFenceRoot` → `isWithin(root, p)`:
39
39
  * 先各自词法归一,再判 `p === root` 或 `p` 以 `root + "/"` 起头)。**两端都只做词法**,不 realpath ——
40
40
  * 写门的构造期本来就没有 I/O(core 同理),承诺 canonical 是承诺一件此刻做不到的事。
41
41
  *
@@ -396,8 +396,8 @@ export interface FsWriteGateWiring {
396
396
  * `boot/resolve-spec.ts` / `boot/parked-revive-gate.ts` 把**同一个值**递成本字段。两处同源是承重的:
397
397
  * 各算一份就会出现「422 门放行而引擎判 unreadable」或反过来。
398
398
  *
399
- * 🔴 **S-213⑦ 改真话**(亲读 core 7.12.0 `dist/core/permission-rule-model.js:365-372` /
400
- * `:340-350`,推翻本注此前的两句):
399
+ * 🔴 **S-213⑦ 改真话**(亲读 core 7.12.0 `dist/core/permission-rule-model.js` /
400
+ * 的 `resolvePathPattern`,推翻本注此前的两句):
401
401
  * · core 对缺席的 `home` **不**回落 `os.homedir()` —— `resolvePathPattern` 走
402
402
  * `lexicalNormalAbsolutePathOf(baseValueOf("home", bases) ?? "")`,base 缺席 ⇒ `undefined` ⇒ 直接回
403
403
  * `{missingBase:"home"}`,该调用的射程判 `unreadable`(fail-closed 的 ask,人来清),从不猜一个目录。
@@ -653,7 +653,7 @@ export declare function resolveStreamApprovalGate(input: StreamApprovalGateInput
653
653
  *
654
654
  * 三源(§8.3)里本仓可执行的是后两源;第一源 `forceDurableGate` 在 HTTP 装配时刻不可解析(§14 §12-5
655
655
  * 亲裁:不可读则该源不做,core 自身的 `runtimeCaps?.forceDurableGate` 消费臂结构性兜底——7.0.1
656
- * `prepare-task.js` 的 durableMandate/contentMandate(:1415/:1417)与 durableQuestionFace(:2136)等),
656
+ * `prepare-task.js` 的 durableMandate/contentMandate 与 durableQuestionFace 等),
657
657
  * 故不在本函数内。
658
658
  *
659
659
  * 🔴 **[ref](半接线旋钮族)**:`windowZero` 的第一源(`STREAM_ASK_WINDOW_MS=0`)自此**不再挂在
@@ -36,7 +36,7 @@ type _GuardProvenanceReverse = AssertAllKeysHandled<Exclude<ProvenanceProjected
36
36
  type AgentMessageProjected = "from" | "body";
37
37
  type _GuardAgentMessage = AssertAllKeysHandled<Exclude<keyof NonNullable<TaskNotificationPayload["agentMessage"]>, AgentMessageProjected>>;
38
38
  type _GuardAgentMessageReverse = AssertAllKeysHandled<Exclude<AgentMessageProjected, keyof NonNullable<TaskNotificationPayload["agentMessage"]>>>;
39
- type BgNotifProjected = "taskId" | "sessionId" | "seq" | "status" | "summary" | "stoppedBy" | "resumable" | "recentSteps" | "editedFiles" | "usage" | "transcriptId" | "rootSessionId" | "parentTaskId" | "parentToolCallId" | "completionId";
39
+ type BgNotifProjected = "taskId" | "sessionId" | "seq" | "status" | "summary" | "stoppedBy" | "resumable" | "recentSteps" | "editedFiles" | "usage" | "transcriptId" | "rootSessionId" | "parentTaskId" | "parentToolCallId" | "completionId" | "spawnName";
40
40
  type BgNotifExcluded = "kind" | "sessionScoped" | "owner" | "scope" | "description" | "agentType" | "name" | "currentAction" | "currentTool" | "parentSessionId" | "startedAt" | "workflowRunId" | "progressTaskId" | "progressParentTaskId" | "model";
41
41
  type _GuardBgNotif = AssertAllKeysHandled<Exclude<keyof BackgroundChildEvent, BgNotifProjected | BgNotifExcluded>>;
42
42
  type _GuardBgNotifReverse = AssertAllKeysHandled<Exclude<BgNotifProjected | BgNotifExcluded, keyof BackgroundChildEvent>>;
@@ -331,7 +331,9 @@ export declare function toolDisclosureEventData(ev: {
331
331
  /** The `task_progress` payload (a subagent's live progress tick) — EXPLICIT WHITELIST shared by all
332
332
  * three legs (bg runs.ts + resume append + the sync live SSE frame): taskId/parent linkage/token rollup verbatim,
333
333
  * `name` UNTRUSTED (core sanitizes for rendering, does NOT secret-redact — a taskName can be secret-shaped) →
334
- * `redactSecrets`. Never the delegated objective/args. E2 identity rides (eventId/
334
+ * `redactSecrets`; `spawnName` is the TRUSTED-chain teammate KEY beside it (core 7.25.0 [ref] S0 — validated at the
335
+ * spawn, so it is an identity, not a label) and rides VERBATIM: redacting an identity key does not make it safe,
336
+ * it makes it WRONG (two legal names collapsing onto one value — see the key's own note). Never the delegated objective/args. E2 identity rides (eventId/
335
337
  * parentToolCallId are core-mint uuidv7 identifiers, not content — consumers bind concurrent subagent ticks
336
338
  * exactly by them; the 1.72 whitelist dropped them, leaving a FIFO heuristic). */
337
339
  export declare function taskProgressEventData(ev: {
@@ -339,6 +341,7 @@ export declare function taskProgressEventData(ev: {
339
341
  taskType?: string;
340
342
  parentTaskId?: string;
341
343
  name?: string;
344
+ spawnName?: string;
342
345
  status?: string;
343
346
  usage?: unknown;
344
347
  currentAction?: string;
@@ -715,6 +718,27 @@ export declare function wiringManifestOperatorEventData(ev: {
715
718
  eventId?: unknown;
716
719
  parentToolCallId?: unknown;
717
720
  }): Record<string, unknown>;
721
+ /**
722
+ * `wiring_manifest` 的**唯一分叉点**(S-528)—— 两条知道调用方身份的 live 消费腿共用它:
723
+ * `/v1/tasks/stream` 的 SSE 帧,与 `POST /v1/tasks`(非流式)200 体里的 `wiringManifest` 起手回执。
724
+ *
725
+ * 🔴 **为什么是一只函数,而不是两处 `cond ? operator : tenant`**:那个三元判的是「这份产物给谁看」——
726
+ * 一件治理面的事([ref] 件一)。写两份就有漂的位置:core 往 operator 面加第 N 段时,跟上的那一处与没跟上
727
+ * 的那一处会让**同一次提交**在两条腿上投出两种宽度,而错的方向是把 operator-only 面发给租户。两条腿从此
728
+ * 只认这一只,新的第三条腿照抄它即可(禁再写一次三元)。
729
+ *
730
+ * 判据本身(`explicitOperatorOk`)仍由调用腿**每请求求一次**再传进来:身份解析要读请求凭据与部署名单,
731
+ * 那是 http 层的事,本文件(投影层)不认识 `req`,也不该认识。
732
+ *
733
+ * `operatorFace` 的两个值都是**正面事实**,没有第三态(所以是 boolean 不是三值):
734
+ * · `true` = 本请求的调用方经 `explicitOperatorOk` 判为 operator ⇒ 投 {@link wiringManifestOperatorEventData};
735
+ * · `false` = 其余全部(**含**「本部署根本没配 operator 名单」)⇒ 投 {@link wiringManifestEventData} 的租户形。
736
+ */
737
+ export declare function wiringManifestFaceEventData(ev: {
738
+ manifest?: unknown;
739
+ eventId?: unknown;
740
+ parentToolCallId?: unknown;
741
+ }, operatorFace: boolean): Record<string, unknown>;
718
742
  /**
719
743
  * `human_input`(core 5.14.0 [ref] 新 `TaskEvent` 臂)—— 「谁把什么喂进了这条 run」的生命周期账本
720
744
  * 帧(objective / 实时 steer / nextTurn / parked-steer 续跑 / wake 五个载体共用一个投影点)。
@@ -273,6 +273,7 @@ export function taskProgressEventData(ev) {
273
273
  ...(typeof ev.taskType === "string" && ev.taskType.length > 0 ? { taskType: ev.taskType } : {}),
274
274
  ...(ev.parentTaskId ? { parentTaskId: ev.parentTaskId } : {}),
275
275
  ...(ev.name ? { name: redactSecrets(ev.name) } : {}),
276
+ ...(ev.spawnName ? { spawnName: ev.spawnName } : {}),
276
277
  ...(typeof ev.model === "string" && ev.model.length > 0 ? { model: ev.model } : {}),
277
278
  ...(ev.status !== undefined ? { status: ev.status } : {}),
278
279
  ...(ev.usage ? { usage: ev.usage } : {}),
@@ -597,6 +598,9 @@ export function wiringManifestOperatorEventData(ev) {
597
598
  ...(str(m.configFingerprint) !== undefined ? { configFingerprint: m.configFingerprint } : {}),
598
599
  };
599
600
  }
601
+ export function wiringManifestFaceEventData(ev, operatorFace) {
602
+ return operatorFace ? wiringManifestOperatorEventData(ev) : wiringManifestEventData(ev);
603
+ }
600
604
  export function humanInputEventData(ev) {
601
605
  const str = (v) => (typeof v === "string" && v.length > 0 ? v : undefined);
602
606
  const a = typeof ev.actor === "object" && ev.actor !== null && !Array.isArray(ev.actor) ? { ...ev.actor } : {};
@@ -74,7 +74,7 @@ export declare function resetPpkScanBytesForTest(): void;
74
74
  *
75
75
  * 🔴 **词义如实**(codex r1 [medium] 三,采纳):这一维说的是「命中的是**哪一条臂**」,**不是**
76
76
  * 「这段文本真的被上游截过」—— server 收到的是纯字节,core 的 `clip()` 是 `slice(0, 80)` **不留标记**
77
- * (亲读 `@sema-agent/core/dist/agents/subagent-steps.js:9-11`),截过与天生就短在 wire 上不可区分。
77
+ * (亲读 `@sema-agent/core/dist/agents/subagent-steps.js`),截过与天生就短在 wire 上不可区分。
78
78
  * 所以一个 `arm="fragment"` 计数可能来自残片,也可能来自一个恰好落在阈值之上的 look-alike;
79
79
  * core [ref] 落地后这一支**趋零是证据、不归零不是证明**。别把它当截断事实的度量用。 */
80
80
  /**
@@ -1774,6 +1774,9 @@ function decodeToFixedPoint(s, maxRounds = 3) {
1774
1774
  }
1775
1775
  return { text: cur, residue: cur.includes("%") };
1776
1776
  }
1777
+ const MARKER_CHARS = ["«", "»"];
1778
+ const MARKER_BYTE_SPELLINGS = MARKER_CHARS.map((ch) => String.fromCharCode(...new TextEncoder().encode(ch)));
1779
+ const carriesRedactionMarker = (spelled, byteView) => MARKER_CHARS.some((ch) => spelled.includes(ch)) || MARKER_BYTE_SPELLINGS.some((b) => byteView.includes(b));
1777
1780
  const VIEW_FILLER_PREFERENCE = ["a", "z", "_", "-", "0"];
1778
1781
  let armValueClassViewsCache;
1779
1782
  function armValueClassViews() {
@@ -1794,13 +1797,15 @@ function boundedUnitView(unit, inClass, filler) {
1794
1797
  }
1795
1798
  function unitCarriesCredential(unit) {
1796
1799
  const decoded = decodeToFixedPoint(unit);
1800
+ if (carriesRedactionMarker(unit, decoded.text))
1801
+ return { hit: false, unreadable: true };
1797
1802
  const views = new Set();
1798
1803
  for (const spelling of [unit, decoded.text])
1799
1804
  for (const v of armValueClassViews())
1800
1805
  views.add(boundedUnitView(spelling, v.inClass, v.filler));
1801
1806
  views.delete("");
1802
1807
  const hit = [...views].some((view) => runRedaction(view, true, false, false, "bounded-unit").text !== view);
1803
- return { hit, residue: decoded.residue };
1808
+ return { hit, unreadable: decoded.residue };
1804
1809
  }
1805
1810
  function displaySafePathname(pathname) {
1806
1811
  const parts = pathname.split("/");
@@ -1810,7 +1815,7 @@ function displaySafePathname(pathname) {
1810
1815
  if (seg === "")
1811
1816
  continue;
1812
1817
  const verdict = unitCarriesCredential(seg);
1813
- if (!verdict.hit && !verdict.residue)
1818
+ if (!verdict.hit && !verdict.unreadable)
1814
1819
  continue;
1815
1820
  if (isAllRedactionTokens(redactSecrets(seg)))
1816
1821
  continue;
@@ -1839,7 +1844,7 @@ function shapeLawCredentialInQueryOrFragment(canonical) {
1839
1844
  if (unit === "")
1840
1845
  continue;
1841
1846
  const verdict = unitCarriesCredential(unit);
1842
- if (verdict.hit || verdict.residue)
1847
+ if (verdict.hit || verdict.unreadable)
1843
1848
  return true;
1844
1849
  const decoded = decodeToFixedPoint(unit).text;
1845
1850
  if (decoded.includes("://") || decoded.includes("@"))