@geoly-ai/social-hub-core 0.0.4 → 0.0.6

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 (77) hide show
  1. package/dist/activity.d.ts +138 -0
  2. package/dist/activity.d.ts.map +1 -0
  3. package/dist/activity.js +577 -0
  4. package/dist/activity.js.map +1 -0
  5. package/dist/activity.test.d.ts +2 -0
  6. package/dist/activity.test.d.ts.map +1 -0
  7. package/dist/activity.test.js +430 -0
  8. package/dist/activity.test.js.map +1 -0
  9. package/dist/client-delivery-config.d.ts +26 -0
  10. package/dist/client-delivery-config.d.ts.map +1 -0
  11. package/dist/client-delivery-config.js +40 -0
  12. package/dist/client-delivery-config.js.map +1 -0
  13. package/dist/client-delivery-config.test.d.ts +2 -0
  14. package/dist/client-delivery-config.test.d.ts.map +1 -0
  15. package/dist/client-delivery-config.test.js +36 -0
  16. package/dist/client-delivery-config.test.js.map +1 -0
  17. package/dist/content-review-scoring.d.ts +67 -0
  18. package/dist/content-review-scoring.d.ts.map +1 -0
  19. package/dist/content-review-scoring.js +128 -0
  20. package/dist/content-review-scoring.js.map +1 -0
  21. package/dist/content-review-scoring.test.d.ts +2 -0
  22. package/dist/content-review-scoring.test.d.ts.map +1 -0
  23. package/dist/content-review-scoring.test.js +109 -0
  24. package/dist/content-review-scoring.test.js.map +1 -0
  25. package/dist/errors.d.ts +3 -0
  26. package/dist/errors.d.ts.map +1 -1
  27. package/dist/errors.js +7 -0
  28. package/dist/errors.js.map +1 -1
  29. package/dist/index.d.ts +4 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +6 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/notification-sources/field-paths.d.ts +93 -0
  34. package/dist/notification-sources/field-paths.d.ts.map +1 -0
  35. package/dist/notification-sources/field-paths.js +86 -0
  36. package/dist/notification-sources/field-paths.js.map +1 -0
  37. package/dist/notification-sources/field-set.d.ts +46 -0
  38. package/dist/notification-sources/field-set.d.ts.map +1 -0
  39. package/dist/notification-sources/field-set.js +178 -0
  40. package/dist/notification-sources/field-set.js.map +1 -0
  41. package/dist/notification-sources/index.d.ts +13 -0
  42. package/dist/notification-sources/index.d.ts.map +1 -0
  43. package/dist/notification-sources/index.js +13 -0
  44. package/dist/notification-sources/index.js.map +1 -0
  45. package/dist/notification-sources/notification-sources.test.d.ts +2 -0
  46. package/dist/notification-sources/notification-sources.test.d.ts.map +1 -0
  47. package/dist/notification-sources/notification-sources.test.js +211 -0
  48. package/dist/notification-sources/notification-sources.test.js.map +1 -0
  49. package/dist/notification-sources/post-performance-node-runtime.d.ts +155 -0
  50. package/dist/notification-sources/post-performance-node-runtime.d.ts.map +1 -0
  51. package/dist/notification-sources/post-performance-node-runtime.js +158 -0
  52. package/dist/notification-sources/post-performance-node-runtime.js.map +1 -0
  53. package/dist/notification-sources/post-performance-node-runtime.test.d.ts +2 -0
  54. package/dist/notification-sources/post-performance-node-runtime.test.d.ts.map +1 -0
  55. package/dist/notification-sources/post-performance-node-runtime.test.js +182 -0
  56. package/dist/notification-sources/post-performance-node-runtime.test.js.map +1 -0
  57. package/dist/notification-sources/post-performance-node.d.ts +34 -0
  58. package/dist/notification-sources/post-performance-node.d.ts.map +1 -0
  59. package/dist/notification-sources/post-performance-node.js +437 -0
  60. package/dist/notification-sources/post-performance-node.js.map +1 -0
  61. package/dist/notification-sources/registry.d.ts +27 -0
  62. package/dist/notification-sources/registry.d.ts.map +1 -0
  63. package/dist/notification-sources/registry.js +222 -0
  64. package/dist/notification-sources/registry.js.map +1 -0
  65. package/dist/notification-sources/source-definition.d.ts +97 -0
  66. package/dist/notification-sources/source-definition.d.ts.map +1 -0
  67. package/dist/notification-sources/source-definition.js +30 -0
  68. package/dist/notification-sources/source-definition.js.map +1 -0
  69. package/dist/openclaw-config.d.ts +9 -0
  70. package/dist/openclaw-config.d.ts.map +1 -1
  71. package/dist/openclaw-config.js +17 -0
  72. package/dist/openclaw-config.js.map +1 -1
  73. package/dist/openclaw-config.test.d.ts +2 -0
  74. package/dist/openclaw-config.test.d.ts.map +1 -0
  75. package/dist/openclaw-config.test.js +49 -0
  76. package/dist/openclaw-config.test.js.map +1 -0
  77. package/package.json +1 -1
