@geoly-ai/social-hub-cli 0.3.19 → 0.3.21

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 (50) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/accounts-list-offset.test.d.ts +2 -0
  3. package/dist/accounts-list-offset.test.d.ts.map +1 -0
  4. package/dist/accounts-list-offset.test.js +53 -0
  5. package/dist/accounts-list-offset.test.js.map +1 -0
  6. package/dist/cmd-manifest.json +4 -2
  7. package/dist/compliance-risk-list-offset.test.d.ts +2 -0
  8. package/dist/compliance-risk-list-offset.test.d.ts.map +1 -0
  9. package/dist/compliance-risk-list-offset.test.js +63 -0
  10. package/dist/compliance-risk-list-offset.test.js.map +1 -0
  11. package/dist/content-review.test.js +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +40 -4
  14. package/dist/index.js.map +1 -1
  15. package/dist/index.test.js +16 -0
  16. package/dist/index.test.js.map +1 -1
  17. package/dist/register-calendar-directives.d.ts +64 -0
  18. package/dist/register-calendar-directives.d.ts.map +1 -0
  19. package/dist/register-calendar-directives.js +135 -0
  20. package/dist/register-calendar-directives.js.map +1 -0
  21. package/dist/register-calendar-directives.test.d.ts +2 -0
  22. package/dist/register-calendar-directives.test.d.ts.map +1 -0
  23. package/dist/register-calendar-directives.test.js +127 -0
  24. package/dist/register-calendar-directives.test.js.map +1 -0
  25. package/dist/register-shared.d.ts.map +1 -1
  26. package/dist/register-shared.js +13 -2
  27. package/dist/register-shared.js.map +1 -1
  28. package/dist/register-shared.test.js +78 -0
  29. package/dist/register-shared.test.js.map +1 -1
  30. package/package.json +2 -2
  31. package/skills/README.md +23 -22
  32. package/skills/manifest.json +6 -1
  33. package/skills/reddit-content-rewrite/SKILL.md +29 -0
  34. package/skills/reddit-content-writing/SKILL.md +53 -4
  35. package/skills/reddit-delivery-check/SKILL.md +38 -6
  36. package/skills/reddit-matrix.lock.json +6 -6
  37. package/skills/reddit-phase-brief/SKILL.md +39 -4
  38. package/skills/reddit-phase-brief/references/phase4-brief-workflow.md +43 -3
  39. package/skills/reddit-strategy-shared/references/content-diversity-contract.md +7 -2
  40. package/skills/reddit-strategy-shared/references/content-review-gate.md +48 -1
  41. package/skills/reddit-strategy-shared/references/handoff-schemas.md +72 -2
  42. package/skills/reddit-strategy-shared/references/native-copy-profile.md +207 -0
  43. package/skills/reddit-strategy-shared/references/persona-card-contract.md +210 -0
  44. package/skills/reddit-strategy-shared/references/version-manifest.md +60 -0
  45. package/skills/reddit-subreddit-compliance/SKILL.md +28 -1
  46. package/skills/social-hub-calendar-directives/SKILL.md +548 -0
  47. package/skills/social-hub-calendar-jobs/SKILL.md +10 -0
  48. package/skills/social-hub-cli/SKILL.md +2 -0
  49. package/skills/social-hub-notifications/SKILL.md +7 -0
  50. package/skills/social-hub-publishing/SKILL.md +5 -0
@@ -535,8 +535,11 @@ ASK / GUIDE / ROUNDUP / OWNER / OPINION / RANT / FOLLOWUP / LIST / REPUTATION
535
535
  `EndingMode` / `LengthBand`(由该 subreddit baseline 的中位数 + IQR 派生)/
536
536
  `AnchorSlot`(`ABURequired=Yes` 时须在该 family 的 `AllowedAnchorSlots` 内;
537
537
  否则填 `N/A`)。
538
- **并预分配两个前置控制字段**(`PrewritingControlFields`,权威定义见
539
- `../../reddit-strategy-shared/references/content-diversity-contract.md`):
538
+ **并预分配三个前置控制字段**(`PrewritingControlFields`;`AssignedFlair` /
539
+ `PostingTrigger` 的权威定义见
540
+ `../../reddit-strategy-shared/references/content-diversity-contract.md`,
541
+ `PersonaCardRef` 见
542
+ `../../reddit-strategy-shared/references/persona-card-contract.md`):
540
543
  - `AssignedFlair`:已在 B-1.0/W0 由台账 `ProposedFlair` 验证锁定(`FlairVerificationStatus`
541
544
  须为 `Verified` 或 `FallbackAssigned`);板块无
542
545
  flair 或允许不选时填哨兵值 `NoFlairAvailable` / `NoFlairRequired`,不得留空、
@@ -550,6 +553,23 @@ ASK / GUIDE / ROUNDUP / OWNER / OPINION / RANT / FOLLOWUP / LIST / REPUTATION
550
553
  ⚠️ **该交叉约束只能在 5-brand 产出 `BrandEntryMode` 之后校验**:本步先记下
551
554
  `PostingTrigger` 候选,5-brand 完成后再判定;不得为了让交叉约束通过而反向决定
552
555
  `BrandEntryMode`。
