progmune-runtime 3.7.63 → 3.7.65

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,81 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.7.65] — 2026-10-11
4
+
5
+ ### 队列/定时/事件处理器无请求面豁免(修冤枉第六刀:授权族告警的「作业处理器不是请求面」根因)
6
+
7
+ **根因**:docmost 补标注里队列作业/定时任务类 FP 占授权族 FP 的
8
+ 10/111——鉴权发生在入队侧(controller 把任务塞进队列时已鉴权),
9
+ BullMQ 处理器 / NestJS Schedule / EventEmitter 监听器的函数体
10
+ 不面向 HTTP 请求,「缺鉴权/缺属主校验」的提问在处理器层不适用。
11
+
12
+ - **机制**:提取器注入 `__progmune_no_request_surface__` 标记——
13
+ 类级 `@Processor(队列名)`(BullMQ,该类全部方法)、方法级
14
+ `@Interval/@Cron/@Timeout/@OnEvent`(须带实参);规则侧仅授权族
15
+ 四条(Unauthenticated Access/Mutation + Ownership Check + Resource
16
+ Ownership)在 safeguard 位接受该标记(与 E2 同款「只做减法」)
17
+ - **反例挡住(是请求面,不注入)**:@Sse(HTTP SSE)、
18
+ @SubscribeMessage(WebSocket)、@MessagePattern/@EventPattern
19
+ (微服务 RPC);`@Processor()` 空实参不是 BullMQ 形态不注入
20
+ - **同条件 A/B(docmost 双扫描,fresh IR 各自重提)**:主告警流
21
+ 748→720(−28 = Ownership Check 10 + Unauthenticated Access 9 +
22
+ Unauthenticated Mutation 9),**主路径 violations 11=11 内容级零差 /
23
+ score 84=84 / suppressed 117=117**;注入关对照 = 748 与 3.7.64 时代
24
+ 基线一致 ⇒ 压降完全归因本刀,非扫描间抖动
25
+ - **可压名单交叉核对**:28 条被压告警逐条列函数/文件,全部命中
26
+ @Processor(FileTask/Notification/History/GeneralQueue ×4 类)或
27
+ @Interval(cleanupSessions/cleanupOldTrash/sendTelemetry)或
28
+ @OnEvent(Page/Space/Workspace Listener)形态,TP 0
29
+ - **回归锁定**:提取器测试 4 例(类级全方法/方法级四种/反例四种/
30
+ 空实参边界)+ 规则侧测试 5 例(四族抑制 + 非授权族不受影响);
31
+ TS 盲测 119 项目零漂移(LOST 0/ADDED 0——generated 语料无装饰器
32
+ 形状,此门对本改动是空过,真实门=docmost A/B + 定向测试,如实记录)
33
+ - **⚠ 过程教训(TSError 型 toggle)**:上一会话用 `return [];` 做
34
+ A/B 开关——unreachable 代码中 TS 控制流分析不执行收窄,`name`
35
+ 保持 `string | undefined` 编译失败,对照扫描 0 字节静默中断两轮。
36
+ toggle 必须用环境变量分支,且对照扫描前必须删 ir.json(IR-STALE
37
+ 复用含标记缓存会让「对照」跑在注入开的数据上)
38
+
39
+ ## [3.7.64] — 2026-10-10
40
+
41
+ ### 守卫沿调用链传播(修冤枉第五刀:授权族告警的「守卫在 controller 层」根因)
42
+
43
+ **根因**:docmost 补标注 111 条授权族告警全 FP,其中 49 条(44%)是
44
+ service/repo 层函数被误报「未认证/无归属检查」——守卫其实写在
45
+ controller 层的 @UseGuards(JwtAuthGuard) 上,名字级检测器看不见。
46
+
47
+ - **机制**:`computeGuardPropagatedSet`(protocol-detector.ts)——
48
+ 「函数的**全部**调用方都已守卫 ⇒ 该函数视为守卫后」,守卫沿调用链
49
+ 向下传播 ≤3 跳,只认 `__progmune_auth_machinery__` 一个标记通道
50
+ (E2 已在带 @UseGuards 装饰器的函数上注入)
51
+ - **保守判据(R1-R4,fail-safe)**:全部调用方已守卫才豁免(任一
52
+ 未守卫→照报);裸名调用分层消歧——后缀唯一→同文件→同目录→类根
53
+ 对齐(XController→XService→XRepo 惯例),唯一才解析、歧义给全部
54
+ 候选挂未解析边;调用方自身排除(controller.create 调 "create" 时
55
+ 同文件层自我捕获的坑)
56
+ - **标记而非静默丢弃(R9)**:被压告警进 `overall.safeguardAlerts.
57
+ suppressed` 独立清单(带 suppressedByGuardPropagation 标记),
58
+ **不进 total/groups/topRanked 主计数**但完整保留可复核;
59
+ 豁免仅作用于 authorization 类规则、且不作用于自身直接带标记的
60
+ 函数(R5/R6,维持 E2 既有语义)
61
+ - **同条件 A/B(docmost 双扫描)**:主告警流 865→748(−117 =
62
+ Unauthenticated Access 46 + Ownership Check 36 + Unauthenticated
63
+ Mutation 31 + Resource Ownership 4),suppressed 清单 117 条全带
64
+ 标记,748+117=865 证据一条不少;**主路径 violations 9=9 /
65
+ score 85=85 完全一致**
66
+ - **可压名单交叉核对**:117 条中 25 条在 174 标注队列(14 同规则
67
+ FP + 11 同函数不同规则 FP);92 条未标注的逐条列调用链复核——全部
68
+ 由直接守卫 controller 或传播守卫 service 调用(附件
69
+ blind-benchmark/auth-guard-crosscheck.ts 可复跑)
70
+ - **回归锁定**:单元测试 12 例(protocol-detector-guard-propagation
71
+ .test.ts:全调用方守卫/歧义 fail-safe/同文件/同目录/类根消歧/深度
72
+ 限制/自定义标记/种子语义)+ 引擎产品路径 2 例(tests/trust/engine
73
+ .test.ts:守卫后进 suppressed 不进主计数 + 无守卫对照组照常上报);
74
+ 引擎套件 25/25;TS 盲测零漂移(注:batch-scan 不经过引擎告警循环,
75
+ 此门对本改动是空过——真实门=引擎测试+docmost A/B,如实记录)
76
+ - 工具入册:`blind-benchmark/auth-guard-propagation-probe.ts`(量化
77
+ 探针)+ `auth-guard-crosscheck.ts`(标注交叉核对)
78
+
3
79
  ## [3.7.63] — 2026-10-10
