progmune-runtime 3.7.65 → 3.7.66

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.7.66] — 2026-10-12
4
+
5
+ ### 队列作业调用方传播(修冤枉第七刀:授权族告警的「只被作业调用的函数没有请求面」根因)
6
+
7
+ **根因**:第六刀(3.7.65)只豁免了作业处理器**自身**——docmost 残留
8
+ 告警中 service/repo 层函数只被 @Processor/@Interval 作业调用(鉴权
9
+ 发生在入队侧 controller,队列下游函数体不面向 HTTP 请求),授权族
10
+ 「缺鉴权/缺属主校验」的提问在它们身上同样不适用。
11
+
12
+ - **机制**:`computeJobSurfacePropagatedSet`(protocol-detector.ts,
13
+ 与第五刀镜像但判据不同)——**B 判据:调用方裸名并集**。凡调用名
14
+ 裸化后等于 F 裸名的函数都是「可能调用方」;并集非空且**全部**
15
+ 已豁免(种子 = 第六刀的 `__progmune_no_request_surface__` 标记)
16
+ ⇒ F 豁免。裸化只增候选(限定形态调用名也进并集)⇒ 真实调用方
17
+ 必在并集中(R75 正向证据)。**歧义下比第五刀更保守**:同名族
18
+ 任一成员被非作业调用,整个裸名族都不豁免(逐边消歧在作业链
19
+ Processor→XxxService 上恒失败——docmost 实测只剩同文件层 4 条)
20
+ - **快照轮次语义**,默认 1 轮(只豁免直接作业下游;传递闭包
21
+ docmost 实测 +16 条但豁免集 120→173,留作数据驱动再开);
22
+ `PROGMUNE_JOB_SURFACE_TOGGLE=off` 环境变量逃生门
23
+ - **标记而非静默丢弃(R9)**:被压告警进 `overall.safeguardAlerts.
24
+ suppressed` 清单(带 suppressedByJobSurfacePropagation 标记),
25
+ 不进 total/groups/topRanked 主计数;豁免仅作用于 authorization
26
+ 类、且不作用于自身直接带标记的函数(R5/R6)
27
+ - **同条件 A/B(docmost 双扫描,fresh IR)**:主告警流 720→697
28
+ (−23 = Unauthenticated Access 14 + Ownership Check 5 +
29
+ Unauthenticated Mutation 4),suppressed 117→140(+23 全带标记);
30
+ **主路径 violations 11=11 内容级零差 / score 84=84 / NEEDS_REVIEW
31
+ 不变**;control(toggle off)= 720/117 与 3.7.65 基线告警集逐位
32
+ 一致 ⇒ 压降完全归因本刀
33
+ - **23 条被压逐条核对**(blind-benchmark/verify-job-surface-66.ts):
34
+ 调用方并集全部为 @Processor 作业(NotificationProcessor.process /
35
+ onCompleted / handleFailedImportJob)或 @Interval 清理作业
36
+ (cleanupOldTrash / cleanupSessions),TP 0;同名族保守性独立复核
37
+ 通过(无任何被压函数存在非作业调用方)
38
+ - **回归锁定**:单元测试 11 例(protocol-detector-job-surface.test.ts:
39
+ 全作业传播/同名裸名多候选并集豁免(第五刀此处会阻断)/同名族保守/
40
+ 混合调用方 fail-safe/无调用方/1 轮边界/maxHops/自调无豁免证据/
41
+ 限定调用名裸化/种子语义/自定义标记)+ 引擎产品路径 2 例
42
+ (tests/trust/engine.test.ts:作业下游进 suppressed 不进主计数 +
43
+ HTTP 对照组照常上报);引擎套件 27/27;
44
+ taintpath 160 / webshape 72 / fr-007 5-0 / fr-016 7-0 维持;
45
+ TS 盲测 LOST 0 / ADDED 0(对 3.7.65 提交基线逐旗标一致;
46
+ batch-scan 不经过引擎告警循环,此门对本改动空过,
47
+ 真实门 = docmost A/B + 定向测试,如实记录)
48
+
3
49
  ## [3.7.65] — 2026-10-11
4
50
 
