@geoly-ai/social-hub-cli 0.3.44 → 0.3.46

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 (60) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/cli-error-channels.test.js +40 -0
  3. package/dist/cli-error-channels.test.js.map +1 -1
  4. package/dist/client.d.ts +7 -1
  5. package/dist/client.d.ts.map +1 -1
  6. package/dist/client.js +19 -2
  7. package/dist/client.js.map +1 -1
  8. package/dist/cmd-manifest.json +15 -2
  9. package/dist/cmd-manifest.test.js +13 -0
  10. package/dist/cmd-manifest.test.js.map +1 -1
  11. package/dist/index.d.ts +39 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +238 -3
  14. package/dist/index.js.map +1 -1
  15. package/dist/output.d.ts +12 -0
  16. package/dist/output.d.ts.map +1 -1
  17. package/dist/output.js +42 -0
  18. package/dist/output.js.map +1 -1
  19. package/dist/permissions-gates-restrictions.d.ts +10 -0
  20. package/dist/permissions-gates-restrictions.d.ts.map +1 -0
  21. package/dist/permissions-gates-restrictions.js +53 -0
  22. package/dist/permissions-gates-restrictions.js.map +1 -0
  23. package/dist/permissions-gates-restrictions.test.d.ts +2 -0
  24. package/dist/permissions-gates-restrictions.test.d.ts.map +1 -0
  25. package/dist/permissions-gates-restrictions.test.js +37 -0
  26. package/dist/permissions-gates-restrictions.test.js.map +1 -0
  27. package/dist/permissions.d.ts.map +1 -1
  28. package/dist/permissions.js +2 -0
  29. package/dist/permissions.js.map +1 -1
  30. package/dist/public-reddit.test.d.ts +2 -0
  31. package/dist/public-reddit.test.d.ts.map +1 -0
  32. package/dist/public-reddit.test.js +339 -0
  33. package/dist/public-reddit.test.js.map +1 -0
  34. package/dist/reddit-account-type-filter.test.d.ts +2 -0
  35. package/dist/reddit-account-type-filter.test.d.ts.map +1 -0
  36. package/dist/reddit-account-type-filter.test.js +92 -0
  37. package/dist/reddit-account-type-filter.test.js.map +1 -0
  38. package/dist/register-extensions.d.ts.map +1 -1
  39. package/dist/register-extensions.js +2 -0
  40. package/dist/register-extensions.js.map +1 -1
  41. package/dist/register-restrictions.d.ts +64 -0
  42. package/dist/register-restrictions.d.ts.map +1 -0
  43. package/dist/register-restrictions.js +431 -0
  44. package/dist/register-restrictions.js.map +1 -0
  45. package/dist/register-restrictions.test.d.ts +2 -0
  46. package/dist/register-restrictions.test.d.ts.map +1 -0
  47. package/dist/register-restrictions.test.js +315 -0
  48. package/dist/register-restrictions.test.js.map +1 -0
  49. package/package.json +3 -3
  50. package/skills/integrity.json +10 -10
  51. package/skills/manifest.json +1 -1
  52. package/skills/social-hub-admin/SKILL.md +39 -0
  53. package/skills/social-hub-calendar-directives/SKILL.md +14 -1
  54. package/skills/social-hub-calendar-jobs/SKILL.md +14 -0
  55. package/skills/social-hub-cli/SKILL.md +2 -0
  56. package/skills/social-hub-graph-compliance/SKILL.md +42 -0
  57. package/skills/social-hub-intelligence/SKILL.md +111 -0
  58. package/skills/social-hub-ops-runtime/SKILL.md +44 -0
  59. package/skills/social-hub-posts/SKILL.md +4 -0
  60. package/skills/social-hub-publishing/SKILL.md +12 -0