4
80
 
5
81
  ### No Input Sanitization 触发词收窄(修冤枉第四刀:删 insert/append/write 通用词)
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const vitest_1 = require("vitest");
37
+ const fs = __importStar(require("fs"));
38
+ const os = __importStar(require("os"));
39
+ const path = __importStar(require("path"));
40
+ const extract_ir_1 = require("./extract-ir");
41
+ /**
42
+ * 修冤枉第六刀(2026-10-10):队列/定时/事件处理器 → 无请求面标记锁定。
43
+ *
44
+ * 数据:docmost 补标注 111 条授权族 FP 里 10 条是队列作业/定时任务——
45
+ * 鉴权发生在入队侧,处理器函数不面向 HTTP 请求,授权族规则按「请求面
46
+ * 缺鉴权」提问时结论不适用。
47
+ *
48
+ * 注入形态:@Processor(队列名) 类级(BullMQ,全方法);
49
+ * @Interval/@Cron/@Timeout/@OnEvent 方法级(须带实参)。
50
+ * 反例(是请求面,不注入):@Sse / @SubscribeMessage / @MessagePattern。
51
+ */
52
+ const MARK = "__progmune_no_request_surface__";
53
+ const TSCONFIG = JSON.stringify({
54
+ compilerOptions: { target: "ES2020", module: "commonjs", strict: false, skipLibCheck: true, noEmit: true },
55
+ include: ["**/*.ts"],
56
+ });
57
+ function makeProject(files) {
58
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pm-nosurface-"));
59
+ fs.writeFileSync(path.join(dir, "tsconfig.json"), TSCONFIG);
60
+ fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify({ name: "fixture", version: "0.0.0", private: true }));
61
+ for (const [rel, body] of Object.entries(files)) {
62
+ const abs = path.join(dir, rel);
63
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
64
+ fs.writeFileSync(abs, body);
65
+ }
66
+ return dir;
67
+ }
68
+ function callsOf(dir, fnName) {
69
+ const fns = (0, extract_ir_1.extractIR)(dir);
70
+ const f = fns.find((x) => x.name === fnName);
71
+ return f ? (f.calls ?? []) : [];
72
+ }
73
+ (0, vitest_1.describe)("extractIR:无请求面标记(修冤枉第六刀)", () => {
74
+ (0, vitest_1.it)("@Processor(队列名) 类级 ⇒ 全部方法注入", () => {
75
+ const dir = makeProject({
76
+ "a.ts": `
77
+ declare const Processor: any;
78
+ @Processor("notification-queue")
79
+ export class NotificationProcessor {
80
+ process() { this.markAsEmailed(); }
81
+ onCompleted() {}
82
+ }
83
+ `,
84
+ });
85
+ (0, vitest_1.expect)(callsOf(dir, "NotificationProcessor.process")).toContain(MARK);
86
+ (0, vitest_1.expect)(callsOf(dir, "NotificationProcessor.onCompleted")).toContain(MARK);
87
+ });
88
+ (0, vitest_1.it)("@Interval/@Cron/@Timeout/@OnEvent 方法级 ⇒ 仅该方法注入", () => {
89
+ const dir = makeProject({
90
+ "a.ts": `
91
+ declare const Interval: any; declare const Cron: any; declare const Timeout: any; declare const OnEvent: any;
92
+ export class SessionService {
93
+ @Interval("session-cleanup", 86400000)
94
+ cleanupSessions() {}
95
+ @Cron("0 0 * * *")
96
+ dailyJob() {}
97
+ @Timeout(5000)
98
+ delayedJob() {}
99
+ @OnEvent("user.created")
100
+ onUserCreated() {}
101
+ normalMethod() {}
102
+ }
103
+ `,
104
+ });
105
+ for (const fn of ["SessionService.cleanupSessions", "SessionService.dailyJob", "SessionService.delayedJob", "SessionService.onUserCreated"]) {
106
+ (0, vitest_1.expect)(callsOf(dir, fn)).toContain(MARK);
107
+ }
108
+ (0, vitest_1.expect)(callsOf(dir, "SessionService.normalMethod")).not.toContain(MARK);
109
+ });
110
+ (0, vitest_1.it)("反例:@Sse/@SubscribeMessage/@MessagePattern/@EventPattern 是请求面,不注入", () => {
111
+ const dir = makeProject({
112
+ "a.ts": `
113
+ declare const Sse: any; declare const SubscribeMessage: any; declare const MessagePattern: any; declare const EventPattern: any;
114
+ export class Gateway {
115
+ @Sse("events")
116
+ events() {}
117
+ @SubscribeMessage("chat")
118
+ onMessage() {}
119
+ @MessagePattern("orders.get")
120
+ getOrder() {}
121
+ @EventPattern("orders.created")
122
+ onOrderCreated() {}
123
+ }
124
+ `,
125
+ });
126
+ for (const fn of ["Gateway.events", "Gateway.onMessage", "Gateway.getOrder", "Gateway.onOrderCreated"]) {
127
+ (0, vitest_1.expect)(callsOf(dir, fn)).not.toContain(MARK);
128
+ }
129
+ });
130
+ (0, vitest_1.it)("边界:@Processor() 空实参不注入(非 BullMQ 形态)", () => {
131
+ const dir = makeProject({
132
+ "a.ts": `
133
+ declare const Processor: any;
134
+ @Processor()
135
+ export class Odd {
136
+ run() {}
137
+ }
138
+ `,
139
+ });
140
+ (0, vitest_1.expect)(callsOf(dir, "Odd.run")).not.toContain(MARK);
141
+ });
142
+ });
@@ -462,6 +462,44 @@ function authMachineryFromDecorators(decorators) {
462
462
  return auth && !skip ? [AUTH_MACHINERY_MARKER] : [];
463
463
  }