@@ -0,0 +1,430 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { activityDateOf, computeEffectiveTimezone, computeRecentActivitySummary, validateCanonicalEventPayload, validateInteractionActionOutcomePayload, INTERACTION_ACTION_OUTCOME_TYPE, } from "./activity.js";
3
+ /**
4
+ * §11.4 验收 fixture:跨午夜、02:00 边界、未结束当日、重试/重复、缺 runId、
5
+ * planned/skipped 不计、DST 春秋切换、tz 缺失、tz 变更、28 天全空、连续沉默。
6
+ */
7
+ const TZ = "America/New_York";
8
+ const NOW = new Date("2026-07-14T20:00:00-04:00"); // 当地 20:00,activityDate=2026-07-14
9
+ let seq = 0;
10
+ function ev(occurredAt, over = {}) {
11
+ seq += 1;
12
+ const payload = {
13
+ occurredAt,
14
+ actionKind: over.actionKind ?? "comment",
15
+ eventSchemaVersion: 1,
16
+ jobId: over.jobId ?? "job-1",
17
+ };
18
+ if (over.eventTimezone !== null)
19
+ payload["eventTimezone"] = over.eventTimezone ?? TZ;
20
+ if (over.runId)
21
+ payload["runId"] = over.runId;
22
+ if (over.sessionId)
23
+ payload["sessionId"] = over.sessionId;
24
+ return {
25
+ id: `ev-${seq}`,
26
+ externalRef: over.externalRef === undefined ? `ref-${seq}` : over.externalRef,
27
+ result: over.result ?? "succeeded",
28
+ payload,
29
+ createdAt: new Date(occurredAt),
30
+ };
31
+ }
32
+ function summarize(events, over = {}) {
33
+ return computeRecentActivitySummary(events, {
34
+ now: NOW,
35
+ effectiveTimezone: TZ,
36
+ timezoneKnown: true,
37
+ ...over,
38
+ });
39
+ }
40
+ function day(s, date) {
41
+ return s.localDays.find((d) => d.date === date);
42
+ }
43
+ describe("activityDateOf (§4.3③ 固定 cutoff)", () => {
44
+ it("本地 00:00–02:00 归前一日,02:00 及以后归当日", () => {
45
+ // 01:30 EDT → 前一日
46
+ expect(activityDateOf(new Date("2026-07-14T01:30:00-04:00"), TZ)).toBe("2026-07-13");
47
+ // 02:00 边界 → 当日
48
+ expect(activityDateOf(new Date("2026-07-14T02:00:00-04:00"), TZ)).toBe("2026-07-14");
49
+ // 23:00 → 当日
50
+ expect(activityDateOf(new Date("2026-07-14T23:00:00-04:00"), TZ)).toBe("2026-07-14");
51
+ });
52
+ it("DST 春季切换日(2026-03-08,02:00→03:00 缺失):03:00 归当日,01:59 归前一日", () => {
53
+ // 07:00Z = EST 02:00 不存在,该 UTC 时刻本地是 03:00 EDT → 当日
54
+ expect(activityDateOf(new Date("2026-03-08T07:00:00Z"), TZ)).toBe("2026-03-08");
55
+ // 06:59Z = 01:59 EST → 前一日
56
+ expect(activityDateOf(new Date("2026-03-08T06:59:00Z"), TZ)).toBe("2026-03-07");
57
+ });
58
+ it("DST 秋季切换日(2026-11-01,01:00 重复):两个真实时刻均归前一日(<02:00)", () => {
59
+ // 05:30Z = 01:30 EDT(第一次);06:30Z = 01:30 EST(第二次)——都在 cutoff 前
60
+ expect(activityDateOf(new Date("2026-11-01T05:30:00Z"), TZ)).toBe("2026-10-31");
61
+ expect(activityDateOf(new Date("2026-11-01T06:30:00Z"), TZ)).toBe("2026-10-31");
62
+ // 07:00Z = 02:00 EST → 当日
63
+ expect(activityDateOf(new Date("2026-11-01T07:00:00Z"), TZ)).toBe("2026-11-01");
64
+ });
65
+ });
66
+ describe("computeRecentActivitySummary (§4.2/§4.3④)", () => {
67
+ it("localDays 恰 windowDays 条,以当前 activityDate 结尾;28 天全空", () => {
68
+ const s = summarize([]);
69
+ expect(s.localDays).toHaveLength(28);
70
+ expect(s.localDays.at(-1).date).toBe("2026-07-14");
71
+ expect(s.localDays.every((d) => d.sessions === 0)).toBe(true);
72
+ expect(s.activeDaysLast28).toBe(0);
73
+ expect(s.consecutiveSilentDays).toBe(27); // 27 个已完结日全静默
74
+ expect(s.lastSessionAt).toBeNull();
75
+ });
76
+ it("同 sessionId 多动作归一个 session,action 各自照数;comments/replies 互斥统计", () => {
77
+ const s = summarize([
78
+ ev("2026-07-13T10:00:00-04:00", {
79
+ sessionId: "s1",
80
+ actionKind: "browse",
81
+ }),
82
+ ev("2026-07-13T10:05:00-04:00", {
83
+ sessionId: "s1",
84
+ actionKind: "comment",
85
+ }),
86
+ ev("2026-07-13T10:07:00-04:00", { sessionId: "s1", actionKind: "reply" }),
87
+ ev("2026-07-13T10:09:00-04:00", { sessionId: "s1", actionKind: "like" }),
88
+ ]);
89
+ const d = day(s, "2026-07-13");
90
+ expect(d).toMatchObject({ sessions: 1, comments: 1, replies: 1, likes: 1 });
91
+ });
92
+ it("缺 sessionId 用 runId;同 runId 跨 job 合并一个 session", () => {
93
+ const s = summarize([
94
+ ev("2026-07-13T10:00:00-04:00", { runId: "r1", jobId: "job-a" }),
95
+ ev("2026-07-13T11:30:00-04:00", { runId: "r1", jobId: "job-b" }),
96
+ ]);
97
+ expect(day(s, "2026-07-13").sessions).toBe(1);
98
+ });
99
+ it("缺 runId:同 jobId 相邻 ≤20min 归同 session,>20min 另起", () => {
100
+ const s = summarize([
101
+ ev("2026-07-13T10:00:00-04:00", {}),
102
+ ev("2026-07-13T10:15:00-04:00", {}), // ≤20min → 同 session
103
+ ev("2026-07-13T10:40:00-04:00", {}), // >20min → 新 session
104
+ ]);
105
+ expect(day(s, "2026-07-13").sessions).toBe(2);
106
+ expect(day(s, "2026-07-13").comments).toBe(3);
107
+ });
108
+ it("重试/重复:无 externalRef 的相同三元组整条去重(action 也只计 1 次)", () => {
109
+ const dup = {
110
+ sessionId: "s1",
111
+ externalRef: null,
112
+ actionKind: "comment",
113
+ };
114
+ const s = summarize([
115
+ ev("2026-07-13T10:00:00-04:00", dup),
116
+ ev("2026-07-13T10:00:00-04:00", dup), // 完全相同 → 去重
117
+ ev("2026-07-13T10:05:00-04:00", dup), // occurredAt 不同 → 真实第二条
118
+ ]);
119
+ const d = day(s, "2026-07-13");
120
+ expect(d.comments).toBe(2);
121
+ expect(d.sessions).toBe(1);
122
+ });
123
+ it("planned/skipped/session_start 与 failed/skipped result 均不计", () => {
124
+ const s = summarize([
125
+ ev("2026-07-13T10:00:00-04:00", { actionKind: "planned" }),
126
+ ev("2026-07-13T10:01:00-04:00", { actionKind: "skipped" }),
127
+ ev("2026-07-13T10:02:00-04:00", { actionKind: "session_start" }),
128
+ ev("2026-07-13T10:03:00-04:00", { result: "failed" }),
129
+ ev("2026-07-13T10:04:00-04:00", { result: "skipped" }),
130
+ ]);
131
+ expect(day(s, "2026-07-13")).toMatchObject({
132
+ sessions: 0,
133
+ comments: 0,
134
+ likes: 0,
135
+ replies: 0,
136
+ });
137
+ });
138
+ it("需求A 安全闸:interaction_action_outcome 事件不计入活动(即使 actionKind=comment)", () => {
139
+ const real = ev("2026-07-13T10:00:00-04:00", {
140
+ sessionId: "s1",
141
+ actionKind: "comment",
142
+ });
143
+ const outcome = ev("2026-07-13T10:05:00-04:00", {
144
+ sessionId: "s1",
145
+ actionKind: "comment",
146
+ });
147
+ outcome.type = INTERACTION_ACTION_OUTCOME_TYPE; // 复用真实 actionKind,但按 type 排除
148
+ const s = summarize([real, outcome]);
149
+ const d = day(s, "2026-07-13");
150
+ expect(d.comments).toBe(1); // 只算真实 comment,outcome 不计
151
+ expect(d.sessions).toBe(1);
152
+ });
153
+ it("跨午夜:本地 01:30 的 session 归前一日", () => {
154
+ const s = summarize([
155
+ ev("2026-07-14T01:30:00-04:00", { sessionId: "s-night" }),
156
+ ]);
157
+ expect(day(s, "2026-07-13").sessions).toBe(1);
158
+ expect(day(s, "2026-07-14").sessions).toBe(0);
159
+ });
160
+ it("consecutiveSilentDays 不含未结束当日;当日有活动不清零昨日静默", () => {
161
+ const s = summarize([
162
+ ev("2026-07-11T10:00:00-04:00", { sessionId: "a" }), // 3 天前活跃
163
+ ev("2026-07-14T10:00:00-04:00", { sessionId: "b" }), // 当日(未结束)
164
+ ]);
165
+ // 已完结的 07-12、07-13 静默 → 2
166
+ expect(s.consecutiveSilentDays).toBe(2);
167
+ });
168
+ it("timezone 变更:事件按自身 eventTimezone 归日,summary 顶层是当前时区", () => {
169
+ // 事件发生时账号在东京(UTC+9):当地 7/13 01:00 → 归 7/12
170
+ const s = summarize([
171
+ ev("2026-07-13T01:00:00+09:00", {
172
+ eventTimezone: "Asia/Tokyo",
173
+ sessionId: "s1",
174
+ }),
175
+ ]);
176
+ expect(day(s, "2026-07-12").sessions).toBe(1);
177
+ expect(s.historicalTzApprox).toBe(false);
178
+ });
179
+ it("历史事件缺 eventTimezone:按当前时区近似归日并标 historicalTzApprox", () => {
180
+ const s = summarize([
181
+ ev("2026-07-13T10:00:00-04:00", { eventTimezone: null, sessionId: "s1" }),
182
+ ]);
183
+ expect(day(s, "2026-07-13").sessions).toBe(1);
184
+ expect(s.historicalTzApprox).toBe(true);
185
+ });
186
+ it("tz 完全未知(裁决④):不做逐日归属,保留绝对时间字段;带 eventTimezone 的事件仍可计 lastSessionAt", () => {
187
+ const s = summarize([
188
+ ev("2026-07-13T10:00:00-04:00", {
189
+ eventTimezone: null,
190
+ sessionId: "a",
191
+ }),
192
+ ev("2026-07-13T12:00:00-04:00", {
193
+ eventTimezone: "Asia/Tokyo",
194
+ sessionId: "b",
195
+ }),
196
+ ], { effectiveTimezone: "unknown", timezoneKnown: false });
197
+ expect(s.timezoneKnown).toBe(false);
198
+ expect(s.localDays).toEqual([]);
199
+ expect(s.consecutiveSilentDays).toBeNull();
200
+ expect(s.activeDaysLast7).toBeNull();
201
+ expect(s.unattributedEvents).toBe(1); // 缺 eventTimezone 那条
202
+ expect(s.lastSessionAt).toBe("2026-07-13T16:00:00.000Z"); // Tokyo 那条
203
+ });
204
+ it("activeDaysLast7/28 按已完结 activityDate 计", () => {
205
+ const s = summarize([
206
+ ev("2026-07-10T10:00:00-04:00", { sessionId: "a" }),
207
+ ev("2026-07-12T10:00:00-04:00", { sessionId: "b" }),
208
+ ev("2026-07-14T10:00:00-04:00", { sessionId: "c" }), // 当日不计
209
+ ]);
210
+ expect(s.activeDaysLast7).toBe(2);
211
+ expect(s.activeDaysLast28).toBe(2);
212
+ });
213
+ });
214
+ describe("validateCanonicalEventPayload (§2.5.2① 全组校验)", () => {
215
+ const now = new Date("2026-07-14T12:00:00Z");
216
+ it("legacy(全缺)放行;半 canonical 拒绝并列出缺项", () => {
217
+ expect(validateCanonicalEventPayload({ foo: 1 })).toEqual({
218
+ ok: true,
219
+ canonical: false,
220
+ });
221
+ const r = validateCanonicalEventPayload({ actionKind: "comment" }, { now });
222
+ expect(r.ok).toBe(false);
223
+ if (!r.ok) {
224
+ expect(r.errors.join(";")).toMatch(/occurredAt/);
225
+ expect(r.errors.join(";")).toMatch(/jobId/);
226
+ }
227
+ });
228
+ it("完整 canonical 通过;occurredAt 超 48h 补录窗或无 offset 拒绝", () => {
229
+ const good = {
230
+ occurredAt: "2026-07-14T11:00:00Z",
231
+ eventTimezone: "America/New_York",
232
+ actionKind: "comment",
233
+ eventSchemaVersion: 1,
234
+ jobId: "job-1",
235
+ };
236
+ expect(validateCanonicalEventPayload(good, { now }).ok).toBe(true);
237
+ expect(validateCanonicalEventPayload({ ...good, occurredAt: "2026-07-10T11:00:00Z" }, { now }).ok).toBe(false);
238
+ expect(validateCanonicalEventPayload({ ...good, occurredAt: "2026-07-14T11:00:00" }, { now }).ok).toBe(false);
239
+ });
240
+ });
241
+ describe("canonical v2 时区(需求A:允许 null + timezoneKnown)", () => {
242
+ const now = new Date("2026-07-14T12:00:00Z");
243
+ const base = {
244
+ occurredAt: "2026-07-14T11:00:00Z",
245
+ actionKind: "comment",
246
+ eventSchemaVersion: 2,
247
+ jobId: "job-1",
248
+ };
249
+ it("v1 仍要求有效 IANA(回归:不受 v2 影响)", () => {
250
+ const v1 = {
251
+ occurredAt: "2026-07-14T11:00:00Z",
252
+ eventTimezone: null,
253
+ actionKind: "comment",
254
+ eventSchemaVersion: 1,
255
+ jobId: "job-1",
256
+ };
257
+ expect(validateCanonicalEventPayload(v1, { now }).ok).toBe(false);
258
+ });
259
+ it("v2 timezoneKnown=false + eventTimezone=null → 通过", () => {
260
+ expect(validateCanonicalEventPayload({ ...base, eventTimezone: null, timezoneKnown: false }, { now }).ok).toBe(true);
261
+ });
262
+ it("v2 timezoneKnown=true + 有效 IANA → 通过", () => {
263
+ expect(validateCanonicalEventPayload({ ...base, eventTimezone: "America/New_York", timezoneKnown: true }, { now }).ok).toBe(true);
264
+ });
265
+ it("v2 timezoneKnown=false 但 eventTimezone 非 null → 拒", () => {
266
+ expect(validateCanonicalEventPayload({ ...base, eventTimezone: "UTC", timezoneKnown: false }, { now }).ok).toBe(false);
267
+ });
268
+ it("v2 缺 timezoneKnown → 拒", () => {
269
+ expect(validateCanonicalEventPayload({ ...base, eventTimezone: null }, { now })
270
+ .ok).toBe(false);
271
+ });
272
+ });
273
+ describe("validateInteractionActionOutcomePayload (需求A §5.1 交叉校验)", () => {
274
+ it("成功:ok=true/succeeded/NONE/stop=false/retryable=false", () => {
275
+ const r = validateInteractionActionOutcomePayload({
276
+ outcome: "succeeded",
277
+ ok: true,
278
+ stop: false,
279
+ retryable: false,
280
+ code: "NONE",
281
+ phase: "verify",
282
+ method: "coordinate-input",
283
+ });
284
+ expect(r.ok).toBe(true);
285
+ });
286
+ it("终止:STOP_CHALLENGE ⇒ stopped/stop=true/retryable=false/ok=false", () => {
287
+ expect(validateInteractionActionOutcomePayload({
288
+ outcome: "stopped",
289
+ ok: false,
290
+ stop: true,
291
+ retryable: false,
292
+ code: "STOP_CHALLENGE",
293
+ phase: "precondition",
294
+ method: null,
295
+ }).ok).toBe(true);
296
+ });
297
+ it("STOP_WRITE_UNCERTAIN ⇒ outcome=uncertain(不是 stopped)", () => {
298
+ const uncertain = {
299
+ ok: false,
300
+ stop: true,
301
+ retryable: false,
302
+ code: "STOP_WRITE_UNCERTAIN",
303
+ phase: "verify",
304
+ method: "coordinate-input",
305
+ };
306
+ expect(validateInteractionActionOutcomePayload({
307
+ ...uncertain,
308
+ outcome: "uncertain",
309
+ }).ok).toBe(true);
310
+ expect(validateInteractionActionOutcomePayload({
311
+ ...uncertain,
312
+ outcome: "stopped",
313
+ }).ok).toBe(false);
314
+ });
315
+ it("可重试:TARGET_STALE ⇒ failed/stop=false/retryable=true", () => {
316
+ expect(validateInteractionActionOutcomePayload({
317
+ outcome: "failed",
318
+ ok: false,
319
+ stop: false,
320
+ retryable: true,
321
+ code: "TARGET_STALE",
322
+ phase: "target",
323
+ method: null,
324
+ }).ok).toBe(true);
325
+ });
326
+ it("非法组合拒:ok=true 但 outcome=stopped", () => {
327
+ expect(validateInteractionActionOutcomePayload({
328
+ outcome: "stopped",
329
+ ok: true,
330
+ stop: false,
331
+ retryable: false,
332
+ code: "NONE",
333
+ phase: "verify",
334
+ method: null,
335
+ }).ok).toBe(false);
336
+ });
337
+ it("未知 code 拒绝(不静默降级)", () => {
338
+ expect(validateInteractionActionOutcomePayload({
339
+ outcome: "stopped",
340
+ ok: false,
341
+ stop: true,
342
+ retryable: false,
343
+ code: "STOP_SOMETHING_NEW",
344
+ phase: "verify",
345
+ method: null,
346
+ }).ok).toBe(false);
347
+ });
348
+ it("method=null 在 input/submit/verify 阶段拒(已执行输入)", () => {
349
+ expect(validateInteractionActionOutcomePayload({
350
+ outcome: "stopped",
351
+ ok: false,
352
+ stop: true,
353
+ retryable: false,
354
+ code: "STOP_POST_CLICK_NO_CHANGE",
355
+ phase: "input",
356
+ method: null,
357
+ }).ok).toBe(false);
358
+ });
359
+ it("method 字符串 'null' 拒(必须 JSON null)", () => {
360
+ expect(validateInteractionActionOutcomePayload({
361
+ outcome: "stopped",
362
+ ok: false,
363
+ stop: true,
364
+ retryable: false,
365
+ code: "STOP_CHALLENGE",
366
+ phase: "precondition",
367
+ method: "null",
368
+ }).ok).toBe(false);
369
+ });
370
+ it("经 validateCanonicalEventPayload(eventType) 集成:outcome 字段非法 → 整体拒", () => {
371
+ const now = new Date("2026-07-14T12:00:00Z");
372
+ const r = validateCanonicalEventPayload({
373
+ occurredAt: "2026-07-14T11:00:00Z",
374
+ eventTimezone: null,
375
+ timezoneKnown: false,
376
+ actionKind: "comment",
377
+ eventSchemaVersion: 2,
378
+ jobId: "job-1",
379
+ outcome: "succeeded",
380
+ ok: false, // 与 succeeded 矛盾
381
+ stop: false,
382
+ retryable: false,
383
+ code: "NONE",
384
+ phase: "verify",
385
+ method: null,
386
+ }, { now, eventType: INTERACTION_ACTION_OUTCOME_TYPE });
387
+ expect(r.ok).toBe(false);
388
+ });
389
+ });
390
+ describe("computeEffectiveTimezone (§4.3① UTC 毒化修复)", () => {
391
+ it("默认填充的 UTC 不算已知;真配置 risk 优先;env 按 lastHealthAt 最新", () => {
392
+ expect(computeEffectiveTimezone({
393
+ risk: { timezone: "UTC", timezoneConfigured: false },
394
+ browserEnvs: [],
395
+ })).toEqual({ effectiveTimezone: "unknown", timezoneKnown: false });
396
+ expect(computeEffectiveTimezone({
397
+ risk: { timezone: "UTC", timezoneConfigured: true }, // 显式配置 UTC=已知
398
+ browserEnvs: [],
399
+ })).toEqual({ effectiveTimezone: "UTC", timezoneKnown: true });
400
+ expect(computeEffectiveTimezone({
401
+ risk: { timezone: "America/Chicago", timezoneConfigured: true },
402
+ browserEnvs: [
403
+ {
404
+ timezone: "Asia/Tokyo",
405
+ timezoneConfigured: true,
406
+ lastHealthAt: new Date(),
407
+ createdAt: new Date(),
408
+ },
409
+ ],
410
+ }).effectiveTimezone).toBe("America/Chicago"); // 冲突裁决:risk 权威
411
+ expect(computeEffectiveTimezone({
412
+ risk: null,
413
+ browserEnvs: [
414
+ {
415
+ timezone: "Asia/Tokyo",
416
+ timezoneConfigured: true,
417
+ lastHealthAt: new Date("2026-07-01"),
418
+ createdAt: new Date("2026-06-01"),
419
+ },
420
+ {
421
+ timezone: "Europe/London",
422
+ timezoneConfigured: true,
423
+ lastHealthAt: new Date("2026-07-10"),
424
+ createdAt: new Date("2026-05-01"),
425
+ },
426
+ ],
427
+ }).effectiveTimezone).toBe("Europe/London");
428
+ });
429
+ });
430
+ //# sourceMappingURL=activity.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity.test.js","sourceRoot":"","sources":["../src/activity.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACL,cAAc,EACd,wBAAwB,EACxB,4BAA4B,EAC5B,6BAA6B,EAC7B,uCAAuC,EACvC,+BAA+B,GAEhC,MAAM,eAAe,CAAC;AAEvB;;;GAGG;AAEH,MAAM,EAAE,GAAG,kBAAkB,CAAC;AAC9B,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,2BAA2B,CAAC,CAAC,CAAC,mCAAmC;AAEtF,IAAI,GAAG,GAAG,CAAC,CAAC;AACZ,SAAS,EAAE,CACT,UAAkB,EAClB,OAQK,EAAE;IAEP,GAAG,IAAI,CAAC,CAAC;IACT,MAAM,OAAO,GAA4B;QACvC,UAAU;QACV,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,SAAS;QACxC,kBAAkB,EAAE,CAAC;QACrB,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,OAAO;KAC7B,CAAC;IACF,IAAI,IAAI,CAAC,aAAa,KAAK,IAAI;QAC7B,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAAC,aAAa,IAAI,EAAE,CAAC;IACtD,IAAI,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;IAC9C,IAAI,IAAI,CAAC,SAAS;QAAE,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC;IAC1D,OAAO;QACL,EAAE,EAAE,MAAM,GAAG,EAAE;QACf,WAAW,EACT,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW;QAClE,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,WAAW;QAClC,OAAO;QACP,SAAS,EAAE,IAAI,IAAI,CAAC,UAAU,CAAC;KAChC,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAChB,MAA4B,EAC5B,OAAoE,EAAE;IAEtE,OAAO,4BAA4B,CAAC,MAAM,EAAE;QAC1C,GAAG,EAAE,GAAG;QACR,iBAAiB,EAAE,EAAE;QACrB,aAAa,EAAE,IAAI;QACnB,GAAG,IAAI;KACR,CAAC,CAAC;AACL,CAAC;AAED,SAAS,GAAG,CAAC,CAA+B,EAAE,IAAY;IACxD,OAAO,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AAClD,CAAC;AAED,QAAQ,CAAC,kCAAkC,EAAE,GAAG,EAAE;IAChD,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,kBAAkB;QAClB,MAAM,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC,2BAA2B,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CACpE,YAAY,CACb,CAAC;QACF,gBAAgB;QAChB,MAAM,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC,2BAA2B,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CACpE,YAAY,CACb,CAAC;QACF,aAAa;QACb,MAAM,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC,2BAA2B,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CACpE,YAAY,CACb,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,oDAAoD;QACpD,MAAM,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC,sBAAsB,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAC/D,YAAY,CACb,CAAC;QACF,2BAA2B;QAC3B,MAAM,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC,sBAAsB,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAC/D,YAAY,CACb,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,+DAA+D;QAC/D,MAAM,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC,sBAAsB,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAC/D,YAAY,CACb,CAAC;QACF,MAAM,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC,sBAAsB,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAC/D,YAAY,CACb,CAAC;QACF,0BAA0B;QAC1B,MAAM,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC,sBAAsB,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAC/D,YAAY,CACb,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,2CAA2C,EAAE,GAAG,EAAE;IACzD,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;QACxB,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QACrC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACpD,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9D,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc;QACxD,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,QAAQ,EAAE,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACtE,MAAM,CAAC,GAAG,SAAS,CAAC;YAClB,EAAE,CAAC,2BAA2B,EAAE;gBAC9B,SAAS,EAAE,IAAI;gBACf,UAAU,EAAE,QAAQ;aACrB,CAAC;YACF,EAAE,CAAC,2BAA2B,EAAE;gBAC9B,SAAS,EAAE,IAAI;gBACf,UAAU,EAAE,SAAS;aACtB,CAAC;YACF,EAAE,CAAC,2BAA2B,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;YACzE,EAAE,CAAC,2BAA2B,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;SACzE,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,YAAY,CAAE,CAAC;QAChC,MAAM,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,GAAG,SAAS,CAAC;YAClB,EAAE,CAAC,2BAA2B,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;YAChE,EAAE,CAAC,2BAA2B,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;SACjE,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAE,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,GAAG,SAAS,CAAC;YAClB,EAAE,CAAC,2BAA2B,EAAE,EAAE,CAAC;YACnC,EAAE,CAAC,2BAA2B,EAAE,EAAE,CAAC,EAAE,qBAAqB;YAC1D,EAAE,CAAC,2BAA2B,EAAE,EAAE,CAAC,EAAE,qBAAqB;SAC3D,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAE,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/C,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAE,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,GAAG,GAAG;YACV,SAAS,EAAE,IAAI;YACf,WAAW,EAAE,IAAqB;YAClC,UAAU,EAAE,SAAS;SACtB,CAAC;QACF,MAAM,CAAC,GAAG,SAAS,CAAC;YAClB,EAAE,CAAC,2BAA2B,EAAE,GAAG,CAAC;YACpC,EAAE,CAAC,2BAA2B,EAAE,GAAG,CAAC,EAAE,YAAY;YAClD,EAAE,CAAC,2BAA2B,EAAE,GAAG,CAAC,EAAE,wBAAwB;SAC/D,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,YAAY,CAAE,CAAC;QAChC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3B,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,CAAC,GAAG,SAAS,CAAC;YAClB,EAAE,CAAC,2BAA2B,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;YAC1D,EAAE,CAAC,2BAA2B,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;YAC1D,EAAE,CAAC,2BAA2B,EAAE,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC;YAChE,EAAE,CAAC,2BAA2B,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;YACrD,EAAE,CAAC,2BAA2B,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;SACvD,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAE,CAAC,CAAC,aAAa,CAAC;YAC1C,QAAQ,EAAE,CAAC;YACX,QAAQ,EAAE,CAAC;YACX,KAAK,EAAE,CAAC;YACR,OAAO,EAAE,CAAC;SACX,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;QAC3E,MAAM,IAAI,GAAG,EAAE,CAAC,2BAA2B,EAAE;YAC3C,SAAS,EAAE,IAAI;YACf,UAAU,EAAE,SAAS;SACtB,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,2BAA2B,EAAE;YAC9C,SAAS,EAAE,IAAI;YACf,UAAU,EAAE,SAAS;SACtB,CAAC,CAAC;QACH,OAAO,CAAC,IAAI,GAAG,+BAA+B,CAAC,CAAC,6BAA6B;QAC7E,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;QACrC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,YAAY,CAAE,CAAC;QAChC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,0BAA0B;QACtD,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,GAAG,SAAS,CAAC;YAClB,EAAE,CAAC,2BAA2B,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;SAC1D,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAE,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/C,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAE,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,GAAG,SAAS,CAAC;YAClB,EAAE,CAAC,2BAA2B,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE,SAAS;YAC9D,EAAE,CAAC,2BAA2B,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE,UAAU;SAChE,CAAC,CAAC;QACH,0BAA0B;QAC1B,MAAM,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,2CAA2C;QAC3C,MAAM,CAAC,GAAG,SAAS,CAAC;YAClB,EAAE,CAAC,2BAA2B,EAAE;gBAC9B,aAAa,EAAE,YAAY;gBAC3B,SAAS,EAAE,IAAI;aAChB,CAAC;SACH,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAE,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/C,MAAM,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,CAAC,GAAG,SAAS,CAAC;YAClB,EAAE,CAAC,2BAA2B,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;SAC1E,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAE,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/C,MAAM,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;QAC3E,MAAM,CAAC,GAAG,SAAS,CACjB;YACE,EAAE,CAAC,2BAA2B,EAAE;gBAC9B,aAAa,EAAE,IAAI;gBACnB,SAAS,EAAE,GAAG;aACf,CAAC;YACF,EAAE,CAAC,2BAA2B,EAAE;gBAC9B,aAAa,EAAE,YAAY;gBAC3B,SAAS,EAAE,GAAG;aACf,CAAC;SACH,EACD,EAAE,iBAAiB,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,EAAE,CACvD,CAAC;QACF,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAChC,MAAM,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC3C,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,QAAQ,EAAE,CAAC;QACrC,MAAM,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,qBAAqB;QAC3D,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC,CAAC,WAAW;IACvE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,GAAG,SAAS,CAAC;YAClB,EAAE,CAAC,2BAA2B,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;YACnD,EAAE,CAAC,2BAA2B,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;YACnD,EAAE,CAAC,2BAA2B,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO;SAC7D,CAAC,CAAC;QACH,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClC,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,8CAA8C,EAAE,GAAG,EAAE;IAC5D,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,sBAAsB,CAAC,CAAC;IAC7C,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,CAAC,6BAA6B,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;YACxD,EAAE,EAAE,IAAI;YACR,SAAS,EAAE,KAAK;SACjB,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,6BAA6B,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QAC5E,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;YACV,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;YACjD,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,IAAI,GAAG;YACX,UAAU,EAAE,sBAAsB;YAClC,aAAa,EAAE,kBAAkB;YACjC,UAAU,EAAE,SAAS;YACrB,kBAAkB,EAAE,CAAC;YACrB,KAAK,EAAE,OAAO;SACf,CAAC;QACF,MAAM,CAAC,6BAA6B,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,MAAM,CACJ,6BAA6B,CAC3B,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,sBAAsB,EAAE,EAC/C,EAAE,GAAG,EAAE,CACR,CAAC,EAAE,CACL,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,MAAM,CACJ,6BAA6B,CAC3B,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,qBAAqB,EAAE,EAC9C,EAAE,GAAG,EAAE,CACR,CAAC,EAAE,CACL,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,8CAA8C,EAAE,GAAG,EAAE;IAC5D,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,sBAAsB,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG;QACX,UAAU,EAAE,sBAAsB;QAClC,UAAU,EAAE,SAAS;QACrB,kBAAkB,EAAE,CAAC;QACrB,KAAK,EAAE,OAAO;KACf,CAAC;IACF,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,EAAE,GAAG;YACT,UAAU,EAAE,sBAAsB;YAClC,aAAa,EAAE,IAAI;YACnB,UAAU,EAAE,SAAS;YACrB,kBAAkB,EAAE,CAAC;YACrB,KAAK,EAAE,OAAO;SACf,CAAC;QACF,MAAM,CAAC,6BAA6B,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,CACJ,6BAA6B,CAC3B,EAAE,GAAG,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,EACtD,EAAE,GAAG,EAAE,CACR,CAAC,EAAE,CACL,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,CACJ,6BAA6B,CAC3B,EAAE,GAAG,IAAI,EAAE,aAAa,EAAE,kBAAkB,EAAE,aAAa,EAAE,IAAI,EAAE,EACnE,EAAE,GAAG,EAAE,CACR,CAAC,EAAE,CACL,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,CACJ,6BAA6B,CAC3B,EAAE,GAAG,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,EACvD,EAAE,GAAG,EAAE,CACR,CAAC,EAAE,CACL,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,MAAM,CACJ,6BAA6B,CAAC,EAAE,GAAG,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC;aACrE,EAAE,CACN,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,yDAAyD,EAAE,GAAG,EAAE;IACvE,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,CAAC,GAAG,uCAAuC,CAAC;YAChD,OAAO,EAAE,WAAW;YACpB,EAAE,EAAE,IAAI;YACR,IAAI,EAAE,KAAK;YACX,SAAS,EAAE,KAAK;YAChB,IAAI,EAAE,MAAM;YACZ,KAAK,EAAE,QAAQ;YACf,MAAM,EAAE,kBAAkB;SAC3B,CAAC,CAAC;QACH,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,MAAM,CACJ,uCAAuC,CAAC;YACtC,OAAO,EAAE,SAAS;YAClB,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,IAAI;YACV,SAAS,EAAE,KAAK;YAChB,IAAI,EAAE,gBAAgB;YACtB,KAAK,EAAE,cAAc;YACrB,MAAM,EAAE,IAAI;SACb,CAAC,CAAC,EAAE,CACN,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,SAAS,GAAG;YAChB,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,IAAI;YACV,SAAS,EAAE,KAAK;YAChB,IAAI,EAAE,sBAAsB;YAC5B,KAAK,EAAE,QAAQ;YACf,MAAM,EAAE,kBAAkB;SAC3B,CAAC;QACF,MAAM,CACJ,uCAAuC,CAAC;YACtC,GAAG,SAAS;YACZ,OAAO,EAAE,WAAW;SACrB,CAAC,CAAC,EAAE,CACN,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,MAAM,CACJ,uCAAuC,CAAC;YACtC,GAAG,SAAS;YACZ,OAAO,EAAE,SAAS;SACnB,CAAC,CAAC,EAAE,CACN,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CACJ,uCAAuC,CAAC;YACtC,OAAO,EAAE,QAAQ;YACjB,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,KAAK;YACX,SAAS,EAAE,IAAI;YACf,IAAI,EAAE,cAAc;YACpB,KAAK,EAAE,QAAQ;YACf,MAAM,EAAE,IAAI;SACb,CAAC,CAAC,EAAE,CACN,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,CACJ,uCAAuC,CAAC;YACtC,OAAO,EAAE,SAAS;YAClB,EAAE,EAAE,IAAI;YACR,IAAI,EAAE,KAAK;YACX,SAAS,EAAE,KAAK;YAChB,IAAI,EAAE,MAAM;YACZ,KAAK,EAAE,QAAQ;YACf,MAAM,EAAE,IAAI;SACb,CAAC,CAAC,EAAE,CACN,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE;QAC3B,MAAM,CACJ,uCAAuC,CAAC;YACtC,OAAO,EAAE,SAAS;YAClB,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,IAAI;YACV,SAAS,EAAE,KAAK;YAChB,IAAI,EAAE,oBAAoB;YAC1B,KAAK,EAAE,QAAQ;YACf,MAAM,EAAE,IAAI;SACb,CAAC,CAAC,EAAE,CACN,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,CACJ,uCAAuC,CAAC;YACtC,OAAO,EAAE,SAAS;YAClB,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,IAAI;YACV,SAAS,EAAE,KAAK;YAChB,IAAI,EAAE,2BAA2B;YACjC,KAAK,EAAE,OAAO;YACd,MAAM,EAAE,IAAI;SACb,CAAC,CAAC,EAAE,CACN,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,CACJ,uCAAuC,CAAC;YACtC,OAAO,EAAE,SAAS;YAClB,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,IAAI;YACV,SAAS,EAAE,KAAK;YAChB,IAAI,EAAE,gBAAgB;YACtB,KAAK,EAAE,cAAc;YACrB,MAAM,EAAE,MAAM;SACf,CAAC,CAAC,EAAE,CACN,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,sBAAsB,CAAC,CAAC;QAC7C,MAAM,CAAC,GAAG,6BAA6B,CACrC;YACE,UAAU,EAAE,sBAAsB;YAClC,aAAa,EAAE,IAAI;YACnB,aAAa,EAAE,KAAK;YACpB,UAAU,EAAE,SAAS;YACrB,kBAAkB,EAAE,CAAC;YACrB,KAAK,EAAE,OAAO;YACd,OAAO,EAAE,WAAW;YACpB,EAAE,EAAE,KAAK,EAAE,iBAAiB;YAC5B,IAAI,EAAE,KAAK;YACX,SAAS,EAAE,KAAK;YAChB,IAAI,EAAE,MAAM;YACZ,KAAK,EAAE,QAAQ;YACf,MAAM,EAAE,IAAI;SACb,EACD,EAAE,GAAG,EAAE,SAAS,EAAE,+BAA+B,EAAE,CACpD,CAAC;QACF,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,2CAA2C,EAAE,GAAG,EAAE;IACzD,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,CACJ,wBAAwB,CAAC;YACvB,IAAI,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE;YACpD,WAAW,EAAE,EAAE;SAChB,CAAC,CACH,CAAC,OAAO,CAAC,EAAE,iBAAiB,EAAE,SAAS,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,CAAC;QAElE,MAAM,CACJ,wBAAwB,CAAC;YACvB,IAAI,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,EAAE,cAAc;YACnE,WAAW,EAAE,EAAE;SAChB,CAAC,CACH,CAAC,OAAO,CAAC,EAAE,iBAAiB,EAAE,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAE7D,MAAM,CACJ,wBAAwB,CAAC;YACvB,IAAI,EAAE,EAAE,QAAQ,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,IAAI,EAAE;YAC/D,WAAW,EAAE;gBACX;oBACE,QAAQ,EAAE,YAAY;oBACtB,kBAAkB,EAAE,IAAI;oBACxB,YAAY,EAAE,IAAI,IAAI,EAAE;oBACxB,SAAS,EAAE,IAAI,IAAI,EAAE;iBACtB;aACF;SACF,CAAC,CAAC,iBAAiB,CACrB,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,eAAe;QAE1C,MAAM,CACJ,wBAAwB,CAAC;YACvB,IAAI,EAAE,IAAI;YACV,WAAW,EAAE;gBACX;oBACE,QAAQ,EAAE,YAAY;oBACtB,kBAAkB,EAAE,IAAI;oBACxB,YAAY,EAAE,IAAI,IAAI,CAAC,YAAY,CAAC;oBACpC,SAAS,EAAE,IAAI,IAAI,CAAC,YAAY,CAAC;iBAClC;gBACD;oBACE,QAAQ,EAAE,eAAe;oBACzB,kBAAkB,EAAE,IAAI;oBACxB,YAAY,EAAE,IAAI,IAAI,CAAC,YAAY,CAAC;oBACpC,SAAS,EAAE,IAAI,IAAI,CAAC,YAAY,CAAC;iBAClC;aACF;SACF,CAAC,CAAC,iBAAiB,CACrB,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * 客户面内容审核(Client Content Delivery)Phase 1 —— feature flag(设计 §10 Deploy A/B)。
3
+ *
4
+ * `CLIENT_DELIVERY_PORTAL_BRAND_IDS`:逗号分隔的 brandId 集合,**默认空 = 对所有 brand 关闭**。
5
+ * - 未在集合内的 brand:portal 端点对 client 返 404、发布链行为**完全不变**(expand-only)。
6
+ * - 在集合内的 brand:开放客户 portal + 发布硬门参与评估。
7
+ *
8
+ * **API 与 worker 必须用同一 env 值**(否则发布门在两条路径判定不一致)。上线 enforce 前须做
9
+ * 多进程 allowlist 指纹一致性核对(Codex:单进程漏配 env = fail-open 绕过)。
10
+ *
11
+ * `CLIENT_DELIVERY_PUBLISH_GATE_ENFORCED`:发布硬门 off→enforce 切换,**默认 off**。
12
+ * - off(默认):发布链**根本不评估** client gate(零 SQL、零行为变化)——Deploy A expand-only。
13
+ * (尚无 shadow 观测模式;未来若要“评估但不拦”的真 shadow,须在此加第三档并让调用方评估后不阻断。)
14
+ * - enforce:客户未当前批准 → 拒排期。**上线 enforce 前须先补齐 claim 时二次校验 / 评论链 gate /
15
+ * 直建 scheduled_job 旁路收口**(Codex 复评:plan-time 门是 preflight,非唯一 chokepoint)。
16
+ */
17
+ /** 解析 allowlist(逗号分隔,trim 去空;解析绝不抛异常)。空/未设 → 空集合。 */
18
+ export declare function clientDeliveryEnabledBrandIds(): Set<string>;
19
+ /**
20
+ * 某 brand 是否开启客户交付。**零 SQL**:空集合或未命中直接 false,供发布门在第一条 SQL 前短路
21
+ * (flag 关时对 evaluateReviewGateInTx 零行为影响)。brandId 为空/未知一律 false(fail-closed)。
22
+ */
23
+ export declare function isClientDeliveryEnabledForBrand(brandId: string | null | undefined): boolean;
24
+ /** 发布硬门是否 enforce(默认 shadow:只有显式 =enforce 才阻断)。 */
25
+ export declare function isClientDeliveryPublishGateEnforced(): boolean;
26
+ //# sourceMappingURL=client-delivery-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-delivery-config.d.ts","sourceRoot":"","sources":["../src/client-delivery-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,qDAAqD;AACrD,wBAAgB,6BAA6B,IAAI,GAAG,CAAC,MAAM,CAAC,CAS3D;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACjC,OAAO,CAGT;AAED,mDAAmD;AACnD,wBAAgB,mCAAmC,IAAI,OAAO,CAE7D"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * 客户面内容审核(Client Content Delivery)Phase 1 —— feature flag(设计 §10 Deploy A/B)。
3
+ *
4
+ * `CLIENT_DELIVERY_PORTAL_BRAND_IDS`:逗号分隔的 brandId 集合,**默认空 = 对所有 brand 关闭**。
5
+ * - 未在集合内的 brand:portal 端点对 client 返 404、发布链行为**完全不变**(expand-only)。
6
+ * - 在集合内的 brand:开放客户 portal + 发布硬门参与评估。
7
+ *
8
+ * **API 与 worker 必须用同一 env 值**(否则发布门在两条路径判定不一致)。上线 enforce 前须做
9
+ * 多进程 allowlist 指纹一致性核对(Codex:单进程漏配 env = fail-open 绕过)。
10
+ *
11
+ * `CLIENT_DELIVERY_PUBLISH_GATE_ENFORCED`:发布硬门 off→enforce 切换,**默认 off**。
12
+ * - off(默认):发布链**根本不评估** client gate(零 SQL、零行为变化)——Deploy A expand-only。
13
+ * (尚无 shadow 观测模式;未来若要“评估但不拦”的真 shadow,须在此加第三档并让调用方评估后不阻断。)
14
+ * - enforce:客户未当前批准 → 拒排期。**上线 enforce 前须先补齐 claim 时二次校验 / 评论链 gate /
15
+ * 直建 scheduled_job 旁路收口**(Codex 复评:plan-time 门是 preflight,非唯一 chokepoint)。
16
+ */
17
+ /** 解析 allowlist(逗号分隔,trim 去空;解析绝不抛异常)。空/未设 → 空集合。 */
18
+ export function clientDeliveryEnabledBrandIds() {
19
+ const raw = process.env["CLIENT_DELIVERY_PORTAL_BRAND_IDS"];
20
+ if (!raw)
21
+ return new Set();
22
+ return new Set(raw
23
+ .split(",")
24
+ .map((s) => s.trim())
25
+ .filter((s) => s.length > 0));
26
+ }
27
+ /**
28
+ * 某 brand 是否开启客户交付。**零 SQL**:空集合或未命中直接 false,供发布门在第一条 SQL 前短路
29
+ * (flag 关时对 evaluateReviewGateInTx 零行为影响)。brandId 为空/未知一律 false(fail-closed)。
30
+ */
31
+ export function isClientDeliveryEnabledForBrand(brandId) {
32
+ if (!brandId)
33
+ return false;
34
+ return clientDeliveryEnabledBrandIds().has(brandId);
35
+ }
36
+ /** 发布硬门是否 enforce(默认 shadow:只有显式 =enforce 才阻断)。 */
37
+ export function isClientDeliveryPublishGateEnforced() {
38
+ return process.env["CLIENT_DELIVERY_PUBLISH_GATE_ENFORCED"] === "enforce";
39
+ }
40
+ //# sourceMappingURL=client-delivery-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-delivery-config.js","sourceRoot":"","sources":["../src/client-delivery-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,qDAAqD;AACrD,MAAM,UAAU,6BAA6B;IAC3C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;IAC5D,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,GAAG,EAAE,CAAC;IAC3B,OAAO,IAAI,GAAG,CACZ,GAAG;SACA,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAC/B,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,+BAA+B,CAC7C,OAAkC;IAElC,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAC3B,OAAO,6BAA6B,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACtD,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,mCAAmC;IACjD,OAAO,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC,KAAK,SAAS,CAAC;AAC5E,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=client-delivery-config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-delivery-config.test.d.ts","sourceRoot":"","sources":["../src/client-delivery-config.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,36 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { clientDeliveryEnabledBrandIds, isClientDeliveryEnabledForBrand, isClientDeliveryPublishGateEnforced, } from "./client-delivery-config.js";
3
+ const BRAND = "brand-a";
4
+ afterEach(() => {
5
+ delete process.env.CLIENT_DELIVERY_PORTAL_BRAND_IDS;
6
+ delete process.env.CLIENT_DELIVERY_PUBLISH_GATE_ENFORCED;
7
+ });
8
+ describe("client-delivery feature flag", () => {
9
+ it("defaults to empty set (all brands off)", () => {
10
+ expect(clientDeliveryEnabledBrandIds().size).toBe(0);
11
+ expect(isClientDeliveryEnabledForBrand(BRAND)).toBe(false);
12
+ });
13
+ it("parses comma-separated allowlist, trimming blanks", () => {
14
+ process.env.CLIENT_DELIVERY_PORTAL_BRAND_IDS = ` ${BRAND} , , brand-b `;
15
+ const set = clientDeliveryEnabledBrandIds();
16
+ expect(set.has(BRAND)).toBe(true);
17
+ expect(set.has("brand-b")).toBe(true);
18
+ expect(set.has("")).toBe(false);
19
+ expect(isClientDeliveryEnabledForBrand(BRAND)).toBe(true);
20
+ expect(isClientDeliveryEnabledForBrand("brand-c")).toBe(false);
21
+ });
22
+ it("null/undefined/empty brandId is fail-closed false", () => {
23
+ process.env.CLIENT_DELIVERY_PORTAL_BRAND_IDS = BRAND;
24
+ expect(isClientDeliveryEnabledForBrand(null)).toBe(false);
25
+ expect(isClientDeliveryEnabledForBrand(undefined)).toBe(false);
26
+ expect(isClientDeliveryEnabledForBrand("")).toBe(false);
27
+ });
28
+ it("publish gate defaults to shadow; only =enforce enforces", () => {
29
+ expect(isClientDeliveryPublishGateEnforced()).toBe(false);
30
+ process.env.CLIENT_DELIVERY_PUBLISH_GATE_ENFORCED = "shadow";
31
+ expect(isClientDeliveryPublishGateEnforced()).toBe(false);
32
+ process.env.CLIENT_DELIVERY_PUBLISH_GATE_ENFORCED = "enforce";
33
+ expect(isClientDeliveryPublishGateEnforced()).toBe(true);
34
+ });
35
+ });
36
+ //# sourceMappingURL=client-delivery-config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-delivery-config.test.js","sourceRoot":"","sources":["../src/client-delivery-config.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EACL,6BAA6B,EAC7B,+BAA+B,EAC/B,mCAAmC,GACpC,MAAM,6BAA6B,CAAC;AAErC,MAAM,KAAK,GAAG,SAAS,CAAC;AAExB,SAAS,CAAC,GAAG,EAAE;IACb,OAAO,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC;IACpD,OAAO,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC;AAC3D,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;IAC5C,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,6BAA6B,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACrD,MAAM,CAAC,+BAA+B,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,OAAO,CAAC,GAAG,CAAC,gCAAgC,GAAG,IAAI,KAAK,eAAe,CAAC;QACxE,MAAM,GAAG,GAAG,6BAA6B,EAAE,CAAC;QAC5C,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,MAAM,CAAC,+BAA+B,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1D,MAAM,CAAC,+BAA+B,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,OAAO,CAAC,GAAG,CAAC,gCAAgC,GAAG,KAAK,CAAC;QACrD,MAAM,CAAC,+BAA+B,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1D,MAAM,CAAC,+BAA+B,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/D,MAAM,CAAC,+BAA+B,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,CAAC,mCAAmC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1D,OAAO,CAAC,GAAG,CAAC,qCAAqC,GAAG,QAAQ,CAAC;QAC7D,MAAM,CAAC,mCAAmC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1D,OAAO,CAAC,GAAG,CAAC,qCAAqC,GAAG,SAAS,CAAC;QAC9D,MAAM,CAAC,mCAAmC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * 内容复审的服务端计分与 verdict 判定(纯函数,权威口径)。
3
+ *
4
+ * LLM 只产出六维原始分与证据;综合分由这里按版本化权重计算,verdict 由
5
+ * precedence 规则判定——防止模型自报综合分漂移/越权(独立模型换代后分布
6
+ * 变化不影响阈值语义)。策略可整体替换(system_settings 覆盖默认),每次
7
+ * 复审记录 thresholdSnapshot 供金丝雀跨版本对比。
8
+ */
9
+ export type ContentReviewDimensionKey = "context_anchoring" | "burstiness" | "ai_tone" | "persona_voice" | "compliance" | "self_repetition";
10
+ export type ContentReviewDimensionScores = Record<ContentReviewDimensionKey, {
11
+ score: number;
12
+ issues: string[];
13
+ }>;
14
+ export type ContentReviewPolicy = {
15
+ policyVersion: string;
16
+ /** 六维权重(加权平均,权重和不必为 1,内部归一)。 */
17
+ weights: Record<ContentReviewDimensionKey, number>;
18
+ /** 综合分 < blockBelow → block;< reviseBelow → revise;否则 pass。 */
19
+ blockBelow: number;
20
+ reviseBelow: number;
21
+ /** 一票否决:该维 < 阈值直接 block(情境锚定最能暴露 AI 且骗不了)。 */
22
+ contextAnchoringBlockBelow: number;
23
+ /** 合规红线:该维 < 阈值直接 block(品牌硬推/导流/站外联系)。 */
24
+ complianceBlockBelow: number;
25
+ /** 静默期账号更克制:各阈值整体上移的偏置。 */
26
+ quietStageThresholdBonus: number;
27
+ };
28
+ export declare const CONTENT_REVIEW_RUBRIC_VERSION = "v1";
29
+ export declare const DEFAULT_CONTENT_REVIEW_POLICY: ContentReviewPolicy;
30
+ export type ContentReviewScoreInput = {
31
+ dimensions: ContentReviewDimensionScores;
32
+ /** 账号阶段;静默期收紧阈值。 */
33
+ stage?: string | null;
34
+ policy?: ContentReviewPolicy;
35
+ };
36
+ export type ContentReviewScoreResult = {
37
+ verdict: "pass" | "revise" | "block";
38
+ computedScore: number;
39
+ /** 触发 verdict 的规则(审计/调参可读)。 */
40
+ decidedBy: "compliance_hard_fail" | "context_anchoring_hard_fail" | "score_block" | "score_revise" | "score_pass";
41
+ thresholdSnapshot: ContentReviewThresholdSnapshot;
42
+ };
43
+ export type ContentReviewThresholdSnapshot = {
44
+ policyVersion: string;
45
+ weights: Record<ContentReviewDimensionKey, number>;
46
+ blockBelow: number;
47
+ reviseBelow: number;
48
+ contextAnchoringBlockBelow: number;
49
+ complianceBlockBelow: number;
50
+ stageBonusApplied: number;
51
+ stage: string | null;
52
+ };
53
+ /** 本次请求实际生效的策略快照(degraded/deterministic 分支也要完整留档)。 */
54
+ export declare function getContentReviewThresholdSnapshot(policy: ContentReviewPolicy, stage?: string | null): ContentReviewThresholdSnapshot;
55
+ /** system_settings 覆盖默认策略时的合并(部分覆盖,未给字段用默认)。 */
56
+ export declare function mergeContentReviewPolicy(override: Partial<ContentReviewPolicy> | null | undefined): ContentReviewPolicy;
57
+ export declare function scoreContentReview(input: ContentReviewScoreInput): ContentReviewScoreResult;
58
+ /**
59
+ * 确定性合规预检(LLM 之前,命中即 block 不烧 token):
60
+ * 链接/站外联系方式是评论红线中可确定性识别的部分;品牌硬推语义仍交 LLM。
61
+ */
62
+ export type DeterministicComplianceHit = {
63
+ rule: "url" | "email" | "handle_solicitation";
64
+ match: string;
65
+ };
66
+ export declare function detectDeterministicComplianceHits(draft: string): DeterministicComplianceHit[];
67
+ //# sourceMappingURL=content-review-scoring.d.ts.map