5
51
  ### 队列/定时/事件处理器无请求面豁免(修冤枉第六刀:授权族告警的「作业处理器不是请求面」根因)
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const protocol_detector_1 = require("./protocol-detector");
5
+ /**
6
+ * 队列作业调用方传播(修冤枉第七刀,2026-10-11)单元测试。
7
+ *
8
+ * B 判据(与第五刀逐边消歧镜像,但判据不同):调用方**裸名并集**——
9
+ * 凡调用名等于 F 裸名的函数都是「可能调用方」;并集非空且全部已豁免
10
+ * (种子 = 带 __progmune_no_request_surface__ 标记的作业处理器)
11
+ * ⇒ F 豁免。歧义下比逐边消歧更保守:同名族任一成员被非作业调用,
12
+ * 整个裸名族都不豁免。快照轮次语义;默认 1 轮。
13
+ * 消费方(engine)负责 R5/R6:只压 authorization 类、且不压直接标记函数。
14
+ */
15
+ const M = "__progmune_no_request_surface__";
16
+ (0, vitest_1.describe)("computeJobSurfacePropagatedSet(作业调用方传播 B 判据)", () => {
17
+ (0, vitest_1.it)("全部调用方都是作业 ⇒ 传播(processor→service)", () => {
18
+ const fns = [
19
+ { name: "NotificationProcessor.process", file: "q.ts", calls: [M, "processPageUpdate"] },
20
+ { name: "PageNotificationService.processPageUpdate", file: "s.ts", calls: ["insertLog"] },
21
+ ];
22
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns);
23
+ (0, vitest_1.expect)(g.has("PageNotificationService.processPageUpdate")).toBe(true);
24
+ });
25
+ (0, vitest_1.it)("调用方含 HTTP 面 ⇒ 不豁免(fail-safe)", () => {
26
+ const fns = [
27
+ { name: "NotificationProcessor.process", file: "q.ts", calls: [M, "processPageUpdate"] },
28
+ { name: "PageController.getPage", file: "c.ts", calls: ["processPageUpdate"] },
29
+ { name: "PageNotificationService.processPageUpdate", file: "s.ts", calls: [] },
30
+ ];
31
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns);
32
+ (0, vitest_1.expect)(g.has("PageNotificationService.processPageUpdate")).toBe(false);
33
+ });
34
+ (0, vitest_1.it)("无调用方(入口函数)⇒ 不豁免", () => {
35
+ const fns = [{ name: "bootstrap", file: "m.ts", calls: [] }];
36
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns);
37
+ (0, vitest_1.expect)(g.has("bootstrap")).toBe(false);
38
+ });
39
+ (0, vitest_1.it)("默认 1 轮:只豁免作业直接下游,不传递闭包", () => {
40
+ const fns = [
41
+ { name: "Job.run", file: "q.ts", calls: [M, "step1"] },
42
+ { name: "Svc.step1", file: "s.ts", calls: ["step2"] },
43
+ { name: "Repo.step2", file: "r.ts", calls: [] },
44
+ ];
45
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns);
46
+ (0, vitest_1.expect)(g.has("Svc.step1")).toBe(true);
47
+ (0, vitest_1.expect)(g.has("Repo.step2")).toBe(false);
48
+ });
49
+ (0, vitest_1.it)("maxHops=2 时传递两跳", () => {
50
+ const fns = [
51
+ { name: "Job.run", file: "q.ts", calls: [M, "step1"] },
52
+ { name: "Svc.step1", file: "s.ts", calls: ["step2"] },
53
+ { name: "Repo.step2", file: "r.ts", calls: [] },
54
+ ];
55
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns, { maxHops: 2 });
56
+ (0, vitest_1.expect)(g.has("Svc.step1")).toBe(true);
57
+ (0, vitest_1.expect)(g.has("Repo.step2")).toBe(true);
58
+ });
59
+ (0, vitest_1.it)("同名族保守:任一成员被非作业调用,整个裸名族都不豁免", () => {
60
+ const fns = [
61
+ { name: "Job.run", file: "q.ts", calls: [M, "create"] },
62
+ { name: "PageService.create", file: "s.ts", calls: [] },
63
+ { name: "CommentService.create", file: "c2.ts", calls: [] },
64
+ { name: "PageController.post", file: "c.ts", calls: ["create"] },
65
+ ];
66
+ // create 裸名族含 CommentService.create——PageController.post(非作业)
67
+ // 也在调用方并集中 ⇒ 族级不豁免,即使 Job.run 只调 PageService.create
68
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns);
69
+ (0, vitest_1.expect)(g.has("PageService.create")).toBe(false);
70
+ (0, vitest_1.expect)(g.has("CommentService.create")).toBe(false);
71
+ });
72
+ (0, vitest_1.it)("限定形态调用名(Class.method)按裸名命中", () => {
73
+ const fns = [
74
+ { name: "Job.run", file: "q.ts", calls: [M, "PageService.processUpdate"] },
75
+ { name: "PageService.processUpdate", file: "s.ts", calls: [] },
76
+ ];
77
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns);
78
+ (0, vitest_1.expect)(g.has("PageService.processUpdate")).toBe(true);
79
+ });
80
+ (0, vitest_1.it)("直接标记函数在返回集合中(种子语义,R6 由消费方过滤)", () => {
81
+ const fns = [{ name: "Job.run", file: "q.ts", calls: [M] }];
82
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns);
83
+ (0, vitest_1.expect)(g.has("Job.run")).toBe(true);
84
+ });
85
+ (0, vitest_1.it)("自定义标记通道", () => {
86
+ const fns = [
87
+ { name: "A", file: "f.ts", calls: ["__custom_mark__", "B"] },
88
+ { name: "B", file: "f.ts", calls: [] },
89
+ ];
90
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns, { marker: "__custom_mark__" });
91
+ (0, vitest_1.expect)(g.has("B")).toBe(true);
92
+ // 默认标记下不传播
93
+ const g2 = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns);
94
+ (0, vitest_1.expect)(g2.has("B")).toBe(false);
95
+ });
96
+ });
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const protocol_detector_1 = require("./protocol-detector");
5
+ /**
6
+ * 作业调用方传播(修冤枉第七刀,2026-10-11)单元测试。
7
+ *
8
+ * B 判据(裸名并集,见 computeJobSurfacePropagatedSet 注释):
9
+ * 函数的调用方并集(调用名=裸名的所有函数)非空且全部带
10
+ * __progmune_no_request_surface__ 标记 ⇒ 豁免;默认 1 轮。
11
+ * 正确性前提:IR 调用名即裸名 ⇒ 真实调用方必在并集中 ⇒
12
+ * 并集全作业蕴含真实调用方全作业(R75 正向证据)。
13
+ * 与第五刀的差别:歧义不阻断——同名族任一成员被非作业调用,
14
+ * 整个裸名族都不豁免(比逐边消歧更保守的族级行为)。
15
+ * 消费方(engine)负责 R5/R6:只压 authorization 类、且不压直接标记函数。
16
+ */
17
+ const M = "__progmune_no_request_surface__";
18
+ (0, vitest_1.describe)("computeJobSurfacePropagatedSet(作业调用方传播,B 判据)", () => {
19
+ (0, vitest_1.it)("全部调用方是作业 ⇒ 传播(Processor→Service)", () => {
20
+ const fns = [
21
+ { name: "NotificationProcessor.process", file: "q/notification.processor.ts", calls: [M, "processPageUpdate"] },
22
+ { name: "PageNotificationService.processPageUpdate", file: "s/page-notification.service.ts", calls: ["insertNotification"] },
23
+ ];
24
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns);
25
+ (0, vitest_1.expect)(g.has("PageNotificationService.processPageUpdate")).toBe(true);
26
+ });
27
+ (0, vitest_1.it)("同名裸名多候选且调用方全作业 ⇒ 并集判据豁免(第五刀此处会阻断)", () => {
28
+ // processDigest 裸名有两个定义;调用方是两个不同的作业
29
+ // 处理器。逐边消歧(第五刀)会挂未解析边阻断;并集判据
30
+ // 检查「所有可能调用方全作业」⇒ 豁免。
31
+ const fns = [
32
+ { name: "EmailProcessor.process", file: "q/email.processor.ts", calls: [M, "processDigest"] },
33
+ { name: "DigestProcessor.process", file: "q/digest.processor.ts", calls: [M, "processDigest"] },
34
+ { name: "PageNotificationService.processDigest", file: "s/page.service.ts", calls: [] },
35
+ { name: "CommentNotificationService.processDigest", file: "s/comment.service.ts", calls: [] },
36
+ ];
37
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns);
38
+ (0, vitest_1.expect)(g.has("PageNotificationService.processDigest")).toBe(true);
39
+ (0, vitest_1.expect)(g.has("CommentNotificationService.processDigest")).toBe(true);
40
+ });
41
+ (0, vitest_1.it)("同名族任一成员被非作业调用 ⇒ 整个裸名族不豁免(保守族级)", () => {
42
+ const fns = [
43
+ { name: "EmailProcessor.process", file: "q.ts", calls: [M, "processDigest"] },
44
+ { name: "PageController.show", file: "c.ts", calls: ["processDigest"] },
45
+ { name: "PageNotificationService.processDigest", file: "s1.ts", calls: [] },
46
+ { name: "CommentNotificationService.processDigest", file: "s2.ts", calls: [] },
47
+ ];
48
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns);
49
+ (0, vitest_1.expect)(g.has("PageNotificationService.processDigest")).toBe(false);
50
+ (0, vitest_1.expect)(g.has("CommentNotificationService.processDigest")).toBe(false);
51
+ });
52
+ (0, vitest_1.it)("调用方含非作业 ⇒ 不豁免(fail-safe)", () => {
53
+ const fns = [
54
+ { name: "EmailProcessor.process", file: "q.ts", calls: [M, "markAsEmailed"] },
55
+ { name: "NotificationService.markAsEmailedDirect", file: "s.ts", calls: ["markAsEmailed"] },
56
+ { name: "NotificationRepo.markAsEmailed", file: "r.ts", calls: [] },
57
+ ];
58
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns);
59
+ (0, vitest_1.expect)(g.has("NotificationRepo.markAsEmailed")).toBe(false);
60
+ });
61
+ (0, vitest_1.it)("无调用方(入口/未连通)⇒ 不豁免", () => {
62
+ const fns = [
63
+ { name: "orphanJobHelper", file: "m.ts", calls: [] },
64
+ ];
65
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns);
66
+ (0, vitest_1.expect)(g.has("orphanJobHelper")).toBe(false);
67
+ });
68
+ (0, vitest_1.it)("默认 1 轮:作业下游的下游不豁免(传递闭包未开)", () => {
69
+ const fns = [
70
+ { name: "NotificationProcessor.process", file: "q.ts", calls: [M, "processPageUpdate"] },
71
+ { name: "PageNotificationService.processPageUpdate", file: "s.ts", calls: ["insertNotification"] },
72
+ { name: "NotificationRepo.insertNotification", file: "r.ts", calls: [] },
73
+ ];
74
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns);
75
+ (0, vitest_1.expect)(g.has("PageNotificationService.processPageUpdate")).toBe(true);
76
+ (0, vitest_1.expect)(g.has("NotificationRepo.insertNotification")).toBe(false);
77
+ });
78
+ (0, vitest_1.it)("maxHops=2 ⇒ 传递两跳成立(快照轮次语义)", () => {
79
+ const fns = [
80
+ { name: "NotificationProcessor.process", file: "q.ts", calls: [M, "processPageUpdate"] },
81
+ { name: "PageNotificationService.processPageUpdate", file: "s.ts", calls: ["insertNotification"] },
82
+ { name: "NotificationRepo.insertNotification", file: "r.ts", calls: [] },
83
+ ];
84
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns, { maxHops: 2 });
85
+ (0, vitest_1.expect)(g.has("NotificationRepo.insertNotification")).toBe(true);
86
+ });
87
+ (0, vitest_1.it)("自调不提供豁免证据(自己不是作业 ⇒ 不能靠自调豁免)", () => {
88
+ const fns = [
89
+ { name: "CleanupService.cleanup", file: "s.ts", calls: ["cleanup"] },
90
+ ];
91
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns);
92
+ (0, vitest_1.expect)(g.has("CleanupService.cleanup")).toBe(false);
93
+ });
94
+ (0, vitest_1.it)("限定形态调用名(Class.method)按裸名命中(裸化只增候选,保守方向)", () => {
95
+ const fns = [
96
+ { name: "Job.run", file: "q.ts", calls: [M, "PageService.processUpdate"] },
97
+ { name: "PageService.processUpdate", file: "s.ts", calls: [] },
98
+ ];
99
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns);
100
+ (0, vitest_1.expect)(g.has("PageService.processUpdate")).toBe(true);
101
+ });
102
+ (0, vitest_1.it)("直接标记函数在返回集合中(种子语义,R6 由消费方过滤)", () => {
103
+ const fns = [{ name: "Job.run", file: "q.ts", calls: [M] }];
104
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns);
105
+ (0, vitest_1.expect)(g.has("Job.run")).toBe(true);
106
+ });
107
+ (0, vitest_1.it)("自定义标记通道", () => {
108
+ const fns = [
109
+ { name: "A", file: "f.ts", calls: ["__custom_mark__", "B"] },
110
+ { name: "B", file: "f.ts", calls: [] },
111
+ ];
112
+ const g = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns, { marker: "__custom_mark__" });
113
+ (0, vitest_1.expect)(g.has("B")).toBe(true);
114
+ // 默认标记下不传播
115
+ const g2 = (0, protocol_detector_1.computeJobSurfacePropagatedSet)(fns);
116
+ (0, vitest_1.expect)(g2.has("B")).toBe(false);
117
+ });
118
+ });
@@ -15,6 +15,7 @@ exports.classifyParamType = classifyParamType;
15
15
  exports.detectSafeguardViolations = detectSafeguardViolations;