464
464
  // ═══════════════════════════════════════════════════════════════
465
+ // 修冤枉第六刀(2026-10-10):队列/定时/事件处理器 → 无请求面标记
466
+ //
467
+ // 数据:docmost 补标注 111 条授权族 FP 里 10 条是队列作业/定时任务——
468
+ // 鉴权发生在入队侧(controller 层),处理器函数本身不面向 HTTP 请求,
469
+ // 授权族规则按「请求面缺鉴权」提问时结论不适用。
470
+ // 当前流量化:44 个队列/定时函数命中 19 条授权族告警(全 FP 类)。
471
+ //
472
+ // 形态:@Processor(队列名) 类级(BullMQ,该类全部方法都是作业);
473
+ // @Interval/@Cron/@Timeout 方法级(NestJS Schedule);
474
+ // @OnEvent 方法级(NestJS EventEmitter2)。
475
+ // ⚠ 反例(是请求面,不注入):@Sse(HTTP SSE)、@SubscribeMessage
476
+ // (WebSocket 消息)、@MessagePattern/@EventPattern(微服务 RPC)。
477
+ // ═══════════════════════════════════════════════════════════════
478
+ const NO_REQUEST_SURFACE_MARKER = "__progmune_no_request_surface__";
479
+ /** 队列/定时/事件装饰器 —— 出现即注入无请求面标记(须带实参:@Interval('name', ms) 等) */
480
+ const NO_SURFACE_CLASS_DECORATOR = /^Processor$/i;
481
+ const NO_SURFACE_METHOD_DECORATOR = /^(?:Interval|Cron|Timeout|OnEvent)$/i;
482
+ /**
483
+ * 由类/方法装饰器判定是否注入无请求面标记(与 authMachineryFromDecorators 同款文本匹配)。
484
+ * @returns [] 或 [NO_REQUEST_SURFACE_MARKER]
485
+ */
486
+ function noRequestSurfaceFromDecorators(decorators) {
487
+ let noSurface = false;
488
+ for (const d of decorators) {
489
+ const text = (d?.getText?.() ?? "").trim();
490
+ const name = /^@([A-Za-z_$][\w$]*)/.exec(text)?.[1];
491
+ if (!name)
492
+ continue;
493
+ if (!NO_SURFACE_CLASS_DECORATOR.test(name) && !NO_SURFACE_METHOD_DECORATOR.test(name))
494
+ continue;
495
+ // 须带实参(@Processor() 空参不是 BullMQ 形态;@Interval 必带名字+毫秒)
496
+ if (!/^@[\w$]*\([^)]+\)/.test(text))
497
+ continue;
498
+ noSurface = true;
499
+ }
500
+ return noSurface ? [NO_REQUEST_SURFACE_MARKER] : [];
501
+ }
502
+ // ═══════════════════════════════════════════════════════════════
465
503
  // E3(2026-09-22):TS 侧「入参已按 schema 校验」语义标记
466
504
  //
467
505
  // 判据(先抄准再写 expect,R22):
@@ -3256,6 +3294,15 @@ function _extractSingleProject(absRoot, tsconfigPath) {
3256
3294
  if (!mCalls.includes(mk))
3257
3295
  mCalls.push(mk);
3258
3296
  }
