@geoly-ai/social-hub-cli 0.3.12 → 0.3.14

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.
@@ -0,0 +1,43 @@
1
+ /**
2
+ * `social-hub content review` 的便利参数合并(作者关系轴)。
3
+ *
4
+ * 抽成纯函数是为了单测:合并逻辑有两条容易写错的边界 ——
5
+ * (1) `resolveJsonArg<unknown>` 的返回只是**类型断言**,运行时 body 仍可能是
6
+ * `null` / 数组 / 字符串,直接展开会静默丢数据;
7
+ * (2) `--thread-permalink` 要写进嵌套的 `thread` 对象,但 `thread.subreddit` / `postTitle`
8
+ * 是服务端必填 —— body 里没有 thread 时**本地报错**,绝不凭空造一个只有 permalink 的
9
+ * thread(那只会换来一个更晚、更难懂的 400)。
10
+ *
11
+ * ⚠️ `authorRelationHint` 只是**非权威提示**:服务端按账号 handle × permalink × 帖子快照
12
+ * 派生权威 `authorRelation`,证据永远覆盖 hint。本模块不做任何「本地推断身份」的事。
13
+ */
14
+ import type { ContentReviewAuthorRelationHint } from "@geoly-ai/social-hub-sdk";
15
+ /** 参数级错误(CLI 侧本地拒绝,不发请求)。 */
16
+ export declare class ContentReviewArgError extends Error {
17
+ constructor(message: string);
18
+ }
19
+ export declare const AUTHOR_RELATION_HINT_VALUES: readonly ContentReviewAuthorRelationHint[];
20
+ /** 与 contracts `contentReviewThreadSchema.threadPermalink` 的上限一致。 */
21
+ export declare const MAX_THREAD_PERMALINK_LENGTH = 2048;
22
+ /**
23
+ * 解析 `--author-relation-hint`。只收精确的枚举值:大小写/空白一律不猜 ——
24
+ * 猜错会把「声称是作者」和「声称不是」搞反,而前者失配是 422 硬失败。
25
+ */
26
+ export declare function parseAuthorRelationHint(raw: string): ContentReviewAuthorRelationHint;
27
+ export type ContentReviewFlagInput = {
28
+ threadPermalink?: string;
29
+ authorRelationHint?: string;
30
+ };
31
+ /**
32
+ * 把便利参数合并进 body(不原地修改入参;便利参数覆盖 `-j` 里的同名值,
33
+ * 与既有 `--brand-context` 的覆盖语义一致)。
34
+ */
35
+ export declare function applyContentReviewFlags(body: unknown, flags: ContentReviewFlagInput): Record<string, unknown>;
36
+ /**
37
+ * 422 `AUTHOR_RELATION_MISMATCH` 的可行动提示。
38
+ *
39
+ * 刻意**不打印服务端 details**:里面含规范化 handle / 帖子作者 / permalink。
40
+ * 也刻意不建议「换一个 permalink 重试」—— 那是 anchor shopping,正是这条 422 要防的。
41
+ */
42
+ export declare const AUTHOR_RELATION_MISMATCH_HINT: string;
43
+ //# sourceMappingURL=content-review-args.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-review-args.d.ts","sourceRoot":"","sources":["../../src/lib/content-review-args.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,0BAA0B,CAAC;AAEhF,6BAA6B;AAC7B,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED,eAAO,MAAM,2BAA2B,EAAE,SAAS,+BAA+B,EACxD,CAAC;AAE3B,qEAAqE;AACrE,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAMhD;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,MAAM,GACV,+BAA+B,CASjC;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,sBAAsB,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAsCzB;AAED;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,QAIiC,CAAC"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * `social-hub content review` 的便利参数合并(作者关系轴)。
3
+ *
4
+ * 抽成纯函数是为了单测:合并逻辑有两条容易写错的边界 ——
5
+ * (1) `resolveJsonArg<unknown>` 的返回只是**类型断言**,运行时 body 仍可能是
6
+ * `null` / 数组 / 字符串,直接展开会静默丢数据;
7
+ * (2) `--thread-permalink` 要写进嵌套的 `thread` 对象,但 `thread.subreddit` / `postTitle`
8
+ * 是服务端必填 —— body 里没有 thread 时**本地报错**,绝不凭空造一个只有 permalink 的
9
+ * thread(那只会换来一个更晚、更难懂的 400)。
10
+ *
11
+ * ⚠️ `authorRelationHint` 只是**非权威提示**:服务端按账号 handle × permalink × 帖子快照
12
+ * 派生权威 `authorRelation`,证据永远覆盖 hint。本模块不做任何「本地推断身份」的事。
13
+ */
14
+ /** 参数级错误(CLI 侧本地拒绝,不发请求)。 */
15
+ export class ContentReviewArgError extends Error {
16
+ constructor(message) {
17
+ super(message);
18
+ this.name = "ContentReviewArgError";
19
+ }
20
+ }
21
+ export const AUTHOR_RELATION_HINT_VALUES = ["post_author", "other"];
22
+ /** 与 contracts `contentReviewThreadSchema.threadPermalink` 的上限一致。 */
23
+ export const MAX_THREAD_PERMALINK_LENGTH = 2048;
24
+ function isPlainRecord(value) {
25
+ return typeof value === "object" && value !== null && !Array.isArray(value);
26
+ }
27
+ /**
28
+ * 解析 `--author-relation-hint`。只收精确的枚举值:大小写/空白一律不猜 ——
29
+ * 猜错会把「声称是作者」和「声称不是」搞反,而前者失配是 422 硬失败。
30
+ */
31
+ export function parseAuthorRelationHint(raw) {
32
+ if (AUTHOR_RELATION_HINT_VALUES.includes(raw) === false) {
33
+ throw new ContentReviewArgError(`--author-relation-hint 只接受 ${AUTHOR_RELATION_HINT_VALUES.join(" | ")}(区分大小写),收到: ${JSON.stringify(raw)}`);
34
+ }
35
+ return raw;
36
+ }
37
+ /**
38
+ * 把便利参数合并进 body(不原地修改入参;便利参数覆盖 `-j` 里的同名值,
39
+ * 与既有 `--brand-context` 的覆盖语义一致)。
40
+ */
41
+ export function applyContentReviewFlags(body, flags) {
42
+ if (!isPlainRecord(body)) {
43
+ throw new ContentReviewArgError(`content review 的 JSON 入参必须是一个对象,收到: ${Array.isArray(body) ? "array" : String(body === null ? "null" : typeof body)}`);
44
+ }
45
+ const next = { ...body };
46
+ if (flags.authorRelationHint !== undefined) {
47
+ next["authorRelationHint"] = parseAuthorRelationHint(flags.authorRelationHint);
48
+ }
49
+ if (flags.threadPermalink !== undefined) {
50
+ // 空串/纯空白不是「不给」:它会覆盖掉 -j 里本来有效的 permalink,而服务端
51
+ // `z.string().trim().min(1)` 只会回一个 400。本地拒绝,给出能直接照做的提示。
52
+ const permalink = flags.threadPermalink.trim();
53
+ if (permalink.length === 0) {
54
+ throw new ContentReviewArgError("--thread-permalink 不能为空:要么给出所审原帖的真实 permalink,要么整个省略该参数(省略=按 unknown 基线审核,不受惩罚)。");
55
+ }
56
+ if (permalink.length > MAX_THREAD_PERMALINK_LENGTH) {
57
+ throw new ContentReviewArgError(`--thread-permalink 超过服务端上限 ${MAX_THREAD_PERMALINK_LENGTH} 字符(收到 ${permalink.length})。`);
58
+ }
59
+ const thread = next["thread"];
60
+ if (!isPlainRecord(thread)) {
61
+ throw new ContentReviewArgError("--thread-permalink 需要 JSON 入参里已有 thread 对象(thread.subreddit / thread.postTitle 是必填,CLI 不会替你编造)。");
62
+ }
63
+ next["thread"] = { ...thread, threadPermalink: permalink };
64
+ }
65
+ return next;
66
+ }
67
+ /**
68
+ * 422 `AUTHOR_RELATION_MISMATCH` 的可行动提示。
69
+ *
70
+ * 刻意**不打印服务端 details**:里面含规范化 handle / 帖子作者 / permalink。
71
+ * 也刻意不建议「换一个 permalink 重试」—— 那是 anchor shopping,正是这条 422 要防的。
72
+ */
73
+ export const AUTHOR_RELATION_MISMATCH_HINT = "提示:你显式声称 --author-relation-hint post_author,但 Hub 侧帖子快照显示本次发布账号不是该帖作者。\n" +
74
+ " 这是硬失败(本次一次 LLM 都没调用,服务端已落安全审计),重试不会自愈,换一个能通过校验的 permalink 更不行。\n" +
75
+ " 请核对:发布账号是否搞错了 / thread.threadPermalink 是否确实指向本次要回复的那篇帖子。\n" +
76
+ " 如果你本来就不是原帖作者:去掉 --author-relation-hint 重跑即可(不声称=正常按证据派生,不影响评分标准)。";
77
+ //# sourceMappingURL=content-review-args.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-review-args.js","sourceRoot":"","sources":["../../src/lib/content-review-args.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,6BAA6B;AAC7B,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AAED,MAAM,CAAC,MAAM,2BAA2B,GACtC,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;AAE3B,qEAAqE;AACrE,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,CAAC;AAEhD,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CACrC,GAAW;IAEX,IACG,2BAAiD,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,KAAK,EAC1E,CAAC;QACD,MAAM,IAAI,qBAAqB,CAC7B,8BAA8B,2BAA2B,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAC1G,CAAC;IACJ,CAAC;IACD,OAAO,GAAsC,CAAC;AAChD,CAAC;AAOD;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CACrC,IAAa,EACb,KAA6B;IAE7B,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,qBAAqB,CAC7B,uCAAuC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,EAAE,CACtH,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAA4B,EAAE,GAAG,IAAI,EAAE,CAAC;IAElD,IAAI,KAAK,CAAC,kBAAkB,KAAK,SAAS,EAAE,CAAC;QAC3C,IAAI,CAAC,oBAAoB,CAAC,GAAG,uBAAuB,CAClD,KAAK,CAAC,kBAAkB,CACzB,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QACxC,8CAA8C;QAC9C,wDAAwD;QACxD,MAAM,SAAS,GAAG,KAAK,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;QAC/C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,qBAAqB,CAC7B,kFAAkF,CACnF,CAAC;QACJ,CAAC;QACD,IAAI,SAAS,CAAC,MAAM,GAAG,2BAA2B,EAAE,CAAC;YACnD,MAAM,IAAI,qBAAqB,CAC7B,8BAA8B,2BAA2B,UAAU,SAAS,CAAC,MAAM,IAAI,CACxF,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9B,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,qBAAqB,CAC7B,iGAAiG,CAClG,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,CAAC;IAC7D,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,6BAA6B,GACxC,0EAA0E;IAC1E,wEAAwE;IACxE,kEAAkE;IAClE,yEAAyE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=content-review-args.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-review-args.test.d.ts","sourceRoot":"","sources":["../../src/lib/content-review-args.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * `content review` 便利参数合并的纯函数单测。
3
+ *
4
+ * 重点不是「flag 能写进 body」,而是几条 fail-closed 边界:
5
+ * - JSON 入参运行时不是对象(null/数组/字符串)必须本地报错,不能静默展开成空对象;
6
+ * - `--thread-permalink` 不会凭空造 thread(subreddit/postTitle 是服务端必填);
7
+ * - hint 只收精确枚举值 —— 猜大小写会把「声称是作者」和「声称不是」搞反,而前者失配是
8
+ * 422 硬失败。
9
+ */
10
+ import { describe, expect, it } from "vitest";
11
+ import { applyContentReviewFlags, AUTHOR_RELATION_MISMATCH_HINT, ContentReviewArgError, MAX_THREAD_PERMALINK_LENGTH, parseAuthorRelationHint, } from "./content-review-args.js";
12
+ const baseBody = {
13
+ draft: "Update: turned out to be the PSU cable.",
14
+ commentType: "top_level",
15
+ thread: {
16
+ subreddit: "buildapc",
17
+ postTitle: "PC reboots under load",
18
+ topComments: ["check the PSU"],
19
+ },
20
+ };
21
+ describe("applyContentReviewFlags", () => {
22
+ it("无 flag 时内容不变,且不原地修改入参", () => {
23
+ const input = structuredClone(baseBody);
24
+ const out = applyContentReviewFlags(input, {});
25
+ expect(out).toEqual(baseBody);
26
+ expect(out).not.toBe(input);
27
+ expect(input).toEqual(baseBody);
28
+ });
29
+ it("--thread-permalink 写进 thread 并保留 thread 其他字段", () => {
30
+ const input = structuredClone(baseBody);
31
+ const out = applyContentReviewFlags(input, {
32
+ threadPermalink: "https://www.reddit.com/r/buildapc/comments/abc/x/",
33
+ });
34
+ expect(out["thread"]).toEqual({
35
+ subreddit: "buildapc",
36
+ postTitle: "PC reboots under load",
37
+ topComments: ["check the PSU"],
38
+ threadPermalink: "https://www.reddit.com/r/buildapc/comments/abc/x/",
39
+ });
40
+ // 原 body.thread 未被就地改写。
41
+ expect(input.thread).not.toHaveProperty("threadPermalink");
42
+ });
43
+ it("flag 覆盖 -j 里的同名值(与 --brand-context 覆盖语义一致)", () => {
44
+ const out = applyContentReviewFlags({
45
+ ...structuredClone(baseBody),
46
+ authorRelationHint: "other",
47
+ thread: { ...baseBody.thread, threadPermalink: "https://old/" },
48
+ }, {
49
+ threadPermalink: "https://new/",
50
+ authorRelationHint: "post_author",
51
+ });
52
+ expect(out["authorRelationHint"]).toBe("post_author");
53
+ expect(out["thread"]["threadPermalink"]).toBe("https://new/");
54
+ });
55
+ it("hint 可以单独给(不强制配 permalink;服务端负责派生 unknown + 记 unverified claim)", () => {
56
+ const out = applyContentReviewFlags(structuredClone(baseBody), {
57
+ authorRelationHint: "post_author",
58
+ });
59
+ expect(out["authorRelationHint"]).toBe("post_author");
60
+ expect(out["thread"]["threadPermalink"]).toBe(undefined);
61
+ });
62
+ it.each([
63
+ ["null", null],
64
+ ["数组", [1, 2]],
65
+ ["字符串", "not-json-object"],
66
+ ["数字", 42],
67
+ ])("根 JSON 是 %s → 本地参数错误", (_label, raw) => {
68
+ expect(() => applyContentReviewFlags(raw, {})).toThrow(ContentReviewArgError);
69
+ });
70
+ it.each([
71
+ ["缺失", {}],
72
+ ["null", { thread: null }],
73
+ ["数组", { thread: [] }],
74
+ ["字符串", { thread: "buildapc" }],
75
+ ])("thread %s 时 --thread-permalink 报错而非凭空造 thread", (_l, body) => {
76
+ expect(() => applyContentReviewFlags(body, { threadPermalink: "https://x/" })).toThrow(/thread 对象/);
77
+ });
78
+ it.each([
79
+ ["空串", ""],
80
+ ["纯空白", " "],
81
+ ["制表/换行", "\t\n"],
82
+ ])("--thread-permalink 是 %s → 报错(否则会静默覆盖掉 -j 里的有效 permalink,把可核验审核降级成 unknown)", (_l, raw) => {
83
+ expect(() => applyContentReviewFlags(structuredClone(baseBody), {
84
+ threadPermalink: raw,
85
+ })).toThrow(/不能为空/);
86
+ });
87
+ it("permalink 两端空白被 trim 后写入", () => {
88
+ const out = applyContentReviewFlags(structuredClone(baseBody), {
89
+ threadPermalink: " https://x/ ",
90
+ });
91
+ expect(out["thread"]["threadPermalink"]).toBe("https://x/");
92
+ });
93
+ it("permalink 超过服务端上限 → 本地报错", () => {
94
+ expect(() => applyContentReviewFlags(structuredClone(baseBody), {
95
+ threadPermalink: "a".repeat(MAX_THREAD_PERMALINK_LENGTH + 1),
96
+ })).toThrow(new RegExp(String(MAX_THREAD_PERMALINK_LENGTH)));
97
+ // 边界值本身放行。
98
+ expect(() => applyContentReviewFlags(structuredClone(baseBody), {
99
+ threadPermalink: "a".repeat(MAX_THREAD_PERMALINK_LENGTH),
100
+ })).not.toThrow();
101
+ });
102
+ it("thread 缺失但只给 hint 时不报错(hint 不依赖 thread 结构)", () => {
103
+ const out = applyContentReviewFlags({ draft: "x" }, { authorRelationHint: "other" });
104
+ expect(out["authorRelationHint"]).toBe("other");
105
+ });
106
+ });
107
+ describe("parseAuthorRelationHint", () => {
108
+ it("接受两个精确枚举值", () => {
109
+ expect(parseAuthorRelationHint("post_author")).toBe("post_author");
110
+ expect(parseAuthorRelationHint("other")).toBe("other");
111
+ });
112
+ it.each(["POST_AUTHOR", "Post_Author", " post_author", "op", "unknown", ""])("拒绝 %o(不猜大小写/空白/别名)", (raw) => {
113
+ expect(() => parseAuthorRelationHint(raw)).toThrow(ContentReviewArgError);
114
+ });
115
+ it("unknown 不是可声明的 hint(它是派生结果)", () => {
116
+ expect(() => parseAuthorRelationHint("unknown")).toThrow(/post_author \| other/);
117
+ });
118
+ });
119
+ describe("AUTHOR_RELATION_MISMATCH_HINT", () => {
120
+ it("说明是硬失败、不建议重试,且不诱导换 permalink(anchor shopping)", () => {
121
+ expect(AUTHOR_RELATION_MISMATCH_HINT).toContain("硬失败");
122
+ expect(AUTHOR_RELATION_MISMATCH_HINT).toContain("重试不会自愈");
123
+ expect(AUTHOR_RELATION_MISMATCH_HINT).toMatch(/换一个能通过校验的 permalink 更不行/);
124
+ });
125
+ });
126
+ //# sourceMappingURL=content-review-args.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-review-args.test.js","sourceRoot":"","sources":["../../src/lib/content-review-args.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"permissions-gates-admin.d.ts","sourceRoot":"","sources":["../src/permissions-gates-admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,uDAAuD;AACvD,eAAO,MAAM,sBAAsB,EAAE,iBAAiB,EAqIrD,CAAC"}
1
+ {"version":3,"file":"permissions-gates-admin.d.ts","sourceRoot":"","sources":["../src/permissions-gates-admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,uDAAuD;AACvD,eAAO,MAAM,sBAAsB,EAAE,iBAAiB,EA8IrD,CAAC"}
@@ -118,11 +118,20 @@ export const ADMIN_PERMISSION_GATES = [
118
118
  resource: "apiKey",
119
119
  action: "update",
120
120
  },
121
+ {
122
+ // 读矩阵也是 admin only —— 端点返回的是**全角色 × 全资源 × 全动作**矩阵 +
123
+ // 字段可见性矩阵(治理配置全貌,不是「我有什么权限」的自查面)。登记它是为了让
124
+ // `auth explain` 本地就说明白,而不是让人打过去吃一个 403。
125
+ command: "permissions matrix",
126
+ resource: "permissionMatrix",
127
+ action: "read",
128
+ note: "admin only;且只认不受限的交互式登录(拒 api_key / 服务 token / 受限 CLI token)",
129
+ },
121
130
  {
122
131
  command: "permissions-update",
123
132
  resource: "permissionMatrix",
124
133
  action: "update",
125
- note: "admin only",
134
+ note: "admin only;且只认不受限的交互式登录(拒 api_key / 服务 token / 受限 CLI token)",
126
135
  },
127
136
  {
128
137
  // 🔴 **update 不是笔误**:能看见「谁在何时改过哪一格权限」是治理信息
@@ -1 +1 @@
1
- {"version":3,"file":"permissions-gates-admin.js","sourceRoot":"","sources":["../src/permissions-gates-admin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"permissions-gates-admin.js","sourceRoot":"","sources":["../src/permissions-gates-admin.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,17 @@ describe("admin permission gates", () => {
9
9
  action: "update",
10
10
  }));
11
11
  });
12
+ // 读矩阵此前**没有登记** —— `auth explain` 于是不会提示它是 admin only,只能等
13
+ // 服务端 403。登记后本地预检就能说明白。
14
+ it("maps `permissions matrix` to permissionMatrix:read (admin only)", () => {
15
+ const gate = findGateForCommand("permissions matrix");
16
+ expect(gate).toEqual(expect.objectContaining({
17
+ resource: "permissionMatrix",
18
+ action: "read",
19
+ }));
20
+ // 读写两条 gate 的 action 必须不同:read/update 的不对称是有意设计。
21
+ expect(findGateForCommand("permissions-update")?.action).toBe("update");
22
+ });
12
23
  it("gates `permissions revisions` on permissionMatrix:UPDATE (not read)", () => {
13
24
  // 回归护栏:能看见「谁在何时把哪一格从 deny 改成 allow」是治理信息(还露出 actor
14
25
  // user id),门槛必须等同「能改权限」。降成 read 会让矩阵只读角色拿到轨迹。
@@ -1 +1 @@
1
- {"version":3,"file":"permissions-gates-admin.test.js","sourceRoot":"","sources":["../src/permissions-gates-admin.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE5E,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,IAAI,GAAG,kBAAkB,CAAC,oBAAoB,CAAC,CAAC;QACtD,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAClB,MAAM,CAAC,gBAAgB,CAAC;YACtB,QAAQ,EAAE,kBAAkB;YAC5B,MAAM,EAAE,QAAQ;SACjB,CAAC,CACH,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,mDAAmD;QACnD,8CAA8C;QAC9C,MAAM,IAAI,GAAG,kBAAkB,CAAC,uBAAuB,CAAC,CAAC;QACzD,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAClB,MAAM,CAAC,gBAAgB,CAAC;YACtB,QAAQ,EAAE,kBAAkB;YAC5B,MAAM,EAAE,QAAQ;SACjB,CAAC,CACH,CAAC;QACF,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,QAAQ,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC9D,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;QAC7C,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;QAChD,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;QACjD,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;QACjD,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,6BAA6B,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,MAAM,YAAY,GAAG,oBAAoB,CAAC,MAAM,CAC9C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,gBAAgB,CACtC,CAAC;QACF,MAAM,CAAC,YAAY,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"permissions-gates-admin.test.js","sourceRoot":"","sources":["../src/permissions-gates-admin.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE5E,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,IAAI,GAAG,kBAAkB,CAAC,oBAAoB,CAAC,CAAC;QACtD,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAClB,MAAM,CAAC,gBAAgB,CAAC;YACtB,QAAQ,EAAE,kBAAkB;YAC5B,MAAM,EAAE,QAAQ;SACjB,CAAC,CACH,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,0DAA0D;IAC1D,wBAAwB;IACxB,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,MAAM,IAAI,GAAG,kBAAkB,CAAC,oBAAoB,CAAC,CAAC;QACtD,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAClB,MAAM,CAAC,gBAAgB,CAAC;YACtB,QAAQ,EAAE,kBAAkB;YAC5B,MAAM,EAAE,MAAM;SACf,CAAC,CACH,CAAC;QACF,iDAAiD;QACjD,MAAM,CAAC,kBAAkB,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,mDAAmD;QACnD,8CAA8C;QAC9C,MAAM,IAAI,GAAG,kBAAkB,CAAC,uBAAuB,CAAC,CAAC;QACzD,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAClB,MAAM,CAAC,gBAAgB,CAAC;YACtB,QAAQ,EAAE,kBAAkB;YAC5B,MAAM,EAAE,QAAQ;SACjB,CAAC,CACH,CAAC;QACF,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,QAAQ,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC9D,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;QAC7C,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;QAChD,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;QACjD,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;QACjD,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,6BAA6B,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,MAAM,YAAY,GAAG,oBAAoB,CAAC,MAAM,CAC9C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,gBAAgB,CACtC,CAAC;QACF,MAAM,CAAC,YAAY,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geoly-ai/social-hub-cli",
3
- "version": "0.3.12",
3
+ "version": "0.3.14",
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.12",
26
- "@geoly-ai/social-hub-sdk": "0.0.51"
25
+ "@geoly-ai/social-hub-authz": "0.0.13",
26
+ "@geoly-ai/social-hub-sdk": "0.0.52"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^22.10.2",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 1,
3
3
  "package": "social-ops-hub",
4
- "cliVersion": "0.3.12",
4
+ "cliVersion": "0.3.14",
5
5
  "repository": "social-ops-hub",
6
6
  "publish": {
7
7
  "registry": "https://skill.sh",
@@ -359,6 +359,12 @@ ExplicitStructureSpec:
359
359
  - `title` + `body` 的完整帖子调用 `social-hub content post-review`
360
360
  - 仅标题候选可作为 `CandidateOnly` 返回,但必须标注不可直接发布、`ContentReviewStatus: NotRun`、`ReadyForUse: No`;一旦配正文,必须按完整帖子重新审核
361
361
  - 评论/回复调用 `social-hub content review`,并传入 subreddit、帖子和父评论上下文
362
+ - 知道所回帖子的确切 permalink 时带上 `thread.threadPermalink`,不知道就省略。作者关系
363
+ (`authorRelation`)由 Hub 自行派生:请求侧的 `authorRelationHint` 只是**非权威提示**、
364
+ 不是身份声明,虚假声称 `post_author` 是 **422 硬失败**而非降级;`post_author`
365
+ **不加分**,`Update ...` 开头既不扣分也不加分。**不得**为了改变评分而添加 permalink、
366
+ hint 或 update 式开头。完整规则与需记录的派生值见
367
+ `../reddit-strategy-shared/references/content-review-gate.md` §3.05
362
368
  - 先选择审核模式:最终发布账号未确定时省略 `accountId`,使用 ContentOnly;已确定时传真实账号,使用 AccountBound。Hub 要求显式品牌关系时,从已批准的策略绑定传 `brandContext`,不得猜测;`stage` 不确定时省略,不传任意值
363
369
  - 首次请求使用 `rewrite: true, attempt: 1`
364
370
  - `completed/revise` 时,应用 Hub 的 `optimized` 或 `dimensions.issues`,保留事实和内容功能后,用改后的**完整文本** `attempt: 2` 重审
@@ -80,6 +80,15 @@ C-B/C-NB, and rows that were previously low risk.
80
80
  arbitrary `stage` value.
81
81
  3. Send `rewrite: true, attempt: 1`. Inspect `executionStatus` before
82
82
  `verdict`.
83
+ For a comment/reply, include `thread.threadPermalink` when you know the
84
+ permalink of the exact post being replied to; omit it otherwise. Hub uses it
85
+ to derive `authorRelation` itself — the request-side `authorRelationHint` is
86
+ a non-authoritative hint, not an identity claim, and a false `post_author`
87
+ claim is a hard 422, not a downgrade. `post_author` grants **no score bonus**
88
+ and an `Update ...` opening is neither penalised nor rewarded. Never add a
89
+ permalink, a hint, or an update-style opening to move the score. Full rules
90
+ and the derived values to record:
91
+ `../reddit-strategy-shared/references/content-review-gate.md` §3.05.
83
92
  4. On `completed/revise`, apply each listed issue without changing approved
84
93
  facts, SKU bindings, disclosures, or the post/comment function. **Do not
85
94
  adopt the returned `optimized` draft wholesale if it changes any protected
@@ -7,7 +7,7 @@
7
7
  "contentSha256": "501408ce9cd1be969ac91c672523cde18bdbb6118836e032539ef30af98f2711"
8
8
  },
9
9
  "reddit-strategy-shared": {
10
- "contentSha256": "04c91d348907fded8cda969af973056dfff70ecfd96011e6dea01c3462f23ab0"
10
+ "contentSha256": "9ffdae74106e1b93b265bd89f6b0b1674f2b37a9dafd43c4a50129ac75f85862"
11
11
  },
12
12
  "reddit-phase-research": {
13
13
  "contentSha256": "e1818757ae92019448a454b956cb38c9cabc7e3c4762da4a6e4a765d687b8dc6"
@@ -19,10 +19,10 @@
19
19
  "contentSha256": "841d9e9928126553af0211da7d74a69af92076fc00d800dff2243075359b2157"
20
20
  },
21
21
  "reddit-content-writing": {
22
- "contentSha256": "262d8cd5bb4709452793e2711e9ea07ff73e5d46ee2e507d6723f364b3cbed01"
22
+ "contentSha256": "9b2f4cda552d5f76b3ab289504f84db599245e7245297a9d974cc55143872d8d"
23
23
  },
24
24
  "reddit-content-rewrite": {
25
- "contentSha256": "fd96108f6436804e80cb4054e5904df9ef59a4c68cec4efd410f1247bbee4735"
25
+ "contentSha256": "3e7df833877fc81a46d66ab7c827fac386a1413862724179e87570d773ab8935"
26
26
  },
27
27
  "reddit-delivery-check": {
28
28
  "contentSha256": "234d7fd1d8f2db68444674c20418c836c1b2488305b20399114dec89a0de32fc"
@@ -94,6 +94,11 @@ it is delivered to the next skill or presented as ready:
94
94
  - post (`title` + `body`) -> `social-hub content post-review`
95
95
  - comment/reply -> `social-hub content review`
96
96
 
97
+ Author-relation semantics (`authorRelation` / `authorRelationHint` /
98
+ `threadPermalink`) are defined in one place only —
99
+ `references/content-review-gate.md` §3.05. It is a Hub-derived identity check,
100
+ not a scoring option; do not restate or reinterpret it elsewhere.
101
+
97
102
  The producer must apply Hub `revise` feedback and submit the changed draft
98
103
  again within the Hub attempt limit. `executionStatus=completed` with
99
104
  `verdict=pass` is necessary for content review.
@@ -125,13 +125,18 @@ social-hub content review -t <team-id> -j '{
125
125
  "postBody": "<post body or concise factual summary>",
126
126
  "parentComment": "<required for a reply>",
127
127
  "topComments": ["<relevant top comments>"],
128
- "subredditToneHint": "<tone>"
128
+ "subredditToneHint": "<tone>",
129
+ "threadPermalink": "<permalink of the exact post being reviewed, when known>"
129
130
  },
130
131
  "rewrite": true,
131
132
  "attempt": 1
132
133
  }'
133
134
  ```
134
135
 
136
+ `threadPermalink` (also settable as `--thread-permalink`) must be the permalink of
137
+ the **exact post this comment belongs to**, or be omitted. It is not a scoring
138
+ option: see the author-relation section below.
139
+
135
140
  Always inspect `executionStatus` before `verdict`:
136
141
 
137
142
  For AccountBound, add the selected account to either payload:
@@ -151,6 +156,7 @@ property for ContentOnly.
151
156
  | `degraded`, timeout, 5xx, auth/context failure | Not passed. Do not use a local self-score as a substitute. Fix Hub availability and rerun. |
152
157
  | HTTP 400 invalid request | Fix the payload schema or omit an unsupported `stage`; do not count the failed request as an attempt or pass. |
153
158
  | HTTP 422 requesting explicit brand context | Resolve an approved `brandKey`/`campaignId` from Hub context or user-approved strategy; do not guess. |
159
+ | HTTP 422 `AUTHOR_RELATION_MISMATCH` | Hard failure, not a downgrade. You claimed `authorRelationHint: post_author` and Hub's post snapshot shows the publishing account is not that post's author. No LLM call was made and a security audit was written. Do not retry, and do not swap in a different permalink. Verify the publishing account and the permalink, or drop the hint and rerun. |
154
160
  | `revise` after the service attempt limit | Not passed. Stop with `ReadyForNextSkill: No`; do not loop forever or lower the standard. |
155
161
 
156
162
  The current Hub review service supports two attempts. `rewrite: true` lets Hub
@@ -159,6 +165,48 @@ facts, SKU bindings, disclosure requirements, subreddit constraints, and the
159
165
  requested content function. Review the actual final text, not only the model's
160
166
  suggested score.
161
167
 
168
+ ## 3.05 Author relation (`authorRelation`) — derived by Hub, never declared
169
+
170
+ `commentType` says where the comment sits (`top_level` / `reply`).
171
+ `authorRelation` says **who is speaking** (`post_author` / `other` / `unknown`).
172
+ They are two independent axes; read both.
173
+
174
+ **Authoritative source.** `authorRelation` and `authorRelationEvidence` appear
175
+ only in the **response**. Hub derives them from the publishing account, the
176
+ permalink of the post under review, and Hub's own snapshot of that post. The
177
+ request field `authorRelationHint` (CLI: `--author-relation-hint`) is an optional,
178
+ **non-authoritative** hint. It is not proof of identity, it is never echoed back
179
+ as the verdict, and evidence always overrides it — claiming `other` while the
180
+ evidence shows you are the author still yields `post_author`.
181
+
182
+ **Evidence conditions.** `post_author` / `verified_snapshot` is possible only when
183
+ the publishing account, the reviewed post's permalink, Hub's snapshot of that
184
+ post, and the snapshot's subreddit + title binding to this thread all line up.
185
+ Sending a hint, or sending a permalink for which Hub has no snapshot, does not
186
+ produce it. Never substitute another post, a guessed account, or an alternative
187
+ permalink to obtain a verifiable combination.
188
+
189
+ **Conflict and unavailability.** An explicit `post_author` claim contradicted by
190
+ the verified author is a **hard 422 `AUTHOR_RELATION_MISMATCH`**: no LLM call, a
191
+ security audit is written, and neither a retry nor a different permalink fixes it.
192
+ Missing permalink, missing snapshot, `[deleted]` author, no account, or a failed
193
+ thread binding yields `unknown` / `unavailable` — reviewed on the **ordinary
194
+ baseline**, not blocked and not penalised for it.
195
+
196
+ **Scoring boundary.** `post_author` carries **no score bonus**. It only widens
197
+ which concrete developments count as a valid thread anchor (progress, experiments,
198
+ results, failed attempts since the original post). Weights, thresholds, and the
199
+ required evidence strength are unchanged, and compliance, authenticity,
200
+ specificity, and repetition requirements are not relaxed. An opening such as
201
+ `Update in case anyone lands here later` is a neutral discourse marker: it is
202
+ **not penalised and not rewarded**; only the concrete material after it is
203
+ assessed, and a vague update can still hit the context-anchoring hard line and be
204
+ blocked.
205
+
206
+ > Agent rule: never add a hint, a permalink, or an update-style opening in order
207
+ > to move the score. Send only facts that already exist in this real thread and
208
+ > can be checked.
209
+
162
210
  ## 3.1 Structure protection on Hub `optimized` (v2.12.25)
163
211
 
164
212
  The Hub review is the **last remaining normalization exit**: it can silently
@@ -220,6 +268,8 @@ Use this compact record shape:
220
268
  - Scores: <authoritative score(s), if returned>
221
269
  - HubRecordIds: <review/audit ids, if returned>
222
270
  - HubRunOrRequestIds: <ids, if returned>
271
+ - AuthorRelation: post_author | other | unknown | NotReturned | NotApplicable (**copy the server-derived response value; never record the hint you sent.** `NotApplicable` for a post review; `NotReturned` when no successful response carried it — including a `AUTHOR_RELATION_MISMATCH` 422, where the CLI deliberately does not surface the server's derived values)
272
+ - AuthorRelationEvidence: verified_snapshot | verified_live | unavailable | mismatch | NotReturned | NotApplicable
223
273
  - BrandFitReviewRequired: true | false
224
274
  - AccountCandidateFeedback: <summary or None>
225
275
  - ReviewMode: ContentOnly | AccountBound
@@ -6,7 +6,7 @@ description: >-
6
6
  六维评审(情境锚定/突发性/AI 腔/人设声音/合规红线/自我重复)并可选优化重写。
7
7
  不用于:相关性判定(该不该评这帖)、发帖正文审查(用 social-hub-post-review)、账号级风控闸门。
8
8
  metadata:
9
- cliVersion: ">=0.2.3"
9
+ cliVersion: ">=0.3.13"
10
10
  ---
11
11
 
12
12
  # social-hub-content-review
@@ -51,6 +51,61 @@ handle;已选账号时传真实值。AccountBound 的人设
51
51
  (styleGuide/toneTags/brandFit/avoidTopics)由 Hub 按 accountId 服务端
52
52
  enrich,**不要**在请求里传人设内容;`personaId` 传了只做一致性校验。
53
53
 
54
+ ## 作者关系轴(authorRelation)——服务端派生,调用方声明不了
55
+
56
+ `commentType` 表达**结构位置**(`top_level` / `reply`);`authorRelation` 表达
57
+ **说话人是谁**(`post_author` / `other` / `unknown`)。两者正交,都要读。
58
+
59
+ ### 权威来源
60
+
61
+ `authorRelation` 与 `authorRelationEvidence` 只出现在**响应**里,由 Hub 按
62
+ 「实际发布账号 handle × 所审原帖 permalink × Hub 侧帖子快照作者」派生。
63
+ 请求里的 `authorRelationHint` 是**可选的非权威提示**——它不是身份声明的证明,
64
+ 不会被回显为审核结论,**证据永远覆盖 hint**(声称 `other` 但证据显示是作者,
65
+ 仍派生 `post_author`)。
66
+
67
+ ```bash
68
+ # permalink 是作者关系轴的唯一校验入口;hint 可省略(省略不影响评分标准)
69
+ social-hub content review -t <team-id> \
70
+ --thread-permalink "https://www.reddit.com/r/buildapc/comments/<id>/<slug>/" \
71
+ -j '{ "accountId": "<真实账号>", "draft": "...", "commentType": "top_level",
72
+ "thread": { "subreddit": "buildapc", "postTitle": "..." } }'
73
+ ```
74
+
75
+ `--author-relation-hint post_author|other` **通常省略**。它不是让 Hub 采用某种身份的开关:
76
+ 省略不影响评分,也不影响 Hub 按证据派生作者关系。只有当上游工作流必须核验
77
+ 「本账号应当就是原帖作者」这一预期时才传 `post_author`——它的作用是让**已验证的账号错配
78
+ 变成硬失败**(422),而不是争取某种待遇。证据不可得时不会 422,但会按 `unknown` 审核,
79
+ 并在服务端记录一条未核验声明。传 `other` 与证据冲突不会硬失败,证据直接覆盖它。
80
+
81
+ ### 证据条件(只给 hint 没有用)
82
+
83
+ 只有**发布账号、原帖 permalink、Hub 侧该帖快照、以及快照的 subreddit+title 与本次
84
+ thread 绑定**全部一致时,Hub 才可能派生 `post_author` / `verified_snapshot`。
85
+ 只传 hint、或只传 permalink 而 Hub 没有该帖快照,都拿不到这个结果。
86
+ 不得改用别的帖子、猜测账号或替代 permalink 去凑一个能验证的组合。
87
+
88
+ ### 冲突与不可得
89
+
90
+ | 情况 | 结果 |
91
+ | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
92
+ | 显式 `authorRelationHint: post_author`,但证据显示不是该帖作者 | **422 `AUTHOR_RELATION_MISMATCH` 硬失败**:LLM 一次都不调用,服务端落安全审计。不是降级,重试与更换 permalink 都不会自愈;核对发布账号与 permalink,或直接去掉 hint 重跑 |
93
+ | 无 permalink / 无快照 / 作者 `[deleted]` / 无账号 / 绑定校验不过 | `unknown` + `unavailable`:**按普通基线审核,不 block、不惩罚** |
94
+
95
+ `unknown` 不是「证据不足所以从严」,它就是改动前的普通基线。
96
+
97
+ ### 评分边界(这是校准,不是加分项)
98
+
99
+ `post_author` **不加分**:rubric 只扩展了「哪些具体进展可以充当有效线程锚点」
100
+ (从原帖开始的进展/实验/结果/失败尝试),**权重、阈值、证据强度一律未变**,
101
+ compliance / 真实性 / 具体性 / 重复度要求一律不放宽。
102
+ `Update in case anyone lands here later` 之类开头被识别为**中性 discourse marker**——
103
+ 不因它扣 scripted,**也不因它加分**;只评估其后的具体材料,空泛的 update 照样可能
104
+ 撞情境锚定硬红线被 `block`。
105
+
106
+ > **agent 行为规则**:不要为了改变评分而添加 hint、permalink 或 update 开头。
107
+ > 只传本次真实线程里已经存在、且可核对的事实。
108
+
54
109
  ## 两种审核模式
55
110
 
56
111
  | 模式 | 请求 | 结论边界 |
@@ -154,6 +209,8 @@ social-hub comment-drafts schedule -t <team-id> --draft <uuid> \
154
209
  - `executionStatus: "degraded"`(LLM 超时/失败) → verdict 不可信,降级层②自审,
155
210
  回执标 `[hub-offline-fallback]`;`degradedReason` 说明原因。
156
211
  - `score` 为服务端按版本化权重计算的权威综合分;`modelScore` 仅审计对照。
212
+ - `authorRelation` / `authorRelationEvidence` 是**服务端派生**的作者关系(见上一节),
213
+ 记录时按响应原值记,**不要记录或回显你传的 `authorRelationHint`**。
157
214
  - 情境锚定与合规是一票否决维度;静默期账号阈值自动收紧 +10。
158
215
  - AccountBound 出现 `brandFitReviewRequired=true` 或阻断型账号反馈时暂停发布。
159
216