16
16
  exports.buildCallerMap = buildCallerMap;
17
17
  exports.computeGuardPropagatedSet = computeGuardPropagatedSet;
18
+ exports.computeJobSurfacePropagatedSet = computeJobSurfacePropagatedSet;
18
19
  exports.detectSafeguardViolationsV7 = detectSafeguardViolationsV7;
19
20
  exports.detectProtocolViolations = detectProtocolViolations;
20
21
  exports.validateProtocolState = validateProtocolState;
@@ -1753,6 +1754,67 @@ function computeGuardPropagatedSet(funcs, opts) {
1753
1754
  }
1754
1755
  return guarded;
1755
1756
  }
1757
+ /**
1758
+ * 修冤枉第七刀(2026-10-11):队列作业调用方传播 ——「函数只被作业调用
1759
+ * ⇒ 无请求面」。与 computeGuardPropagatedSet(第五刀)镜像,但判据不同:
1760
+ *
1761
+ * 第五刀对被调名做逐边分层消歧(后缀唯一→同文件→同目录→类根),歧义
1762
+ * fail-safe 挂未解析边 ⇒ 任何歧义阻断豁免。而作业链(Processor→XxxService)
1763
+ * 跨目录、类根不同名,分层消歧恒失败——docmost 实测第七刀若沿用该框架
1764
+ * 只剩同文件层 4 条(预测压掉 4/600)。
1765
+ *
1766
+ * 本判据(B 判据):调用方**裸名并集**——凡调用名裸化后等于 F 裸名的函数
1767
+ * 都是「可能调用方」;并集非空且全部已豁免 ⇒ F 豁免。正确性:裸化只增
1768
+ * 候选(限定形态调用名也被收进并集)⇒ 真实调用方必在并集中 ⇒ 并集全作业
1769
+ * 蕴含真实调用方全作业(R75 正向证据)。歧义下比逐边消歧更保守:同名族
1770
+ * 任一成员被非作业调用,整个裸名族都不豁免。
1771
+ * 快照轮次语义与第五刀一致;默认 1 轮(只豁免直接作业下游;传递闭包
1772
+ * docmost 实测 +16 条告警命中但豁免集从 120 膨胀到 173,留作数据驱动再开)。
1773
+ */
1774
+ function computeJobSurfacePropagatedSet(funcs, opts) {
1775
+ const marker = opts?.marker ?? "__progmune_no_request_surface__";
1776
+ const maxHops = opts?.maxHops ?? 1;
1777
+ const bySuffix = new Map();
1778
+ for (const f of funcs) {
1779
+ const bare = f.name.split(".").pop() || f.name;
1780
+ if (!bySuffix.has(bare))
1781
+ bySuffix.set(bare, []);
1782
+ bySuffix.get(bare).push(f.name);
1783
+ }
1784
+ const callersByBare = new Map();
1785
+ for (const f of funcs) {
1786
+ for (const c of f.calls || []) {
1787
+ const bareC = c.split(".").pop() || c; // 裸化:限定形态调用名也进并集(只增候选,保守方向)
1788
+ if (!bySuffix.has(bareC))
1789
+ continue; // 非项目函数名 → 无豁免语义
1790
+ if (!callersByBare.has(bareC))
1791
+ callersByBare.set(bareC, new Set());
1792
+ callersByBare.get(bareC).add(f.name);
1793
+ }
1794
+ }
1795
+ const exempt = new Set();
1796
+ for (const f of funcs)
1797
+ if ((f.calls || []).includes(marker))
1798
+ exempt.add(f.name);
1799
+ for (let round = 0; round < maxHops; round++) {
1800
+ const toAdd = [];
1801
+ for (const f of funcs) {
1802
+ if (exempt.has(f.name))
1803
+ continue;
1804
+ const bare = f.name.split(".").pop() || f.name;
1805
+ const callers = callersByBare.get(bare);
1806
+ if (!callers || callers.size === 0)
1807
+ continue; // 无调用方 → 不豁免
1808
+ if ([...callers].every((c) => exempt.has(c)))
1809
+ toAdd.push(f.name);
1810
+ }
1811
+ if (toAdd.length === 0)
1812
+ break;
1813
+ for (const n of toAdd)
1814
+ exempt.add(n);
1815
+ }
1816
+ return exempt;
1817
+ }
1756
1818
  /**
1757
1819
  * v7: Build safeguard context — own → same-file → caller chain.
1758
1820
  *
package/dist/sdk.js CHANGED
@@ -20,7 +20,7 @@ const risk_model_1 = require("./risk-model");
20
20
  const protocol_knowledge_1 = require("./protocol-knowledge");
21
21
  const evidence_repository_1 = require("./evidence-repository");
22
22
  /** Runtime version — stable public identifier. Internal layers evolve underneath. */
