@geoly-ai/social-hub-sdk 0.0.46 → 0.0.48
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/dist/index.d.ts +487 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +219 -4
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +97 -0
- package/dist/index.test.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -475,6 +475,75 @@ export type ListAuditLogsParams = {
|
|
|
475
475
|
createdFrom?: string;
|
|
476
476
|
createdTo?: string;
|
|
477
477
|
};
|
|
478
|
+
/** `GET /v1/teams/:teamId/permissions-matrix/revisions` 的查询参数(迁移 0128)。 */
|
|
479
|
+
export type PermissionsMatrixRevisionsParams = {
|
|
480
|
+
/**
|
|
481
|
+
* 只查一张轨迹表。省略 = 两张都查。
|
|
482
|
+
* 给了 `action` 隐含 `permission`、给了 `field` 隐含 `field`(服务端会拒绝矛盾组合)。
|
|
483
|
+
*/
|
|
484
|
+
kind?: "permission" | "field";
|
|
485
|
+
role?: string;
|
|
486
|
+
resource?: string;
|
|
487
|
+
/** 只对 permission 轨迹有意义。不能与 `field` 同时给。 */
|
|
488
|
+
action?: string;
|
|
489
|
+
/** 只对 field 轨迹有意义。不能与 `action` 同时给。 */
|
|
490
|
+
field?: string;
|
|
491
|
+
/** keyset 游标(排他上界):上一页响应的 `nextPermissionRevisionNo`,原样回传。 */
|
|
492
|
+
beforePermissionRevisionNo?: number;
|
|
493
|
+
/** keyset 游标(排他上界):上一页响应的 `nextFieldRevisionNo`,原样回传。 */
|
|
494
|
+
beforeFieldRevisionNo?: number;
|
|
495
|
+
/** 1–500,服务端默认 50。 */
|
|
496
|
+
limit?: number;
|
|
497
|
+
};
|
|
498
|
+
/**
|
|
499
|
+
* `GET /v1/teams/:teamId/permissions-matrix/revisions` 的响应。
|
|
500
|
+
*
|
|
501
|
+
* 🔴 结构真源是 `@social-ops-hub/contracts` 的
|
|
502
|
+
* `permissionsMatrixRevisionsResponseSchema`。这里**重新声明**而不是 re-export:
|
|
503
|
+
* SDK 是公开发布包,contracts 是 `private: true` —— 直接引用会生成一份消费者装不上的
|
|
504
|
+
* `.d.ts`。改契约时**两处一起改**。
|
|
505
|
+
*
|
|
506
|
+
* ⚠️ 五个顶层字段全部必填:两个数组恒为数组(`kind=field` 时另一张是**空数组**而不是
|
|
507
|
+
* 缺字段)、两个游标恒为 `number | null`、`note` 恒在。对新版 API 可以直接用,不必再写
|
|
508
|
+
* `res.permissionRevisions ?? []` —— 那种防御会把契约违约读成「没有变更记录」,也就是一个
|
|
509
|
+
* 错误的审计结论。**但**需要兼容尚未升级的旧 Hub 时,运行时防御仍然要留(滚动部署期)。
|
|
510
|
+
*
|
|
511
|
+
* ⚠️ 两个 `next*RevisionNo` 是 **continuation token 而不是 has-more 信号**:本页非空
|
|
512
|
+
* 就一定有值(哪怕已是最后一页),`null` 只代表本页为空。到底判定 = 再请求一次拿到空数组。
|
|
513
|
+
*/
|
|
514
|
+
export type PermissionOverrideRevisionRowDto = {
|
|
515
|
+
revisionNo: number;
|
|
516
|
+
/** 开放字符串(历史轨迹:registry 改名/删除后旧行仍要可读),不是 enum。 */
|
|
517
|
+
role: string;
|
|
518
|
+
resource: string;
|
|
519
|
+
action: string;
|
|
520
|
+
/** 三态:`true` allow / `false` deny / `null` 未设置。 */
|
|
521
|
+
oldOverrideAllowed: boolean | null;
|
|
522
|
+
newOverrideAllowed: boolean | null;
|
|
523
|
+
changeKind: "insert" | "update" | "delete";
|
|
524
|
+
/** api_key / cli_token 主体天然为 null —— 正常状态,不是「未知用户」。 */
|
|
525
|
+
actorUserId: string | null;
|
|
526
|
+
actorKind: string | null;
|
|
527
|
+
/** 「从哪个团队入口发起」,**不是**「只影响这个团队」:override 存储是全局的。 */
|
|
528
|
+
contextTeamId: string | null;
|
|
529
|
+
requestId: string | null;
|
|
530
|
+
/** null = 早期记录(写路径未设审计 GUC),无法精确重放当时的 effective 权限。 */
|
|
531
|
+
authzPolicyVersion: string | null;
|
|
532
|
+
changedAt: string;
|
|
533
|
+
};
|
|
534
|
+
export type FieldVisibilityRevisionRowDto = Omit<PermissionOverrideRevisionRowDto, "action" | "oldOverrideAllowed" | "newOverrideAllowed"> & {
|
|
535
|
+
field: string;
|
|
536
|
+
oldOverrideReadable: boolean | null;
|
|
537
|
+
newOverrideReadable: boolean | null;
|
|
538
|
+
};
|
|
539
|
+
export type PermissionsMatrixRevisionsResponseDto = {
|
|
540
|
+
permissionRevisions: PermissionOverrideRevisionRowDto[];
|
|
541
|
+
fieldRevisions: FieldVisibilityRevisionRowDto[];
|
|
542
|
+
nextPermissionRevisionNo: number | null;
|
|
543
|
+
nextFieldRevisionNo: number | null;
|
|
544
|
+
/** 口径提示:轨迹记的是 override 存储值,不是 effective 权限。 */
|
|
545
|
+
note: string;
|
|
546
|
+
};
|
|
478
547
|
export type ListAccountGraphParams = {
|
|
479
548
|
limit?: number;
|
|
480
549
|
cursor?: string;
|
|
@@ -700,6 +769,7 @@ export declare class SocialHubClient {
|
|
|
700
769
|
imported: number;
|
|
701
770
|
updated: number;
|
|
702
771
|
skipped: number;
|
|
772
|
+
conflicted?: number;
|
|
703
773
|
}>;
|
|
704
774
|
listScheduledJobs(teamId: string, params?: ListScheduledJobsParams): Promise<{
|
|
705
775
|
items: unknown[];
|
|
@@ -1087,6 +1157,31 @@ export declare class SocialHubClient {
|
|
|
1087
1157
|
items: unknown[];
|
|
1088
1158
|
}>;
|
|
1089
1159
|
getPermissionsMatrix(teamId: string): Promise<unknown>;
|
|
1160
|
+
/**
|
|
1161
|
+
* 权限 **override** 变更轨迹(迁移 0128)。
|
|
1162
|
+
*
|
|
1163
|
+
* 记录的是**谁在何时插入 / 修改 / 删除了哪一条**权限或字段可见性 override ——
|
|
1164
|
+
* 不只是「deny 改成 allow」,收紧与删除同样留痕。
|
|
1165
|
+
*
|
|
1166
|
+
* 🔴 gate 是 `permissionMatrix:**update**`(不是 `read`):能看见「谁改了权限」
|
|
1167
|
+
* 本身就是治理信息,还会露出 actor user id。
|
|
1168
|
+
*
|
|
1169
|
+
* ⚠️ 口径:记录的是 **override 存储值**的变更,**不是 effective 权限**的变更
|
|
1170
|
+
* (effective = 静态 authz 矩阵 ⊕ override ⊕ 硬钳制)。`authzPolicyVersion` 是为了
|
|
1171
|
+
* 重放时能用**当时**的规则解释**当时**的 override。
|
|
1172
|
+
* ⚠️ 它**可能为 null**:只有经标准应用写路径(设了审计 GUC)产生的行才带版本号,
|
|
1173
|
+
* **未显式设置该 GUC 的** raw DML / psql 直插留下的行没有 —— 那些行只能看出 override 变了什么,
|
|
1174
|
+
* **无法**可靠还原当时的 effective 权限。
|
|
1175
|
+
*
|
|
1176
|
+
* 翻页是 keyset:把响应里的 `nextPermissionRevisionNo` / `nextFieldRevisionNo`
|
|
1177
|
+
* **原样**回传成下一次的 `beforePermissionRevisionNo` / `beforeFieldRevisionNo`。
|
|
1178
|
+
* 它们是 continuation token 而不是 has-more 信号:本页非空就一定有值,要再请求一次
|
|
1179
|
+
* 拿到空数组才算到底。
|
|
1180
|
+
*
|
|
1181
|
+
* ⚠️ 服务端对互斥组合是 **400 而不是空结果**:`action` 只属于 permission 轨迹、
|
|
1182
|
+
* `field` 只属于 field 轨迹,同时给两个、或给了与 `kind` 矛盾的游标都会被拒。
|
|
1183
|
+
*/
|
|
1184
|
+
listPermissionsMatrixRevisions(teamId: string, params?: PermissionsMatrixRevisionsParams): Promise<PermissionsMatrixRevisionsResponseDto>;
|
|
1090
1185
|
listAccountGraphEdges(teamId: string, params?: ListAccountGraphParams): Promise<{
|
|
1091
1186
|
items: unknown[];
|
|
1092
1187
|
nextCursor?: string;
|
|
@@ -1972,12 +2067,27 @@ export declare class SocialHubClient {
|
|
|
1972
2067
|
items: unknown[];
|
|
1973
2068
|
total?: number;
|
|
1974
2069
|
}>;
|
|
2070
|
+
/**
|
|
2071
|
+
* 行业池目录(catalog)。**表本身是系统级共享数据**:`(industryKey, subreddit)` 全局唯一,
|
|
2072
|
+
* 行上的 `teamId` 只是建行时写下的**不可变历史归因署名**,不是隔离边界。
|
|
2073
|
+
*
|
|
2074
|
+
* ⚠️ 因此默认列表是一个 **attribution-scoped view**:只列出当前 team 署名的行。
|
|
2075
|
+
* 想跨团队看(例如提交前查重「这个板块是不是已经有人录过了」)必须给
|
|
2076
|
+
* `aggregateVisibleTeams: true` —— 否则会漏掉别的团队署名的同名行,
|
|
2077
|
+
* 提交后被系统级 upsert 静默更新掉那一行。
|
|
2078
|
+
*
|
|
2079
|
+
* ⚠️ `aggregateVisibleTeams` 在这里同样退化成**署名过滤器**,且范围是**当前主体可见的团队**,
|
|
2080
|
+
* 不是无条件全表:不可见团队署名的行仍然看不到。它与「真团队隔离资源」上的同名参数
|
|
2081
|
+
* (那里是数据边界)语义不同,别照搬理解。
|
|
2082
|
+
*/
|
|
1975
2083
|
listSubredditPoolsCatalog(teamId: string, params?: {
|
|
1976
2084
|
limit?: number;
|
|
1977
2085
|
offset?: number;
|
|
1978
2086
|
status?: string;
|
|
1979
2087
|
industryKey?: string;
|
|
1980
2088
|
poolKind?: string;
|
|
2089
|
+
/** 跨**可见团队**的署名聚合视图(不是无条件全表)。 */
|
|
2090
|
+
aggregateVisibleTeams?: boolean;
|
|
1981
2091
|
}): Promise<{
|
|
1982
2092
|
items: unknown[];
|
|
1983
2093
|
total: number;
|
|
@@ -2014,11 +2124,18 @@ export declare class SocialHubClient {
|
|
|
2014
2124
|
id: string;
|
|
2015
2125
|
deleted: boolean;
|
|
2016
2126
|
}>;
|
|
2127
|
+
/**
|
|
2128
|
+
* S0–S3 准入规则。与 catalog 同款:**系统级共享**(`subreddit` 全局唯一),
|
|
2129
|
+
* 行上的 `teamId` 只是历史归因署名。默认列表按署名过滤,
|
|
2130
|
+
* 跨团队查重必须 `aggregateVisibleTeams: true`(范围 = 当前主体可见的团队)。
|
|
2131
|
+
*/
|
|
2017
2132
|
listSubredditPoolTierRules(teamId: string, params?: {
|
|
2018
2133
|
limit?: number;
|
|
2019
2134
|
offset?: number;
|
|
2020
2135
|
tier?: string;
|
|
2021
2136
|
enabled?: boolean;
|
|
2137
|
+
/** 跨**可见团队**的署名聚合视图(不是无条件全表)。 */
|
|
2138
|
+
aggregateVisibleTeams?: boolean;
|
|
2022
2139
|
}): Promise<{
|
|
2023
2140
|
items: unknown[];
|
|
2024
2141
|
total: number;
|
|
@@ -2305,16 +2422,102 @@ export declare class SocialHubClient {
|
|
|
2305
2422
|
subscriptionId: string;
|
|
2306
2423
|
cursorSeq: string;
|
|
2307
2424
|
}>;
|
|
2308
|
-
/**
|
|
2425
|
+
/**
|
|
2426
|
+
* GET .../notification-events —— **运维查看面**(元数据摘要,不含 payload)。
|
|
2427
|
+
*
|
|
2428
|
+
* ⚠️ 这不是订阅消费面:它没有 watermark / receipt、不推进任何游标,因此可以自由
|
|
2429
|
+
* 倒序与跳页。要不漏地消费事件请走 {@link SocialOpsHubClient.pullNotificationEvents}。
|
|
2430
|
+
*
|
|
2431
|
+
* 边界语义(服务端冻结):
|
|
2432
|
+
* - `afterSeq` 排他**下界**、`beforeSeq` 排他**上界**;
|
|
2433
|
+
* - `order` **只**决定从区间的哪一端取 N 条与返回顺序,不改变上下界含义;
|
|
2434
|
+
* - 默认 `order: "desc"` = 最近 N 条;
|
|
2435
|
+
* - `afterSeq >= beforeSeq` → 400(矛盾区间恒为空,静默返回空会伪装成「没有更多了」)。
|
|
2436
|
+
*
|
|
2437
|
+
* 翻页写法:`order: "desc"` 首屏 → 下一页传 `beforeSeq = 上一页 page.lastSeq`。
|
|
2438
|
+
*/
|
|
2309
2439
|
listNotificationEvents(teamId: string, params?: {
|
|
2310
2440
|
afterSeq?: string;
|
|
2441
|
+
beforeSeq?: string;
|
|
2442
|
+
order?: "asc" | "desc";
|
|
2311
2443
|
sourceId?: string;
|
|
2312
2444
|
limit?: number;
|
|
2313
2445
|
}): Promise<{
|
|
2314
2446
|
items: NotificationEventSummaryDto[];
|
|
2447
|
+
/** 本页首尾 seq —— **不是** stream 的 latest/earliest。 */
|
|
2448
|
+
page: {
|
|
2449
|
+
order: "asc" | "desc";
|
|
2450
|
+
firstSeq: string | null;
|
|
2451
|
+
lastSeq: string | null;
|
|
2452
|
+
};
|
|
2315
2453
|
latestSeq: string;
|
|
2316
2454
|
earliestAvailableSeq: string;
|
|
2317
2455
|
}>;
|
|
2456
|
+
/**
|
|
2457
|
+
* 节点 schedule 的**运维可观测面** —— `notification_node_schedules` 的读取端点。
|
|
2458
|
+
*
|
|
2459
|
+
* 🔴 为什么不能从事件流反推:**kill switch 期间的 `missed` 与锚点消失的 `cancelled`
|
|
2460
|
+
* 根本不产生任何事件**。靠事件流反推只会把它们算成 0,于是「今晚一条都没发」
|
|
2461
|
+
* 与「今晚本来就没有到期节点」在页面上长得一模一样。
|
|
2462
|
+
*
|
|
2463
|
+
* ⚠️ 时间窗**必须有界**(服务端默认最近 30 天、上限 90 天)。窗口上限只限制**跨度**,
|
|
2464
|
+
* 不限制窗口必须在最近 90 天内 —— 查更早的窗口完全合法,但可能已经在保留期之外,
|
|
2465
|
+
* 因此 summary / list 都随响应回 `retention`(见 {@link NotificationScheduleRetentionDto})。
|
|
2466
|
+
*/
|
|
2467
|
+
private schedulesBase;
|
|
2468
|
+
/** GET .../notification-node-schedules/summary —— 按 source × status 聚合计数。 */
|
|
2469
|
+
getNotificationNodeSchedulesSummary(teamId: string, params?: {
|
|
2470
|
+
sourceId?: string;
|
|
2471
|
+
dueFrom?: string;
|
|
2472
|
+
dueTo?: string;
|
|
2473
|
+
}): Promise<NotificationNodeSchedulesSummaryDto>;
|
|
2474
|
+
/**
|
|
2475
|
+
* GET .../notification-node-schedules —— 按 source / status / dueAt 窗口列表。
|
|
2476
|
+
*
|
|
2477
|
+
* 分页是 **keyset**(`nextCursor` 为 opaque 串,原样回传即可);`nextCursor = null`
|
|
2478
|
+
* 表示没有下一页。⚠️ 弱一致:并发 backfill 可能插入更早 `dueAt` 的新行。
|
|
2479
|
+
*/
|
|
2480
|
+
listNotificationNodeSchedules(teamId: string, params?: {
|
|
2481
|
+
sourceId?: string;
|
|
2482
|
+
status?: NotificationScheduleStatusDto;
|
|
2483
|
+
subjectId?: string;
|
|
2484
|
+
dueFrom?: string;
|
|
2485
|
+
dueTo?: string;
|
|
2486
|
+
cursor?: string;
|
|
2487
|
+
limit?: number;
|
|
2488
|
+
}): Promise<{
|
|
2489
|
+
items: NotificationNodeScheduleSummaryDto[];
|
|
2490
|
+
window: {
|
|
2491
|
+
dueFrom: string;
|
|
2492
|
+
dueTo: string;
|
|
2493
|
+
};
|
|
2494
|
+
nextCursor: string | null;
|
|
2495
|
+
retention: NotificationScheduleRetentionDto;
|
|
2496
|
+
}>;
|
|
2497
|
+
/**
|
|
2498
|
+
* GET .../notification-node-schedules/:scheduleId —— 单条状态原因与关联事件 seq。
|
|
2499
|
+
*
|
|
2500
|
+
* ⚠️ **没有** `lastError` 原文:那是任意异常字符串(可能含 SQL / 内部路径)。
|
|
2501
|
+
* 可分支的原因在 `failureReason`(归一化枚举)与 `cancelReason`;
|
|
2502
|
+
* `hasLastError` 只说明「存在原始异常」,原文在服务端日志里。
|
|
2503
|
+
*/
|
|
2504
|
+
getNotificationNodeSchedule(teamId: string, scheduleId: string): Promise<NotificationNodeScheduleDetailDto>;
|
|
2505
|
+
/**
|
|
2506
|
+
* GET /v1/settings/notification-schedule-purge-cron/status —— schedule 保留期 GC 的
|
|
2507
|
+
* cron 运行状态。
|
|
2508
|
+
*
|
|
2509
|
+
* 🔴 **系统级、不带 teamId**:状态是 worker 写的全局单 key,不是某个 team 的清理进度。
|
|
2510
|
+
* 权限 `systemSetting:read`(仅 admin/manager)—— 响应带 `workerId` 与异常原文摘要。
|
|
2511
|
+
*
|
|
2512
|
+
* 🔴 **`null` ≠ `0`**:计数为 `null` 表示这次快照没有这个读数(从没跑过 / 只 registered /
|
|
2513
|
+
* failed 未产出),为 `0` 才是「跑了且真的是零」。判「跑没跑过」看
|
|
2514
|
+
* `hasRunSinceRegistration`,别拿 `deleted ?? 0` 糊。
|
|
2515
|
+
*
|
|
2516
|
+
* ⚠️ 三个**非错误**字段:`protectedGroups`(sentinel 不变量有意保护)、
|
|
2517
|
+
* `lockBusyGroups`(被物化/dispatcher 持锁,下轮重试)、`backlog`(独立 `LIMIT 1` probe
|
|
2518
|
+
* 判定的「确实还有候选」)。只有 `failedGroups > 0` 与 `status === "failed"` 是真异常。
|
|
2519
|
+
*/
|
|
2520
|
+
getNotificationSchedulePurgeCronStatus(): Promise<NotificationSchedulePurgeCronStatusDto>;
|
|
2318
2521
|
/** GET .../notification-events/:seq —— 单条完整 envelope(按事件行冻结的权限重验)。 */
|
|
2319
2522
|
getNotificationEventBySeq(teamId: string, seq: string): Promise<NotificationEventEnvelopeDto>;
|
|
2320
2523
|
/** GET .../notification-sources —— registry 声明 + 本 team 的 active config 摘要。 */
|
|
@@ -2340,8 +2543,23 @@ export declare class SocialHubClient {
|
|
|
2340
2543
|
createNotificationSourceConfig(teamId: string, sourceId: string, body: UpsertNotificationSourceConfigSdkBody, params?: {
|
|
2341
2544
|
activate?: boolean;
|
|
2342
2545
|
}): Promise<NotificationSourceConfigDto>;
|
|
2343
|
-
/**
|
|
2344
|
-
|
|
2546
|
+
/**
|
|
2547
|
+
* POST .../notification-sources/:sourceId/configs/:configId/activate。
|
|
2548
|
+
*
|
|
2549
|
+
* `expectedActive` 是**可选的 compare-and-set**:
|
|
2550
|
+
* - 省略 = 保持历史的 last-serialized-writer-wins(两个管理员会互相静默覆盖);
|
|
2551
|
+
* - `null` = 「我认为当前没有 active」;
|
|
2552
|
+
* - `{ configId, version? }` = 「我认为当前 active 就是这一行」。
|
|
2553
|
+
*
|
|
2554
|
+
* 不符 → **409 `SOURCE_CONFIG_ACTIVE_STATE_CONFLICT`**,`details` 带真实的
|
|
2555
|
+
* `actualActiveConfigId` / `actualActiveVersion`,无需再 GET 一次即可重试。
|
|
2556
|
+
*
|
|
2557
|
+
* 🔴 人工写路径(CLI / 后台页)**应当总是带上它** —— 服务端无法替调用方判断
|
|
2558
|
+
* 「你有没有看过当前状态」。
|
|
2559
|
+
*/
|
|
2560
|
+
activateNotificationSourceConfig(teamId: string, sourceId: string, configId: string, body?: {
|
|
2561
|
+
expectedActive?: NotificationExpectedActiveDto;
|
|
2562
|
+
}): Promise<NotificationSourceConfigDto>;
|
|
2345
2563
|
/**
|
|
2346
2564
|
* PUT .../notification-sources/:sourceId/enabled —— 实时 kill switch。
|
|
2347
2565
|
*
|
|
@@ -2355,6 +2573,49 @@ export declare class SocialHubClient {
|
|
|
2355
2573
|
updatedVersions: number;
|
|
2356
2574
|
enabled: boolean;
|
|
2357
2575
|
}>;
|
|
2576
|
+
private templatesBase;
|
|
2577
|
+
/** GET /v1/system/notification-source-templates —— 每个已发版 source 的当前 active 模板。 */
|
|
2578
|
+
listNotificationSourceTemplates(): Promise<{
|
|
2579
|
+
items: {
|
|
2580
|
+
sourceId: string;
|
|
2581
|
+
canonicalTopic: string;
|
|
2582
|
+
sourceDefinitionVersion: number;
|
|
2583
|
+
payloadSchemaVersion: string;
|
|
2584
|
+
activeTemplate: NotificationSourceTemplateDto | null;
|
|
2585
|
+
}[];
|
|
2586
|
+
}>;
|
|
2587
|
+
/** GET .../templates —— 该 source 的模板版本历史(version 降序)。 */
|
|
2588
|
+
listNotificationSourceTemplateVersions(sourceId: string, params?: {
|
|
2589
|
+
limit?: number;
|
|
2590
|
+
}): Promise<{
|
|
2591
|
+
items: NotificationSourceTemplateDto[];
|
|
2592
|
+
}>;
|
|
2593
|
+
/** GET .../templates/:id(裸模板行)。 */
|
|
2594
|
+
getNotificationSourceTemplate(sourceId: string, templateId: string): Promise<NotificationSourceTemplateDto>;
|
|
2595
|
+
/**
|
|
2596
|
+
* POST .../templates —— 创建新的不可变模板 version(201)。
|
|
2597
|
+
*
|
|
2598
|
+
* ⚠️ `activate` **默认 false**(与 team config 的 create 相反):已开启 auto-derivation 的
|
|
2599
|
+
* identity 创建新版本会继承 `true`,若再默认立即激活,一次「存个草稿」就当场改掉了
|
|
2600
|
+
* 全局派生模板。
|
|
2601
|
+
*/
|
|
2602
|
+
createNotificationSourceTemplate(sourceId: string, body: UpsertNotificationSourceTemplateSdkBody, params?: {
|
|
2603
|
+
activate?: boolean;
|
|
2604
|
+
}): Promise<NotificationSourceTemplateDto>;
|
|
2605
|
+
/** POST .../templates/:id/activate —— 激活某个已建版本(仍需总闸才会派生)。 */
|
|
2606
|
+
activateNotificationSourceTemplate(sourceId: string, templateId: string): Promise<NotificationSourceTemplateDto>;
|
|
2607
|
+
/**
|
|
2608
|
+
* PUT .../templates/auto-derivation —— 🔴 **自动派生总闸**(系统 admin only)。
|
|
2609
|
+
*
|
|
2610
|
+
* 打开 = 全体「该 source 一条 config 都没有」的 team 在下一次帖子物化 / backfill /
|
|
2611
|
+
* **建订阅**时各自派生出一条 team config,其已发布帖随即开始产生节点事件。
|
|
2612
|
+
* 按 identity 改**全部** version 行(rollout 状态是 identity 级共享状态)。
|
|
2613
|
+
*/
|
|
2614
|
+
setNotificationSourceTemplateAutoDerivation(sourceId: string, autoDerivationEnabled: boolean): Promise<{
|
|
2615
|
+
totalVersions: number;
|
|
2616
|
+
updatedVersions: number;
|
|
2617
|
+
autoDerivationEnabled: boolean;
|
|
2618
|
+
}>;
|
|
2358
2619
|
}
|
|
2359
2620
|
export type NotificationSubscriptionStatus = "active" | "paused"
|
|
2360
2621
|
/** 游标落后于保留 floor,已无法保证不漏;需人工确认后 reset。 */
|
|
@@ -2446,6 +2707,179 @@ export type UpsertNotificationSourceConfigSdkBody = {
|
|
|
2446
2707
|
displayName: string;
|
|
2447
2708
|
/** 展示用,**不参与**路由/过滤/去重。 */
|
|
2448
2709
|
displayTopicLabel?: string | null;
|
|
2710
|
+
/**
|
|
2711
|
+
* 创建即激活(`activate` 默认 true)时的 CAS 期望值 —— 与独立 activate 端点同语义。
|
|
2712
|
+
* 🔴 少了它,「新建一个版本并激活」就是绕过 CAS 的后门。
|
|
2713
|
+
* `activate: false` 时带它会 400(矛盾请求)。
|
|
2714
|
+
*/
|
|
2715
|
+
expectedActive?: NotificationExpectedActiveDto;
|
|
2716
|
+
};
|
|
2717
|
+
/**
|
|
2718
|
+
* 激活的 compare-and-set 期望值。
|
|
2719
|
+
* - 字段缺省 = 不启用 CAS(last-serialized-writer-wins);
|
|
2720
|
+
* - `null` = 「我认为当前没有 active」;
|
|
2721
|
+
* - 对象 = 「我认为当前 active 就是这一行」(`version` 给了就一起校验)。
|
|
2722
|
+
*
|
|
2723
|
+
* ⚠️ 刻意是**一个不可自相矛盾的对象**而不是两个平行字段:
|
|
2724
|
+
* 「只校验其中一个、静默忽略另一个」正是 CAS 最典型的失效方式。
|
|
2725
|
+
*/
|
|
2726
|
+
export type NotificationExpectedActiveDto = null | {
|
|
2727
|
+
configId: string;
|
|
2728
|
+
version?: number;
|
|
2729
|
+
};
|
|
2730
|
+
export type NotificationScheduleStatusDto = "scheduled" | "emitted" | "missed" | "cancelled" | "superseded";
|
|
2731
|
+
export type NotificationNodeScheduleStatusCountsDto = {
|
|
2732
|
+
scheduled: number;
|
|
2733
|
+
emitted: number;
|
|
2734
|
+
missed: number;
|
|
2735
|
+
cancelled: number;
|
|
2736
|
+
superseded: number;
|
|
2737
|
+
};
|
|
2738
|
+
/**
|
|
2739
|
+
* 该状态在请求窗口内的**完整性** —— 空结果的解释权全在这里。
|
|
2740
|
+
*
|
|
2741
|
+
* - `full`:窗口整体在保证保留期内,**没有就是真的没有**;
|
|
2742
|
+
* - `partial`:窗口横跨保留 floor,更早的部分**可能**已被清理;
|
|
2743
|
+
* - `outside`:窗口整体早于 floor,看到的行只是每组永久保留的 sentinel 残留,
|
|
2744
|
+
* **不能**当作完整历史。
|
|
2745
|
+
*/
|
|
2746
|
+
export type NotificationScheduleWindowCompletenessDto = "full" | "partial" | "outside";
|
|
2747
|
+
/**
|
|
2748
|
+
* schedule 的保留期披露。
|
|
2749
|
+
*
|
|
2750
|
+
* `notification_node_schedules` 按状态分档清理:`emitted` / `superseded` 180 天、
|
|
2751
|
+
* `missed` / `cancelled` 730 天(判据是 `dueAt`);`scheduled` 永不删。
|
|
2752
|
+
* ⚠️ `guaranteedRetainedFrom` 是**保证**保留到的最早 `dueAt`,不是「实际最老行」——
|
|
2753
|
+
* GC 落后、批预算用完、每组永久 sentinel 都会让更老的行继续存在。
|
|
2754
|
+
*/
|
|
2755
|
+
export type NotificationScheduleRetentionDto = {
|
|
2756
|
+
policy: {
|
|
2757
|
+
scheduled: {
|
|
2758
|
+
mode: "indefinite";
|
|
2759
|
+
retentionDays: null;
|
|
2760
|
+
guaranteedRetainedFrom: null;
|
|
2761
|
+
};
|
|
2762
|
+
} & Record<"emitted" | "superseded" | "missed" | "cancelled", {
|
|
2763
|
+
mode: "bounded";
|
|
2764
|
+
retentionDays: number;
|
|
2765
|
+
guaranteedRetainedFrom: string;
|
|
2766
|
+
}>;
|
|
2767
|
+
completenessByStatus: Record<NotificationScheduleStatusDto, NotificationScheduleWindowCompletenessDto>;
|
|
2768
|
+
/** 仅在请求带 `status` 过滤时给出;不带过滤时为 null(五种状态 floor 不同,压不成标量)。 */
|
|
2769
|
+
windowCompleteness: NotificationScheduleWindowCompletenessDto | null;
|
|
2770
|
+
};
|
|
2771
|
+
export type NotificationNodeSchedulesSummaryDto = {
|
|
2772
|
+
/** 服务端补齐后的实际时间窗(`dueFrom` 含、`dueTo` 不含)。 */
|
|
2773
|
+
window: {
|
|
2774
|
+
dueFrom: string;
|
|
2775
|
+
dueTo: string;
|
|
2776
|
+
};
|
|
2777
|
+
items: {
|
|
2778
|
+
sourceId: string;
|
|
2779
|
+
counts: NotificationNodeScheduleStatusCountsDto;
|
|
2780
|
+
total: number;
|
|
2781
|
+
}[];
|
|
2782
|
+
totals: NotificationNodeScheduleStatusCountsDto;
|
|
2783
|
+
/** 🔴 `missed: 0` 到底是「没异常」还是「已过保留期」,只能靠它区分。 */
|
|
2784
|
+
retention: NotificationScheduleRetentionDto;
|
|
2785
|
+
};
|
|
2786
|
+
/**
|
|
2787
|
+
* `notification_node_schedules` 保留期 GC cron 的运行状态
|
|
2788
|
+
* (`GET /v1/settings/notification-schedule-purge-cron/status`)。
|
|
2789
|
+
*
|
|
2790
|
+
* 🔴 **每个计数都可能是 `null`,而 `null` ≠ `0`**:`null` = 这次快照没有这个读数,
|
|
2791
|
+
* `0` = 真的跑了且是零。前端空态措辞只能依据 `hasRunSinceRegistration` + `status`。
|
|
2792
|
+
*/
|
|
2793
|
+
export type NotificationSchedulePurgeCronStatusDto = {
|
|
2794
|
+
/** API 侧 env 回退值。⚠️ 不权威 —— worker 只认自己进程的环境变量。 */
|
|
2795
|
+
schedule: string;
|
|
2796
|
+
/**
|
|
2797
|
+
* 推算 `nextRunAt` 时实际用到的表达式 = `registeredCron ?? schedule`。
|
|
2798
|
+
* ⚠️ **不证明 worker 还活着**(没有 heartbeat),`nextRunAt` 因此是推算值。
|
|
2799
|
+
*/
|
|
2800
|
+
effectiveCron: string;
|
|
2801
|
+
/** 按 `effectiveCron` **推算**的下一次触发;表达式非法时为 null。 */
|
|
2802
|
+
nextRunAt: string | null;
|
|
2803
|
+
/**
|
|
2804
|
+
* 🔴 「跑过没有」的唯一判据(`runId !== null`)。
|
|
2805
|
+
* ⚠️ 是「**自本次 worker 注册以来**」——worker 重启会把运行字段清回 null。
|
|
2806
|
+
*/
|
|
2807
|
+
hasRunSinceRegistration: boolean;
|
|
2808
|
+
registeredCron: string | null;
|
|
2809
|
+
registeredAt: string | null;
|
|
2810
|
+
runId: string | null;
|
|
2811
|
+
/** `HOSTNAME#pid`:多实例下判断 success/failed 是否同一次运行。 */
|
|
2812
|
+
workerId: string | null;
|
|
2813
|
+
startedAt: string | null;
|
|
2814
|
+
finishedAt: string | null;
|
|
2815
|
+
/** ⚠️ `null` = **没有可读的状态快照**(可能没起过,也可能状态写失败/被清/坏了)。 */
|
|
2816
|
+
status: "registered" | "running" | "success" | "failed" | null;
|
|
2817
|
+
durationMs: number | null;
|
|
2818
|
+
/** 逐档删除数(状态档 → 行数);`{}` = 没有任何一档删到东西。 */
|
|
2819
|
+
deletedByTier: Record<string, number>;
|
|
2820
|
+
deleted: number | null;
|
|
2821
|
+
groupsPurged: number | null;
|
|
2822
|
+
/** ⚠️ **不是错误**:被 sentinel / `has_scheduled` 不变量有意保护、本轮跳过的组。 */
|
|
2823
|
+
protectedGroups: number | null;
|
|
2824
|
+
/** ⚠️ **不是错误**:锁被物化/dispatcher 占着,下一轮自然重试。 */
|
|
2825
|
+
lockBusyGroups: number | null;
|
|
2826
|
+
/** 🔴 唯一的组级真异常信号。 */
|
|
2827
|
+
failedGroups: number | null;
|
|
2828
|
+
/** 撞到单次删除预算上限(容量信号,不是错误)。 */
|
|
2829
|
+
budgetExhausted: boolean | null;
|
|
2830
|
+
/** 🔴 独立 `LIMIT 1` probe 判定:`true` = **确实还有**候选,不是「可能还有」。 */
|
|
2831
|
+
backlog: boolean | null;
|
|
2832
|
+
lastError: string | null;
|
|
2833
|
+
};
|
|
2834
|
+
/**
|
|
2835
|
+
* schedule 的归一化失败原因。
|
|
2836
|
+
*
|
|
2837
|
+
* ⚠️ `processing_error` **不代表终态**:记账只 bump attempts,绝不按次数判死,
|
|
2838
|
+
* 所以它会出现在仍会重试的 `scheduled` 行上。要判终态看 `status`。
|
|
2839
|
+
*/
|
|
2840
|
+
export type NotificationScheduleFailureReasonDto = "kill_switch" | "max_lateness_exceeded" | "processing_error";
|
|
2841
|
+
/** `GET .../notification-node-schedules` 的行(**不含 payload、不含 lastError 原文**)。 */
|
|
2842
|
+
export type NotificationNodeScheduleSummaryDto = {
|
|
2843
|
+
id: string;
|
|
2844
|
+
teamId: string;
|
|
2845
|
+
sourceId: string;
|
|
2846
|
+
subjectType: string;
|
|
2847
|
+
subjectId: string;
|
|
2848
|
+
sourceConfigVersion: number;
|
|
2849
|
+
policyVersion: string;
|
|
2850
|
+
nodeKey: string;
|
|
2851
|
+
offsetSeconds: number;
|
|
2852
|
+
maxLatenessSeconds: number;
|
|
2853
|
+
status: NotificationScheduleStatusDto;
|
|
2854
|
+
anchorAt: string;
|
|
2855
|
+
dueAt: string;
|
|
2856
|
+
emittedAt: string | null;
|
|
2857
|
+
attempts: number;
|
|
2858
|
+
failureReason: NotificationScheduleFailureReasonDto | null;
|
|
2859
|
+
cancelReason: string | null;
|
|
2860
|
+
brandId: string | null;
|
|
2861
|
+
socialAccountId: string | null;
|
|
2862
|
+
subreddit: string | null;
|
|
2863
|
+
createdAt: string;
|
|
2864
|
+
updatedAt: string;
|
|
2865
|
+
};
|
|
2866
|
+
/**
|
|
2867
|
+
* 单条详情。
|
|
2868
|
+
*
|
|
2869
|
+
* ⚠️ `status = "emitted"` 且 `eventId = null` 是**合法历史状态**:事件过 tombstone 期后
|
|
2870
|
+
* 整行删除,FK 的 `ON DELETE SET NULL` 会清掉 `event_id`。别当数据损坏。
|
|
2871
|
+
*/
|
|
2872
|
+
export type NotificationNodeScheduleDetailDto = NotificationNodeScheduleSummaryDto & {
|
|
2873
|
+
sourceDefinitionVersion: number;
|
|
2874
|
+
sourceConfigVersionId: string;
|
|
2875
|
+
fieldSetVersion: string;
|
|
2876
|
+
retentionDays: number;
|
|
2877
|
+
eventId: string | null;
|
|
2878
|
+
/** 关联事件的 seq(十进制串,**绝不 Number 化**);事件已清理或从未发射时为 null。 */
|
|
2879
|
+
eventSeq: string | null;
|
|
2880
|
+
supersededByScheduleId: string | null;
|
|
2881
|
+
/** 是否存在原始异常文本(原文不出网)。 */
|
|
2882
|
+
hasLastError: boolean;
|
|
2449
2883
|
};
|
|
2450
2884
|
export type NotificationSourceConfigDto = {
|
|
2451
2885
|
id: string;
|
|
@@ -2472,6 +2906,56 @@ export type NotificationSourceConfigDto = {
|
|
|
2472
2906
|
configuredFields?: string[];
|
|
2473
2907
|
/** registry 推导的字段级 authz 并集(AND 语义),只读。 */
|
|
2474
2908
|
requiredPermissions?: string[];
|
|
2909
|
+
/**
|
|
2910
|
+
* provenance(迁移 0127):`"template"` = 系统从模板派生,`"manual"` = 人工建的。
|
|
2911
|
+
* 派生行**就是**真正的 team config(events / schedules 的组合租户外键指着它)。
|
|
2912
|
+
*/
|
|
2913
|
+
origin?: "manual" | "template";
|
|
2914
|
+
templateId?: string | null;
|
|
2915
|
+
templateVersion?: number | null;
|
|
2916
|
+
};
|
|
2917
|
+
/**
|
|
2918
|
+
* 系统级 source 模板(迁移 0127)。**没有 teamId** —— 它是一份系统级默认档位 + 字段集,
|
|
2919
|
+
* team 首次需要时派生出一条真正的 team config 行。
|
|
2920
|
+
*
|
|
2921
|
+
* 🔴 `autoDerivationEnabled` **不是** kill switch:它只控制「还要不要给**新** team
|
|
2922
|
+
* bootstrap」,对已派生出来的 config 完全无效(那要用 team config 的 `enabled`)。
|
|
2923
|
+
*/
|
|
2924
|
+
export type NotificationSourceTemplateDto = {
|
|
2925
|
+
id: string;
|
|
2926
|
+
sourceId: string;
|
|
2927
|
+
version: number;
|
|
2928
|
+
isActive: boolean;
|
|
2929
|
+
autoDerivationEnabled: boolean;
|
|
2930
|
+
nodes: NotificationNodeSpecDto[];
|
|
2931
|
+
filters: NotificationFiltersDto;
|
|
2932
|
+
selectedFields: string[];
|
|
2933
|
+
/** 创建时的 registry 快照,**仅供展示/审计**:派生时用当时的 registry 重算。 */
|
|
2934
|
+
configuredFields: string[];
|
|
2935
|
+
fieldSetVersion: string;
|
|
2936
|
+
requiredPermissions: string[];
|
|
2937
|
+
retentionDays: number;
|
|
2938
|
+
displayName: string;
|
|
2939
|
+
displayTopicLabel: string | null;
|
|
2940
|
+
/** 与当前 registry **不完全相等**时派生会跳过(template_registry_mismatch)。 */
|
|
2941
|
+
sourceDefinitionVersion: number;
|
|
2942
|
+
payloadSchemaVersion: string;
|
|
2943
|
+
activatedAt: string | null;
|
|
2944
|
+
supersededAt: string | null;
|
|
2945
|
+
createdAt: string;
|
|
2946
|
+
updatedAt: string;
|
|
2947
|
+
};
|
|
2948
|
+
/** 创建模板新版本的写 body(镜像 `upsertNotificationSourceTemplateBodySchema`)。 */
|
|
2949
|
+
export type UpsertNotificationSourceTemplateSdkBody = {
|
|
2950
|
+
/** 必须与路径上的 sourceId 一致,否则 400。 */
|
|
2951
|
+
sourceId: string;
|
|
2952
|
+
nodes: NotificationNodeSpecDto[];
|
|
2953
|
+
filters?: NotificationFiltersDto;
|
|
2954
|
+
/** 只能是 registry `optionalFields` 的子集,ASCII 升序去重。 */
|
|
2955
|
+
selectedFields?: string[];
|
|
2956
|
+
retentionDays?: number;
|
|
2957
|
+
displayName: string;
|
|
2958
|
+
displayTopicLabel?: string | null;
|
|
2475
2959
|
};
|
|
2476
2960
|
/**
|
|
2477
2961
|
* 事件信封。四层版本 + `includedFields` 决定形状,消费方按 `payloadSchemaVersion` 分支;
|