@geoly-ai/social-hub-cli 0.3.8 → 0.3.9
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 +22 -0
- package/dist/cmd-manifest.json +18 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +35 -0
- package/dist/index.test.js.map +1 -1
- package/dist/permissions-gates-admin.d.ts.map +1 -1
- package/dist/permissions-gates-admin.js +8 -0
- package/dist/permissions-gates-admin.js.map +1 -1
- package/dist/permissions-gates-admin.test.js +10 -0
- package/dist/permissions-gates-admin.test.js.map +1 -1
- package/dist/permissions-gates-notify.d.ts.map +1 -1
- package/dist/permissions-gates-notify.js +81 -1
- package/dist/permissions-gates-notify.js.map +1 -1
- package/dist/permissions-gates-notify.test.js +10 -3
- package/dist/permissions-gates-notify.test.js.map +1 -1
- package/dist/register-notify.d.ts +38 -0
- package/dist/register-notify.d.ts.map +1 -1
- package/dist/register-notify.js +324 -9
- package/dist/register-notify.js.map +1 -1
- package/dist/register-notify.test.js +40 -1
- package/dist/register-notify.test.js.map +1 -1
- package/package.json +3 -3
- package/skills/manifest.json +1 -1
- package/skills/reddit-matrix.lock.json +14 -14
- package/skills/reddit-voc-volume/services/firecrawl/examples/o1_web_crawler/o1_web_crawler.py +152 -152
- package/skills/social-hub-admin/SKILL.md +41 -1
- package/skills/social-hub-notifications/SKILL.md +207 -14
- package/skills/social-hub-openclaw-context/SKILL.md +10 -0
- package/skills/social-hub-subreddit-pools/SKILL.md +51 -2
|
@@ -9,7 +9,7 @@ description: >-
|
|
|
9
9
|
不用于 OpenClaw 任务领取(那是 social-hub-ops-runtime 的 claim/complete,有租约与终态)、
|
|
10
10
|
也不用于飞书审核通知渠道(social-hub-admin)。
|
|
11
11
|
metadata:
|
|
12
|
-
cliVersion: ">=0.3.
|
|
12
|
+
cliVersion: ">=0.3.9"
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
# social-hub-notifications
|
|
@@ -59,11 +59,31 @@ stdout **只有事件**(`--format jsonl` 时每行一个完整 envelope)。
|
|
|
59
59
|
|
|
60
60
|
## 建订阅
|
|
61
61
|
|
|
62
|
-
> **首次启用必读:该 team
|
|
63
|
-
>
|
|
64
|
-
>
|
|
65
|
-
>
|
|
66
|
-
>
|
|
62
|
+
> **首次启用必读:该 team 必须有一条 active 的 source config,否则建订阅会被拒**
|
|
63
|
+
> ——这是故意的,避免产生「看起来 active、永远收不到事件」的死订阅。
|
|
64
|
+
>
|
|
65
|
+
> config 有两条来路,**满足任意一条即可**:
|
|
66
|
+
>
|
|
67
|
+
> 1. **系统级模板自动派生**(推荐,迁移 0127)。如果运维已经为该 source
|
|
68
|
+
> 建好模板、激活、并打开了自动派生总闸,那么**这个 team 什么都不用做** ——
|
|
69
|
+
> 建订阅这一步本身就会当场派生出一条真正的 team config,
|
|
70
|
+
> 哪怕这个 team **从来没发过帖**。这正是「agent 在另一台机器上,先建订阅再等事件」
|
|
71
|
+
> 这个场景能走通的原因。查状态:`social-hub notify templates list`
|
|
72
|
+
> (或 `notify source-configs sources` 看该 team 的 `activeTemplate` 摘要)。
|
|
73
|
+
> 2. **人工建一条 team config**:见 [后台侧配置](#后台侧配置运维不是-agent-日常)。
|
|
74
|
+
> ⚠️ 一旦某个 team 有过**任何**人工版本(哪怕只是 draft、哪怕已经全部作废),
|
|
75
|
+
> 它就**永久脱离模板**、再也不会被自动派生顶回来。
|
|
76
|
+
>
|
|
77
|
+
> 两条都不满足时是 **409 `SUBSCRIPTION_SOURCE_NOT_CONFIGURED`**(不是 400;这个码是**可自愈**的信号:
|
|
78
|
+
> 等运维开总闸 / 建 config 后重试即可),message 里带上**为什么没派生成功**,直接照着排:
|
|
79
|
+
>
|
|
80
|
+
> | 文案里的 reason | 含义 / 处理 |
|
|
81
|
+
> | ---------------------------- | ----------------------------------------------------------------------------------- |
|
|
82
|
+
> | `template_missing` | 该 source 根本没有 active 模板 → 运维 `notify templates create/activate` |
|
|
83
|
+
> | `template_disabled` | 模板有了但**总闸没开** → 运维 `notify templates set-auto-derivation --enabled true` |
|
|
84
|
+
> | `identity_exists` | 这个 team 有过人工 config(可能全是 draft/已作废)→ 走人工路径激活一条 |
|
|
85
|
+
> | `template_registry_mismatch` | 模板钉的版本与当前 registry 不一致 → 运维要出新模板版本 |
|
|
86
|
+
> | `lock_busy` | 运维正在改模板的极短窗口 → **直接重试** |
|
|
67
87
|
|
|
68
88
|
```bash
|
|
69
89
|
# 一台机器一个订阅、各自独立游标(订阅之间是广播,不是竞争消费)
|
|
@@ -304,14 +324,143 @@ reset 会 **bump `receiptEpoch`**,作废全部在途 receipt。**绝不要**
|
|
|
304
324
|
|
|
305
325
|
## 常见故障对照
|
|
306
326
|
|
|
307
|
-
| 现象 | 原因
|
|
308
|
-
| ---------------------------------------------- |
|
|
309
|
-
| `--follow` 一直不动、心跳报同一个 `pendingSeq` | 你没 ack(多半是未知 schema 被隔离了)
|
|
310
|
-
| 游标永远卡在某条 | 把 tombstone 当业务事件处理失败了
|
|
311
|
-
| ack 一直 409 | 在重放旧 receipt
|
|
312
|
-
| pull 403 且 details 有 `missingPermissions` | 字段权限不足
|
|
313
|
-
| 某字段突然不见了 | 后台改了 `selectedFields`(`fieldSetVersion` 会变)
|
|
314
|
-
| 一整段时间没有事件 | source 被 kill switch 关了
|
|
327
|
+
| 现象 | 原因 | 处理 |
|
|
328
|
+
| ---------------------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
329
|
+
| `--follow` 一直不动、心跳报同一个 `pendingSeq` | 你没 ack(多半是未知 schema 被隔离了) | 看 stderr 的 eventId;升级 handler 后重跑 |
|
|
330
|
+
| 游标永远卡在某条 | 把 tombstone 当业务事件处理失败了 | `payloadStatus: "expired"` **必须** ack |
|
|
331
|
+
| ack 一直 409 | 在重放旧 receipt | 重新 pull 拿新 receipt;409 是 CAS 语义,不做幂等成功 |
|
|
332
|
+
| pull 403 且 details 有 `missingPermissions` | 字段权限不足 | **整个订阅**被拒(不做字段级脱敏):减少 `--require-fields` 或补权限 |
|
|
333
|
+
| 某字段突然不见了 | 后台改了 `selectedFields`(`fieldSetVersion` 会变) | 按 envelope 版本分支;**不要**自己补默认值 |
|
|
334
|
+
| 一整段时间没有事件 | source 被 kill switch 关了 | 先 `notify schedules summary`(**不要**从事件流反推:kill switch 的 missed 不产生事件),再 `notify source-configs list`;disabled 期间到点的节点标 missed,**重新启用不补发** |
|
|
335
|
+
| 某个帖子一条通知都没收到 | 主体被删/未发布/无锚点 → schedule 被 cancel | `notify schedules list --subject <snapshotId> --status cancelled`,看 `cancelReason` |
|
|
336
|
+
| 老 schedule 该被清理却一直在 | 保留期 GC cron 没跑 / 追不上 | `notify schedules purge-status`:`hasRunSinceRegistration: false` = **自本次 worker 注册以来**没观测到运行(**不是**「跑了零删除」;刚重启完看到 false 是正常的);连续多轮 `backlog: true` = 真的追不上,调预算/频率。`protectedGroups` 非零是**正常**的 sentinel 保护,别当积压 |
|
|
337
|
+
| 建订阅被拒,文案带 `template_disabled` | 系统级模板做好了但**自动派生总闸没开** | 运维 `notify templates set-auto-derivation --source <id> --enabled true --apply` |
|
|
338
|
+
| 建订阅被拒,文案带 `template_missing` | 该 source 根本没有系统级模板,该 team 也没人工 config | 运维二选一:建模板(`notify templates create/activate` + 开总闸)或人工建 team config |
|
|
339
|
+
| 激活 409 `SOURCE_CONFIG_ACTIVE_STATE_CONFLICT` | 你的 `--expect-active` 与服务端实际 active 不符(别人抢先改了) | 读 `details.actualActiveConfigId` / `actualActiveVersion`,确认对方的改动后再决定是否重试(**无需**再 GET 一次) |
|
|
340
|
+
|
|
341
|
+
## 运维查看面(`notify events` / `notify schedules`)
|
|
342
|
+
|
|
343
|
+
> 🔴 **这不是消费面。** `notify events list` 不推进游标、不签 receipt,拿它当消费手段
|
|
344
|
+
> 事件必然被漏掉。要不漏地消费只有 `notify pull` + `notify ack` 一条路。
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
# 最近 N 条事件(元数据摘要,不含 payload)。默认 --order desc
|
|
348
|
+
social-hub notify events list -t <team> --limit 20
|
|
349
|
+
# 往更老翻页:把上一页响应的 page.lastSeq 传给 --before
|
|
350
|
+
social-hub notify events list -t <team> --order desc --before <page.lastSeq> --limit 20
|
|
351
|
+
# 指定窗口 / 正序追
|
|
352
|
+
social-hub notify events list -t <team> --after 100 --before 200 --order asc
|
|
353
|
+
|
|
354
|
+
# 单条完整 envelope(按事件行上**冻结**的权限逐条重验,可能比当前 config 更严)
|
|
355
|
+
social-hub notify events get -t <team> --seq 12345
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
**边界语义(记牢,翻页写反了会静默停在空白页):**
|
|
359
|
+
|
|
360
|
+
- `--after` 恒为排他**下界**、`--before` 恒为排他**上界**;
|
|
361
|
+
- `--order` **只**决定取区间哪一端的 N 条与返回顺序,**不改变**上下界含义;
|
|
362
|
+
- `--after >= --before` → **400**(矛盾区间恒为空,不会静默返回空数组);
|
|
363
|
+
- 响应里的 `latestSeq` / `earliestAvailableSeq` 是**流**的,与排序方向无关;
|
|
364
|
+
本页首尾在 `page.firstSeq` / `page.lastSeq`;
|
|
365
|
+
- tombstone(`payloadStatus: "expired"`)照常出现在列表里 —— 过滤掉会让你以为序号有空洞。
|
|
366
|
+
|
|
367
|
+
### 节点 schedule:`missed` / `cancelled` 只有查这里才看得见
|
|
368
|
+
|
|
369
|
+
> 🔴 **绝不能从事件流反推 schedule 状态。** kill switch 期间到点的 `missed` 与
|
|
370
|
+
> 锚点消失的 `cancelled` **根本不产生任何事件**。靠事件流反推只会把它们算成 0,
|
|
371
|
+
> 于是「今晚一条都没发」与「今晚本来就没有到期节点」长得一模一样。
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
# 按 source × status 聚合(scheduled/emitted/missed/cancelled/superseded)
|
|
375
|
+
social-hub notify schedules summary -t <team>
|
|
376
|
+
social-hub notify schedules summary -t <team> --due-from 2026-07-01T00:00:00Z --due-to 2026-07-08T00:00:00Z
|
|
377
|
+
|
|
378
|
+
# 列表(keyset 分页:把响应的 nextCursor 原样回传;null = 没有下一页)
|
|
379
|
+
social-hub notify schedules list -t <team> --status missed --limit 50
|
|
380
|
+
social-hub notify schedules list -t <team> --status missed --cursor <nextCursor>
|
|
381
|
+
|
|
382
|
+
# 单条:状态原因 + 关联事件 seq
|
|
383
|
+
social-hub notify schedules get -t <team> --id <scheduleId>
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
- 时间窗**必须有界**:缺省 = 最近 30 天,上限 **90 天**(超了 400)。
|
|
387
|
+
`--due-from` **含**、`--due-to` **不含**,相邻窗口拼接不会重复计数。
|
|
388
|
+
- 🔴 **空结果 ≠ 没有异常** —— 本表有保留期 GC(`emitted` / `superseded` **180 天**、
|
|
389
|
+
`missed` / `cancelled` **730 天**,判据是 `dueAt`;`scheduled` 永不删)。
|
|
390
|
+
90 天只限制窗口**跨度**,不限制窗口必须在最近 90 天内:查一年前的 30 天窗口完全合法,
|
|
391
|
+
但那已经在 `emitted` 档的保留期之外。summary / list 的响应都带 `retention`:
|
|
392
|
+
- `retention.completenessByStatus.<status>`:`full` = 窗口整体在保证保留期内,**没有就是真没有**;
|
|
393
|
+
`partial` = 窗口横跨保留 floor,更早的部分**可能**已被清理;`outside` = 窗口整体在保留期之外。
|
|
394
|
+
- `retention.policy.<status>.guaranteedRetainedFrom`:**保证**保留到的最早 `dueAt`
|
|
395
|
+
(不是实际最老行 —— 每个 subject 组会永久留一行 sentinel,比它更老的行也可能还在)。
|
|
396
|
+
- `retention.windowCompleteness`:只在**带 `--status` 过滤**时给出,否则为 `null`。
|
|
397
|
+
报结论时按它分流:`full` 才能说「窗口内没有异常」;`partial` / `outside` 只能说
|
|
398
|
+
「该窗口早于保证保留期,更早的历史**可能**已被清理」。
|
|
399
|
+
- 失败原因是**归一化枚举** `failureReason`:`kill_switch` / `max_lateness_exceeded` /
|
|
400
|
+
`processing_error`。**没有** `lastError` 原文(那是任意异常字符串,可能含内部细节);
|
|
401
|
+
`hasLastError` 只告诉你「存在原始异常」,原文在服务端日志里。
|
|
402
|
+
- ⚠️ `processing_error` **不代表终态** —— 它会出现在仍会重试的 `scheduled` 行上
|
|
403
|
+
(记账只 bump `attempts`,绝不按次数把节点判死)。判终态看 `status`。
|
|
404
|
+
- ⚠️ `status: "emitted"` 且 `eventId: null` 是**合法历史状态**:事件过 tombstone 期后整行
|
|
405
|
+
被清理,FK 会把 `event_id` 置 null。不是数据损坏。
|
|
406
|
+
|
|
407
|
+
### 保留期 GC 自己跑得怎么样(`notify schedules purge-status`)
|
|
408
|
+
|
|
409
|
+
上面那个 `retention` 讲的是**策略**(保证保留到哪天)。真正执行清理的是 worker 里的
|
|
410
|
+
GC cron(默认每天 **03:17**,与 dispatcher 刻意错开时刻,避免抢同一把 subject group 锁)。
|
|
411
|
+
它跑没跑、追不追得上,只有这里看得到:
|
|
412
|
+
|
|
413
|
+
```bash
|
|
414
|
+
# 系统级:**没有 --team**(状态是 worker 写的全局单 key,不是某个 team 的清理进度)
|
|
415
|
+
social-hub notify schedules purge-status
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
- 权限 `systemSetting:read`(**仅 admin/manager**)。与既有两个 cron status 端点同口径,
|
|
419
|
+
比 `notificationEvent:read` 更严 —— 响应带 `workerId` 与异常原文摘要。
|
|
420
|
+
|
|
421
|
+
🔴 **`null` ≠ `0`,别拿 `?? 0` 糊过去。** 判「跑没跑过」只看一个字段:
|
|
422
|
+
|
|
423
|
+
| 场景 | `hasRunSinceRegistration` | `status` | `deleted` |
|
|
424
|
+
| ---------------------- | ------------------------- | -------------- | --------- |
|
|
425
|
+
| 没有可读的状态快照 | `false` | `null` | `null` |
|
|
426
|
+
| 注册了但还没到点 | `false` | `"registered"` | `null` |
|
|
427
|
+
| 跑了、但这轮没东西可删 | `true` | `"success"` | `0` |
|
|
428
|
+
| 跑了、删了 | `true` | `"success"` | `>0` |
|
|
429
|
+
| 跑挂了 | `true` | `"failed"` | `null` |
|
|
430
|
+
| 正在跑 | `true` | `"running"` | `null` |
|
|
431
|
+
|
|
432
|
+
⚠️ 第一行的准确说法是「**没有可读的状态快照**」:可能 worker 从没起过这段代码,也可能状态
|
|
433
|
+
写失败 / 被清掉 / 存的值坏到无法归一。**不能**断言成「worker 从未启动」。
|
|
434
|
+
|
|
435
|
+
⚠️ `hasRunSinceRegistration` 的语义是「**自本次 worker 注册以来**跑过」,不是「历史上跑过」:
|
|
436
|
+
worker 每次重启都会重写 registered 快照并把运行字段清回 `null`,昨天跑成功的记录不会保留。
|
|
437
|
+
所以刚重启完看到 `false` 是正常的,不等于 GC 从来没工作过。
|
|
438
|
+
|
|
439
|
+
🔴 **三个字段长得像故障,但都不是**:
|
|
440
|
+
|
|
441
|
+
| 字段 | 含义 | 是不是错误 |
|
|
442
|
+
| ----------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
443
|
+
| `protectedGroups` | 被 sentinel / `has_scheduled` 不变量**有意保护**、本轮跳过不删的 subject 组 | **不是**。sentinel 保证永不把一个 subject 组删空 —— 删空了同一个帖子会再收到第二轮通知。这个数常年非零才是正常状态 |
|
|
444
|
+
| `lockBusyGroups` | 该组的 advisory lock 正被物化/dispatcher 持有,本轮让路 | **不是**。下一轮自然重试。只有连续多轮居高不下才值得看锁竞争 |
|
|
445
|
+
| `backlog` | 由**独立的 `LIMIT 1` probe** 判定的「还有待清理候选」 | **不是**错误,但是**确定的**追赶压力信号 |
|
|
446
|
+
| `budgetExhausted` | 本轮撞到单次删除预算上限而收工 | **不是**。容量信号 |
|
|
447
|
+
| `failedGroups` | 组级删除真的抛异常 | 🔴 **是**。>0 要查 worker 日志 |
|
|
448
|
+
|
|
449
|
+
- 🔴 `backlog` 的判据要说清楚:它**不是**拿「最后一批恰好取满」当证据(那种猜法在总数
|
|
450
|
+
恰好整除批大小时会假阳性)。它是清理跑完后单独发一条 `LIMIT 1` probe 去问「还有没有
|
|
451
|
+
符合删除条件的行」。因此 `backlog: true` 表示**确实还有**待清理候选,而不是「可能还有」;
|
|
452
|
+
`backlog: false` 表示**确实追平了**。
|
|
453
|
+
- 判「GC 追不上」的正确姿势:连续多天 `backlog: true`(尤其同时 `budgetExhausted: true`),
|
|
454
|
+
说明单轮预算或频率不够,调 `NOTIFICATION_SCHEDULE_PURGE_CRON` / 预算,而不是去看
|
|
455
|
+
`protectedGroups`。
|
|
456
|
+
- `deletedByTier` 是逐档删除数(状态档 → 行数);`{}` = 这轮没有任何一档删到东西。
|
|
457
|
+
- `schedule` 是 **API 进程**的 env 回退值,**不权威**(worker 只认自己进程的
|
|
458
|
+
`NOTIFICATION_SCHEDULE_PURGE_CRON`)。推算下次触发用的是 `effectiveCron`
|
|
459
|
+
—— 它优先取 worker 注册时自报的 `registeredCron`。⚠️ `registeredCron` 只说明「某个 worker
|
|
460
|
+
曾经用这个表达式注册过」,**不证明它还活着**(没有 heartbeat),所以 `nextRunAt` 是**推算值**,
|
|
461
|
+
不是「保证会在这一刻跑」。`nextRunAt` 按 `effectiveCron` 推算,
|
|
462
|
+
表达式非法时为 `null`(不会 500)。
|
|
463
|
+
⚠️ `nextRunAt` 用 **API 进程的本地时区**计算;API 与 worker 的 `TZ` 不一致时它会算偏。
|
|
315
464
|
|
|
316
465
|
## 后台侧配置(运维,不是 agent 日常)
|
|
317
466
|
|
|
@@ -328,11 +477,55 @@ social-hub notify source-configs create -t <team> --source post-performance-node
|
|
|
328
477
|
social-hub notify source-configs create ... --no-activate --apply # 只建 draft
|
|
329
478
|
social-hub notify source-configs activate -t <team> --source post-performance-node.v1 --id <uuid> --apply
|
|
330
479
|
|
|
480
|
+
# 🔴 两个管理员同时激活不同版本时,**不带 CAS 的那次会静默覆盖对方**。
|
|
481
|
+
# 先看当前 active,再把它作为期望值传回去:
|
|
482
|
+
social-hub notify source-configs sources -t <team> # 取 activeConfig.id / .version
|
|
483
|
+
social-hub notify source-configs activate -t <team> --source post-performance-node.v1 \
|
|
484
|
+
--id <uuid> --expect-active <当前 activeConfig.id> --expect-active-version <version> --apply
|
|
485
|
+
# 「我认为当前根本没有 active」:
|
|
486
|
+
social-hub notify source-configs activate ... --expect-active none --apply
|
|
487
|
+
# 创建即激活也支持同一套(否则「新建并激活」就是绕过 CAS 的后门):
|
|
488
|
+
social-hub notify source-configs create ... --expect-active <当前 activeConfig.id> --apply
|
|
489
|
+
|
|
331
490
|
# 实时 kill switch —— 立刻影响【全部在途 schedule】,且不 bump sourceConfigVersion
|
|
332
491
|
social-hub notify source-configs set-enabled -t <team> \
|
|
333
492
|
--source post-performance-node.v1 --enabled false --apply
|
|
334
493
|
```
|
|
335
494
|
|
|
495
|
+
### 系统级模板(`notify templates`,改的是**全体 team** 的默认值)
|
|
496
|
+
|
|
497
|
+
用途:节点档位是**运营口径**,不该逐团队配。模板给出一份系统级默认档位 + 字段集,
|
|
498
|
+
team 首次物化 / backfill / **建订阅**时各自派生出一条真正的 team config 行
|
|
499
|
+
(`origin = "template"`,`activeConfig` 里能看到 `templateId` / `templateVersion`)。
|
|
500
|
+
|
|
501
|
+
🔴 **不带 `-t/--team`** —— 这些端点在 `/v1/system/*`,只有**系统 admin**(且必须是不受限的
|
|
502
|
+
交互式登录:api_key / 服务 token / job-bound 或带动作白名单的 CLI token 一律 403)能写。
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
# 各 source 的当前 active 模板 + 总闸状态
|
|
506
|
+
social-hub notify templates list
|
|
507
|
+
social-hub notify templates versions --source post-performance-node.v1 [--limit 50]
|
|
508
|
+
social-hub notify templates get --source post-performance-node.v1 --id <uuid>
|
|
509
|
+
|
|
510
|
+
# 建模板版本(默认【不】激活;body 与 source config 的 upsert 同款,多一个必填 sourceId)
|
|
511
|
+
social-hub notify templates create --source post-performance-node.v1 --json-file ./tpl.json --apply
|
|
512
|
+
social-hub notify templates activate --source post-performance-node.v1 --id <uuid> --apply
|
|
513
|
+
|
|
514
|
+
# 🔴 自动派生总闸 —— 打开 = 全体没配过该 source 的 team 开始产生通知
|
|
515
|
+
social-hub notify templates set-auto-derivation \
|
|
516
|
+
--source post-performance-node.v1 --enabled true --apply
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
**两道闸缺一不可**:模板 `isActive`(activate)**且** `autoDerivationEnabled`(set-auto-derivation)。
|
|
520
|
+
所以「建了模板还是没人收到通知」多半是总闸没开 —— `notify templates list` 一眼可见。
|
|
521
|
+
|
|
522
|
+
⚠️ **`autoDerivationEnabled` 不是 kill switch**(刻意不叫 `enabled` 就是为了防混):
|
|
523
|
+
关掉它**只**停止给**新** team 派生;已经派生出来的 team config 照常发通知。
|
|
524
|
+
要停某个 team,用那个 team 的 `notify source-configs set-enabled --enabled false`。
|
|
525
|
+
|
|
526
|
+
⚠️ 模板出新版本**不会**跟进已派生的 team(它们已经有版本行了 → `identity_exists`)。
|
|
527
|
+
模板只管 bootstrap,不做全量下发。
|
|
528
|
+
|
|
336
529
|
### 🔴 写 `selectedFields` 前必读(踩过两次的坑)
|
|
337
530
|
|
|
338
531
|
1. **必须 ASCII 升序去重**,否则 400。这是 `fieldSetVersion` 哈希稳定性的要求(同一字段集合必须得到同一标识)。
|
|
@@ -95,6 +95,16 @@ social-hub context job -t <team-id> --job <scheduledJobId>
|
|
|
95
95
|
|
|
96
96
|
互动选板 **主读** `subreddit-candidates`;发布前查池可用 `account-pools`。
|
|
97
97
|
|
|
98
|
+
> ⚠️ **`account-pools` 的 `allowedActions` 按 `sourceScope` 收敛**(2026-08-03 起):
|
|
99
|
+
>
|
|
100
|
+
> - `sourceScope: "account"`(人设/品牌展开)→ `browse` / `upvote` / `comment` / `comment_deep` / `publish_post`
|
|
101
|
+
> - `sourceScope: "system"`(全局共享 catalog)→ **只有** `browse` / `upvote`
|
|
102
|
+
>
|
|
103
|
+
> 系统级共享池**不作评论/发帖目标**。此前该端点对所有行一律返回 5 个动作,
|
|
104
|
+
> 与 `subreddit-candidates` 的口径(早已排除 system-scope 评论候选)不一致 ——
|
|
105
|
+
> 那是**错误的授权信号**,不是可用能力。若你的脚本此前拿 system 行发帖/评论,
|
|
106
|
+
> 请改从 `subreddit-candidates` 取候选。
|
|
107
|
+
|
|
98
108
|
```bash
|
|
99
109
|
# 账号板块聚合(系统级 + 账号级;与 account-pool-context 同响应)
|
|
100
110
|
social-hub query account-pools -t <team-id> --account A1 \
|
|
@@ -11,10 +11,53 @@ description: >-
|
|
|
11
11
|
|
|
12
12
|
先读 `social-hub-shared` 完成 `auth login` 与 `context use`。
|
|
13
13
|
|
|
14
|
+
## ⚠️ 先看清楚:catalog / tier-rules 是**系统级共享**,aliases 才是团队级
|
|
15
|
+
|
|
16
|
+
| 资源 | 层级 | 全局唯一键 | `-t <team>` 的作用 |
|
|
17
|
+
| ------------ | -------------- | ------------------------------- | -------------------------------- |
|
|
18
|
+
| `catalog` | **系统级共享** | `(industryKey, subreddit)` | 鉴权 + 新建行署名 + 列表视图过滤 |
|
|
19
|
+
| `tier-rules` | **系统级共享** | `subreddit` | 鉴权 + 新建行署名 + 列表视图过滤 |
|
|
20
|
+
| `aliases` | 团队级(真隔离) | `(team, aliasRef, industryKey)` | 真正的数据边界 |
|
|
21
|
+
|
|
22
|
+
对 catalog / tier-rules:
|
|
23
|
+
|
|
24
|
+
- **任何团队的改动对全局生效**。板块目录回答的是"这个行业有哪些板块值得抓",
|
|
25
|
+
是跨团队共享的知识;抓取任务(worker)读的就是全表,不分团队。
|
|
26
|
+
- `-t` **不做数据隔离**:`catalog-update` / `catalog-delete` / `tier-rules-update` /
|
|
27
|
+
`tier-rules-delete` 的 `--id` 是**全局 id**,任何团队路径都能命中任何一行。
|
|
28
|
+
所以 `catalog-list -t A` 列不出来的行,`catalog-delete -t A --id <行id>` 照样能改
|
|
29
|
+
—— 这是系统级资源的固有结果,**不是 bug**。
|
|
30
|
+
- `catalog-list` / `tier-rules-list` 的 `-t` 只是**按归属署名过滤列表视图**
|
|
31
|
+
(一个 view filter,不是授权边界)。⚠️ **列表只能看到本团队署名的行,但改是全局的**
|
|
32
|
+
—— 别把"列表里没有"当成"这个板块不存在"。
|
|
33
|
+
⚠️ 加 `--industry-key` **绕不过**署名过滤(它只是再叠一个条件)。要跨团队查重,
|
|
34
|
+
必须显式加 `--aggregate-visible-teams`(`catalog-list` / `tier-rules-list` 都支持):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# 建行前查重:这个 (industryKey, subreddit) 是不是已经有别的团队录过了
|
|
38
|
+
social-hub pools catalog-list -t <team-id> --aggregate-visible-teams --industry-key coffee -n 200
|
|
39
|
+
social-hub pools tier-rules-list -t <team-id> --aggregate-visible-teams -n 200
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
🔴 **它是「跨可见团队的署名聚合视图」,不是无条件全表**:范围 = 当前登录主体
|
|
43
|
+
**可见的团队**,不可见团队署名的行仍然看不到(API key 主体还会被直接拒)。
|
|
44
|
+
所以 `--aggregate-visible-teams` 查不到 ≠ 全局不存在 —— 它把漏判概率压到很低,
|
|
45
|
+
但**不是**并发正确性保证。真正兜底重复的是 DB 的全局唯一约束 + upsert 语义:
|
|
46
|
+
撞上了会**更新那一行**(见下),不会新建副本。
|
|
47
|
+
|
|
48
|
+
- `catalog-create` / `tier-rules-create` 是 **upsert**:提交一个已存在的
|
|
49
|
+
`(industryKey, subreddit)` / `subreddit` 会**更新那一行**(可能是别的团队建的),
|
|
50
|
+
不会新建一份团队私有副本。
|
|
51
|
+
- **归属署名(行上的 `teamId`)不可变**:只在真正新建时写入,后续任何团队编辑都不改它。
|
|
52
|
+
想知道"谁改的"看审计,别看这一列。
|
|
53
|
+
- ⚠️ **`catalog-delete` 实际是 archive**(`status='archived'`),而且影响**全局**:
|
|
54
|
+
任一团队归档一个板块,**所有团队的抓取任务都不再选它**。归档前先确认没有别的团队
|
|
55
|
+
在依赖这个板块。
|
|
56
|
+
|
|
14
57
|
## 命令组
|
|
15
58
|
|
|
16
59
|
```bash
|
|
17
|
-
social-hub pools catalog-list -t <team-id> [--pool-kind pet|common|industry|brand|other]
|
|
60
|
+
social-hub pools catalog-list -t <team-id> [--pool-kind pet|common|industry|brand|other] [--industry-key <key>] [--status active|paused|archived] [--aggregate-visible-teams]
|
|
18
61
|
social-hub pools catalog-create -t <team-id> -j '{"industryKey":"...","industryLabel":"...","subreddit":"..."}'
|
|
19
62
|
social-hub pools catalog-update -t <team-id> --id <pool-id> -j '{...}'
|
|
20
63
|
social-hub pools catalog-delete -t <team-id> --id <pool-id>
|
|
@@ -24,7 +67,7 @@ social-hub pools aliases-create -t <team-id> -j '{"aliasRef":"persona_pools.P23.
|
|
|
24
67
|
social-hub pools aliases-update -t <team-id> --id <alias-id> -j '{...}'
|
|
25
68
|
social-hub pools aliases-delete -t <team-id> --id <alias-id>
|
|
26
69
|
|
|
27
|
-
social-hub pools tier-rules-list -t <team-id>
|
|
70
|
+
social-hub pools tier-rules-list -t <team-id> [--tier S0|S1|S2|S3] [--enabled true|false] [--aggregate-visible-teams]
|
|
28
71
|
social-hub pools tier-rules-create -t <team-id> -j '{...}'
|
|
29
72
|
social-hub pools tier-rules-update -t <team-id> --id <rule-id> -j '{...}'
|
|
30
73
|
social-hub pools tier-rules-delete -t <team-id> --id <rule-id>
|
|
@@ -53,6 +96,12 @@ social-hub pools tier-rules-delete -t <team-id> --id <rule-id>
|
|
|
53
96
|
|
|
54
97
|
## 常见排障
|
|
55
98
|
|
|
99
|
+
- `catalog-delete -t A --id X` 改到了"别的团队的同名行":**这是预期行为**,不是 bug。
|
|
100
|
+
catalog 全局只有一行同 `(industryKey, subreddit)`,`-t` 不参与定位。改之前先用
|
|
101
|
+
`catalog-list --aggregate-visible-teams`(或后台页面的跨团队聚合视图)确认这一行的
|
|
102
|
+
归属与内容。
|
|
103
|
+
- `tier-rules-update` 想把规则的 `subreddit` 改成另一条规则已占用的值 → 返回 409。
|
|
104
|
+
以前这里会静默改到那另一条规则上、无视 URL 里的 `--id`;现在明确报冲突。
|
|
56
105
|
- Pool Aliases 页面为空:先确认是否跑过 `db:seed` 或 persona pool bootstrap;再用 `aliases-list` 查当前 team,而不是只看 catalog。
|
|
57
106
|
- `personaPools.unresolvedRefs` 仍有 `persona_pools.Pxx.starter/advanced`:说明 alias 缺失,或 alias 指向的 `industryKey` catalog rows 未创建。
|
|
58
107
|
- catalog 有数据但候选为空:检查 subreddit 是否被 `bot_bouncer_blocklist`、`previously_banned`、team blocklist 或 tier policy 过滤。
|