23
- exports.RUNTIME_VERSION = "3.7.65";
23
+ exports.RUNTIME_VERSION = "3.7.66";
24
24
  function verify(filePath) {
25
25
  const cert = (0, certify_1.certify)(filePath);
26
26
  const kb = (0, protocol_knowledge_1.buildKnowledgeBase)();
@@ -1504,6 +1504,14 @@ async function collectProtocolViolations(ctx, callGraph) {
1504
1504
  // ≤3 跳、歧义 fail-safe。豁免仅作用于 authorization 类规则
1505
1505
  // 且函数自身不直接带标记(R5/R6)——标记而非静默丢弃。
1506
1506
  const guardPropagated = (0, protocol_detector_1.computeGuardPropagatedSet)(functions);
1507
+ // 修冤枉第七刀(2026-10-11):队列作业调用方传播——「函数只被
1508
+ // 作业/定时/事件处理器调用 ⇒ 无请求面」。B 判据(裸名并集,见
1509
+ // computeJobSurfacePropagatedSet):并集非空且全部已豁免才豁免,
1510
+ // 默认 1 轮。豁免仅作用于 authorization 类且函数自身不直接带
1511
+ // 标记(R5/R6)——与第五刀同:标记而非静默丢弃。
1512
+ const jobSurfacePropagated = process.env.PROGMUNE_JOB_SURFACE_TOGGLE === "off"
1513
+ ? new Set()
1514
+ : (0, protocol_detector_1.computeJobSurfacePropagatedSet)(functions);
1507
1515
  for (const f of functions) {
1508
1516
  const fCalls = f.calls || [];
1509
1517
  let svs;
@@ -1517,7 +1525,9 @@ async function collectProtocolViolations(ctx, callGraph) {
1517
1525
  continue;
1518
1526
  const params = (f.params || []).map((p) => ({ n: p.name, t: p.type || "" }));
1519
1527
  const directlyMarked = fCalls.includes("__progmune_auth_machinery__");
1528
+ const directlyJobMarked = fCalls.includes("__progmune_no_request_surface__");
1520
1529
  const guardExempt = !directlyMarked && guardPropagated.has(f.name);
1530
+ const jobExempt = !directlyJobMarked && jobSurfacePropagated.has(f.name);
1521
1531
  for (const v of svs) {
1522
1532
  const alert = {
1523
1533
  rule: v.rule,
@@ -1527,13 +1537,22 @@ async function collectProtocolViolations(ctx, callGraph) {
1527
1537
  nRules: svs.length,
1528
1538
  params,
1529
1539
  };
1530
- if (guardExempt && v.category === "authorization") {
1531
- alert.suppressedByGuardPropagation = true;
1532
- safeguardAlertsSuppressed.push(alert);
1533
- }
1534
- else {
1535
- safeguardAlertsRaw.push(alert);
1540
+ if (v.category === "authorization") {
1541
+ let suppressed = false;
1542
+ if (guardExempt) {
1543
+ alert.suppressedByGuardPropagation = true;
1544
+ suppressed = true;
1545
+ }
1546
+ if (jobExempt) {
1547
+ alert.suppressedByJobSurfacePropagation = true;
1548
+ suppressed = true;
1549
+ }
1550
+ if (suppressed) {
1551
+ safeguardAlertsSuppressed.push(alert);
1552
+ continue;
1553
+ }
1536
1554
  }
1555
+ safeguardAlertsRaw.push(alert);
1537
1556
  }
1538
1557
  }
1539
1558
  safeguardAlerts = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "progmune-runtime",
3
- "version": "3.7.65",
3
+ "version": "3.7.66",
4
4
  "description": "Progmune — AI Trust Decision Engine. Verify AI-generated code before it reaches production. Outputs APPROVED / NEEDS_REVIEW / BLOCKED with evidence.",
5
5
  "files": [
6
6
  "dist/",