@sema-agent/server 7.11.0 → 7.12.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 (48) hide show
  1. package/README.md +1 -1
  2. package/USAGE.md +56 -0
  3. package/dist/auth-keys.d.ts +28 -4
  4. package/dist/auth-keys.js +60 -15
  5. package/dist/boot/parked-revive-gate.d.ts +18 -2
  6. package/dist/boot/parked-revive-gate.js +136 -14
  7. package/dist/boot/permission-rules-audit.d.ts +49 -0
  8. package/dist/boot/permission-rules-audit.js +57 -0
  9. package/dist/boot/resolve-spec.js +43 -12
  10. package/dist/budget.js +22 -0
  11. package/dist/config-types.d.ts +17 -9
  12. package/dist/config.d.ts +28 -2
  13. package/dist/config.js +348 -79
  14. package/dist/governance-ask-marks.js +8 -2
  15. package/dist/http/route-ctx.d.ts +6 -3
  16. package/dist/http/routes/approvals-assistant.js +2 -1
  17. package/dist/http/routes/capabilities.js +44 -9
  18. package/dist/http/routes/rules.d.ts +19 -7
  19. package/dist/http/routes/rules.js +180 -4
  20. package/dist/http/server.d.ts +4 -2
  21. package/dist/http/server.js +81 -3
  22. package/dist/http/wire-types.d.ts +48 -0
  23. package/dist/main.js +19 -1
  24. package/dist/observability/fail-open.d.ts +4 -0
  25. package/dist/observability/fail-open.js +4 -0
  26. package/dist/observability/metrics.js +2 -1
  27. package/dist/observability/tool-trace.d.ts +5 -1
  28. package/dist/observability/tool-trace.js +33 -6
  29. package/dist/parked-decide.d.ts +13 -3
  30. package/dist/parked-decide.js +10 -1
  31. package/dist/plugins/permission-rule-store-file.d.ts +83 -0
  32. package/dist/plugins/permission-rule-store-file.js +371 -0
  33. package/dist/plugins/permission-rule-store-sql.d.ts +7 -0
  34. package/dist/plugins/permission-rule-store-sql.js +11 -0
  35. package/dist/plugins/store-backend.d.ts +12 -6
  36. package/dist/plugins/store-backend.js +58 -9
  37. package/dist/rules-consent.d.ts +69 -1
  38. package/dist/rules-consent.js +43 -1
  39. package/dist/run-local.js +120 -13
  40. package/dist/runtime-governance.js +9 -3
  41. package/dist/task-settings.d.ts +44 -0
  42. package/dist/task-settings.js +57 -1
  43. package/dist/tool-approval.d.ts +6 -1
  44. package/dist/tool-approval.js +106 -27
  45. package/dist/trace/core-keyset-guard.d.ts +13 -2
  46. package/dist/trace/project.d.ts +19 -2
  47. package/dist/trace/project.js +24 -4
  48. package/package.json +2 -2