3297
+ // 修冤枉第六刀:队列/定时/事件处理器 → 无请求面标记(与 E2 并列,
3298
+ // 语义相反——E2 是「有鉴权」,本标记是「鉴权问题不适用」)
3299
+ for (const mk of noRequestSurfaceFromDecorators([
3300
+ ...(cls.getDecorators?.() ?? []),
3301
+ ...(m.getDecorators?.() ?? []),
3302
+ ])) {
3303
+ if (!mCalls.includes(mk))
3304
+ mCalls.push(mk);
3305
+ }
3259
3306
  funcs.push({
3260
3307
  name: `${cn}.${mn}`,
3261
3308
  params: mParams.map((p) => ({ name: p.getName(), type: getParamType(p), typeDetail: getParamTypeDetail(p) })),
@@ -0,0 +1,130 @@
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-10)单元测试。
7
+ *
8
+ * 保守判据(R1-R4,fail-safe 方向):全部调用方已守卫才豁免、
9
+ * ≤3 跳、只认 __progmune_auth_machinery__ 标记、歧义调用按未守卫处理。
10
+ * 消费方(engine)负责 R5/R6:只压 authorization 类、且不压直接标记函数。
11
+ */
12
+ const M = "__progmune_auth_machinery__";
13
+ (0, vitest_1.describe)("computeGuardPropagatedSet(守卫传播保守判据)", () => {
14
+ (0, vitest_1.it)("全部调用方已守卫 ⇒ 传播(controller→service)", () => {
15
+ const fns = [
16
+ { name: "PageController.create", file: "a.ts", calls: [M, "createPage"] },
17
+ { name: "PageService.createPage", file: "b.ts", calls: ["insertPage"] },
18
+ { name: "PageRepo.insertPage", file: "c.ts", calls: [] },
19
+ ];
20
+ const g = (0, protocol_detector_1.computeGuardPropagatedSet)(fns);
21
+ (0, vitest_1.expect)(g.has("PageService.createPage")).toBe(true);
22
+ });
23
+ (0, vitest_1.it)("传递两跳(controller→service→repo)", () => {
24
+ const fns = [
25
+ { name: "PageController.create", file: "a.ts", calls: [M, "createPage"] },
26
+ { name: "PageService.createPage", file: "b.ts", calls: ["insertPage"] },
27
+ { name: "PageRepo.insertPage", file: "c.ts", calls: [] },
28
+ ];
29
+ const g = (0, protocol_detector_1.computeGuardPropagatedSet)(fns);
30
+ (0, vitest_1.expect)(g.has("PageRepo.insertPage")).toBe(true);
31
+ });
32
+ (0, vitest_1.it)("任一调用方未守卫 ⇒ 不豁免(fail-safe)", () => {
33
+ const fns = [
34
+ { name: "PageController.create", file: "a.ts", calls: [M, "createPage"] },
35
+ { name: "ImportProcessor.run", file: "q.ts", calls: ["createPage"] },
36
+ { name: "PageService.createPage", file: "b.ts", calls: [] },
37
+ ];
38
+ const g = (0, protocol_detector_1.computeGuardPropagatedSet)(fns);
39
+ (0, vitest_1.expect)(g.has("PageService.createPage")).toBe(false);
40
+ });
41
+ (0, vitest_1.it)("无调用方(入口函数)⇒ 不豁免", () => {
42
+ const fns = [{ name: "bootstrap", file: "m.ts", calls: [] }];
43
+ const g = (0, protocol_detector_1.computeGuardPropagatedSet)(fns);
44
+ (0, vitest_1.expect)(g.has("bootstrap")).toBe(false);
45
+ });
46
+ (0, vitest_1.it)("同名多处定义 ⇒ 歧义 fail-safe ⇒ 不豁免", () => {
47
+ const fns = [
48
+ { name: "AController.do", file: "a.ts", calls: [M, "update"] },
49
+ { name: "UserRepo.update", file: "r1.ts", calls: [] },
50
+ { name: "SpaceRepo.update", file: "r2.ts", calls: [] },
51
+ ];
52
+ const g = (0, protocol_detector_1.computeGuardPropagatedSet)(fns);
53
+ // "update" 有两个候选,调用方 AController.do 的调用无法唯一解析
54
+ (0, vitest_1.expect)(g.has("UserRepo.update")).toBe(false);
55
+ (0, vitest_1.expect)(g.has("SpaceRepo.update")).toBe(false);
56
+ });
57
+ (0, vitest_1.it)("歧义时同文件唯一候选 ⇒ 消歧成立", () => {
58
+ const fns = [
59
+ { name: "AController.do", file: "a.ts", calls: [M, "update"] },
60
+ // UserRepo.update 与调用方同文件(helpers 场景)
61
+ { name: "UserRepo.update", file: "a.ts", calls: [] },
62
+ { name: "SpaceRepo.update", file: "r2.ts", calls: [] },
63
+ ];
64
+ const g = (0, protocol_detector_1.computeGuardPropagatedSet)(fns);
65
+ (0, vitest_1.expect)(g.has("UserRepo.update")).toBe(true);
66
+ (0, vitest_1.expect)(g.has("SpaceRepo.update")).toBe(false);
67
+ });
68
+ (0, vitest_1.it)("歧义时同目录唯一候选 ⇒ 消歧成立", () => {
69
+ const fns = [
70
+ { name: "AController.do", file: "d1/x.ts", calls: [M, "update"] },
71
+ { name: "UserRepo.update", file: "d1/y.ts", calls: [] },
72
+ { name: "SpaceRepo.update", file: "d2/z.ts", calls: [] },
73
+ ];
74
+ const g = (0, protocol_detector_1.computeGuardPropagatedSet)(fns);
75
+ (0, vitest_1.expect)(g.has("UserRepo.update")).toBe(true);
76
+ (0, vitest_1.expect)(g.has("SpaceRepo.update")).toBe(false);
77
+ });
78
+ (0, vitest_1.it)("歧义时类根对齐唯一候选 ⇒ 消歧成立(MVC 命名惯例)", () => {
79
+ const fns = [
80
+ { name: "PageController.create", file: "c/page.controller.ts", calls: [M, "create"] },
81
+ { name: "PageService.create", file: "s/page.service.ts", calls: [] },
82
+ { name: "SpaceService.create", file: "s/space.service.ts", calls: [] },
83
+ ];
84
+ const g = (0, protocol_detector_1.computeGuardPropagatedSet)(fns);
85
+ (0, vitest_1.expect)(g.has("PageService.create")).toBe(true);
86
+ (0, vitest_1.expect)(g.has("SpaceService.create")).toBe(false);
87
+ });
88
+ (0, vitest_1.it)("类根不唯一 ⇒ 保持 fail-safe", () => {
89
+ const fns = [
90
+ { name: "PageController.create", file: "c.ts", calls: [M, "create"] },
91
+ { name: "PageService.create", file: "s1.ts", calls: [] },
92
+ { name: "PageRepo.create", file: "r1.ts", calls: [] },
93
+ ];
94
+ // 类根 "Page" 命中两个候选(Service+Repo)⇒ 不唯一 ⇒ 不解析
95
+ const g = (0, protocol_detector_1.computeGuardPropagatedSet)(fns);
96
+ (0, vitest_1.expect)(g.has("PageService.create")).toBe(false);
97
+ (0, vitest_1.expect)(g.has("PageRepo.create")).toBe(false);
98
+ });
99
+ (0, vitest_1.it)("深度限制:第 4 跳不传播", () => {
100
+ const fns = [
101
+ { name: "C0", file: "f.ts", calls: [M, "C1"] },
102
+ { name: "C1", file: "f.ts", calls: ["C2"] },
103
+ { name: "C2", file: "f.ts", calls: ["C3"] },
104
+ { name: "C3", file: "f.ts", calls: ["C4"] },
105
+ { name: "C4", file: "f.ts", calls: [] },
106
+ ];
107
+ // C1 = 跳1, C2 = 跳2, C3 = 跳3, C4 = 跳4(maxDepth=3 传播 3 轮:C1,C2,C3)
108
+ const g = (0, protocol_detector_1.computeGuardPropagatedSet)(fns, { maxDepth: 3 });
109
+ (0, vitest_1.expect)(g.has("C1")).toBe(true);
110
+ (0, vitest_1.expect)(g.has("C2")).toBe(true);
111
+ (0, vitest_1.expect)(g.has("C3")).toBe(true);
112
+ (0, vitest_1.expect)(g.has("C4")).toBe(false);
113
+ });
114
+ (0, vitest_1.it)("自定义标记通道", () => {
115
+ const fns = [
116
+ { name: "A", file: "f.ts", calls: ["__custom_mark__", "B"] },
117
+ { name: "B", file: "f.ts", calls: [] },
118
+ ];
119
+ const g = (0, protocol_detector_1.computeGuardPropagatedSet)(fns, { marker: "__custom_mark__" });
120
+ (0, vitest_1.expect)(g.has("B")).toBe(true);
121
+ // 默认标记下不传播
122
+ const g2 = (0, protocol_detector_1.computeGuardPropagatedSet)(fns);
123
+ (0, vitest_1.expect)(g2.has("B")).toBe(false);
124
+ });
125
+ (0, vitest_1.it)("直接标记函数在返回集合中(种子语义,R6 由消费方过滤)", () => {
126
+ const fns = [{ name: "AController.do", file: "a.ts", calls: [M] }];
127
+ const g = (0, protocol_detector_1.computeGuardPropagatedSet)(fns);
128
+ (0, vitest_1.expect)(g.has("AController.do")).toBe(true);
129
+ });
130
+ });
@@ -0,0 +1,44 @@
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-10):无请求面标记的规则侧接受位锁定。
7
+ *
8
+ * 语义:队列/定时/事件处理器不面向 HTTP 请求,鉴权发生在入队侧——
9
+ * 授权族四条的「缺鉴权/缺属主校验」提问在处理器层不适用(N/A 抑制)。
10
+ * 只抑制 authorization 类;其他族规则不受标记影响。
11
+ */
12
+ const M = "__progmune_no_request_surface__";
13
+ const has = (vs, rule) => vs.some((v) => v.rule === rule);
14
+ (0, vitest_1.describe)("no_request_surface 标记:授权族四条接受、其他族不受影响", () => {
15
+ const calls = ["getWorkspaceUrl", M];
16
+ (0, vitest_1.it)("Unauthenticated Access 被抑制", () => {
17
+ const bare = (0, protocol_detector_1.detectSafeguardViolations)(["getWorkspaceUrl"], "NotificationProcessor.getWorkspaceUrl", "typescript", ["job"], true);
18
+ (0, vitest_1.expect)(has(bare, "Authorization (Unauthenticated Access)")).toBe(true);
19
+ const withMark = (0, protocol_detector_1.detectSafeguardViolations)(calls, "NotificationProcessor.getWorkspaceUrl", "typescript", ["job"], true);
20
+ (0, vitest_1.expect)(has(withMark, "Authorization (Unauthenticated Access)")).toBe(false);
21
+ });
22
+ (0, vitest_1.it)("Unauthenticated Mutation 被抑制", () => {
23
+ const bare = (0, protocol_detector_1.detectSafeguardViolations)(["updateTable"], "HistoryProcessor.process", "typescript", ["job"], true);
24
+ (0, vitest_1.expect)(has(bare, "Authorization (Unauthenticated Mutation)")).toBe(true);
25
+ const withMark = (0, protocol_detector_1.detectSafeguardViolations)(["updateTable", M], "HistoryProcessor.process", "typescript", ["job"], true);
26
+ (0, vitest_1.expect)(has(withMark, "Authorization (Unauthenticated Mutation)")).toBe(false);
27
+ });
28
+ (0, vitest_1.it)("Ownership Check 被抑制(queue job 的属主校验提问不适用)", () => {
29
+ const bare = (0, protocol_detector_1.detectSafeguardViolations)(["deleteSessions"], "SessionService.cleanupSessions", "typescript", ["job"], true);
30
+ (0, vitest_1.expect)(has(bare, "Authorization (Ownership Check)")).toBe(true);
31
+ const withMark = (0, protocol_detector_1.detectSafeguardViolations)(["deleteSessions", M], "SessionService.cleanupSessions", "typescript", ["job"], true);
32
+ (0, vitest_1.expect)(has(withMark, "Authorization (Ownership Check)")).toBe(false);
33
+ });
34
+ (0, vitest_1.it)("Resource Ownership 被抑制", () => {
35
+ const bare = (0, protocol_detector_1.detectSafeguardViolations)(["removeIcon"], "AttachmentProcessor.process", "typescript", ["job"], true);
36
+ (0, vitest_1.expect)(has(bare, "Authorization (Resource Ownership)")).toBe(true);
37
+ const withMark = (0, protocol_detector_1.detectSafeguardViolations)(["removeIcon", M], "AttachmentProcessor.process", "typescript", ["job"], true);
38
+ (0, vitest_1.expect)(has(withMark, "Authorization (Resource Ownership)")).toBe(false);
39
+ });
40
+ (0, vitest_1.it)("非授权族规则不受标记影响(渲染 sink 照报)", () => {
41
+ const vs = (0, protocol_detector_1.detectSafeguardViolations)(["render", M], "EmailProcessor.renderTemplate", "typescript");
42
+ (0, vitest_1.expect)(has(vs, "No Input Sanitization")).toBe(true);
43
+ });
44
+ });
@@ -14,6 +14,7 @@ exports.isAuthFunctionName = isAuthFunctionName;
14
14
  exports.classifyParamType = classifyParamType;