@@ -0,0 +1,315 @@
1
+ /**
2
+ * `social-hub restrictions ...`(板块限制 A4):命令树、写命令双闸、参数 → 请求的映射、
3
+ * 受限拦截 409 的 stderr 结构化输出。
4
+ */
5
+ import { Command } from "commander";
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
7
+ const client = vi.hoisted(() => ({
8
+ listSubredditRestrictions: vi.fn(),
9
+ listTeamSubredditRestrictions: vi.fn(),
10
+ getTeamSubredditRestrictionFanout: vi.fn(),
11
+ getSubredditRestriction: vi.fn(),
12
+ getSubredditRestrictionFanout: vi.fn(),
13
+ createSubredditRestriction: vi.fn(),
14
+ confirmSubredditRestriction: vi.fn(),
15
+ revokeSubredditRestriction: vi.fn(),
16
+ reportSubredditRestriction: vi.fn(),
17
+ checkSubredditRestriction: vi.fn(),
18
+ }));
19
+ const gates = vi.hoisted(() => ({ calls: [] }));
20
+ /** 当前 context 的 team(null = 没有 context team)。 */
21
+ const ctxTeam = vi.hoisted(() => ({ value: "ctx-team" }));
22
+ vi.mock("./context-state.js", async (importOriginal) => ({
23
+ ...(await importOriginal()),
24
+ resolveEffectiveTeamId: (explicit) => explicit?.trim() || ctxTeam.value || undefined,
25
+ }));
26
+ vi.mock("./client.js", async (importOriginal) => ({
27
+ ...(await importOriginal()),
28
+ requireClient: () => client,
29
+ resolveTeamId: (explicit) => explicit ?? "ctx-team",
30
+ }));
31
+ vi.mock("./permission-runner.js", () => ({
32
+ withPermissionGates: async (input, run) => {
33
+ gates.calls.push(input);
34
+ await run({ role: "admin" });
35
+ },
36
+ }));
37
+ const { buildCreateRestrictionBody, buildListRestrictionsParams, buildReportBody, parseEvidenceOption, registerRestrictionCommands, } = await import("./register-restrictions.js");
38
+ const { collectCommandPaths } = await import("./cmd-manifest.js");
39
+ const { formatCliError } = await import("./output.js");
40
+ const { HubHttpError } = await import("./client.js");
41
+ const ID = "3f1c2b1e-6a2a-4a5a-9a1a-2b3c4d5e6f70";
42
+ const ACC = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb";
43
+ function program() {
44
+ const p = new Command();
45
+ p.exitOverride();
46
+ registerRestrictionCommands(p);
47
+ return p;
48
+ }
49
+ const run = (...args) => program().parseAsync(["node", "social-hub", "restrictions", ...args]);
50
+ let logSpy;
51
+ let errSpy;
52
+ let exitSpy;
53
+ beforeEach(() => {
54
+ for (const fn of Object.values(client))
55
+ fn.mockReset().mockResolvedValue({ ok: true });
56
+ gates.calls = [];
57
+ ctxTeam.value = "ctx-team";
58
+ logSpy = vi.spyOn(console, "log").mockImplementation(() => { });
59
+ errSpy = vi.spyOn(console, "error").mockImplementation(() => { });
60
+ exitSpy = vi.spyOn(process, "exit").mockImplementation(((code) => {
61
+ throw new Error(`process.exit(${code})`);
62
+ }));
63
+ });
64
+ afterEach(() => {
65
+ logSpy.mockRestore();
66
+ errSpy.mockRestore();
67
+ exitSpy.mockRestore();
68
+ });
69
+ describe("命令树", () => {
70
+ it("八个叶子命令齐全", () => {
71
+ const paths = collectCommandPaths(program()).map((e) => e.path);
72
+ expect(paths).toEqual(expect.arrayContaining([
73
+ "restrictions",
74
+ "restrictions list",
75
+ "restrictions get",
76
+ "restrictions fanout",
77
+ "restrictions create",
78
+ "restrictions confirm",
79
+ "restrictions revoke",
80
+ "restrictions report",
81
+ "restrictions check",
82
+ ]));
83
+ });
84
+ it("写命令都有 --dry-run / --apply;读命令都没有 --apply", () => {
85
+ const group = program().commands.find((c) => c.name() === "restrictions");
86
+ const flags = (name) => group.commands.find((c) => c.name() === name).options.map((o) => o.long);
87
+ for (const name of ["create", "confirm", "revoke", "report"]) {
88
+ expect(flags(name)).toEqual(expect.arrayContaining(["--dry-run", "--apply"]));
89
+ }
90
+ for (const name of ["list", "get", "fanout", "check"]) {
91
+ expect(flags(name)).not.toContain("--apply");
92
+ }
93
+ });
94
+ });
95
+ describe("参数 → 请求", () => {
96
+ it("list 默认走 team 路由(context team);--state 多值、--updated-since、分页透传", async () => {
97
+ await run("list", "--state", "provisional, verification_overdue", "--updated-since", "2026-09-20T00:00:00.000Z", "--limit", "20");
98
+ const expected = {
99
+ state: ["provisional", "verification_overdue"],
100
+ updatedSince: "2026-09-20T00:00:00.000Z",
101
+ subreddit: undefined,
102
+ limit: 20,
103
+ offset: undefined,
104
+ };
105
+ expect(client.listTeamSubredditRestrictions).toHaveBeenCalledWith("ctx-team", expected);
106
+ expect(client.listSubredditRestrictions).not.toHaveBeenCalled();
107
+ expect(gates.calls[0]).toMatchObject({
108
+ gates: [{ resource: "compliance", action: "read" }],
109
+ teamId: "ctx-team",
110
+ });
111
+ // 显式 -t 优先于 context。
112
+ await run("list", "-t", "team-9");
113
+ expect(client.listTeamSubredditRestrictions).toHaveBeenLastCalledWith("team-9", expect.anything());
114
+ });
115
+ it("list --system 才走系统级 admin 面(userOnly + systemSubredditRestriction)", async () => {
116
+ await run("list", "--system", "--state", "revoked");
117
+ expect(client.listSubredditRestrictions).toHaveBeenCalledWith(expect.objectContaining({ state: ["revoked"] }));
118
+ expect(client.listTeamSubredditRestrictions).not.toHaveBeenCalled();
119
+ expect(gates.calls[0]).toMatchObject({
120
+ gates: [{ resource: "systemSubredditRestriction", action: "list" }],
121
+ userOnly: true,
122
+ });
123
+ });
124
+ it("没有 -t 也没有 context team → 报错,不静默改走 admin 面", async () => {
125
+ ctxTeam.value = null;
126
+ await expect(run("list")).rejects.toThrow("process.exit(1)");
127
+ expect(client.listTeamSubredditRestrictions).not.toHaveBeenCalled();
128
+ expect(client.listSubredditRestrictions).not.toHaveBeenCalled();
129
+ await expect(run("list", "--system", "-t", "x")).rejects.toThrow("process.exit(1)");
130
+ });
131
+ it("get:位置参数 id(系统级)", async () => {
132
+ await run("get", ID);
133
+ expect(client.getSubredditRestriction).toHaveBeenCalledWith(ID);
134
+ });
135
+ it("fanout 默认走 team 路由,不发 teamId;--team-id 只能配 --system", async () => {
136
+ await run("fanout", ID, "-t", "team-1", "--version", "2", "--outcome", "blocked");
137
+ expect(client.getTeamSubredditRestrictionFanout).toHaveBeenCalledWith("team-1", ID, { version: 2, outcome: "blocked", limit: undefined, offset: undefined });
138
+ expect(client.getSubredditRestrictionFanout).not.toHaveBeenCalled();
139
+ await expect(run("fanout", ID, "--team-id", "other-team")).rejects.toThrow("process.exit(1)");
140
+ expect(client.getTeamSubredditRestrictionFanout).toHaveBeenCalledTimes(1);
141
+ });
142
+ it("fanout --system:系统级,可带 --team-id", async () => {
143
+ await run("fanout", ID, "--system", "--team-id", "team-2");
144
+ expect(client.getSubredditRestrictionFanout).toHaveBeenCalledWith(ID, {
145
+ version: undefined,
146
+ outcome: undefined,
147
+ teamId: "team-2",
148
+ limit: undefined,
149
+ offset: undefined,
150
+ });
151
+ expect(gates.calls[0]).toMatchObject({
152
+ gates: [{ resource: "systemSubredditRestriction", action: "read" }],
153
+ userOnly: true,
154
+ });
155
+ });
156
+ it("create --apply:body 透传;--dry-run 不发请求", async () => {
157
+ await run("create", "--subreddit", "r/ADHDLiving", "--kind", "banned", "--reason", "whole sub banned", "--evidence-level", "strong", "--evidence", '[{"type":"banned_page","url":"https://www.reddit.com/r/adhdliving"}]', "--apply");
158
+ expect(client.createSubredditRestriction).toHaveBeenCalledWith({
159
+ subreddit: "r/ADHDLiving",
160
+ kind: "banned",
161
+ evidenceLevel: "strong",
162
+ reason: "whole sub banned",
163
+ evidence: [
164
+ { type: "banned_page", url: "https://www.reddit.com/r/adhdliving" },
165
+ ],
166
+ });
167
+ client.createSubredditRestriction.mockClear();
168
+ await run("create", "--subreddit", "x1", "--kind", "banned", "--reason", "r", "--evidence-level", "weak", "--dry-run");
169
+ expect(client.createSubredditRestriction).not.toHaveBeenCalled();
170
+ expect(String(logSpy.mock.calls.at(-1)?.[0])).toContain('"dryRun": true');
171
+ });
172
+ it("写命令既无 --apply 也无 --dry-run → 拒绝并 exit(1),不发请求", async () => {
173
+ await expect(run("revoke", ID, "--reason", "lifted")).rejects.toThrow("process.exit(1)");
174
+ expect(client.revokeSubredditRestriction).not.toHaveBeenCalled();
175
+ });
176
+ it("confirm / revoke:--expected-version 不被吞", async () => {
177
+ await run("confirm", ID, "--expected-version", "3", "--apply");
178
+ expect(client.confirmSubredditRestriction).toHaveBeenCalledWith(ID, {
179
+ expectedVersion: 3,
180
+ });
181
+ await run("revoke", ID, "--reason", "lifted", "--expected-version", "4", "--apply");
182
+ expect(client.revokeSubredditRestriction).toHaveBeenCalledWith(ID, {
183
+ reason: "lifted",
184
+ expectedVersion: 4,
185
+ });
186
+ });
187
+ it("report:team 路径 + body(--account → socialAccountId,--scope 透传)", async () => {
188
+ await run("report", "-t", "team-1", "--subreddit", "adhdliving", "--account", ACC, "--kind", "private", "--evidence", '[{"type":"private_page"}]', "--evidence-level", "strong", "--reason", "private", "--expires-at", "2026-09-24T00:00:00.000Z", "--scope", "system", "--apply");
189
+ expect(client.reportSubredditRestriction).toHaveBeenCalledWith("team-1", {
190
+ subreddit: "adhdliving",
191
+ socialAccountId: ACC,
192
+ kind: "private",
193
+ evidence: [{ type: "private_page" }],
194
+ evidenceLevel: "strong",
195
+ reason: "private",
196
+ expiresAt: "2026-09-24T00:00:00.000Z",
197
+ scope: "system",
198
+ });
199
+ expect(gates.calls[0]).toMatchObject({
200
+ gates: [{ resource: "compliance", action: "create" }],
201
+ teamId: "team-1",
202
+ });
203
+ });
204
+ it("check:缺省 team 用 context;--account 可选", async () => {
205
+ await run("check", "--subreddit", "r/foo");
206
+ expect(client.checkSubredditRestriction).toHaveBeenCalledWith("ctx-team", {
207
+ subreddit: "r/foo",
208
+ account: undefined,
209
+ });
210
+ });
211
+ });
212
+ describe("本地参数校验(不发请求)", () => {
213
+ it("非法 state / kind / evidence / 时间 → 抛错", () => {
214
+ expect(() => buildListRestrictionsParams({ state: "active" })).toThrow(/--state/);
215
+ expect(() => buildListRestrictionsParams({ updatedSince: "yesterday" })).toThrow(/--updated-since/);
216
+ expect(() => buildCreateRestrictionBody({
217
+ subreddit: "x",
218
+ kind: "suspended",
219
+ reason: "r",
220
+ evidenceLevel: "strong",
221
+ })).toThrow(/--kind/);
222
+ expect(() => buildCreateRestrictionBody({
223
+ subreddit: "x",
224
+ kind: "banned",
225
+ reason: "r",
226
+ evidenceLevel: "strong",
227
+ state: "provisional",
228
+ })).toThrow(/--expires-at/);
229
+ expect(() => parseEvidenceOption("{}")).toThrow(/JSON array/);
230
+ expect(() => parseEvidenceOption('[{"url":"x"}]')).toThrow(/type/);
231
+ expect(() => buildReportBody({
232
+ subreddit: "x",
233
+ account: ACC,
234
+ kind: "banned",
235
+ evidence: "[]",
236
+ evidenceLevel: "strong",
237
+ reason: "r",
238
+ expiresAt: "2026-09-24T00:00:00.000Z",
239
+ })).toThrow(/at least one/);
240
+ });
241
+ it("正向对照:合法输入不抛", () => {
242
+ expect(parseEvidenceOption('[{"type":"t"}]')).toEqual([{ type: "t" }]);
243
+ expect(parseEvidenceOption(undefined)).toBeUndefined();
244
+ });
245
+ });
246
+ describe("hubRequest 把服务端 error.details 带进 HubHttpError", () => {
247
+ it("409 响应的 details 原样挂在错误上(否则 formatCliError 无数据可打)", async () => {
248
+ const { hubRequest } = await import("./client.js");
249
+ const prev = {
250
+ url: process.env["SOCIAL_HUB_API_URL"],
251
+ key: process.env["SOCIAL_HUB_API_KEY"],
252
+ };
253
+ process.env["SOCIAL_HUB_API_URL"] = "https://hub.test";
254
+ process.env["SOCIAL_HUB_API_KEY"] = "k";
255
+ const details = { code: "TARGET_BLOCKED", reason: "target_blocked" };
256
+ vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
257
+ ok: false,
258
+ status: 409,
259
+ text: async () => JSON.stringify({
260
+ error: { code: "CONFLICT", message: "x", details },
261
+ }),
262
+ }));
263
+ try {
264
+ const err = await hubRequest("/v1/x").catch((e) => e);
265
+ expect(err).toBeInstanceOf(HubHttpError);
266
+ expect(err.details).toEqual(details);
267
+ }
268
+ finally {
269
+ vi.unstubAllGlobals();
270
+ process.env["SOCIAL_HUB_API_URL"] = prev.url;
271
+ process.env["SOCIAL_HUB_API_KEY"] = prev.key;
272
+ if (prev.url === undefined)
273
+ delete process.env["SOCIAL_HUB_API_URL"];
274
+ if (prev.key === undefined)
275
+ delete process.env["SOCIAL_HUB_API_KEY"];
276
+ }
277
+ });
278
+ });
279
+ describe("受限拦截 409 的 stderr 输出", () => {
280
+ const details = {
281
+ code: "JOB_BLOCKED",
282
+ reason: "target_blocked",
283
+ source: "restriction",
284
+ id: ID,
285
+ kind: "banned",
286
+ subreddit: "adhdliving",
287
+ };
288
+ it("HubHttpError(带 details)→ 结构化 JSON,details 完整保留", () => {
289
+ const out = JSON.parse(formatCliError(new HubHttpError(409, "CONFLICT", "HTTP 409: job is blocked", details)));
290
+ expect(out.error).toMatchObject({
291
+ status: 409,
292
+ code: "CONFLICT",
293
+ message: "job is blocked",
294
+ details,
295
+ retryable: false,
296
+ });
297
+ });
298
+ it("SDK 形状的错误(status/code/details 字段)同样识别", () => {
299
+ const err = Object.assign(new Error("HTTP 409: blocked"), {
300
+ status: 409,
301
+ code: "CONFLICT",
302
+ details: { ...details, code: "TARGET_BLOCKED" },
303
+ });
304
+ expect(JSON.parse(formatCliError(err)).error.details.code).toBe("TARGET_BLOCKED");
305
+ });
306
+ it("其它 409(VERSION_MISMATCH)与非 409 文案不变", () => {
307
+ const other = new HubHttpError(409, "CONFLICT", "HTTP 409: stale", {
308
+ code: "VERSION_MISMATCH",
309
+ });
310
+ expect(formatCliError(other)).toBe("HTTP 409: stale");
311
+ const notFound = new HubHttpError(404, "NOT_FOUND", "HTTP 404: nope", details);
312
+ expect(formatCliError(notFound)).toBe("HTTP 404: nope");
313
+ });
314
+ });
315
+ //# sourceMappingURL=register-restrictions.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-restrictions.test.js","sourceRoot":"","sources":["../src/register-restrictions.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geoly-ai/social-hub-cli",
3
- "version": "0.3.44",
3
+ "version": "0.3.46",
4
4
  "type": "module",