556
+ - `PersonaCardRef`:本 Key 的 Persona Card 引用(+ `PersonaCardVersion` /
557
+ `PersonaCardHash`)。**Card 由本 skill 产出,撰写 skill 不得临场写**——否则等于让
558
+ 稿子自己批准自己需要的知识边界。字段与生命周期见
559
+ `../../reddit-strategy-shared/references/persona-card-contract.md`:
560
+ `knowledgeAccess`(单值)/ `permittedExperienceClaims`(`{ownership, delivery,
561
+ wear, service}` 的子集,**可以为空集**)/ `attentionFocus` / `questionIntent`,
562
+ 以及逐稿的 `postingWhy` / `knows[]` / `doesNotKnow[]` / `permittedFactRefs` /
563
+ `brandRelationshipDisclosed`。
564
+ ⚠️ `postingWhy` 与 `PostingTrigger` **是两个字段,互不替代**:后者是「这篇帖为什么
565
+ 现在值得发」的受控五值,前者是「这个账号自己为什么发」。二者必须能共存,否则
566
+ `BlockingGap`。
567
+ ⚠️ `permittedExperienceClaims` 只能来自**已提交的上下文**,不得从品牌关系推断
568
+ (合作方也可能真实拥有该产品);五个角色名(Researcher / Owner / GiftBuyer /
569
+ CategoryEnthusiast / LifestyleUser)只是**可选标签,任何门禁都不读它**。
570
+ 评论 / 自评 / 外部评论填 `N/A`——本期不把评论纳入 Card。
571
+ ⚠️ Card 是**已声明的发帖上下文,不是已验证身份**:`LocalOnly`,不进任何 Hub 请求,
572
+ 也不代表发布账号已解析。
553
573
  **5-brand · `BrandPlacementGate`(G2,硬门):** `BrandEntryMode` 与「标题是否出现
554
574
  品牌名」**只能由证据决定,不得由 P-B 标签推导**。
555
575
  > **出处(真实返工):** 需求方质问「**为什么你把 P-B 的标题都统一加上了 aporro
@@ -714,6 +734,25 @@ ASK / GUIDE / ROUNDUP / OWNER / OPINION / RANT / FOLLOWUP / LIST / REPUTATION
714
734
  **PrewritingControlFields(前置控制字段,主帖必填,先于 baseline 与结构):**
715
735
  - AssignedFlair: (现行 flair 之一;无 flair 板块填 NoFlairAvailable / NoFlairRequired,不留空)
716
736
  - PostingTrigger: (ConcreteFailure / PurchaseDecision / MeasurementResult / CompatibilityGap / UnsolicitedTestimony;须满足 × BrandEntryMode 交叉约束)
737
+ - PersonaCardRef: (本 Key 的 Persona Card 引用;评论填 N/A)
738
+ - PersonaCardVersion: (整数;改 Card 必须递增,并作废下游已做的边界自检)
739
+ - PersonaCardHash: (该 Card 正文的 sha256;改写 skill 只保持、不得改)
740
+ - knowledgeAccess: (research / owned / used / observed / supplied_by_brand,单选)
741
+ - permittedExperienceClaims: ({ownership, delivery, wear, service} 的子集;**空集是合法且常见的**)
742
+ - attentionFocus: (这个账号在该品类里真正在意什么)
743
+ - questionIntent: (它真正想弄清什么;没有就写 None)
744
+ - postingWhy: (这个账号自己为什么发;**不是 PostingTrigger 的复述**,二者须能共存)
745
+ - knows: (本稿可假定它知道什么;可多条)
746
+ - doesNotKnow: (本稿**不得**表现出掌握什么;可多条)
747
+ - permittedFactRefs: (本稿可用的已批准产品事实引用)
748
+ - brandRelationshipDisclosed: (Yes / No / NotApplicable —— 指**稿件声明了什么**,不是事实为何)
749
+ - DeclaredBy: (是谁授权了这些主张:人或角色,**不能是生成 agent 自己**)
750
+ - ClaimAuthorizationRef: (授权 permittedExperienceClaims 的已提交材料引用)
751
+ - RoleLabels: (可选、非权威;任何门禁都不读它)
752
+
753
+ ⚠️ `permittedExperienceClaims` 非空但 `DeclaredBy` / `ClaimAuthorizationRef` 缺失时,该 Card
754
+ 记录的是「Brief 声明了什么」而不是「谁授权了什么」——只能按前者汇报,不得称已授权。
755
+ `PersonaCardHash` 只防篡改,不代表授权。
717
756
 
718
757
  **NarrativeLayering(G1,主帖与评论均必填,先于标题方向与大纲):**
719
758
  - InternalCoverageIntent: (仅内部:FAQ 编号/原文、fanout 子查询、关键词、缺口象限码、prompt_id;可多条)