15
15
  exports.detectSafeguardViolations = detectSafeguardViolations;
16
16
  exports.buildCallerMap = buildCallerMap;
17
+ exports.computeGuardPropagatedSet = computeGuardPropagatedSet;
17
18
  exports.detectSafeguardViolationsV7 = detectSafeguardViolationsV7;
18
19
  exports.detectProtocolViolations = detectProtocolViolations;
19
20
  exports.validateProtocolState = validateProtocolState;
@@ -384,6 +385,9 @@ const SAFEGUARD_RULES = [
384
385
  safeguards: [
385
386
  { pattern: /\b(checkOwner|isOwner|ownerId\s*[!=]==?|authorId\s*[!=]==?|userId\s*[!=]==?|createdBy\s*[!=]==?|\.owner\s*[!=]==?|\.user\s*[!=]==?)\b/i, label: "ownership_check" },
386
387
  { pattern: /\b(hasPermission|checkPermission|checkAccess|isAuthorized|checkRole|requireRole|adminCheck|isAdmin|canModify|canDelete|canEdit)\b/i, label: "authz_check" },
388
+ // 修冤枉第六刀:队列/定时/事件处理器不面向请求面——鉴权发生在入队侧,
389
+ // 「改资源没查属主」的提问在处理器层不适用(docmost 补标注 FP 类)
390
+ { pattern: /\b(__progmune_no_request_surface__)\b/, label: "no_request_surface" },
387
391
  { pattern: /\b(__progmune_ownership_checked__)\b/, label: "inline_ownership_check" },
388
392
  // §46 ②:资源属主被**赋值为当前主体**(`{ userId: user.id }` / `where:{userId: user.id}`)。
389
393
  // 这类函数的被操作对象就是(或已被限定在)主体自己,谈不上「改别人的东西」。
@@ -427,7 +431,7 @@ const SAFEGUARD_RULES = [
427
431
  paramTypeSafeguards: [SESSION_BEARING_TYPE_RE],
428
432
  trigger: /\b(list|download|view|fetch)(?:[A-Z]\w*|_\w+)|get(?:[A-Z]\w+|_\w+)/i,
429
433
  safeguards: [
430
- { pattern: /\b(getUser|validateToken|verifyToken|verifySession|validateSession|getSessionUser|getSession\b|getCurrentUser|token\w*(Check|Verify|Valid)|session\w*(Check|Verify|Valid)|auth\w*(Check|Verify|Valid|Guard|Middleware|Required)|requireAuth|withAuth|authenticate\w*(User|Request|Token)?|checkAuth|isAuth|hasAuth|checkAccess|hasAccess|get_user|get_session_user|get_current_user|validate_session|verify_token|require_auth|with_auth|check_auth|auth_required|authenticate_user|authenticate_request|authenticate_token|token_check|token_verify|token_valid|session_check|session_verify|session_valid|auth_check|auth_guard|auth_middleware|get_current_user_authorizer|current_user_authorizer|login_required|permission_required|user_passes_test|check_authorization|check_permission|jwt\.decode|decode_token|__progmune_auth_checked__|__progmune_credential_check__|__progmune_drf_permissions__|__progmune_auth_machinery__)\b/i, label: "auth_check" },
434
+ { pattern: /\b(getUser|validateToken|verifyToken|verifySession|validateSession|getSessionUser|getSession\b|getCurrentUser|token\w*(Check|Verify|Valid)|session\w*(Check|Verify|Valid)|auth\w*(Check|Verify|Valid|Guard|Middleware|Required)|requireAuth|withAuth|authenticate\w*(User|Request|Token)?|checkAuth|isAuth|hasAuth|checkAccess|hasAccess|get_user|get_session_user|get_current_user|validate_session|verify_token|require_auth|with_auth|check_auth|auth_required|authenticate_user|authenticate_request|authenticate_token|token_check|token_verify|token_valid|session_check|session_verify|session_valid|auth_check|auth_guard|auth_middleware|get_current_user_authorizer|current_user_authorizer|login_required|permission_required|user_passes_test|check_authorization|check_permission|jwt\.decode|decode_token|__progmune_auth_checked__|__progmune_credential_check__|__progmune_drf_permissions__|__progmune_auth_machinery__|__progmune_no_request_surface__)\b/i, label: "auth_check" },
431
435
  // §40 C1:裸 can —— verdaccio 的 can('publish') 中间件就是这里的授权判定
432
436
  { pattern: exports.AUTHZ_BARE_CAN_RE, label: "bare_can", callsOnly: true },
433
437
  ],
@@ -463,7 +467,7 @@ const SAFEGUARD_RULES = [
463
467
  // (listPosts/getPost/deletePost fire via identifier-parsed words).
464
468
  trigger: /\b(add|create|update|set|publish|insert|submit)(?:[A-Z]\w*|_\w+)|(?:[A-Z]\w*|_\w+)(Add|Create|Update|Set|Publish|Insert|Submit)\b/i,
465
469
  safeguards: [
466
- { pattern: /\b(getUser|validateToken|verifyToken|verifySession|validateSession|getSessionUser|getSession\b|getCurrentUser|token\w*(Check|Verify|Valid)|session\w*(Check|Verify|Valid)|auth\w*(Check|Verify|Valid|Guard|Middleware|Required)|requireAuth|withAuth|authenticate\w*(User|Request|Token)?|checkAuth|isAuth|hasAuth|checkAccess|hasAccess|get_user|get_session_user|get_current_user|validate_session|verify_token|require_auth|with_auth|check_auth|auth_required|authenticate_user|authenticate_request|authenticate_token|token_check|token_verify|token_valid|session_check|session_verify|session_valid|auth_check|auth_guard|auth_middleware|get_current_user_authorizer|current_user_authorizer|login_required|permission_required|user_passes_test|check_authorization|check_permission|__progmune_auth_checked__|__progmune_credential_check__|__progmune_drf_permissions__|__progmune_auth_machinery__)\b/i, label: "auth_check" },
470
+ { pattern: /\b(getUser|validateToken|verifyToken|verifySession|validateSession|getSessionUser|getSession\b|getCurrentUser|token\w*(Check|Verify|Valid)|session\w*(Check|Verify|Valid)|auth\w*(Check|Verify|Valid|Guard|Middleware|Required)|requireAuth|withAuth|authenticate\w*(User|Request|Token)?|checkAuth|isAuth|hasAuth|checkAccess|hasAccess|get_user|get_session_user|get_current_user|validate_session|verify_token|require_auth|with_auth|check_auth|auth_required|authenticate_user|authenticate_request|authenticate_token|token_check|token_verify|token_valid|session_check|session_verify|session_valid|auth_check|auth_guard|auth_middleware|get_current_user_authorizer|current_user_authorizer|login_required|permission_required|user_passes_test|check_authorization|check_permission|__progmune_auth_checked__|__progmune_credential_check__|__progmune_drf_permissions__|__progmune_auth_machinery__|__progmune_no_request_surface__)\b/i, label: "auth_check" },
467
471
  // §40 C1:裸 can —— 同上
468
472
  { pattern: exports.AUTHZ_BARE_CAN_RE, label: "bare_can", callsOnly: true },
469
473
  // §46 ①:凭签发入口豁免 —— 独立 safeguard,**不并入 auth_check**。
@@ -623,6 +627,8 @@ const SAFEGUARD_RULES = [
623
627
  safeguards: [
624
628
  { pattern: /\b(ownerId\s*[!=]==?|authorId\s*[!=]==?|userId\s*[!=]==?|createdBy|\.owner\s*[!=]==?)/i, label: "ownership_comparison" },
625
629
  { pattern: /\b(__progmune_ownership_checked__)\b/, label: "inline_ownership_check" },
630
+ // 修冤枉第六刀:同上(无请求面 ⇒ 属主校验提问不适用)
631
+ { pattern: /\b(__progmune_no_request_surface__)\b/, label: "no_request_surface" },
626
632
  // §38:同 Ownership Check,组合式谓语一并接上(这条规则更严,更需要)
627
633
  { pattern: exports.AUTHZ_PREDICATE_RE, label: "casl_predicate" },
628
634
  { pattern: exports.AUTHZ_CAN_HELPER_RE, label: "can_helper" },
@@ -1623,6 +1629,130 @@ function buildCallerMap(funcs) {
1623
1629
  }
1624
1630
  return callerMap;
1625
1631
  }
1632
+ /**
1633
+ * 修冤枉第五刀(2026-10-10):守卫沿调用链传播——
1634
+ * 授权族告警的「守卫在 controller 层、名字级看不见」根因的保守豁免。
1635
+ *
1636
+ * 语义:「函数的**全部**直接调用方都已守卫 ⇒ 该函数视为守卫后」。
1637
+ * 证据:docmost 补标注 111 条授权族告警全 FP,其中 49 条(44%)是
1638
+ * controller 已 @UseGuards、service/repo 层函数被误报。保守量化
1639
+ * (blind-benchmark/auth-guard-propagation-probe.ts):
1640
+ * docmost 745 条授权族告警可压 47 条(6.3%),TS 盲测可压 0 条。
1641
+ *
1642
+ * 保守规则(R1-R4,fail-safe 方向):
1643
+ * R1 只有【全部】调用方已守卫才豁免;任一调用方无守卫证据 → 照报
1644
+ * R2 守卫沿调用链向下传播 ≤ maxDepth(默认 3)跳
1645
+ * R3 传播通道只认 `marker`(默认 __progmune_auth_machinery__)一个标记
1646
+ * R4 裸名调用解析分层消歧(唯一才解析,否则给全部候选挂未解析边):
1647
+ * ① 名字后缀唯一 ② 同文件唯一 ③ 同目录唯一
1648
+ * ④ 类根对齐唯一(调用方 XController/Service → 候选类根同为 X)
1649
+ * R6 豁免只给【非直接标记】函数——自身带标记的维持既有 E2 语义
1650
+ * (只接受标记的规则由 detector 自身处理;不接受标记的规则照报)
1651
+ *
1652
+ * 返回集合含直接标记函数本身(消费方须自行应用 R6 过滤),
1653
+ * 因为传递闭包的种子就是它们。
1654
+ */
1655
+ function computeGuardPropagatedSet(funcs, opts) {
1656
+ const marker = opts?.marker ?? "__progmune_auth_machinery__";
1657
+ const maxDepth = opts?.maxDepth ?? 3;
1658
+ const byName = new Map();
1659
+ for (const f of funcs)
1660
+ if (!byName.has(f.name))
1661
+ byName.set(f.name, f);
1662
+ // 后缀索引:裸名 → 候选全限定名(>1 ⇒ 歧义)
1663
+ const bySuffix = new Map();
1664
+ for (const f of funcs) {
1665
+ const bare = f.name.split(".").pop() || f.name;
1666
+ if (!bySuffix.has(bare))
1667
+ bySuffix.set(bare, []);
1668
+ bySuffix.get(bare).push(f.name);
1669
+ }
1670
+ // 反向调用图:被调【全限定名】→ 调用方列表(resolved=false = 歧义 fail-safe)。
1671
+ // 边解析分层消歧(R4):① 后缀唯一 ② 同文件唯一 ③ 同目录唯一
1672
+ // ④ 类根对齐唯一(XController→XService→XRepo 的 MVC 命名惯例);
1673
+ // 全部失败 ⇒ 给全部同名候选挂未解析边(fail-safe)。
1674
+ const dirOf = (p) => (p ? p.split("/").slice(0, -1).join("/") : "");
1675
+ const rootOf = (n) => {
1676
+ const cls = n.split(".")[0] || n;
1677
+ return cls.replace(/(Controller|Service|Repo|Repository|Listener|Processor|Handler|Extension|Util|Utils|Helper|Driver|Provider)$/, "");
1678
+ };
1679
+ const calleeToCallers = new Map();
1680
+ for (const f of funcs) {
1681
+ for (const c of f.calls || []) {
1682
+ if (!bySuffix.has(c))
1683
+ continue; // 非项目函数名(外部库调用)→ 无守卫语义
1684
+ // 排除调用方自身:controller.create 调 "create" 时,自己就是后缀
1685
+ // 候选之一,同文件/同目录层会错误自我捕获(递归/自调不提供守卫证据)
1686
+ const cands = bySuffix.get(c).filter((n) => n !== f.name);
1687
+ if (cands.length === 0)
1688
+ continue;
1689
+ let targets = null;
1690
+ if (cands.length === 1) {
1691
+ targets = cands;
1692
+ }
1693
+ else {
1694
+ const sameFile = cands.filter((n) => {
1695
+ const g = byName.get(n);
1696
+ return g && g.file && f.file && g.file === f.file;
1697
+ });
1698
+ if (sameFile.length === 1)
1699
+ targets = sameFile;
1700
+ else {
1701
+ const sameDir = cands.filter((n) => {
1702
+ const g = byName.get(n);
1703
+ return g && g.file && f.file && dirOf(g.file) === dirOf(f.file);
1704
+ });
1705
+ if (sameDir.length === 1)
1706
+ targets = sameDir;
1707
+ else {
1708
+ const rootMatch = cands.filter((n) => rootOf(n) === rootOf(f.name));
1709
+ if (rootMatch.length === 1)
1710
+ targets = rootMatch;
1711
+ }
1712
+ }
1713
+ }
1714
+ if (targets === null) {
1715
+ for (const n of cands) {
1716
+ if (!calleeToCallers.has(n))
1717
+ calleeToCallers.set(n, []);
1718
+ calleeToCallers.get(n).push({ caller: f.name, resolved: false });
1719
+ }
1720
+ }
1721
+ else {
1722
+ for (const n of targets) {
1723
+ if (!calleeToCallers.has(n))
1724
+ calleeToCallers.set(n, []);
1725
+ calleeToCallers.get(n).push({ caller: f.name, resolved: true });
1726
+ }
1727
+ }
1728
+ }
1729
+ }
1730
+ // 种子:直接带标记的函数
1731
+ const guarded = new Set();
1732
+ for (const f of funcs)
1733
+ if ((f.calls || []).includes(marker))
1734
+ guarded.add(f.name);
1735
+ // 不动点传播(≤ maxDepth 轮):被调方全部调用方已守卫 ⇒ 已守卫。
1736
+ // 快照语义:每轮基于【轮初】守卫集计算、轮末统一应用——
1737
+ // 轮内级联会让深度失去意义(同轮后半段函数借前半段的新增一步到位)。
1738
+ for (let round = 0; round < maxDepth; round++) {
1739
+ const toAdd = [];
1740
+ for (const f of funcs) {
1741
+ if (guarded.has(f.name))
1742
+ continue;
1743
+ const callers = calleeToCallers.get(f.name) || [];
1744
+ if (callers.length === 0)
1745
+ continue; // 无调用方(入口/未连通)→ 不豁免
1746
+ if (callers.every((c) => c.resolved && guarded.has(c.caller)))
1747
+ toAdd.push(f.name);
1748
+ }
1749
+ if (toAdd.length === 0)
1750
+ break;
1751
+ for (const n of toAdd)
1752
+ guarded.add(n);
1753
+ }
1754
+ return guarded;
1755
+ }
1626
1756
  /**
1627
1757
  * v7: Build safeguard context — own → same-file → caller chain.
1628
1758
  *
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.63";
23
+ exports.RUNTIME_VERSION = "3.7.65";
24
24
  function verify(filePath) {
25
25
  const cert = (0, certify_1.certify)(filePath);
26
26
  const kb = (0, protocol_knowledge_1.buildKnowledgeBase)();
@@ -1496,7 +1496,14 @@ async function collectProtocolViolations(ctx, callGraph) {
1496
1496
  // 不改 decision/score——排序告警是给人工审查的入口(§49.16:
1497
1497
  // 排序不删告警,一条不少;groupAlerts 分组防整族沉底)。
1498
1498
  const safeguardAlertsRaw = [];
1499
+ const safeguardAlertsSuppressed = [];
1499
1500
  const svLang = ctx.language || "typescript";
1501
+ // 修冤枉第五刀(2026-10-10):守卫传播豁免——授权族告警的
1502
+ // 「守卫在 controller 层」根因。保守判据见
1503
+ // computeGuardPropagatedSet(R1-R4):全部调用方已守卫才豁免、
1504
+ // ≤3 跳、歧义 fail-safe。豁免仅作用于 authorization 类规则
1505
+ // 且函数自身不直接带标记(R5/R6)——标记而非静默丢弃。
1506
+ const guardPropagated = (0, protocol_detector_1.computeGuardPropagatedSet)(functions);
1500
1507
  for (const f of functions) {
1501
1508
  const fCalls = f.calls || [];
1502
1509
  let svs;
@@ -1509,21 +1516,31 @@ async function collectProtocolViolations(ctx, callGraph) {
1509
1516
  if (svs.length === 0)
1510
1517
  continue;
1511
1518
  const params = (f.params || []).map((p) => ({ n: p.name, t: p.type || "" }));
1519
+ const directlyMarked = fCalls.includes("__progmune_auth_machinery__");
1520
+ const guardExempt = !directlyMarked && guardPropagated.has(f.name);
1512
1521
  for (const v of svs) {
1513
- safeguardAlertsRaw.push({
1522
+ const alert = {
1514
1523
  rule: v.rule,
1515
1524
  file: f.file,
1516
1525
  function: f.name,
1517
1526
  calls: fCalls,
1518
1527
  nRules: svs.length,
1519
1528
  params,
1520
- });
1529
+ };
1530
+ if (guardExempt && v.category === "authorization") {
1531
+ alert.suppressedByGuardPropagation = true;
1532
+ safeguardAlertsSuppressed.push(alert);
1533
+ }
1534
+ else {
1535
+ safeguardAlertsRaw.push(alert);
1536
+ }
1521
1537
  }
1522
1538
  }
1523
1539
  safeguardAlerts = {
1524
1540
  total: safeguardAlertsRaw.length,
1525
1541
  groups: (0, alert_ranker_1.groupAlerts)(safeguardAlertsRaw),
1526
1542
  topRanked: (0, alert_ranker_1.rankAlerts)(safeguardAlertsRaw, { minPerRule: 1 }),
1543
+ suppressed: safeguardAlertsSuppressed,
1527
1544
  };
1528
1545
  }
1529
1546
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "progmune-runtime",
3
- "version": "3.7.63",
3
+ "version": "3.7.65",
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/",