5
5
  "description": "social-hub CLI for Social Ops Hub",
6
6
  "repository": {
@@ -22,8 +22,8 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "commander": "^12.1.0",
25
- "@geoly-ai/social-hub-authz": "0.0.20",
26
- "@geoly-ai/social-hub-sdk": "0.1.16"
25
+ "@geoly-ai/social-hub-sdk": "0.1.18",
26
+ "@geoly-ai/social-hub-authz": "0.0.21"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^22.10.2",
@@ -4,7 +4,7 @@
4
4
  "fileCount": 1657,
5
5
  "files": {
6
6
  "README.md": "5246d4ae2712e3e656b101041929183d7ae7f8ea487dacf5854187826ab7c335",
7
- "manifest.json": "333b605af67607055adc717b4a1da318a07fea8b0a0f30e4f1aa86ac3cf03384",
7
+ "manifest.json": "34af31216b03b5f3bfc6628839b86ba423e829e104073c16522e3f77929d6cdb",
8
8
  "reddit-answers-research/SKILL.md": "8936215363b5b222f4010fd18349cb2c56b473ce659bfeb21116ad5448faa640",
9
9
  "reddit-answers-research/references/browser-playbook.md": "a8f61d3d43ca93d46dadc8a092a515ddcd32df12fb1d08d334cce2e02709d915",
10
10
  "reddit-answers-research/references/report-template.md": "9aa9acf555ba8b2ca503bdbdf5750a5f53c66e19989bf6807fa6200c7317e306",
@@ -1635,24 +1635,24 @@
1635
1635
  "reddit-voc-volume/tests/test_archived_comments.py": "81fd6da65498f5cca81a10bae28bd6a9164efee1be4545dcab8d99984780a54e",
1636
1636
  "reddit-voc-volume/tests/test_links_fallback.py": "0b6756cbef92567c680fe56525f89ca791502d6173414f833b50e305ef56c65a",
1637
1637
  "social-hub-accounts/SKILL.md": "096f7969f572a0cecbf244d814ba43422b8b556a98b751dc1ece7f9066864e4d",
1638
- "social-hub-admin/SKILL.md": "f8f9ef070a1bbe62018bbbb362101deccafd4eb76c2dbd192321cc2226e15497",
1639
- "social-hub-calendar-directives/SKILL.md": "f544586b6474f07d072fc0666bd4969492dd6557794b2bb1495e497699d60a84",
1640
- "social-hub-calendar-jobs/SKILL.md": "7b300722376b6652f214ecb41ff77c36f65e28a13846b61c99c44042dfdde1f0",
1641
- "social-hub-cli/SKILL.md": "256b848caaf25399accc4290dd07fc4bf2f06a4e5de080d92d7c144ee51d22f9",
1638
+ "social-hub-admin/SKILL.md": "bce961e34268c736df5bc92e5b9edfdd98393d19dc2122f388112e4250e1102d",
1639
+ "social-hub-calendar-directives/SKILL.md": "27f82cd33c14bc6a51282f59560930f1600d48cb7df1a308505c457b2777ad6f",
1640
+ "social-hub-calendar-jobs/SKILL.md": "b79287c9e2dcb541518d5930c5fbf54fe81d17034a0406ca77cd2266afcbf943",
1641
+ "social-hub-cli/SKILL.md": "17211c1347c1e985aec132247c050ccaeb126234ef211d33ee873a8394caed2e",
1642
1642
  "social-hub-cli/evals/evals.json": "ba4851f8bce01c2ee4128ef224c6bcc284f5d8f2058c90bad0f7156596e5cf28",
1643
1643
  "social-hub-content-classification/SKILL.md": "400c1fa5940337a5930334ce809f0673e98e36f5279a0bfb1b3279da2a48d9dc",
1644
1644
  "social-hub-content-review/SKILL.md": "6d10a84ab56552fd953863b97978bddc0e925e3a2667d64a7a4c51e33a3ddb58",
1645
1645
  "social-hub-events-observability/SKILL.md": "f4c17487c64bb1f48381d8d60e97cfa160a94a0f120153b1c0e798a0c7ee7ebe",
1646
- "social-hub-graph-compliance/SKILL.md": "2270aeb321733f0318f75da4838ca1ca03996c6cac59e5a12f7152bd1ecdfa26",
1647
- "social-hub-intelligence/SKILL.md": "4c59d8b0f05668f97c40bf7e80b8cd9a992bb892846fedd2b15954f887923240",
1646
+ "social-hub-graph-compliance/SKILL.md": "438696ebb5eab671e30b59268767ce65eb5f1f2172378d422940fdb2b7b54f8a",
1647
+ "social-hub-intelligence/SKILL.md": "a9abe4578e55c3ea33c8375b9eeb70617be512430498bfadc26f91a37d5ac943",
1648
1648
  "social-hub-migration/SKILL.md": "1c13f231bb81ae8d242c834eec93a499830481804c66f6dba95922972f56f9ce",
1649
1649
  "social-hub-notifications/SKILL.md": "f80f4cf2f9ef0dbf60bd6e8bb4ad7e27dc0ff1fe59f4c7e79e49cc691ed4be6a",
1650
1650
  "social-hub-openclaw-context/SKILL.md": "8592c4d04987f54ead6f171b1b251233b776469ee6c12a7c97c9dc71df46e6ad",
1651
- "social-hub-ops-runtime/SKILL.md": "2fcf257f9c2a36236564fc9056605f2df1d706e0a6f290de68b05ac44890ae3e",
1651
+ "social-hub-ops-runtime/SKILL.md": "8935b9f725ba4bda9b0640c5b12348b3996c90622603cb084d878802a6933c66",
1652
1652
  "social-hub-post-review/SKILL.md": "20a0d4886d1fc70397f9f3e5e25672e69608b1cce56971e1b2401e817fbf3cb6",
1653
- "social-hub-posts/SKILL.md": "e4b501f521cb36448e3f49313e47855b93dc4f2ad7772e55309e6a680890c3ee",
1653
+ "social-hub-posts/SKILL.md": "b7bc5fa0253b56ce25d848e59425a994335f9a4f32451c87b72bc788ccb3d1ea",
1654
1654
  "social-hub-posts/evals/evals.json": "af4add3e4edcdf148c1594fbc1b33b90e13d144029aa0f8094b3cda05c95f1f1",
1655
- "social-hub-publishing/SKILL.md": "6fac6b40d1379a98c718a2acd1a1564352f56e13cc48593d9ff3f8d657d7df18",
1655
+ "social-hub-publishing/SKILL.md": "efb3e86a719b8483ed539ca54d1289c7928b8a81339a06cfc8ce2ce427866ce4",
1656
1656
  "social-hub-publishing/evals/evals.json": "e75719b796d83ec2dac8d0cac9dc38060faebfe88711d3255243a90afa0125ba",
1657
1657
  "social-hub-scrape/SKILL.md": "b6d35f950010cbbe31fa78ba5c32be834b509fef8ab1411bfae58bfec2992339",
1658
1658
  "social-hub-shared/SKILL.md": "a99afb2455df96906aeb36b59e8470c3e711904923cc10a539a40e667459f116",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 1,
3
3
  "package": "social-ops-hub",
4
- "cliVersion": "0.3.44",
4
+ "cliVersion": "0.3.46",
5
5
  "repository": "social-ops-hub",
6
6
  "publish": {
7
7
  "registry": "https://skill.sh",
@@ -54,6 +54,12 @@ social-hub members team-update-role -t <team-id> --user <userId> --role supervis
54
54
  social-hub members team-remove -t <team-id> --user <userId> --apply
55
55
  ```
56
56
 
57
+ `members list` / `users system-list` 的 `role` 字段口径(2026-09-23 起):**该用户所有 active 成员身份中按 ROLE_RANK
58
+ 取最高**(`internal < senior < supervisor < manager < client < admin`,与登录会话角色同一口径);**没有任何 active 成员身份时为
59
+ `null`**(该用户行照常返回,不再显示成 `internal`)。`users system-list` 里停用账号的 `role` 也是 `null`。
60
+ `members list` 的 `membershipId` / `status` / `joinedAt` 仍取自该用户最早加入的那条 active membership,与 `role` 不一定是同一条(无 active membership 的用户这三项是合成值:`membershipId = "system-<userId>"`、`joinedAt` = 账号创建时间);
61
+ 要看每个团队各自的角色用 `members teams-list --user <userId>`。`?role=` 过滤不会命中 `role: null` 的行。
62
+
57
63
  ## Users
58
64
 
59
65
  ```bash
@@ -155,6 +161,39 @@ social-hub brand-auth revoke --account <ref> --brand <brandKey> [-t <team-id>] [
155
161
 
156
162
  发布前三点 gate 语义见 [`../../docs/brand-mention-gate-operations.md`](../../docs/brand-mention-gate-operations.md)。
157
163
 
164
+ ## 板块限制(restrictions,系统级整板限制)
165
+
166
+ 整板 banned / private / quarantined / unavailable 的系统级限制。**一条限制会让全部 team 在该板块的写稿、排期、
167
+ 发布同时停下**,所以管理面只给 **admin**,并且只接受**不受限的登录**(`auth login` 的 CLI token 或网页 session):
168
+ API key、绑定 job/account 或带动作白名单的 CLI token **读写都 403**(权限资源 `systemSubredditRestriction`,
169
+ override 只能收紧,manager 拿不到任何一格)。
170
+
171
+ ```bash
172
+ # 读(系统级 admin 视图:list / fanout 必须显式 --system;不带 --system 走的是 team 只读路由,见下)
173
+ social-hub restrictions list --system [--state provisional,verification_overdue] [--updated-since <iso>] [--subreddit <name>] [--limit 50] [--offset 0]
174
+ social-hub restrictions get <id> # 含全量证据;list 只回证据摘要 evidenceSummary
175
+ social-hub restrictions fanout <id> --system [--version <n>] [--outcome blocked] [--team-id <uuid>]
176
+ # 全部 team 的扇出汇总计数 + complete + 回执明细。阶段 B(扇出 worker)上线前恒为空、complete:false —— 不是故障。
177
+
178
+ # 写:一律 --dry-run 预览 / --apply 执行
179
+ social-hub restrictions create --subreddit r/<name> --kind banned --evidence-level strong \
180
+ --reason "..." [--state provisional --expires-at <iso>] [--evidence '<json 数组>'] --apply
181
+ # 默认直接 confirmed;provisional 必须带 --expires-at。已有活跃限制 → 409 details.code=RESTRICTION_EXISTS
182
+ social-hub restrictions confirm <id> [--expected-version <n>] --apply # provisional / verification_overdue → confirmed
183
+ social-hub restrictions revoke <id> --reason "..." [--expected-version <n>] --apply # 唯一的解封路径
184
+ # 版本不符 → 409 VERSION_MISMATCH;状态不对 → 409 INVALID_STATE(都在 error.details.code)
185
+ ```
186
+
187
+ 状态机:`provisional`(已上报待复核,**照样拦**)→ 复核期限过了无人确认 → `verification_overdue`(**继续拦**);
188
+ admin `confirm` → `confirmed`;admin `revoke` → `revoked`(唯一不拦的状态,可再建新限制)。
189
+ revoke **不会**恢复已经被拦成 `blocked` 的 job,需要人重新排期。
190
+
191
+ **运营侧 agent 拉取状态(再去私信 admin)不走这里**:用 team 只读路由
192
+ `restrictions list -t <team-id>` / `restrictions fanout <id> -t <team-id>`(`compliance:read`,manager 可用,
193
+ 受限 token 也放行;fanout 只含本 team 的回执),见 `social-hub-graph-compliance`。
194
+ runner 侧的 `restrictions check` / `restrictions report` 同样见 `social-hub-graph-compliance`;
195
+ 执行链路上 409 `TARGET_BLOCKED` / `JOB_BLOCKED` 的识别与 agent 停止规则见 `social-hub-ops-runtime`。
196
+
158
197
  ## Agent teams
159
198
 
160
199
  ```bash
@@ -421,10 +421,23 @@ PATCH { "status": "succeeded", "permalink": "…", "executionToken": "<刚才那
421
421
  | `execution_token_not_expected` | 初次抢占时带了 token | 代码 bug:抢占不该带 token |
422
422
  | `expected_revision_not_applicable` | 终态回写带了 `expectedRevision` | 代码 bug:去掉它 |
423
423
  | `revision_stale` / `revision_unknown` | 抢占时的版本钉子对不上 | 同 preflight 同名 reason 的处置 |
424
+ | `target_blocked` | 目标板块/账号受限(见下) | 删 cron、安静退出。**不重试**、不回写终态 |
424
425
 
425
- ⚠️ 这些都走 **HTTP 409**、`code = "EXECUTION_NOT_ACQUIRED"`、`details.reason` 取上表的值。
426
+ ⚠️ 这些都走 **HTTP 409**、`details.reason` 取上表的值;前六个的 `code = "EXECUTION_NOT_ACQUIRED"`。
426
427
  **按 reason 编程分流,别拿人类文案做子串匹配。**
427
428
 
429
+ #### `target_blocked`:板块受限
430
+
431
+ 抢 `scheduled → running` 时,若目标板块已被系统级限制(整板 banned / private / quarantined / unavailable,
432
+ 含待复核的 provisional 与逾期未复核的 verification_overdue)或该账号在该板块有生效 sanction / bot_bouncer 名单,
433
+ 服务端拒绝抢占:HTTP 409,`error.code = "CONFLICT"`,
434
+ `details = { code: "TARGET_BLOCKED", reason: "target_blocked", source, id, kind?, subreddit }`。
435
+
436
+ - 识别看 `details.reason === "target_blocked"`(或 `details.code === "TARGET_BLOCKED"`),不看 message。
437
+ - 你**没有**拿到 token,这一轮什么都没发生:删本机 cron、退出。**不要重试**——只有 admin 撤销限制才会解除。
438
+ - 已经持有 token 的在途执行不受影响:终态回写照常放行(服务端只在抢占那一步判定)。
439
+ - 条目随后会由服务端以 `cancel`(cancel reason `target_blocked`)广播撤销,按常规 cancel 处置即可。
440
+
428
441
  ## ack 时机(本 source 局部改写通用规则)
429
442
 
430
443
  通用 notifications skill 说"副作用完成后才 ack"——这条仍然成立,但**这个 source 的副作用是
@@ -43,6 +43,20 @@ social-hub jobs retry -t <team-id> -i <jobId>
43
43
  social-hub jobs cancel -t <team-id> -i <jobId>
44
44
  ```
45
45
 
46
+ ## 🔴 板块受限:建/改排期被拒(409 `TARGET_BLOCKED`)
47
+
48
+ 目标板块受系统级限制,或该账号在该板块有生效 sanction / bot_bouncer 名单时,
49
+ `jobs create` / `jobs batch` / `jobs retry`(以及建计划/排期、改期、切 delivery mode)会被拒:
50
+ HTTP **409**,`error.code = "CONFLICT"`,`error.details = { code: "TARGET_BLOCKED", reason: "target_blocked",
51
+ source, id, kind?, subreddit }`。
52
+
53
+ - **按 `error.details.code` 识别,不要匹配 message。**
54
+ - **不要重试、不要换个 runAt 再建**:重试只会拿到同一个 409。换一个目标板块,或跳过。解除路径看 `details.source`:`restriction` 只有 admin `restrictions revoke`;`sanction`(团队/账号级)由有权限的人把该 sanction 改为 lifted;`blocklist` 由 admin 维护(不是 agent 的事,报告即可)。
55
+ - 已被拦下的 job 状态是 `blocked`(`lastError` 形如 `target_blocked:<source>:<id>`),是**永久终态**:
56
+ `jobs retry` 只接受 failed / cancelled,解封后要**新建** job。
57
+ - 执行侧(heartbeat / complete / fail / skip)的受限语义与停止规则见 `social-hub-ops-runtime`;
58
+ 自查与上报见 `social-hub-graph-compliance` 的 `restrictions check` / `restrictions report`。
59
+
46
60
  ## OpenClaw / Agent 运行时
47
61
 
48
62
  优先 ops 链路(见 `social-hub-ops-runtime`):
@@ -46,6 +46,8 @@ social-hub version --json
46
46
  | 帖子发布前审核/优化(post-review) | `social-hub-post-review` |
47
47
  | 事件 / dashboard / audit | `social-hub-events-observability` |
48
48
  | 图谱 / 合规 / 风控 | `social-hub-graph-compliance` |
49
+ | 板块限制拉取/自查/上报 restrictions list/fanout/check -t | `social-hub-graph-compliance` |
50
+ | 板块限制管理 restrictions create/revoke/--system | `social-hub-admin` |
49
51
  | settings / invites / members / api-keys | `social-hub-admin` |
50
52
  | brand-registry / brand-fit / trigger 注册表四眼 / brand-auth | `social-hub-admin` |
51
53
  | 飞书身份映射同步(告警 @ 谁) | `social-hub-admin` |
@@ -73,6 +73,48 @@ social-hub compliance rule-caches-upsert -t <team-id> --sub example -j '{
73
73
  }'
74
74
  ```
75
75
 
76
+ ## 板块限制:runner 自查与上报(`restrictions check` / `restrictions report`)
77
+
78
+ 整板限制(banned / private / quarantined / unavailable)存在系统级 `subreddit_restrictions`,
79
+ **只有 admin 能直接建 / 确认 / 撤销**(见 `social-hub-admin`)。agent / runner 用下面这些 **team 级**入口:
80
+
81
+ ```bash
82
+ # 拉取限制状态(compliance:read;manager 可用,受限 token 也放行)。Hub 不推送、不发群:
83
+ # agent 按 --updated-since 增量拉取,自行私信 admin。
84
+ social-hub restrictions list -t <team-id> [--state provisional,verification_overdue] [--updated-since <iso>] [--subreddit <name>] [--limit 50] [--offset 0]
85
+ # → {items,total,limit,offset,hasMore};每项带 state / expiresAt / lastVerifiedAt / createdBy / updatedAt / evidenceSummary(不含全量证据)
86
+ social-hub restrictions fanout <restriction-id> -t <team-id> [--version <n>] [--outcome blocked]
87
+ # → 只统计、只返回**本 team** 的回执(counts / receipts);complete 按全局扇出判断;
88
+ # 阶段 B(扇出 worker)上线前恒为空、complete:false —— 不是故障。
89
+ # 缺省 -t 用当前 context 的 team;两者都没有会报错(不会改走 admin 面)。--system 是 admin 专用的系统级视图。
90
+
91
+ # 动手前自查(compliance:read)。不带 --account 只判系统级;带上才同时判该账号的 sanction。
92
+ social-hub restrictions check -t <team-id> --subreddit <name> [--account <accountId>]
93
+ # → {"allowed":true,...} 或 {"allowed":false,"scope":"system|account","source":"restriction|blocklist|sanction",
94
+ # "id":"...","kind":"...","state":"provisional|confirmed|verification_overdue|null","expiresAt":...}
95
+ # allowed:false = 本轮跳过这个目标,不要重试。
96
+
97
+ # 发现受限时上报(compliance:create;写命令,需 --apply,可先 --dry-run)
98
+ social-hub restrictions report -t <team-id> --subreddit <name> --account <accountId> \
99
+ --kind banned --evidence '[{"type":"banned_page","url":"https://www.reddit.com/r/<name>","observedAt":"<iso>"}]' \
100
+ --evidence-level strong --reason "banner: r/<name> has been banned" \
101
+ --expires-at <复核期限 iso> [--scope system] --apply
102
+ ```
103
+
104
+ `report` 的落点(第一版刻意保守,**服务端封禁页解析器在阶段 C 才做**,现在信的是你的声明):
105
+
106
+ | 你传的 | 实际写入 | 响应 |
107
+ | -------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ |
108
+ | `--scope system` + `--evidence-level strong` | 系统级 **provisional**(立即对全部 team 生效,要求 manager/admin;supervisor → 403) | `target:"system"`,`created:false` = 板块已有限制,原样返回现行行摘要 |
109
+ | `--scope system` + `weak` | 降级为该 team+账号的 sanction | `target:"account"`,`downgraded:true`,`downgradeReason:"weak_evidence"` |
110
+ | 缺省 / `--scope account` | 该 team+账号的 sanction(banned → `banned`,其余 → `restricted`) | `target:"account"`,`downgraded:false` |
111
+
112
+ - 证据至少一条、`--expires-at` 必填(复核期限)。provisional 过期**不会**解封,而是转 `verification_overdue` 继续拦,
113
+ 只有 admin `restrictions revoke` 才解封。
114
+ - 落 sanction 时**不会**把复核期限写成 sanction 过期(那会自动解封);期限记在 reason 前缀和事件里。
115
+ - 只有「整板对所有人都不可用」才用 `--scope system`。账号自己被 ban(「you've been banned from r/X」)用默认 account。
116
+ - 执行链路上被拦(409 `TARGET_BLOCKED` / `JOB_BLOCKED`)的识别与停止规则见 `social-hub-ops-runtime`。
117
+
76
118
  ## 系统级板块黑名单(Bot Bouncer 等)
77
119
 
78
120
  与 **per-account** `sanctions-*` 不同:名单为 **系统级**(`/v1/system/subreddit-blocklists`),全 team 共享,表示 Reddit 平台客观事实(如 bot-bouncer 安装 sub),无需 `-t`。