@@ -430,9 +430,25 @@ export class ToolApprovalCoordinator {
430
430
  * `req.args` 是 `unknown` ⇒ 窄读,**禁裸 as-cast**(宪法 [2704]:一个形状漂了的 args 若被 cast,
431
431
  * 会把 `undefined` 当命令送进 `prepareCardApproval`,那是放宽面上的静默垃圾)。
432
432
  */
433
- buildRuleLaneMaterial(req) {
433
+ buildRuleLaneMaterial(req, owner, governanceForced) {
434
434
  if (this.ruleConsent === undefined)
435
435
  return undefined;
436
+ // 🔴 **治理档的 ask 不进规则车道**(#204 件7)。`governanceForced` 的语义(wire 契约逐字)是「这只
437
+ // ask 的门来自运维治理层,客户端表态掀不掉」;而一条持久规则会让引擎侧的规则命中臂短路掉后续
438
+ // **同命令**的 ask(core `hooks.js` 只排除 `decisionReason === "hook"` 与 `requiresRealApproval`),
439
+ // 于是一次「不再询问」= 静默拆掉 operator 的档位,且拆完之后遥测里连 ask 都不再出现。
440
+ // 与下面 `owner === null` 同族:这是**铸侧第一道门**,回决那侧 `persistRuleAfterDecision` 有同源的
441
+ // 第二道(拒因 `rule_governance_forced`)。两侧同源的理由见那一行。
442
+ if (governanceForced)
443
+ return undefined;
444
+ // 🔴 **归属人也是合取项**(codex 对抗复审 R1-F1,#203,验真后修)。这一条与回决那侧
445
+ // `persistRuleAfterDecision` 的第二道门(`owner === null ⇒ rule_lane_unavailable`)**同源** ——
446
+ // 一条规则是「**某个人**的持久同意」,没有人就没有桶可写。两侧不同源时的后果不是多问一次,而是
447
+ // 一张卡上渲出一格按下去恒被拒的「不再询问」= 本域顶注自己定义的 wire 谎言。
448
+ // 它在 #203 之前碰不到:local 车道没有规则店 ⇒ 上一行就返回了;本车给 local 接上 File 店之后,
449
+ // 「单机 + 无 principal」的部署第一次同时满足其余三项。
450
+ if (owner === null)
451
+ return undefined;
436
452
  const suggestions = req.ruleSuggestions;
437
453
  if (!Array.isArray(suggestions) || suggestions.length === 0)
438
454
  return undefined;
@@ -750,12 +766,24 @@ export class ToolApprovalCoordinator {
750
766
  const id = uuidv7();
751
767
  const child = isChildAsk(req);
752
768
  const bounded = boundArgs(req.args);
753
- // #154 车二:规则候选的投影素材。**三个合取项**,缺一不投:
769
+ // [2942]/[2943]:治理来源标的**一次** peek(非消费式)。此前它只在下面的帧字面量里就地读 ——
770
+ // #204 件7 起规则车道也要它(治理档的 ask 不进车道),两处读同一个值必须是**同一次** peek:
771
+ // 分开读会让「帧说这是治理门 / 车道说不是」这种自相矛盾的卡在理论上存在。
772
+ const governanceForced = (this.governanceAskMarks ?? governanceAskMarksFor(this.streamKey(primary.owner, primary.sessionId, origin.taskId))).isMarked(req.toolCallId);
773
+ // 🔴 **规则车道用的是本地 peek 与持久行的并集**(#204 件7,codex 对抗复审 R1-中,验真后修)。
774
+ // 本地表是进程内的:failover / 重启后幂等命中一条既有行时它恒空,而行上的卡里存着落库那一刻的判定。
775
+ // 下面 `ensureAsk` 的对账段按「行 = 真源」同步**帧上**那个键(那是出处属性,行说了算);但规则车道
776
+ // 是一次**放宽**动作,它的合成必须 fail-closed —— 任一侧说「治理门」就不进车道。只同步帧键会留下
777
+ // 一张「说是治理门却仍带 ruleSuggestions」的卡,且回决侧第二道门跟着没武装。
778
+ let effectiveGovernanceForced = governanceForced;
779
+ // #154 车二:规则候选的投影素材。**四个合取项**,缺一不投:
754
780
  // ① 规则店真装配(`this.ruleConsent` 在场)—— 店缺席仍投 = 无处可兑的「不再询问」= wire 谎言;
755
- // ② 引擎真铸了候选(`req.ruleSuggestions` 非空)—— 命令不可匹配(复合/重定向/替换)时 core 交空数组;
756
- // ③ 命令原字节读得出(回决时**引擎**要拿它重铸候选;读不出就没有可兑付的路,投候选等于骗人)
781
+ // ② 这只 ask 不是治理档产的(#204 件7)—— 治理门上铸规则 = 静默拆掉 operator 档位;
782
+ // ③ 引擎真铸了候选(`req.ruleSuggestions` 非空)—— 命令不可匹配(复合/重定向/替换)时 core 交空数组;
783
+ // ④ 命令原字节读得出(回决时**引擎**要拿它重铸候选;读不出就没有可兑付的路,投候选等于骗人)。
757
784
  // 读 `req.args.command` 走窄读:`args` 是 `unknown`,禁裸 as-cast(宪法 [2704])。
758
- const ruleLaneMaterial = this.buildRuleLaneMaterial(req);
785
+ // `let`:上面那条并集裁定可能在对账后把它撤回 undefined(行说这是治理门)
786
+ let ruleLaneMaterial = this.buildRuleLaneMaterial(req, primary.owner, governanceForced);
759
787
  const frame = {
760
788
  type: "tool_approval",
761
789
  approvalId: id,
@@ -784,10 +812,8 @@ export class ToolApprovalCoordinator {
784
812
  : {}),
785
813
  message: typeof req.message === "string" ? redactDeep(req.message) : "",
786
814
  // [2942]/[2943]:治理来源标(additive,只在为真时在场——缺席绝不编 false,见字段顶注)。
787
- // 读的是**非消费式** peek:同一 toolCallId 的重播/failover 再入必须拿到同一份判定。
788
- ...((this.governanceAskMarks ?? governanceAskMarksFor(this.streamKey(primary.owner, primary.sessionId, origin.taskId))).isMarked(req.toolCallId)
789
- ? { governanceForced: true }
790
- : {}),
815
+ // 值来自上方那一次**非消费式** peek(同一 toolCallId 的重播/failover 再入必须拿到同一份判定)。
816
+ ...(governanceForced ? { governanceForced: true } : {}),
791
817
  // #154 车二:候选逐字透传(条件见 `ruleLaneMaterial` 上方注)。
792
818
  ...(ruleLaneMaterial !== undefined ? { ruleSuggestions: ruleLaneMaterial.suggestions } : {}),
793
819
  ...(bounded.omitted ? { argsOmitted: true } : { args: bounded.args }),
@@ -952,6 +978,22 @@ export class ToolApprovalCoordinator {
952
978
  frame.governanceForced = true;
953
979
  else
954
980
  delete frame.governanceForced;
981
+ // 🔴 #204 件7(codex 对抗复审 R1-中,验真后修):行说这是治理门 ⇒ **规则车道当场撤回**。
982
+ // 上面那行只同步了帧上的出处键;素材、两族帧上的候选、以及回决侧第二道门的输入都还是按**本地**
983
+ // 表算的,而本地表在 failover / 重启后恒空。不撤就正是本件要防的形:一张卡同时带着
984
+ // `governanceForced:true` 与 `ruleSuggestions`,回决口还照收 —— operator 档位被一次点击拆掉。
985
+ // 撤三处(缺一即漏):①旧帧的候选键;②呈卡帧读的**持久卡**上的同名键(它来自行,不是本地铸的);
986
+ // ③素材本身(它是下面 `pendingEntry.ruleLane` 的唯一来源,也就是回决口的等式左边)。
987
+ // 反向(行说不是治理门、本地表说是)**刻意不放开**:见上方并集裁定 —— 少渲一格是安全方向。
988
+ if (cardForFrame.governanceForced === true && !effectiveGovernanceForced) {
989
+ effectiveGovernanceForced = true;
990
+ ruleLaneMaterial = undefined;
991
+ delete frame.ruleSuggestions;
992
+ if (cardForFrame.ruleSuggestions !== undefined) {
993
+ const { ruleSuggestions: _dropped, ...rest } = cardForFrame;
994
+ cardForFrame = rest;
995
+ }
996
+ }
955
997
  }
956
998
  catch (err) {
957
999
  this.noteStoreError(err, "ensureAsk");
@@ -1029,10 +1071,34 @@ export class ToolApprovalCoordinator {
1029
1071
  // 稍后才赋值的引用格(鸡生蛋:settle 是 pendingEntry 的一个字段,pendingEntry 建好之后才能把它自己
1030
1072
  // 塞进这个引用里)。
1031
1073
  let selfEntry;
1032
- const settle = (allowed, outcome, updatedInput) => {
1074
+ // core 5.23.0(#187/#114②):本次结算的**宿主自报来源**(见 {@link HostSettledBy})
1075
+ // 🔴 记在 `settle` 的 **done 卫兵之内**,不是在调用点旁边立一个标志:窗到期的臂是「先算、后 settle」,
1076
+ // 而 `settle` 可能因为人已经答过了而整个是空操作 —— 标志式写法会在那种赛跑里给一个**人类的**终局盖上
1077
+ // 「窗到期」的章。只有真正落定这次终局的那一次调用才有资格留下自报词。
1078
+ let hostSettled;
1079
+ /**
1080
+ * 已落定的本地终局 → `AskOutcome` 的**唯一**成形口。本方法有两个出口(方法尾的正常路,与
1081
+ * `earlyAbortDuringEnsure` 的早退路),它们此前各写了一份等价分派 —— 结果就是本批的 `settledBy`
1082
+ * 只加到了一份上(codex 复审 round2 抓获)。合成一个函数,判据只写一次。
1083
+ * · `windowRouteUnavailable` ⇒ park 路由(持久终局是 PARKING/PARKED,压过一切);
1084
+ * · allow + 编辑 ⇒ 对象臂带 `updatedInput`;
1085
+ * · deny + 宿主自报 ⇒ 对象臂带 `settledBy`(见 {@link HostSettledBy};allow 侧恒不带 —— core 对
1086
+ * `{allow:true, settledBy:"timeout"}` 是响亮拒,而自报只在 deny 分支产生);
1087
+ * · 其余 ⇒ 裸 boolean(falsy 纪律)。
1088
+ */
1089
+ const shapeOutcome = (settled) => {
1090
+ if (windowRouteUnavailable)
1091
+ return "unavailable";
1092
+ if (settled.allowed) {
1093
+ return settled.updatedInput !== undefined ? { allow: true, updatedInput: settled.updatedInput } : true;
1094
+ }
1095
+ return hostSettled !== undefined ? { allow: false, settledBy: hostSettled } : false;
1096
+ };
1097
+ const settle = (allowed, outcome, updatedInput, hostSettledBy) => {
1033
1098
  if (done)
1034
1099
  return;
1035
1100
  done = true;
1101
+ hostSettled = hostSettledBy;
1036
1102
  if (timer)
1037
1103
  clearTimeout(timer);
1038
1104
  for (const [sig, fn] of abortListeners)
@@ -1123,7 +1189,9 @@ export class ToolApprovalCoordinator {
1123
1189
  const windowExpired = () => {
1124
1190
  void (async () => {
1125
1191
  if (!this.askStore || askId === undefined || batchId === undefined) {
1126
- settle(false, "expired");
1192
+ // D1(无持久 ask 店)——终局**完全**由本仓这个窗决定,没有第二个写者。core 5.23.0 起如实自报
1193
+ // `"timeout"`:这条腿的窗归宿主,引擎观察不到它走完,不说就被记成「一个人拒绝了」。
1194
+ settle(false, "expired", undefined, "timeout");
1127
1195
  return;
1128
1196
  }
1129
1197
  try {
@@ -1157,7 +1225,10 @@ export class ToolApprovalCoordinator {
1157
1225
  // 两种结局都成立:真提交了 ⇒ 与行一致;没提交 ⇒ core 自己走 durable park,不劣于 deny。
1158
1226
  if (err instanceof ApprovalStoreDeadlineError)
1159
1227
  windowRouteUnavailable = true;
1160
- settle(false, "expired"); // fail-open(D5):店抖动不挡窗到期的进程内机械照旧完成
1228
+ // fail-open(D5):店抖动不挡窗到期的进程内机械照旧完成。自报仍是 `"timeout"` 且如实 —— 结束这次
1229
+ // 等待的**确实**是本仓的窗(店只是没能给出更精确的终局);deadline 那一支已经改判 park 路由,
1230
+ // 走不到用得上这个词的返回臂。
1231
+ settle(false, "expired", undefined, "timeout");
1161
1232
  }
1162
1233
  })();
1163
1234
  };
@@ -1271,12 +1342,11 @@ export class ToolApprovalCoordinator {
1271
1342
  // `done` 为真而落定值是 `true`,写死 false 会把一次真实的人类批准悄悄吞成拒绝(且与 durable 行矛盾)。
1272
1343
  // 改成把**已经落定的那个值**如实交回去(与方法尾部的返回值分派同一套判据)。
1273
1344
  if (done) {
1274
- const early = await allowedP;
1275
- if (windowRouteUnavailable)
1276
- return "unavailable"; // 持久终局是 PARKING/PARKED park 路由
1277
- if (early.allowed && early.updatedInput !== undefined)
1278
- return { allow: true, updatedInput: early.updatedInput };
1279
- return early.allowed;
1345
+ // 🔴 [3372] codex 复审 round2(验真后修):这里**曾经**是尾部那套分派的手抄第二份,于是本批给
1346
+ // 窗到期加的 `settledBy` 自报只进了尾部、没进这里 —— 「窗先落定、取消臂随后才走完」的交错里,
1347
+ // 一个由**窗**决定的终局从这个出口以裸 `false` 溜出去,core 照旧记成人类拒绝。两个出口现在共用
1348
+ // {@link shapeOutcome} 这**一个**成形函数,结构上不可能再分家(红先钉:codex-R2-2)
1349
+ return shapeOutcome(await allowedP);
1280
1350
  }
1281
1351
  }
1282
1352
  // Register BEFORE emitting so a (fast) respond can never miss the entry. 子代 ask 的生存期挂子代
@@ -1291,9 +1361,12 @@ export class ToolApprovalCoordinator {
1291
1361
  ...(child ? { originTaskId: req.sourceTaskId } : { targetCtxs: new Set(aliveCtxs), onTargetGone }),
1292
1362
  ...(primary.sessionId ? { sessionId: primary.sessionId } : {}),
1293
1363
  toolName: req.toolName,
1294
- // #154 车二:规则车道素材 —— 与帧上的 `ruleSuggestions` **同一个条件**(店在场 ∧ 引擎真铸了候选
1295
- // 命令原字节读得出)。三者任一不成立 ⇒ 不登记,回决带 `persistRule` 时如实拒(不猜命令)。
1364
+ // #154 车二:规则车道素材 —— 与帧上的 `ruleSuggestions` **同一个条件**(店在场 ∧ 非治理档 引擎真
1365
+ // 铸了候选 ∧ 命令原字节读得出)。任一不成立 ⇒ 不登记,回决带 `persistRule` 时如实拒(不猜命令)。
1296
1366
  ...(ruleLaneMaterial !== undefined ? { ruleLane: ruleLaneMaterial } : {}),
1367
+ // #204 件7:治理标随条目落定(回决侧第二道门的输入,理由见字段顶注)。读的是**并集后**的值
1368
+ // (`effectiveGovernanceForced`),不是本地 peek —— 幂等命中既有行那条路上,只有并集才是武装的。
1369
+ ...(effectiveGovernanceForced ? { governanceForced: true } : {}),
1297
1370
  ...(askId !== undefined && batchId !== undefined ? { askId, batchId } : {}),
1298
1371
  };
1299
1372
  selfEntry = pendingEntry; // 供 settle() 结算时把自己从 pendingByAskId 的 Set 里摘除(上方顶注)
@@ -1448,13 +1521,12 @@ export class ToolApprovalCoordinator {
1448
1521
  // 方法顶注。
1449
1522
  if (!emitSettled && lastOutcome === "expired")
1450
1523
  return "unavailable";
1451
- // [1458] 编辑放行对象臂:core AskOutcome 公开类型已含 `{allow, updatedInput?}`([1462])——
1452
- // 宽铸退役,真类型直返(core BAGGAGE #17 严格化前提=对象臂 allow 恒严格 true,我方满足)。
1453
- // deny 恒返 false(falsy 纪律)。
1454
- if (settled.allowed && settled.updatedInput !== undefined) {
1455
- return { allow: true, updatedInput: settled.updatedInput };
1456
- }
1457
- return settled.allowed;
1524
+ // [1458] 编辑放行对象臂 + core 5.23.0(#187/#114②)宿主自报臂 —— 两条判据都在
1525
+ // {@link shapeOutcome} 里,与早退出口**共用同一份**(codex 复审 round2:此前这里与早退臂各写一份
1526
+ // 等价分派,本批的 `settledBy` 于是只加到了其中一份上)。
1527
+ // 到得了这里说明卡**确实送达过**(emit 全灭 / park 路由两条早就 return 掉了),所以自报的「窗到期」
1528
+ // 是这次等待的真实结局,而不是「没人可送」。
1529
+ return shapeOutcome(settled);
1458
1530
  }
1459
1531
  /** `POST /v1/tool-approvals/:id/respond` — settle a parked approval with the shell's decision. Owner-gated with a
1460
1532
  * 404 (no existence oracle), body validated first (400 is existence-independent) — question/steer parity. The HTTP
@@ -1507,6 +1579,13 @@ export class ToolApprovalCoordinator {
1507
1579
  status: result.status,
1508
1580
  body: { ...result.body, rulePersisted, ...(ruleRefusal !== undefined ? { ruleRefusal } : {}) },
1509
1581
  });
1582
+ // 🔴 **治理档第二道门**(#204 件7),排在 `rule_lane_unavailable` 那道**之前**:两道门都会命中一只
1583
+ // 治理 ask(铸侧不登记素材 ⇒ `material === undefined`),但回给壳的词必须是精确的那一个 ——
1584
+ // `rule_lane_unavailable` 意为「这台部署压根不供规则」,壳据它可以从此不渲这一格,而这里的真相是
1585
+ // 「车道好好的,只是**这一只** ask 归 operator 管」。与铸侧 `buildRuleLaneMaterial` 的同源门成对:
1586
+ // 少了这一道,一次回滚/旧壳(或将来某条不经铸侧登记的回决路径)就能把 operator 档位绕过去。
1587
+ if (entry.governanceForced === true)
1588
+ return withFlag(false, "rule_governance_forced");
1510
1589
  if (lane === undefined || material === undefined || owner === null)
1511
1590
  return withFlag(false, "rule_lane_unavailable");
1512
1591
  // 🔴 codex 交叉复审 round3 [high] 二(验真后修,**真扩权面**):`updatedInput`(ctrl+g 编辑放行)
@@ -15,7 +15,7 @@
15
15
  * 维护:core 新键到来时,transparently 透传的加进对应 PROJECTED 并去实现投影;有意不上 wire 的加进
16
16
  * EXCLUDED 并写一行理由。谁改投影(project.ts / fleet-bus.ts / roster-store-sql.ts)谁同步本文件。
17
17
  */
18
- import type { TaskNotificationPayload, BackgroundChildEvent, RosterEntry, AskRequest, TaskEvent, MailboxMessage, MailboxStore } from "@sema-agent/core";
18
+ import type { TaskNotificationPayload, BackgroundChildEvent, RosterEntry, AskRequest, TaskEvent, TraceEvent, MailboxMessage, MailboxStore } from "@sema-agent/core";
19
19
  /** 编译期断言:T 必须收敛到 never(有残余键 = tsc 红)。 */
20
20
  type AssertAllKeysHandled<T extends never> = T;
21
21
  type NotificationProjected = "task_id" | "task_type" | "toolUseId" | "status" | "summary" | "result" | "output_file" | "usage" | "sessionId" | "seq" | "lines" | "stoppedBy" | "source" | "exitCode" | "partial" | "diagnostics" | "recentSteps" | "editedFiles" | "resumable" | "completionId" | "error" | "errorCode";
@@ -36,6 +36,14 @@ type _GuardMailbox = AssertAllKeysHandled<Exclude<keyof MailboxMessage, MailboxP
36
36
  type MailboxAppendMessage = Parameters<MailboxStore["append"]>[2];
37
37
  type MailboxAppendProjected = "from" | "content" | "sentAt" | "hopChain";
38
38
  type _GuardMailboxAppend = AssertAllKeysHandled<Exclude<keyof MailboxAppendMessage, MailboxAppendProjected>>;
39
+ type PermissionTraceKind = Extract<TraceEvent["kind"], `permission.${string}`>;
40
+ type PermissionTraceProjected = "permission.persisted_rule_allowed" | "permission.rule_store_unreadable";
41
+ type PermissionTraceDropped = "permission.sandbox_admitted" | "permission.rule_sync_resurrected" | "permission.rule_sync_dropped" | "permission.org_snapshot_unavailable";
42
+ type _GuardPermissionTrace = AssertAllKeysHandled<Exclude<PermissionTraceKind, PermissionTraceProjected | PermissionTraceDropped>>;
43
+ type _GuardPermissionTraceReverse = AssertAllKeysHandled<Exclude<PermissionTraceProjected | PermissionTraceDropped, PermissionTraceKind>>;
44
+ /** `T` 非 `never` 时收敛到 `never`(空集 ⇒ 残留 `"EMPTY"` ⇒ tsc 红)。 */
45
+ type AssertNonEmpty<T> = [T] extends [never] ? "EMPTY" : never;
46
+ type _GuardPermissionTraceNonEmpty = AssertAllKeysHandled<AssertNonEmpty<PermissionTraceKind>>;
39
47
  export type CoreKeysetGuards = [
40
48
  _GuardNotification,
41
49
  _GuardBgNotif,
@@ -43,7 +51,10 @@ export type CoreKeysetGuards = [
43
51
  _GuardAsk,
44
52
  _GuardTaskEvent,
45
53
  _GuardMailbox,
46
- _GuardMailboxAppend
54
+ _GuardMailboxAppend,
55
+ _GuardPermissionTrace,
56
+ _GuardPermissionTraceReverse,
57
+ _GuardPermissionTraceNonEmpty
47
58
  ];
48
59
  export {};
49
60
  //# sourceMappingURL=core-keyset-guard.d.ts.map
@@ -291,8 +291,25 @@ export declare function workspaceChangedEventData(ev: {
291
291
  * 某个可选设施」的部署形自述,而那三个早已租户可见。消费端的真需求也在租户侧:审批卡要不要渲染
292
292
  * 「不再询问」这一格,取决于这台 worker 有没有规则店 —— 剥掉它,客户端只能靠试(提交后被拒)才知道。
293
293
  * 它**不在** `governance` 段里(core 自己把它放在段外),也不构成那 4 个治理布尔的侧信道。
294
- * ⚠️ 恒在段(core 侧 `permissionRules` 无 `?`):本仓未接规则店 `storeWired: false`,这是**真值**
295
- * 不是缺席 —— 消费端读到 false 就是「这台 worker 没有这条车道」,不许当成「这版引擎还没这个概念」。
294
+ * ⚠️ 恒在段(core 侧 `permissionRules` 无 `?`):两个真值都可能出现,**别把 false 读成缺席**。
295
+ * 本仓自 #154 车二 / design/203 起真接了规则店(`main.ts` 在 `PERMISSION_RULES_ENABLED` 为真 ∧
296
+ * backend 在场时把 `permissionRuleStore` 装进 `RunnerDeps`,SQL 与 File 两形)⇒ 那样的部署报
297
+ * `storeWired: true`;旋钮关掉或后端缺席的部署仍报 `false`。消费端读到 false 就是「这台 worker
298
+ * 今天没有这条车道」,不许当成「这版引擎还没这个概念」。
299
+ * · `permissionRules.syncWired` / `permissionRules.orgGoverned`(布尔两位;core 5.23.0 design/182 §9/§7
300
+ * 给**同一段**加的员,[3372] 提货批 codex 复审 high 抓获——首版把它们静默剥了,正是本仓建这份台账要防
301
+ * 的「投影白名单剥键」病)—— **裁:租户可见,与同段的 `storeWired` 同待遇**。判据三条:
302
+ * ① **同段同类**:core 把它们放进 `permissionRules` 而不是 `governance`(唯一带 audience 标签的段),
303
+ * 且形状与 `storeWired` 逐字同构(恒在的布尔,present-and-false 表示「有这个概念,这里没开」);
304
+ * ② **消费端真需求在租户侧**:这两位说的是「按下『不再询问』之后,这条同意到底落到多大的信任域里」——
305
+ * `syncWired` = 这台 worker 会把规则并进跨设备/传输/服务端的**同一个同意信任域**;`orgGoverned` =
306
+ * 组织治理层武装着(且它的 fail-closed 契约意味着读不到 org 快照时**每一次**放行都会被抬成真人审批)。
307
+ * 同一张审批卡上的同一个动作,后果因这两位而不同,剥掉它们等于让客户端渲一格语义不明的开关;
308
+ * ③ **不是治理侧信道**:两位都不参与 `governance` 那 4 个布尔的还原(它们描述规则店这条车道自身的
309
+ * 形状,与 lockedConfig/compliance/memoryAdmission/retention 无函数关系)。
310
+ * ⚠️ 与 `storeWired` **不同**:本仓这两条至今未接(`RunnerDeps.permissionRuleSyncWired` /
311
+ * `permissionRuleOrg` 全树零声明点,2026-08-10 #204 复扫亲验)⇒ 今天恒 `false`,这是**真值**不是
312
+ * 缺席。哪天接上其中任一条,连本句一起改 —— 这一行是它的登记处。
296
313
  * 未来 core 加一段而它没有 audience 标签时,**先在这里补一条裁定再决定挑不挑键** —— 段级完备性钉
297
314
  * (test/wiring-manifest-projection.test.ts)会在那一刻先把人拦下来。
298
315
  */
@@ -381,8 +381,25 @@ export function workspaceChangedEventData(ev) {
381
381
  * 某个可选设施」的部署形自述,而那三个早已租户可见。消费端的真需求也在租户侧:审批卡要不要渲染
382
382
  * 「不再询问」这一格,取决于这台 worker 有没有规则店 —— 剥掉它,客户端只能靠试(提交后被拒)才知道。
383
383
  * 它**不在** `governance` 段里(core 自己把它放在段外),也不构成那 4 个治理布尔的侧信道。
384
- * ⚠️ 恒在段(core 侧 `permissionRules` 无 `?`):本仓未接规则店 `storeWired: false`,这是**真值**
385
- * 不是缺席 —— 消费端读到 false 就是「这台 worker 没有这条车道」,不许当成「这版引擎还没这个概念」。
384
+ * ⚠️ 恒在段(core 侧 `permissionRules` 无 `?`):两个真值都可能出现,**别把 false 读成缺席**。
385
+ * 本仓自 #154 车二 / design/203 起真接了规则店(`main.ts` 在 `PERMISSION_RULES_ENABLED` 为真 ∧
386
+ * backend 在场时把 `permissionRuleStore` 装进 `RunnerDeps`,SQL 与 File 两形)⇒ 那样的部署报
387
+ * `storeWired: true`;旋钮关掉或后端缺席的部署仍报 `false`。消费端读到 false 就是「这台 worker
388
+ * 今天没有这条车道」,不许当成「这版引擎还没这个概念」。
389
+ * · `permissionRules.syncWired` / `permissionRules.orgGoverned`(布尔两位;core 5.23.0 design/182 §9/§7
390
+ * 给**同一段**加的员,[3372] 提货批 codex 复审 high 抓获——首版把它们静默剥了,正是本仓建这份台账要防
391
+ * 的「投影白名单剥键」病)—— **裁:租户可见,与同段的 `storeWired` 同待遇**。判据三条:
392
+ * ① **同段同类**:core 把它们放进 `permissionRules` 而不是 `governance`(唯一带 audience 标签的段),
393
+ * 且形状与 `storeWired` 逐字同构(恒在的布尔,present-and-false 表示「有这个概念,这里没开」);
394
+ * ② **消费端真需求在租户侧**:这两位说的是「按下『不再询问』之后,这条同意到底落到多大的信任域里」——
395
+ * `syncWired` = 这台 worker 会把规则并进跨设备/传输/服务端的**同一个同意信任域**;`orgGoverned` =
396
+ * 组织治理层武装着(且它的 fail-closed 契约意味着读不到 org 快照时**每一次**放行都会被抬成真人审批)。
397
+ * 同一张审批卡上的同一个动作,后果因这两位而不同,剥掉它们等于让客户端渲一格语义不明的开关;
398
+ * ③ **不是治理侧信道**:两位都不参与 `governance` 那 4 个布尔的还原(它们描述规则店这条车道自身的
399
+ * 形状,与 lockedConfig/compliance/memoryAdmission/retention 无函数关系)。
400
+ * ⚠️ 与 `storeWired` **不同**:本仓这两条至今未接(`RunnerDeps.permissionRuleSyncWired` /
401
+ * `permissionRuleOrg` 全树零声明点,2026-08-10 #204 复扫亲验)⇒ 今天恒 `false`,这是**真值**不是
402
+ * 缺席。哪天接上其中任一条,连本句一起改 —— 这一行是它的登记处。
386
403
  * 未来 core 加一段而它没有 audience 标签时,**先在这里补一条裁定再决定挑不挑键** —— 段级完备性钉
387
404
  * (test/wiring-manifest-projection.test.ts)会在那一刻先把人拦下来。
388
405
  */
@@ -434,10 +451,13 @@ export function wiringManifestEventData(ev) {
434
451
  ...(bool(fleetIn.backgroundAgentStore) !== undefined ? { backgroundAgentStore: fleetIn.backgroundAgentStore } : {}),
435
452
  ...(bool(fleetIn.hostChildEventSink) !== undefined ? { hostChildEventSink: fleetIn.hostChildEventSink } : {}),
436
453
  });
437
- // core 5.18.0 design/179 段(裁定见头注:租户可见)。逐键挑,与其余段同规:非布尔按缺席处理
438
- // (不铸假值——一个编出来的 `false` 会被读成「确实没接规则店」,而真相是这段读不出来)。
454
+ // core 5.18.0 design/179 段 + 5.23.0 design/182 §9/§7 两位加员(裁定见头注:三位同判租户可见)
455
+ // 逐键挑,与其余段同规:非布尔按缺席处理(不铸假值——一个编出来的 `false` 会被读成「确实没接/没开」,
456
+ // 而真相是这段读不出来)。
439
457
  const permissionRules = nonEmpty({
440
458
  ...(bool(rulesIn.storeWired) !== undefined ? { storeWired: rulesIn.storeWired } : {}),
459
+ ...(bool(rulesIn.syncWired) !== undefined ? { syncWired: rulesIn.syncWired } : {}),
460
+ ...(bool(rulesIn.orgGoverned) !== undefined ? { orgGoverned: rulesIn.orgGoverned } : {}),
441
461
  });
442
462
  return {
443
463
  ...(num(m.schemaVersion) !== undefined ? { schemaVersion: m.schemaVersion } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "7.11.0",
3
+ "version": "7.12.0",
4
4
  "description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -54,7 +54,7 @@
54
54
  "build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
55
55
  },
56
56
  "dependencies": {
57
- "@sema-agent/core": "^5.22.0",
57
+ "@sema-agent/core": "^5.23.0",
58
58
  "@sema-agent/registry-core": "^0.16.0",
59
59
  "e2b": "^2.28.0",
60
60
  "libsodium-wrappers": "^0.8.4",