@@ -904,7 +943,8 @@ C-B 评论**不填本块**,改用评论模板的 `InheritedBrandPlacementBasis
904
943
  [ ] 【v2.12.26】项目差异化窗口已锁定(WindowCount 8–12 且 WindowDays 30–60 同时生效 / WindowScope: PostOnly / WindowSort / BaselineMaxAgeDays / IndexRevision),写入指纹索引文件头
905
944
  [ ] 【v2.12.26】每个**主帖** Key 的「客户内容原型」均命中 memory/phase0-客户内容原型映射.md 的 Approved 行,MappingRuleID 与 MappingVersion 已记入 Routing Decision;无未映射代号(自评继承配对主帖的原型值,外部评论填「—」,不单独要求映射)
906
945
  [ ] 【v2.12.26】每条主帖 Brief 均含 9 项 LockedFingerprintFields:ContentFunction / StructureFamily / OpeningMove / EvidenceShape / BrandEntryMode / CaveatMode / EndingMode / LengthBand / AnchorSlot
907
- [ ] 【P0-C】每条主帖 Brief 均含 2 项 PrewritingControlFields:AssignedFlair(非空,现行 flair 或 NoFlairAvailable/NoFlairRequired;且已按 B-1.0/W0 对台账 ProposedFlair 完成验证锁定,FlairVerificationStatus ∈ {Verified, FallbackAssigned},无 Rejected 遗留)+ PostingTrigger(受控五值,满足 × BrandEntryMode 交叉约束)
946
+ [ ] 【P0-C】每条主帖 Brief 均含 3 项 PrewritingControlFields:AssignedFlair(非空,现行 flair 或 NoFlairAvailable/NoFlairRequired;且已按 B-1.0/W0 对台账 ProposedFlair 完成验证锁定,FlairVerificationStatus ∈ {Verified, FallbackAssigned},无 Rejected 遗留)+ PostingTrigger(受控五值,满足 × BrandEntryMode 交叉约束)+ PersonaCardRef(非空,带 PersonaCardVersion / PersonaCardHash)
947
+ [ ] 每条主帖 Brief 的 Persona Card 完整:knowledgeAccess 单值、permittedExperienceClaims 只取自 {ownership, delivery, wear, service}(空集合法)、postingWhy 非空且能与 PostingTrigger 共存;评论行填 N/A。permittedExperienceClaims 未从品牌关系推断,RoleLabels 未被任何判据使用
908
948
  [ ] 【P0-C】每张 baseline 卡记录 flair 覆盖:BaselineScope / BaselineFlair / FlairSampleCount / FlairCoverageStatus(Exact 或 ApprovedFallback;Insufficient → BlockingGap)/ FallbackApprovalRef(fallback 时须署名)
909
949
  [ ] 【v2.12.26】每条主帖 Brief 均含 4 项 TraceFields:SampleSetID / RecentCollisionCheck / MappingRuleID / MappingVersion
910
950
  [ ] 【v2.12.26】每条主帖 Brief 均含 Routing Decision 块,五层候选收敛轨迹齐全(Baseline / Function / Evidence / PrimaryAndCompliance / MappingPredicates + MatchedPredicate),SelectedCandidateRank 与 SkippedCandidates(CandidateRank + RejectionReason)已记录;跳过更靠前候选的理由均为 1–5 层硬失败,差异化仅作 tie-breaker,CollisionRepairPath 已记录
@@ -103,13 +103,18 @@ LastUpdatedAt: <ISO timestamp>
103
103
  | `LengthBand` | 依据该 subreddit baseline 的中位数 + IQR 派生的档位,**不设跨板块固定词数** |
104
104
  | `AnchorSlot` | 见 ABU 七种合法位置;无需 ABU 时填 `N/A` |
105
105
 
106
- **两个前置控制字段(`PrewritingControlFields`,与 9 个 `LockedFingerprintField`
106
+ **三个前置控制字段(`PrewritingControlFields`,与 9 个 `LockedFingerprintField`
107
107
  并列但不并入锁字段集,也不进硬碰撞三元组):**
108
108
 
109
109
  | 字段 | 受控值 |
110
110
  |---|---|
111
111
  | `AssignedFlair` | 该主帖 Key 的目标发布 flair(**受控于该 subreddit 的现行 flair 列表**,不得自造)。主帖必填、不得留空;板块无 flair 或允许不选时用哨兵值 `NoFlairAvailable` / `NoFlairRequired`(不得与评论的 `N/A` 混用)。评论 / 回复填 `N/A` |
112
112
  | `PostingTrigger` | `ConcreteFailure` / `PurchaseDecision` / `MeasurementResult` / `CompatibilityGap` / `UnsolicitedTestimony`(闭集)。回答「这篇帖为什么现在值得发」 |
113
+ | `PersonaCardRef` | 该主帖 Key 的 Persona Card 引用(+ `PersonaCardVersion` / `PersonaCardHash`)。回答「这个账号凭什么知道这些、可以声称哪些第一手经历」。字段与生命周期见 `persona-card-contract.md`。评论 / 回复填 `N/A` |
114
+
115
+ ⚠️ `PersonaCardRef` 与另两个前置字段同样由 Brief 预分配、writer 不得自选,但它**不是指纹字段**:
116
+ 不进 `memory/phase4-叙事指纹索引.md` 的固定表、不进 `HashSchemaVersion` 的 hash 定义、
117
+ 不进硬碰撞三元组。指纹索引的表结构与 hash 定义不因本字段变更。
113
118
 
114
119
  字段语义补充:
115
120
 
@@ -460,7 +465,7 @@ FingerprintSyncStatus: PendingWritingOwner
460
465
 
461
466
  | 层 | 执行者 | 内容 | 失败行为 |
462
467
  |---|---|---|---|
463
- | 1 · 预分配 | `reddit-phase-brief` | 在 Brief 里预先锁定 9 个结构字段 + 2 个 `PrewritingControlField`(`AssignedFlair` / `PostingTrigger`)并写入 `Planned` 指纹行;**不让 writer 临场自选** | Brief 缺字段(含 `AssignedFlair` / `PostingTrigger`)→ `BlockingGap`,不得进入写作 |
468
+ | 1 · 预分配 | `reddit-phase-brief` | 在 Brief 里预先锁定 9 个结构字段 + 3 个 `PrewritingControlField`(`AssignedFlair` / `PostingTrigger` / `PersonaCardRef`)并写入 `Planned` 指纹行(**`PersonaCardRef` 只进 Brief,不写入指纹行**);**不让 writer 临场自选** | Brief 缺字段(含 `AssignedFlair` / `PostingTrigger` / `PersonaCardRef`)→ `BlockingGap`,不得进入写作 |
464
469
  | 2 · 写作硬门禁 | `reddit-content-writing` | 开写前必须读取有效窗口并完成碰撞检查;写完后从终稿推导实际指纹与 `PostingTrigger` | 未读窗口 / 未做碰撞检查 / 实际与 `Planned` 结构字段或 `PostingTrigger` 不符 → 停止并退回 Brief |
465
470
  | 3 · 交付反查 | `reddit-delivery-check` | 反向校验窗口无碰撞、映射已批准、结构未被 rewrite 改动、`AnchorSlot` 合法 | 任一不符 → `OverallStatus: NeedsFix` |
466
471
  | 4 · hash 绑定 | `reddit-delivery-check` | 重算终稿 hash,与指纹行、review log 三方比对 | 任意两者不一致 → 阻断交付 |
@@ -33,7 +33,54 @@ when the brand or campaign context is approved and known.
33
33
  subreddit-rule fit from supplied evidence, context, natural intent, and
34
34
  compliance red lines.
35
35
  - persona_consistency is not a completed account-persona check. Report it as
36
- NotAssessed or WeaklyVerified, never as confirmed account fit.
36
+ NotAssessed, never as confirmed account fit. There is no partially-verified
37
+ tier: Hub stamps this dimension `not_assessed` with reason
38
+ `no_persona_voice_profile` and drops it from the denominator, so NotAssessed is
39
+ the whole of what the payload supports. Reporting a middle grade would be
40
+ inventing a confidence the review never established.
41
+ - Hub now says so in the payload instead of leaving you to infer it. Every
42
+ dimension carries `status`, which is server-stamped and overrides anything the
43
+ model claimed. When `status` is `not_assessed`, that dimension was excluded
44
+ from the score entirely — its weight was removed from the denominator, and
45
+ `notAssessedReason` says why (`no_persona_voice_profile`, `no_account`,
46
+ `no_completed_review_excerpts`, `no_subreddit_rules`, `not_applicable`,
47
+ `insufficient_context`, `dimension_missing`, `not_evaluated_deterministic`).
48
+ - `thresholdSnapshot` carries the same facts in aggregate form:
49
+ `scoredDimensions`, `excludedDimensions` (`{dimension, reason}[]` on both the
50
+ post and comment endpoints), `denominator`, and `applicabilitySignature`. On a
51
+ `degraded` result, `unevaluatedDimensions` lists dimensions that were
52
+ assessable but never got a model answer — those are not exclusions and are not
53
+ reflected in the denominator.
54
+ - `dimensionsEvaluated: false` means no dimension was actually scored at all
55
+ (a deterministic compliance short-circuit, or a degraded run). On those
56
+ results `scoredDimensions`, `denominator`, and `applicabilitySignature`
57
+ describe the denominator that *would* have applied, not a review that
58
+ happened. Never read them as coverage.
59
+ - `status: guardrail` is a third case, and it is not the same as `scored`. A
60
+ guardrail dimension was assessable, but it never enters the score — it can only
61
+ push a `pass` down to `revise`. The two subreddit style dimensions are the
62
+ guardrails: `subreddit_style_fit` is a guardrail whenever an approved style
63
+ profile applied, and `brand_exposure_fit` additionally requires a brand policy
64
+ to be in play — on an organic review it comes back `not_assessed` even with a
65
+ style profile present. Report guardrails as observations, never as part of the
66
+ score.
67
+ - Only `status: scored` dimensions are in the score. `scoredDimensions` in the
68
+ snapshot lists exactly those, so filtering the payload for `scored` and
69
+ filtering the snapshot agree; anything else (`not_assessed`, `guardrail`) is
70
+ outside both the numerator and the denominator.
71
+ - Report every `status: not_assessed` dimension as NotAssessed. Do not report it
72
+ as a low score, a passing score, or a partial check.
73
+ - A `not_assessed` dimension still carries a `score` number. That number is the
74
+ raw model output kept for audit only; it did not enter the average or any
75
+ threshold. Never quote it as a result and never aggregate it.
76
+ - In ContentOnly, expect `persona_consistency` (posts) and `persona_voice`
77
+ (comments) to come back `not_assessed`, plus `self_repetition` when the
78
+ account has no recent-comment excerpts on file. That is correct behaviour, not
79
+ a degraded review.
80
+ - Because the denominator depends on what was assessable, scores are only
81
+ comparable within the same `rubricVersion` **and** the same
82
+ `thresholdSnapshot.applicabilitySignature`. Do not compare or average a
83
+ ContentOnly score against an AccountBound one.
37
84
  - accountCandidateFeedback is advisory account-selection feedback in this mode.
38
85
  It must not block a content-only completed/pass result.
39
86
  - A ContentOnly brandFitReviewRequired signal is deferred to AccountBound. It
@@ -46,9 +46,49 @@ Required:
46
46
  - `NeedsVerificationCandidates`
47
47
  - `NotRecommended`
48
48
  - `MissingEvidence`
49
+ - `EvidenceGatheredAt`
50
+ - `EvidenceValidUntil`
49
51
 
50
52
  `AboutPageEvidenceStatus=NeedsAcquisition` is required when `reddit-voc-volume about-page` reports `about_page_empty_rendered_content`; downstream skills must not treat that as `NotVisible`.
51
53
 
54
+ **Compliance evidence is a point-in-time snapshot, and this handoff carries the
55
+ time it was taken — not a standing clearance.** A batch is scheduled and
56
+ delivered after this report is written, and rules and pinned posts change in
57
+ between, so the report must say how old it is.
58
+
59
+ - `EvidenceGatheredAt` / `EvidenceValidUntil` are **batch-level worst-case
60
+ scalars**, defined precisely so a single pair is checkable: `EvidenceGatheredAt`
61
+ is the **earliest** gathering time among the delivery-required evidence items,
62
+ and `EvidenceValidUntil` is the **earliest** validity horizon among them. The
63
+ delivery-required items are `RulesEvidenceStatus` / `PinnedEvidenceStatus` /
64
+ `BotBannerEvidenceStatus` / `InstalledAppsStatus` / `AboutPageEvidenceStatus`.
65
+ Per-item timestamps may also be recorded, and must be when the items differ
66
+ enough that one worst-case pair would hide a stale item.
67
+ - **`Unknown` is not fresh.** If any delivery-required item has an unknown
68
+ gathering time or no declared horizon, the batch cannot be cleared on these
69
+ scalars: it is re-gathered and re-judged before delivery proceeds.
70
+ - **Expired evidence is re-gathered and re-judged; it is never carried forward.**
71
+ A stale rules snapshot is `NeedsAcquisition`, and the affected rows hold. Soft
72
+ style judgements may degrade gracefully when a source is missing; a compliance
73
+ judgement may not — "we could not check" is not "it is allowed".
74
+ - `reddit-delivery-check` re-checks freshness **at delivery** against this
75
+ handoff. A single check at compliance time does not satisfy the gate.
76
+
77
+ **A timestamp only re-opens a question; it never answers one.** Freshness applies
78
+ to *gathered evidence*. It does not apply to a permission that was never
79
+ adjudicated in the first place: where brand/vendor participation or a disclosure
80
+ requirement has no recorded human decision, it stays in `MissingEvidence` and the
81
+ rows hold, and no expiry or refresh converts that into a clearance.
82
+
83
+ **Two Gate 0 questions have no modelled answer and must not be presented as
84
+ decided:** whether the board permits brand or vendor participation at all, and
85
+ whether a disclosure is required. Neither is derivable from a style profile's
86
+ brand-friendliness note (that is a cultural description) nor from raw collected
87
+ evidence (that is unadjudicated material). Where a batch depends on either, the
88
+ answer belongs in `MissingEvidence`, the affected rows carry the corresponding
89
+ `PostRisk` / `CommentRisk`, and clearance is a human decision recorded by the
90
+ user. Do not add a field that makes an unadjudicated question look answered.
91
+
52
92
  ## Planning To Brief
53
93
 
54
94
  Required:
@@ -85,7 +125,12 @@ Required:
85
125
  - `memory/phase4-叙事指纹索引.md` exists as a fixed table with the schema header (`WindowCount`, `WindowDays`, `WindowScope`, `WindowSort`, `BaselineMaxAgeDays`, `HashSchemaVersion`, `IndexRevision`), and has exactly one active `Planned` row for every briefed post Key
86
126
  - a valid `SampleSetID` (flair-level subreddit baseline, 5–10 recent real posts, word-count median + IQR, with `AcquiredAt` / `ValidUntil`) for every target subreddit in the batch, recording flair coverage (`BaselineScope` / `BaselineFlair` / `FlairCoverageStatus`); missing or expired baseline, or `FlairCoverageStatus: Insufficient`, is a `BlockingGap`
87
127
  - every main-post Brief row's `AssignedFlair` was **verified against the ledger's `ProposedFlair`**, not re-chosen: `FlairVerificationStatus` is `Verified` or `FallbackAssigned` (the latter only for `ProposedFlair: Unknown(NoSRP)`); `Rejected` is a `BlockingGap` back to `reddit-phase-planning` D2
88
- - every post Brief row carries the nine `LockedFingerprintFields` (`ContentFunction`, `StructureFamily`, `OpeningMove`, `EvidenceShape`, `BrandEntryMode`, `CaveatMode`, `EndingMode`, `LengthBand`, `AnchorSlot`), the two `PrewritingControlFields` (`AssignedFlair` non-empty; `PostingTrigger` from the controlled five values satisfying the `PostingTrigger × BrandEntryMode` cross-constraint), and the four `TraceFields` (`SampleSetID`, `RecentCollisionCheck`, `MappingRuleID`, `MappingVersion`)
128
+ - every post Brief row carries the nine `LockedFingerprintFields` (`ContentFunction`, `StructureFamily`, `OpeningMove`, `EvidenceShape`, `BrandEntryMode`, `CaveatMode`, `EndingMode`, `LengthBand`, `AnchorSlot`), the three `PrewritingControlFields` (`AssignedFlair` non-empty; `PostingTrigger` from the controlled five values satisfying the `PostingTrigger × BrandEntryMode` cross-constraint; `PersonaCardRef` non-empty with its `PersonaCardVersion` / `PersonaCardHash`), and the four `TraceFields` (`SampleSetID`, `RecentCollisionCheck`, `MappingRuleID`, `MappingVersion`)
129
+ - `PersonaCardRef` is required for **main-post rows only** (contract:
130
+ `references/persona-card-contract.md`). Self-comments, external comments, and
131
+ titles carry `N/A`. The Card is authored by `reddit-phase-brief`; a row whose
132
+ Card is missing is a `BlockingGap` back to Brief, never a Card the writer
133
+ supplies
89
134
  - every post Brief row carries a `Routing Decision` block with the five candidate-narrowing steps, `MatchedPredicate`, `SelectedStructureFamily`, and `CollisionRepairPath`
90
135
  - `RecentCollisionCheck` is `Pass` (or `Exception` with an approved `ExceptionRef`) for every row
91
136
  - `brief_approval_mode`
@@ -135,6 +180,9 @@ MappingRuleID: # when a client prototype applies
135
180
  StructureFamily:
136
181
  AssignedFlair: # PrewritingControlField; non-empty; NoFlairAvailable/NoFlairRequired for flairless boards
137
182
  PostingTrigger: # PrewritingControlField; controlled 5 values; not in the collision triple
183
+ PersonaCardRef: # PrewritingControlField; Brief-assigned, post-only
184
+ PersonaCardVersion: # integer; a change invalidates prior boundary checks
185
+ PersonaCardHash: # sha256 of the assigned Card; rewrite preserves, never edits
138
186
  OpeningMove:
139
187
  EvidenceShape:
140
188
  BrandEntryMode:
@@ -152,6 +200,7 @@ DraftHash: # PENDING until the post-review recompute
152
200
 
153
201
  **Not required for `Comment` / `Reply` / `Title`:** `PrimaryPostType`,
154
202
  `StructureFamily`, `SampleSetID`, `AnchorSlot`, `RecentCollisionCheck`,
203
+ `PersonaCardRef` / `PersonaCardVersion` / `PersonaCardHash`,
155
204
  `DraftHash` from a fingerprint row. These artifacts carry no fingerprint row
156
205
  (`WindowScope: PostOnly`); they still need Hub review and `FinalDraftSha256`,
157
206
  and the fabricated-human-signal rules still apply to them.
@@ -168,6 +217,14 @@ Rules:
168
217
  - Rewrite may not reorder sections, add a failed attempt, add a question ending, add a caveat, add a community-observation line, move the brand mention, change `AnchorSlot`, or change any of the nine `LockedFingerprintFields`.
169
218
  - Rewrite must **preserve `PostingTrigger`** for `ArtifactType: Post`: a body edit that changes the post's real-world trigger (or that breaks the `PostingTrigger × BrandEntryMode` cross-constraint) is out of allowance — flag it instead of applying, so the finished body still re-derives to the `Planned` `PostingTrigger`.
170
219
  - Hub `optimized` output that changes a protected structure field may not be adopted wholesale; record `HubStructureConflictStatus: None | Escalated` (see `content-review-gate.md` §3.1). `Escalated` holds the draft and routes to `reddit-phase-brief`.
220
+ - Rewrite **preserves `PersonaCardRef` / `PersonaCardVersion` / `PersonaCardHash`
221
+ unchanged** and may not author, extend, or reinterpret a Card
222
+ (`references/persona-card-contract.md`). Repairing a claim so that it stops
223
+ contradicting the assigned Card is in allowance — it is the existing
224
+ `FactualExpression` duty, since an unauthorised firsthand claim is an
225
+ unsupported claim. Needing a *wider* boundary than the Card grants is not:
226
+ return `PersonaCardConflict: Yes` with the contradicting phrase and route to
227
+ `reddit-phase-brief` for a new Card version.
171
228
  - `reddit-content-rewrite` does **not** own `memory/phase4-叙事指纹索引.md`. When its edits change the body, it returns `FingerprintSyncRequired: Yes` / `FingerprintSyncStatus: PendingWritingOwner`; `reddit-content-writing` (the index owner) performs the re-review and hash write-back.
172
229
 
173
230
  ## Brand Risk To Writing
@@ -198,13 +255,26 @@ Required:
198
255
  - `memory/content-review-log.md`
199
256
  - `memory/phase4-叙事指纹索引.md` has exactly one active `Final` row for every delivered **main-post** Key (`WindowScope: PostOnly`; comments and external comments need no fingerprint row, only Hub `FinalDraftSha256`)
200
257
  - each `Final` row's `DraftHash` equals the `FinalDraftSha256` in `memory/content-review-log.md` and a recomputation from the delivered text using `HashSchemaVersion: reddit-final-draft-sha256-v1`
201
- - all nine `LockedFingerprintFields` of a delivered post match the Brief's `Planned` values, and the two `PrewritingControlFields` match too: `AssignedFlair` equals the Brief value (and the baseline flair coverage it was written against), and the delivered body's re-derived `PostingTrigger` equals the `Planned` value while satisfying the `PostingTrigger × BrandEntryMode` cross-constraint
258
+ - all nine `LockedFingerprintFields` of a delivered post match the Brief's `Planned` values, and the three `PrewritingControlFields` match too: `AssignedFlair` equals the Brief value (and the baseline flair coverage it was written against), the delivered body's re-derived `PostingTrigger` equals the `Planned` value while satisfying the `PostingTrigger × BrandEntryMode` cross-constraint, and `PersonaCardRef` / `PersonaCardVersion` / `PersonaCardHash` equal the Brief's assigned Card with `PersonaCardSelfCheck: Pass` recorded against that same version
202
259
  - no delivered post Key collides with any other Key in the rolling window unless an approved `ExceptionRef` exists
203
260
  - every post/comment to be delivered has Hub `executionStatus=completed` and `verdict=pass`
204
261
  - final delivery rows have `ReviewMode: AccountBound` and a real `accountId`
205
262
  - ContentOnly rows may hand off to account selection with `AccountBindingStatus: Pending`, but cannot be marked `ReadyForDelivery: Yes`
206
263
  - no delivered row has unresolved `brandFitReviewRequired` or blocking `accountCandidateFeedback`
207
264
  - Hub review feedback was applied to the exact final draft
265
+ - every dimension the Hub result reports as `not_assessed` is carried through as
266
+ NotAssessed, with its `notAssessedReason` — never as a low score, a passing
267
+ score, or a partial check, and never averaged. Scores are compared only within
268
+ the same `applicabilitySignature`; the semantics are in
269
+ `references/content-review-gate.md` and are not restated per row
270
+ - compliance evidence is still fresh **as of delivery**, re-checked against the
271
+ Compliance Output handoff; evidence that expired since that report holds the
272
+ affected rows
273
+ - when `AcceptanceProfile: NativeCopy` was selected, `NativeCopyProfileStatus` is
274
+ recorded. While it is `Unavailable(ServerContractMissing)` no row may be marked
275
+ `ReadyForDelivery: Yes` under that profile, and the profile may not be silently
276
+ downgraded to `Default` to release the batch
277
+ (`references/native-copy-profile.md`)
208
278
  - single Feishu draft document exists when Feishu delivery was selected
209
279
  - `memory/phase5-交付清单.md` is written at delivery
210
280
 
@@ -0,0 +1,207 @@
1
+ # Native Copy Acceptance Profile
2
+
3
+ An **optional overlay** for clients who commission a stricter native-copy
4
+ standard than the matrix default.
5
+
6
+ ```markdown
7
+ AcceptanceProfile: Default | NativeCopy
8
+ ```
9
+
10
+ `Default` is the standing behaviour of every skill in this matrix and is what
11
+ runs when the field is absent. **Nothing in this file changes `Default`.** Every
12
+ rule here applies only while `AcceptanceProfile: NativeCopy` is selected for the
13
+ batch.
14
+
15
+ This separation is the whole point of the file. A brand-mention ceiling, a
16
+ required question ending, or a fixed output format folded into the default layer
17
+ would impose one client's house voice on every brand — which is precisely what
18
+ `reddit-content-rewrite` was redefined as a constraint-preserving repairer to
19
+ prevent. Keep client strictness in the overlay.
20
+
21
+ ## 1. Current status: `Unavailable`
22
+
23
+ **`NativeCopyProfileStatus: Unavailable(ServerContractMissing)`.**
24
+
25
+ The profile requires the server to state, per review, which scope was requested,
26
+ which was resolved, and whether the declaration was honoured or inferred. Social
27
+ Hub does not expose that today — there is no scope field on the review request
28
+ and no scope receipt on the response.
29
+
30
+ While the status is `Unavailable`:
31
+
32
+ - Planning, Brief, Persona Cards, drafting, and the local self-checks below all
33
+ run normally.
34
+ - Hub review runs normally, under `Default` semantics, and its verdict is read
35
+ exactly as `references/content-review-gate.md` describes.
36
+ - **No row may be marked `ReadyForDelivery: Yes` under this profile.** Report
37
+ `NativeCopyProfileStatus: Unavailable(ServerContractMissing)` and name the
38
+ missing capability.
39
+ - **Never silently fall back to `Default` and call the result a profile pass.**
40
+ Fail closed: a client who commissioned the stricter standard must not be handed
41
+ a delivery that was measured against the looser one. Equally, never strip
42
+ fields to make a call succeed and then treat the result as satisfying the
43
+ profile — a request that had to be weakened to be accepted did not verify what
44
+ it was supposed to verify.
45
+
46
+ Selecting `NativeCopy` is therefore useful now for the writing-side discipline it
47
+ imposes, and cannot yet produce a delivery. That is intended, not a defect.
48
+
49
+ ## 2. Review scope: local intent only
50
+
51
+ The four quadrants — account-bound vs persona-less, managed brand vs organic —
52
+ are real and the planner should be explicit about which one a batch is written
53
+ for. Record that as **intent**:
54
+
55
+ ```markdown
56
+ LocalReviewIntent:
57
+ accountBinding: account_bound | persona_less
58
+ brandTreatment: managed_brand | organic
59
+ HubReviewScopeStatus: Unavailable(ServerContractMissing)
60
+ ```
61
+
62
+ `LocalReviewIntent` is a planner declaration in matrix memory. It is **not** sent
63
+ to Hub, and it is **not** a claim about how Hub actually resolved the review.
64
+
65
+ Two disciplines follow from writing it down:
66
+
67
+ - **"Managed brand" means a managed brand context, not a brand string in the
68
+ body.** A batch does not become `organic` by omitting the brand context from
69
+ the review call. If the content is written for a managed brand, the intent is
70
+ `managed_brand` regardless of what the request carries.
71
+ - **Omitting an account is a decision, not an accident.** `persona_less` must be
72
+ a recorded choice. "We forgot to resolve the publishing account" and "this is
73
+ deliberately content-only work" produce identical review calls, so the
74
+ distinction only exists if the planner states it.
75
+
76
+ Do not invent or report a server-side scope status. When Hub later echoes a real
77
+ scope and the source of that scope, record what it returns; until then
78
+ `HubReviewScopeStatus` is `Unavailable(ServerContractMissing)` and the matrix
79
+ makes no claim about which quadrant the server believed it was reviewing.
80
+
81
+ ## 3. Tightenings that are in scope
82
+
83
+ **Each tightening is applied by exactly the stage named below, and the split is
84
+ not cosmetic.** Structural tightenings touch locked fingerprint fields, so only
85
+ `reddit-phase-brief` may apply them, at assignment. Register tightenings are about
86
+ how the prose reads, so the writer observes them while producing the first draft.
87
+
88
+ `reddit-content-rewrite` applies **neither kind**: it may not add a structural
89
+ constraint (that would change a locked field after the collision check) and it may
90
+ not newly impose or escalate a register rule (that would be house-voice
91
+ normalisation of a draft it was asked only to repair).
92
+
93
+ | Tightening | Owner stage | Rule |
94
+ | ------------------------------ | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
95
+ | Brand-mention ceiling | `reddit-phase-brief` (placement) | A per-post ceiling may be set for the batch. It constrains `BrandEntryMode` / placement **at assignment**, and never becomes a global default |
96
+ | Restricted `EndingMode` set | `reddit-phase-brief` | The profile may narrow the *allowed set* the Brief picks from. See §4 |
97
+ | PDP / spec-speak language | `reddit-content-writing` | Avoid product-page register and specification recitation where the account's `knowledgeAccess` does not support it |
98
+ | Fake-Reddit-English | `reddit-content-writing` | No slang, emoji, or broken grammar added to *seem* native. This is the existing fabricated-signal red line, stated in the other direction |
99
+ | Full official product names | `reddit-content-writing` | Only against an explicitly maintained, approved list of forbidden strings. Never inferred from an SKU or model name — see §6 |
100
+
101
+ **Scoring thresholds are out of scope.** A stricter numeric bar (a client's own
102
+ 100-point scale, a 90/80 two-tier gate) is **not** part of this train. It is not a
103
+ tightening you may apply, and no local computation may produce a profile score, a
104
+ profile pass, or delivery eligibility. If a client scale is useful for internal
105
+ discussion, keep it as a non-delivery preview and label it as such. The
106
+ authoritative verdict is Hub's, under `Default` semantics, read per
107
+ `references/content-review-gate.md`.
108
+
109
+ ## 4. Endings are assigned, not appended
110
+
111
+ A client requirement that posts end on a specific question is a constraint on
112
+ **which `EndingMode` the Brief may assign**, not a licence to append a question
113
+ to a finished draft.
114
+
115
+ `EndingMode` is one of the nine `LockedFingerprintFields` and one of the three
116
+ fields in the collision triple (`references/content-diversity-contract.md`). A
117
+ writer or rewriter adding a question ending changes a locked field, bypasses
118
+ structural pre-assignment, and voids the collision check that was run against the
119
+ old value.
120
+
121
+ So: the profile narrows the allowed set, `reddit-phase-brief` assigns from the
122
+ narrowed set and runs the collision check on the result, and the writer preserves
123
+ what was assigned. If the narrowed set leaves no collision-free option, that is a
124
+ `BlockingGap` back to planning — not a reason to append.
125
+
126
+ The same reasoning applies to every other tightening that touches a locked field:
127
+ express it as a constraint on assignment, never as a post-hoc edit.
128
+
129
+ ## 5. Content-logic types stay separate
130
+
131
+ A client taxonomy of content logic types — question, buying decision, personal
132
+ story, ownership experience, show & tell, styling, quality check — may be recorded
133
+ as a profile-local label:
134
+
135
+ ```markdown
136
+ NativeCopyLogicType: # profile-local label; optional
137
+ ```
138
+
139
+ It **may not** be mapped onto, merged with, or substituted for `ContentFunction`.
140
+ They are different taxonomies with different value sets, and routing already keys
141
+ off `ContentFunction`; overloading it would corrupt live routing decisions for
142
+ every non-profile batch.
143
+
144
+ **The ownership / brand-review collision is real and must be handled at
145
+ assignment.** `PostingTrigger: UnsolicitedTestimony` requires
146
+ `BrandEntryMode: 不出现`, and conversely any `BrandEntryMode ≠ 不出现` forbids
147
+ `UnsolicitedTestimony` (`references/content-diversity-contract.md`). A client
148
+ category that is simultaneously "ownership testimony" and "names the brand" cannot
149
+ be satisfied by both at once. Resolve it in the Brief by choosing which of the two
150
+ the post actually is, and record the choice. Any mapping table that quietly places
151
+ a brand-naming ownership post on `UnsolicitedTestimony` is a hard contradiction,
152
+ not a preference to reconcile in the draft.
153
+
154
+ ## 6. What the profile explicitly does not get
155
+
156
+ - **Forbidden product-name strings are maintained, never derived.** Approved
157
+ product facts (SKU, model name) cannot be recombined into an official
158
+ product-page title; attempting it produces both misses and false positives on
159
+ ordinary phrasing. Only an explicit, approved list of forbidden strings is
160
+ actionable, and a client who wants this enforcement has to supply and maintain
161
+ that list.
162
+ - **No cross-post template detection exists server-side.** The rolling
163
+ fingerprint index is a matrix memory file. It is strong within one project's
164
+ memory and it is **not** a service-side gate: it does not span authors,
165
+ campaigns, or runs, and it cannot see a sibling batch. A client pattern
166
+ checklist may be used as supplementary observation against the existing
167
+ fingerprint fields; it must not be presented as a guarantee that no two posts
168
+ anywhere share a template, and it must not become a second fingerprint system
169
+ alongside the contract's.
170
+ - **The brand-removal question is a diagnostic.** "Would this post still be worth
171
+ posting with the brand name deleted?" is a useful prompt for judging whether the
172
+ brand is the post's only payoff. It is not a mechanical rule: a vendor review, a
173
+ purchase-decision post, or a board that requires vendor identification is
174
+ legitimately incomplete without the brand. Ask whether the brand is the *sole*
175
+ payoff; never run a literal string deletion and fail the result.
176
+ - **Exemplar phrases are not a phrase library.** Sampled community wording may be
177
+ read as analysis or as negative examples. Copying another user's wording into a
178
+ draft is forbidden by both this profile and the matrix's own red lines.
179
+ - **No fixed output format.** A client's prescribed multi-section deliverable
180
+ format is not adopted. Its useful content maps onto existing handoff fields —
181
+ flagged risks into `BlockingGaps`, brand-mention reasoning into the Brief's
182
+ placement rationale. A second mandatory output shape layered over
183
+ `references/handoff-schemas.md` produces two contracts that disagree.
184
+ - **No relaxation of the fabricated-signal rules.** Nothing in the profile
185
+ licenses invented experience, invented flaws, invented community history, or
186
+ deliberate typos. Where the profile and the matrix red lines overlap, they are
187
+ the same rule stated once — do not restate it as a second rule with its own
188
+ wording.
189
+
190
+ ## 7. Versioning
191
+
192
+ The matrix package version, this profile, and the server's review semantics are
193
+ **independent identifiers and are not kept in step**. Do not require them to
194
+ match, and do not assert that a Hub response carries a particular review-semantics
195
+ version.
196
+
197
+ This profile has been authored against post-review `v4` and content-review `v5`
198
+ semantics. That is a statement about what it was written for, not a runtime
199
+ assertion: **never branch on a version string in a response.**
200
+
201
+ The capability the profile is waiting on is a server-stated review scope —
202
+ `review-scope.v1` in the plan's naming. When the server begins declaring its
203
+ capabilities and echoing the scope it resolved, this section is what changes: the
204
+ matrix will read that declaration and that scope, and `NativeCopyProfileStatus`
205
+ will follow from whether the required capability is present. Until then the status
206
+ stays `Unavailable(ServerContractMissing)`, and no skill infers the capability
207
+ from a version string, a successful call, or the absence of an error.