@sellable/mcp 0.1.0

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 (200) hide show
  1. package/.claude-plugin/plugin.json +12 -0
  2. package/.mcp.json +9 -0
  3. package/README.md +355 -0
  4. package/dist/api.d.ts +21 -0
  5. package/dist/api.js +73 -0
  6. package/dist/auth.d.ts +60 -0
  7. package/dist/auth.js +246 -0
  8. package/dist/engage-memory.d.ts +63 -0
  9. package/dist/engage-memory.js +354 -0
  10. package/dist/index-dev.d.ts +2 -0
  11. package/dist/index-dev.js +17 -0
  12. package/dist/index.d.ts +7 -0
  13. package/dist/index.js +8 -0
  14. package/dist/server.d.ts +1 -0
  15. package/dist/server.js +499 -0
  16. package/dist/skills.d.ts +11 -0
  17. package/dist/skills.js +97 -0
  18. package/dist/tools/auth.d.ts +30 -0
  19. package/dist/tools/auth.js +124 -0
  20. package/dist/tools/blueprint-commit.d.ts +174 -0
  21. package/dist/tools/blueprint-commit.js +286 -0
  22. package/dist/tools/bootstrap.d.ts +64 -0
  23. package/dist/tools/bootstrap.js +246 -0
  24. package/dist/tools/campaigns.d.ts +589 -0
  25. package/dist/tools/campaigns.js +892 -0
  26. package/dist/tools/cells.d.ts +58 -0
  27. package/dist/tools/cells.js +48 -0
  28. package/dist/tools/context.d.ts +88 -0
  29. package/dist/tools/context.js +271 -0
  30. package/dist/tools/csv-domains.d.ts +73 -0
  31. package/dist/tools/csv-domains.js +464 -0
  32. package/dist/tools/csv-linkedin.d.ts +102 -0
  33. package/dist/tools/csv-linkedin.js +712 -0
  34. package/dist/tools/direct-campaigns.d.ts +240 -0
  35. package/dist/tools/direct-campaigns.js +250 -0
  36. package/dist/tools/engage-bootstrap.d.ts +94 -0
  37. package/dist/tools/engage-bootstrap.js +205 -0
  38. package/dist/tools/engage-discovery.d.ts +78 -0
  39. package/dist/tools/engage-discovery.js +150 -0
  40. package/dist/tools/engage-memory.d.ts +181 -0
  41. package/dist/tools/engage-memory.js +143 -0
  42. package/dist/tools/engage-state.d.ts +72 -0
  43. package/dist/tools/engage-state.js +62 -0
  44. package/dist/tools/enrichment.d.ts +167 -0
  45. package/dist/tools/enrichment.js +174 -0
  46. package/dist/tools/flow-preflight.d.ts +68 -0
  47. package/dist/tools/flow-preflight.js +138 -0
  48. package/dist/tools/framework.d.ts +44 -0
  49. package/dist/tools/framework.js +153 -0
  50. package/dist/tools/interaction-mode.d.ts +27 -0
  51. package/dist/tools/interaction-mode.js +102 -0
  52. package/dist/tools/leads.d.ts +2417 -0
  53. package/dist/tools/leads.js +2307 -0
  54. package/dist/tools/linkedin.d.ts +210 -0
  55. package/dist/tools/linkedin.js +229 -0
  56. package/dist/tools/navigation.d.ts +91 -0
  57. package/dist/tools/navigation.js +381 -0
  58. package/dist/tools/one-off.d.ts +229 -0
  59. package/dist/tools/one-off.js +273 -0
  60. package/dist/tools/processing.d.ts +70 -0
  61. package/dist/tools/processing.js +56 -0
  62. package/dist/tools/prompts.d.ts +211 -0
  63. package/dist/tools/prompts.js +210 -0
  64. package/dist/tools/provider-preflight.d.ts +21 -0
  65. package/dist/tools/provider-preflight.js +59 -0
  66. package/dist/tools/readiness.d.ts +261 -0
  67. package/dist/tools/readiness.js +510 -0
  68. package/dist/tools/rows.d.ts +126 -0
  69. package/dist/tools/rows.js +105 -0
  70. package/dist/tools/rubrics.d.ts +497 -0
  71. package/dist/tools/rubrics.js +681 -0
  72. package/dist/tools/senders.d.ts +44 -0
  73. package/dist/tools/senders.js +69 -0
  74. package/dist/tools/sequencer.d.ts +127 -0
  75. package/dist/tools/sequencer.js +194 -0
  76. package/dist/tools/tables.d.ts +35 -0
  77. package/dist/tools/tables.js +36 -0
  78. package/dist/tools/verify-row.d.ts +36 -0
  79. package/dist/tools/verify-row.js +38 -0
  80. package/dist/tools/workspaces.d.ts +140 -0
  81. package/dist/tools/workspaces.js +139 -0
  82. package/dist/utils/workspace-root.d.ts +1 -0
  83. package/dist/utils/workspace-root.js +39 -0
  84. package/package.json +46 -0
  85. package/skills/building-gtm-tables/SKILL.md +216 -0
  86. package/skills/building-gtm-tables/core/auto-execute.yaml +19 -0
  87. package/skills/building-gtm-tables/core/blueprint-schema.json +72 -0
  88. package/skills/building-gtm-tables/references/brief-to-blueprint.md +334 -0
  89. package/skills/building-gtm-tables/references/column-type-catalog.md +318 -0
  90. package/skills/building-gtm-tables/references/common-blueprints.fixtures.ts +199 -0
  91. package/skills/building-gtm-tables/references/common-blueprints.md +44 -0
  92. package/skills/building-gtm-tables/references/failure-taxonomy.md +197 -0
  93. package/skills/building-gtm-tables/references/uat-seed-prompts.md +37 -0
  94. package/skills/building-gtm-tables/references/verify-loop.md +74 -0
  95. package/skills/campaign-messages/SKILL.md +173 -0
  96. package/skills/campaign-messages/flow.v1.json +75 -0
  97. package/skills/craft-message/SKILL.md +401 -0
  98. package/skills/create-campaign/ARCHITECTURE.md +232 -0
  99. package/skills/create-campaign/DISCUSS.md +296 -0
  100. package/skills/create-campaign/FLOW_ASCII.md +240 -0
  101. package/skills/create-campaign/HOST-PARITY-CHECKLIST.md +49 -0
  102. package/skills/create-campaign/README.md +142 -0
  103. package/skills/create-campaign/SKILL.md +286 -0
  104. package/skills/create-campaign/context/README.md +67 -0
  105. package/skills/create-campaign/context/_TEMPLATE.md +12 -0
  106. package/skills/create-campaign/context/context.md +35 -0
  107. package/skills/create-campaign/context/learnings.md +16 -0
  108. package/skills/create-campaign/context/registry.json +19 -0
  109. package/skills/create-campaign/core/flow.v1.json +217 -0
  110. package/skills/create-campaign/core/policy.md +191 -0
  111. package/skills/create-campaign/core/providers/apollo.json +35 -0
  112. package/skills/create-campaign/core/providers/prospeo.json +34 -0
  113. package/skills/create-campaign/core/providers/registry.json +31 -0
  114. package/skills/create-campaign/core/providers/sales-nav.json +37 -0
  115. package/skills/create-campaign/core/providers/signal-discovery.json +42 -0
  116. package/skills/create-campaign/references/brief-template.md +64 -0
  117. package/skills/create-campaign/references/campaign-quality.md +84 -0
  118. package/skills/create-campaign/references/copy-calibration-examples.md +120 -0
  119. package/skills/create-campaign/references/offer-patterns.md +108 -0
  120. package/skills/create-campaign/references/provider-selection-strategy.md +212 -0
  121. package/skills/create-campaign/references/question-examples.md +167 -0
  122. package/skills/create-campaign/references/token-fill-examples.md +81 -0
  123. package/skills/create-campaign-brief/ARCHITECTURE.md +72 -0
  124. package/skills/create-campaign-brief/DISCUSS.md +64 -0
  125. package/skills/create-campaign-brief/README.md +176 -0
  126. package/skills/create-campaign-brief/SKILL.md +537 -0
  127. package/skills/create-campaign-brief/references/brief-synthesis-rules.md +100 -0
  128. package/skills/create-campaign-brief/references/brief-template.md +220 -0
  129. package/skills/create-campaign-brief/references/campaign-idea-options.md +30 -0
  130. package/skills/create-campaign-brief/references/copy-appendix-template.md +62 -0
  131. package/skills/create-campaign-brief/references/draft-lifecycle.md +23 -0
  132. package/skills/create-campaign-brief/references/examples/MANIFEST.json +89 -0
  133. package/skills/create-campaign-brief/references/examples/briefs/clover.md +223 -0
  134. package/skills/create-campaign-brief/references/examples/briefs/galley.md +222 -0
  135. package/skills/create-campaign-brief/references/examples/briefs/gelee.md +220 -0
  136. package/skills/create-campaign-brief/references/examples/briefs/hey-digital.md +234 -0
  137. package/skills/create-campaign-brief/references/examples/briefs/persona.md +231 -0
  138. package/skills/create-campaign-brief/references/examples/briefs/revvix.md +220 -0
  139. package/skills/create-campaign-brief/references/examples/briefs/sellable-dev.md +220 -0
  140. package/skills/create-campaign-brief/references/examples/briefs/superposition.md +233 -0
  141. package/skills/create-campaign-brief/references/examples/briefs/superpower.md +219 -0
  142. package/skills/create-campaign-brief/references/examples/briefs/westpark-villas.md +220 -0
  143. package/skills/create-campaign-brief/references/icp-lock-question-bank.md +43 -0
  144. package/skills/create-campaign-brief/references/messaging-inputs.md +58 -0
  145. package/skills/create-campaign-brief/references/output-acceptance-rubric.md +62 -0
  146. package/skills/create-campaign-brief/references/phase75-active-runtime-message-pack.md +248 -0
  147. package/skills/create-campaign-brief/references/phase75-canonical-brief-template.md +319 -0
  148. package/skills/create-campaign-brief/references/phase75-good-brief-and-messaging-examples.md +445 -0
  149. package/skills/create-campaign-brief/references/quick-research-protocol.md +39 -0
  150. package/skills/create-campaign-brief/references/reference-sheet-protocol.md +60 -0
  151. package/skills/create-campaign-brief/references/zero-shot-iteration-rules.md +66 -0
  152. package/skills/create-campaign-v2/SKILL.md +1619 -0
  153. package/skills/create-campaign-v2/core/auto-execute.README.md +219 -0
  154. package/skills/create-campaign-v2/core/auto-execute.yaml +121 -0
  155. package/skills/create-campaign-v2/core/flow.v2.json +1643 -0
  156. package/skills/create-campaign-v2/core/policy.md +82 -0
  157. package/skills/create-campaign-v2/references/ai-tells.md +253 -0
  158. package/skills/create-campaign-v2/references/approval-gate-framing.md +346 -0
  159. package/skills/create-campaign-v2/references/draft-lifecycle.md +110 -0
  160. package/skills/create-campaign-v2/references/escalation-ladder.md +119 -0
  161. package/skills/create-campaign-v2/references/filter-leads.md +495 -0
  162. package/skills/create-campaign-v2/references/final-handoff-contract.md +176 -0
  163. package/skills/create-campaign-v2/references/gold-standard-message-examples.md +394 -0
  164. package/skills/create-campaign-v2/references/gold-standard-message-patterns.md +314 -0
  165. package/skills/create-campaign-v2/references/gold-standard-message-validation-example.md +212 -0
  166. package/skills/create-campaign-v2/references/lead-validation-preview.md +172 -0
  167. package/skills/create-campaign-v2/references/parallel-critique-protocol.md +368 -0
  168. package/skills/create-campaign-v2/references/sample-validation-loop.md +289 -0
  169. package/skills/create-campaign-v2/references/step-13-import-leads.md +151 -0
  170. package/skills/create-campaign-v2/references/step-15-re-cascade.md +90 -0
  171. package/skills/create-campaign-v2/references/thomas-revision-filters.md +521 -0
  172. package/skills/create-campaign-v2/references/thomas-variant-selection.md +202 -0
  173. package/skills/create-campaign-v2/references/tier-routing-matrix.md +66 -0
  174. package/skills/create-campaign-v2/references/validation-criteria.md +367 -0
  175. package/skills/create-campaign-v2/references/watch-link-handoff.md +106 -0
  176. package/skills/create-campaign-v2-validation/SKILL.md +296 -0
  177. package/skills/create-post/SKILL.md +1308 -0
  178. package/skills/create-rubric/SKILL.md +251 -0
  179. package/skills/engage/SKILL.md +549 -0
  180. package/skills/engage/core/README.md +23 -0
  181. package/skills/engage/core/proven-searches.json +11 -0
  182. package/skills/engage/core/style-guide.template.md +47 -0
  183. package/skills/engage/core/tracked-people.json +10 -0
  184. package/skills/enrich-prospects/SKILL.md +97 -0
  185. package/skills/find-leads/SKILL.md +467 -0
  186. package/skills/generate-messages/SKILL.md +2361 -0
  187. package/skills/interview/SKILL.md +132 -0
  188. package/skills/interview/core/ENGAGE_STYLE_GUIDE.template.md +54 -0
  189. package/skills/interview/core/ICP.template.md +54 -0
  190. package/skills/interview/core/VOICE_PROFILE.template.md +101 -0
  191. package/skills/providers/apollo.md +520 -0
  192. package/skills/providers/prospeo.md +398 -0
  193. package/skills/providers/sales-nav.md +372 -0
  194. package/skills/providers/signal-discovery.md +495 -0
  195. package/skills/research/SKILL.md +258 -0
  196. package/skills/research/config.json +9 -0
  197. package/skills/research/override.md +13 -0
  198. package/skills/research-prospect/SKILL.md +99 -0
  199. package/skills/research-sender/SKILL.md +158 -0
  200. package/skills/workflow-sequences/SKILL.md +85 -0
@@ -0,0 +1,202 @@
1
+ # Thomas Variant Selection
2
+
3
+ This reference governs which rows in a Plan 85-03 critique sample earn
4
+ an Opus / craft-message rewrite. Load this file only when
5
+ `messaging.critique.enabled === true` AND
6
+ `messaging.critique.opus.enabled === true` in
7
+ `core/auto-execute.yaml`. When Opus is off, the protocol never
8
+ consults variant selection.
9
+
10
+ ## Principle
11
+
12
+ Opus is reserved for the highest-value subset only. A critique pass
13
+ that sends every row to Opus defeats the cost model and blurs the
14
+ signal about what the plain generator already gets right. Variant
15
+ selection is a narrow filter on top of the bounded critique sample,
16
+ not a second sample.
17
+
18
+ The rule of thumb: if the plain generator shipped a message that's
19
+ "fine but not sharp" and the lead is unusually high value, Opus
20
+ earns its place. Otherwise, the synthesizer's rewrite is enough.
21
+
22
+ ## When This File Loads
23
+
24
+ Load this file only when BOTH flags are on:
25
+
26
+ - `messaging.critique.enabled === true`
27
+ - `messaging.critique.opus.enabled === true`
28
+
29
+ Do not consult variant selection when Opus is off. Do not consult it
30
+ during Step 13, Step 14, or Step 16.
31
+
32
+ ## Inputs
33
+
34
+ - The row being critiqued (lead row plus its plain generated message).
35
+ - The three critic envelopes for this row (targeting, copy, voice).
36
+ - `lead-sample.json` for the current cohort.
37
+ - `messaging.critique.opus.*` config values.
38
+ - The current Opus budget headroom (tracked across the pass).
39
+
40
+ ## Hard Caps
41
+
42
+ Two caps that short-circuit selection before any analysis:
43
+
44
+ 1. **`opus.maxMessagesPerPass`** (default 2). Once N rows have been
45
+ escalated to Opus in the current pass, every subsequent row falls
46
+ back to the non-Opus synthesis output regardless of how sharp the
47
+ selection signals are. Log `opus_cap_tripped` with the row id.
48
+ 2. **`opus.budgetUsdCap`** (default 1.5 USD). Budget trip halts Opus
49
+ for the rest of this pass. Log `opus_budget_cap_tripped`. The
50
+ non-Opus critique continues on remaining rows.
51
+
52
+ If either cap is tripped at row N, variant selection short-circuits
53
+ to "no Opus" for rows N+1, N+2, ... of the current pass.
54
+
55
+ ## Selection Signals
56
+
57
+ A row EARNS Opus only when the signals below point the same way. One
58
+ strong signal is not enough. Three is usually required unless a
59
+ single signal is a hard "buyer of buyers" marker.
60
+
61
+ ### Signal 1 — Lead value tier
62
+
63
+ The lead row reads as high-value on the validated filter:
64
+
65
+ - Title matches the buyer role the brief calls out as the primary
66
+ decision-maker (not an adjacent influencer).
67
+ - Company size / stage matches the brief's ideal band (not merely
68
+ "in bucket").
69
+ - Company name is a named target account, or a logo the brief
70
+ treats as a proof marker for this motion.
71
+
72
+ Not high-value:
73
+
74
+ - Generic IC title at a bucket-match company.
75
+ - Right function but visibly wrong stage (pre-seed when brief says
76
+ Series B+).
77
+ - Company that barely passed the filter on a weak supporting rule.
78
+
79
+ ### Signal 2 — Critic divergence toward "revise"
80
+
81
+ The three critic envelopes disagree about how to fix the plain
82
+ message, and at least two of them have `verdict = "revise"` with
83
+ substantive `revisions[*]`. In other words: the plain message
84
+ clearly has room, and the critics can SEE what's missing — but the
85
+ synthesizer's merge is likely to be a compromise rather than a
86
+ clean rewrite.
87
+
88
+ Not a divergence signal:
89
+
90
+ - All three critics said `keep`. The plain message is already good;
91
+ Opus would over-polish.
92
+ - One critic said `reject` and the other two said `keep`. The
93
+ rewrite is a fallback case, not a high-value upgrade case.
94
+ - Two critics said `revise` but their revisions point at the same
95
+ sentence. The synthesizer can merge that cleanly.
96
+
97
+ ### Signal 3 — Personalization upside
98
+
99
+ The lead row carries a rich per-lead signal that the plain
100
+ generator only partially used:
101
+
102
+ - A per-lead post, hire, or visible tool the brief supports but the
103
+ plain opener didn't reference.
104
+ - A named account the brief lists as a named target.
105
+ - An event the brief's proof inventory can anchor to but the plain
106
+ message settled for a generic hook.
107
+
108
+ Not personalization upside:
109
+
110
+ - Plain message already anchored on the per-lead signal.
111
+ - The lead has no per-lead signal; only category-level personalization
112
+ is available.
113
+ - The richest signal in the row is a field the brief doesn't support
114
+ (proposing a NEW token — that path is operator review, not Opus).
115
+
116
+ ### Signal 4 — Motion sharpness payoff
117
+
118
+ The matched motion is one where a crafted rewrite visibly changes
119
+ buyer response (proof-led specialist, stack-replacement, job-post
120
+ signal). Event-led and signal-led motions where the hook is already
121
+ concrete rarely need Opus — the plain generator's hook carries.
122
+
123
+ Not a sharpness signal:
124
+
125
+ - Fallback motion with a weak hook. Opus won't rescue a motion-
126
+ mismatched draft; send it back to the brief instead.
127
+ - Event-led motion where the plain message's opener already nailed
128
+ the event.
129
+
130
+ ## Selection Rule
131
+
132
+ A row earns Opus only when:
133
+
134
+ - At least three of the four signals above point toward high value
135
+ AND at least one signal is Signal 1 (lead value tier), OR
136
+ - Signal 1 is a hard "named target account or logo-tier buyer" marker,
137
+ AND at least one other signal is positive.
138
+
139
+ If neither clause is true, the row uses the non-Opus synthesizer
140
+ output. Log `opus_not_selected` with the row id and the signals
141
+ considered.
142
+
143
+ ## Selection Output
144
+
145
+ When a row is selected for Opus, the critique protocol:
146
+
147
+ 1. Checks the Opus budget and count caps. If either is tripped,
148
+ skip Opus for this row (and subsequent rows in this pass) and
149
+ keep the non-Opus rewrite.
150
+ 2. Runs `craft_message(row, synthesis_rewrite, critics_out)`.
151
+ 3. Applies the SAME token-contract enforcement to the Opus output
152
+ that it applies to any synthesis output:
153
+ - `rejectOnFakeProof` — invented proof forces fallback.
154
+ - `rejectOnUnsupportedToken` — unsupported token forces fallback.
155
+ - `synthesis.enforceFinalizerPass` — finalizer pass must pass.
156
+ 4. On pass, persists the Opus rewrite for that row. On fail, falls
157
+ back to the non-Opus rewrite (and if that also failed, to the
158
+ plain message).
159
+ 5. Logs `opus_selected` (with signals) on entry and
160
+ `opus_rewrite_persisted` / `opus_rewrite_rejected` on exit.
161
+
162
+ ## What Opus Is NOT Allowed To Do
163
+
164
+ Opus does not get a different token contract. Every rule that binds
165
+ the plain generator and the non-Opus synthesizer binds Opus too:
166
+
167
+ - Use only supported tokens from the validated brief.
168
+ - No invented metric, logo, customer, funding detail, or product claim.
169
+ - No personalization that can't be traced to `lead-sample.json`.
170
+ - No new proposed token promoted into the persisted rewrite.
171
+ - Same shape rules: one-sentence-per-line body, PS earns its place,
172
+ no resume-list PS, no jargon a 5th-grade reader can't parse.
173
+
174
+ An Opus rewrite that breaks any of these is a silent fallback, not
175
+ a win.
176
+
177
+ ## Fallback Priority
178
+
179
+ When multiple rewrite layers are in play, the persisted message
180
+ follows this priority (first passing wins):
181
+
182
+ 1. Opus rewrite (if selected AND token-contract-clean AND budget
183
+ available).
184
+ 2. Non-Opus synthesis rewrite (if token-contract-clean AND
185
+ finalizer-clean).
186
+ 3. Plain generated message (always a valid fallback).
187
+
188
+ The plain message is NEVER skipped in favor of a broken rewrite.
189
+
190
+ ## Hard Rules
191
+
192
+ - Opus is off by default. Variant selection is never consulted when
193
+ Opus is off.
194
+ - A row earns Opus only when multiple selection signals align. One
195
+ signal is not enough unless it's a named-target or logo-tier Signal
196
+ 1.
197
+ - `opus.maxMessagesPerPass` and `opus.budgetUsdCap` are hard caps.
198
+ Once tripped, the rest of the pass runs non-Opus.
199
+ - Opus is bound by the same token contract as the plain generator
200
+ and the non-Opus synthesizer.
201
+ - Token-contract-clean Opus > non-Opus synthesis > plain message,
202
+ in that fallback order. Never persist a broken rewrite.
@@ -0,0 +1,66 @@
1
+ # Tier Routing Matrix
2
+
3
+ This reference governs `attach_recommended_sequence` routing decisions
4
+ made at Step 16 (`awaiting-user-greenlight`). Load before calling
5
+ `attach_recommended_sequence` and on every resume into Step 16.
6
+
7
+ ## Principle
8
+
9
+ The sequence template a campaign uses depends on the sender's LinkedIn
10
+ tier. Premium / SalesNav senders have InMail access on open profiles;
11
+ Standard senders do not and must go through invite → accept → DM.
12
+
13
+ The tail reads the sender's current tier + synced limits (via the
14
+ sender-limits sync path fixed in commit `4002b8878`) and picks the
15
+ right template automatically. Operators can override on the campaign
16
+ builder UI but the autonomous tail default is this matrix.
17
+
18
+ ## Matrix
19
+
20
+ | Sender tier | Template | Fallback | Notes |
21
+ | ----------------------------------- | ---------------------- | ----------------------------------------- | -------------------------------------- |
22
+ | **Premium InMail** | `INMAIL_OPEN` | `INVITE → DM` if open profile check fails | Direct InMail on open profiles |
23
+ | **Sales Navigator Core / Advanced** | `INMAIL_OPEN` | `INVITE → DM` if credit exhausted | Open profile + InMail credits |
24
+ | **Sales Navigator Advanced Plus** | `INMAIL_OPEN` | `INVITE → DM` | Team seat, same InMail path |
25
+ | **LinkedIn Premium Business** | `INMAIL_OPEN` | `INVITE → DM` | Personal Premium tier |
26
+ | **LinkedIn Standard (no Premium)** | `INVITE → accept → DM` | (none; must go invite path) | Cannot send InMail; must connect first |
27
+
28
+ ## Tier detection
29
+
30
+ Tier is detected via `enrichProfile` on the sender's LinkedIn URL. The
31
+ response includes `liTier` (one of `PREMIUM_INMAIL`, `SALES_NAVIGATOR_CORE`,
32
+ `SALES_NAVIGATOR_ADVANCED`, `SALES_NAVIGATOR_ADVANCED_PLUS`,
33
+ `PREMIUM_BUSINESS`, `STANDARD`). `attach_recommended_sequence` reads
34
+ this value + the sender-limits sync (which triggered post-enrich per
35
+ commit `4002b8878`) to pick the template.
36
+
37
+ ## Why `attach_recommended_sequence` is load-bearing
38
+
39
+ Without tier-aware routing, the tail would default to a single template
40
+ (historically INVITE → DM) and waste InMail credits on SalesNav senders
41
+ OR try to InMail from a Standard sender (which fails with `not_authorized`
42
+ per Unipile).
43
+
44
+ Commit `14e4cf59a` shipped the tier routing; commit `4002b8878` fixed
45
+ a race where tier limits drifted from actual limits after `enrichProfile`
46
+ writes. Both are required for the matrix to work reliably.
47
+
48
+ ## Validation before `attach_recommended_sequence`
49
+
50
+ The tail MUST verify:
51
+
52
+ 1. Sender LinkedIn is enriched within the last 24 hours (fresh tier data).
53
+ 2. `syncSenderLimits` has been called since the last `enrichProfile`
54
+ write (closes the `4002b8878` race window).
55
+ 3. The detected tier maps cleanly to one of the template rows above.
56
+ If tier is `UNKNOWN` or ambiguous, escalate rather than guess.
57
+
58
+ ## Hard rules
59
+
60
+ - `attach_recommended_sequence` is the only way to pick a template in
61
+ the autonomous tail. Do NOT hand-wire a sequence at Step 16.
62
+ - Tier mismatch at attach time is an escalation, NOT a silent fallback.
63
+ - A Standard sender MUST NOT be attached to an INMAIL_OPEN template.
64
+ This would produce sends that fail at Unipile with `not_authorized`.
65
+ - An `attach_recommended_sequence` that returns `template: UNKNOWN`
66
+ is an escalation — do not proceed to Step 16 greenlight.
@@ -0,0 +1,367 @@
1
+ # Validation Criteria
2
+
3
+ Phase 84 is a chained-artifact validation pass over the Phase 83 brief. The
4
+ goal is to preserve the brief thesis while proving that leads, filters, and
5
+ message direction hold up against real preview evidence.
6
+
7
+ ## Primary Contract
8
+
9
+ - `brief.md` is the Phase 83 thesis input
10
+ - `lead-review.md` and `lead-sample.json` are the required outputs of lead preview
11
+ - `lead-filter.md` is the primary output of the middle step
12
+ - `rubric.json` is optional and secondary
13
+ - `message-validation.md` is the final validation output
14
+
15
+ ## What Each Step May Change
16
+
17
+ ### Find Leads
18
+
19
+ Allowed:
20
+
21
+ - create or replace `lead-review.md`
22
+ - create or replace `lead-sample.json`
23
+
24
+ Not allowed:
25
+
26
+ - rewrite the Phase 83 thesis in `brief.md`
27
+ - create `lead-filter.md`
28
+ - create `message-validation.md`
29
+ - mutate campaign state
30
+
31
+ ### Filter Leads
32
+
33
+ Allowed:
34
+
35
+ - create or replace `lead-filter.md`
36
+ - create or replace `rubric.json`
37
+ - append `recent_posts[]` arrays to FIT rows in `lead-sample.json` as
38
+ part of the Post-Filter Signal Enrichment Pass (see `filter-leads.md`
39
+ — top K=10 FIT rows on Sales Nav lane only, via
40
+ `mcp__sellable__fetch_linkedin_posts`)
41
+
42
+ Not allowed:
43
+
44
+ - replace `lead-review.md` or `lead-sample.json` unless a lead revision was requested
45
+ - create approval packet or commit gate before filter completion
46
+ - rely on more than one optional/supporting filter rule
47
+ - call `check_rubric`
48
+ - call `save_rubrics`
49
+
50
+ ### Generate Message
51
+
52
+ Allowed:
53
+
54
+ - create or replace `message-validation.md`
55
+
56
+ Not allowed:
57
+
58
+ - run before `lead-review.md` and `lead-sample.json` exist
59
+ - require or infer `campaignId`
60
+ - call `update_cell`
61
+ - call `update_campaign_brief`
62
+ - call `get_rows`
63
+ - call `get_campaign`
64
+ - fetch fresh web or LinkedIn research
65
+ - invent personalization signals that were not validated in `lead-sample.json`
66
+ - widen safe claims beyond `brief.md`
67
+ - leave unresolved `{{token}}`
68
+ - introduce unsupported tokens
69
+ - invent proof, metrics, logos, or customer names
70
+ - skip the dry-mode preamble
71
+
72
+ Required:
73
+
74
+ - **Gold-standard skeleton enforcement.** When the brief's Message
75
+ Thesis says "no greeting / no warm-up", VALIDATE against the
76
+ gold-standard archive (`references/gold-standard-message-examples.md`).
77
+ At least one gold example must exist with that opener style. If NONE
78
+ exists, surface the deviation as a WARNING in `message-validation.md`
79
+ Findings and require explicit operator confirmation before shipping
80
+ the candidate. The default opener is `Hey {{first_name}},` on its own
81
+ line, followed by a blank line and then the first real sentence, unless
82
+ the brief justifies absence with reference to a specific archived winner.
83
+ If a candidate uses `two options:`, option `a)` and option `b)` must be
84
+ separated by a blank line so the CTA scans cleanly on mobile.
85
+ - **All 5 Substance Filters from `thomas-revision-filters.md` MUST
86
+ PASS per candidate.** A candidate that fails any substance filter
87
+ (Earned-right, Presumption, Vague-proof, Read-as-1:1, Founder-origin
88
+ coherence) is BLOCKED. The Finalizer Pass CANNOT select a blocked
89
+ candidate. If all 3 candidates fail, route to `revise-message` with
90
+ the failure reasons enumerated per candidate (cite the filter name and
91
+ the offending line).
92
+
93
+ ## Lead Preview Expectations
94
+
95
+ Lead preview must:
96
+
97
+ - use `find-leads` campaignless preview mode
98
+ - avoid `campaignOfferId`
99
+ - avoid `import_leads`
100
+ - avoid `selectedLeadListId`
101
+ - avoid `confirm_lead_list`
102
+ - avoid DB-backed campaign mutation
103
+
104
+ `lead-review.md` must include:
105
+
106
+ - status: `confirmed`, `rejected`, or `unclear`
107
+ - confidence
108
+ - provider path used
109
+ - preview count
110
+ - ICP match rate
111
+ - volume comparison
112
+ - expected LinkedIn funnel: likely connection acceptance range, likely reply
113
+ range, and whether the estimate is sample-backed, historical, founder-supplied,
114
+ or directional
115
+ - source decision: best path, why it won, pros, cons/tradeoffs, and discarded
116
+ source paths with the reason each lost
117
+ - false-positive patterns
118
+ - recommendation for `filter leads` or revision
119
+
120
+ For normal LinkedIn discovery, `lead-review.md` must include literal
121
+ customer-visible headings:
122
+
123
+ - `## Source Decision`
124
+ - `## Expected LinkedIn Funnel`
125
+ - `## Pros`
126
+ - `## Tradeoffs`
127
+ - `## Discarded Paths`
128
+
129
+ `## Expected LinkedIn Funnel` must include expected source volume, expected
130
+ usable leads after filtering, likely connection acceptance range, likely reply
131
+ range, and estimate basis. If exact performance data is unavailable, use a
132
+ directional range and label it `directional`, not definitive.
133
+
134
+ `lead-sample.json` must include enough structured lead data for downstream
135
+ filtering:
136
+
137
+ - person name
138
+ - title
139
+ - company
140
+ - profile URL when available
141
+ - source provider
142
+ - any visible signals used in the review
143
+
144
+ ## Filter Leads Expectations
145
+
146
+ Filter leads must treat `lead-filter.md` as the primary artifact.
147
+
148
+ `lead-filter.md` must include:
149
+
150
+ - `Status`
151
+ - `Decision`
152
+ - `Who We'll Keep`
153
+ - `Who We'll Exclude`
154
+ - `Sample False Positives`
155
+ - `Pass Rate`
156
+ - `Recommendation`
157
+ - `Implementation Details`
158
+
159
+ `lead-filter.md` should also include:
160
+
161
+ - one `Optional Supporting Rule` only when it clearly helps downstream
162
+ messaging or prioritization
163
+ - technical `Required Keep Rules` and `Required Exclude Rules` only when needed
164
+ to preserve implementation detail; if present, put them after the
165
+ user-facing sections
166
+ - explicit notes tying repeated false positives from the sample to the final
167
+ exclude rules
168
+
169
+ Filter rules should preserve the recurring campaign families when they are
170
+ relevant to the brief and sample:
171
+
172
+ - buyer role and seniority
173
+ - wrong-function exclusion
174
+ - company-type exclusion
175
+ - competitor/vendor/intermediary exclusion
176
+ - geography
177
+ - company size
178
+ - active current-role status
179
+
180
+ `rubric.json`, when present, must be a translation of the same keep/exclude
181
+ rules and not a second scoring framework:
182
+
183
+ - use the production rubric fields: `checkName`, `description`, `criterion`,
184
+ `reason`, `isRequiredCheck`, `allowPartialCredit`, and `strictMatching`
185
+ - default all rules to `isRequiredCheck: true`
186
+ - allow at most one `isRequiredCheck: false` supporting rule
187
+ - prefer 2-5 total criteria; do not map every bullet to a separate rubric row
188
+ - include the same 2-5 production-shaped criteria in `lead-filter.md` under
189
+ `Implementation Details` as fenced JSON even when `rubric.json` is omitted
190
+ - require `Implementation Details` JSON to parse into an object with
191
+ `leadScoringRubrics`
192
+ - require every criterion to be answerable from row data, enrichment, or public
193
+ research
194
+ - reject duplicate rules that restate the same idea
195
+ - if the sidecar fails to parse, continue with `lead-filter.md` and block only
196
+ downstream sidecar consumers until fixed
197
+
198
+ ## Message Validation Expectations
199
+
200
+ Message validation must run in caller-declared `DRY MODE`.
201
+
202
+ Inputs:
203
+
204
+ - `brief.md`
205
+ - `lead-filter.md`
206
+ - `lead-sample.json`
207
+ - `gold-standard-message-examples.md` when `brief.md` lacks a message appendix
208
+
209
+ `message-validation.md` must include:
210
+
211
+ - `Status`
212
+ - `Mode: DRY MODE (no DB mutation)`
213
+ - `Template Used`
214
+ - `Primary Example`
215
+ - `Secondary Influence`
216
+ - `Lead Sample Basis`
217
+ - `Strongest Reply Reason`
218
+ - `Pre-Draft Buyer-Role Analysis`
219
+ - `Campaign Element Pool`
220
+ - `Gold Standard Strategy Map`
221
+ - `Current Campaign Translation`
222
+ - `Element Scoring`
223
+ - `Agent Dialogue Cross-Review`
224
+ - `Proof Inventory`
225
+ - `Token Fill Rules`
226
+ - `Token Adherence Table`
227
+ - `Candidate Messages`
228
+ - `Finalizer Pass`
229
+ - `Selected Winner`
230
+ - `Findings`
231
+ - `Recommendation`
232
+
233
+ The sample set must:
234
+
235
+ - contain 2-3 sample messages
236
+ - use the find-leads message handoff rows or probable good-fit rows from
237
+ `lead-sample.json`; if `lead-filter.md` already exists, use only leads that
238
+ still pass it
239
+ - use only supported tokens documented in the brief
240
+ - contain no unresolved `{{token}}` placeholders
241
+ - resolve every token used in the sample output
242
+ - contain no invented proof, metrics, logos, or customer names
243
+ - keep proof claims inside the brief's safe-claims boundary
244
+ - reject personalization that cannot be traced back to `lead-sample.json`
245
+ - apply Thomas revision filters before the step is marked `confirmed`
246
+ - choose the highest-specificity validated strategy available in this order:
247
+ event-led, signal-led, job-post-led, then proof-led specialist fallback
248
+ - retrieve against the full archived gold-standard library and choose a
249
+ Primary Example plus at most one narrow Secondary Influence
250
+ - run an `Agent Dialogue Cross-Review` before drafts survive: skeptical
251
+ prospect, offer strategist, and gold-standard editor must reconcile to
252
+ approved elements and banned elements before candidate messages are written
253
+ - **treat the archived examples as the quality bar and motion reference,
254
+ not a paste source.** Write fresh sentences from the validated brief that
255
+ could plausibly belong in the archive for this motion
256
+ - exact-template preservation applies **only when the archived winner is the
257
+ same company as the brief**; otherwise match the motion skeleton without
258
+ recoloring the exemplar's wording
259
+ - build a proof inventory before drafting and use at least one compelling proof
260
+ element when the brief supports it
261
+ - document token fill rules before drafting the winner
262
+ - draft 3 candidates, then run a Finalizer Pass that picks the best opener,
263
+ proof sentence, bridge, and CTA across them and assembles the winner
264
+ - the winner entry must name which candidate each borrowed piece came from,
265
+ or declare that one candidate swept the Finalizer Pass (with the winning
266
+ axis named)
267
+ - write at a 5th-grade reading level by default. Jargon is limited to
268
+ brief-native product and tool names (e.g. Shopify, HubSpot, ServiceNow).
269
+ Cut glue language — any B2B compound noun the buyer wouldn't say naturally
270
+ in conversation — even when the brief uses it
271
+ - Casing must look intentional. Lowercase casual style is allowed for common
272
+ words when the chosen motion uses it, but proper nouns, recipient names,
273
+ company names, product names, acronyms, and the pronoun `I` stay capitalized.
274
+ Do not output broken casing like `i'm`, `futureclinic`, or `michael`
275
+ - higher reading levels are only allowed when the archived winner for this
276
+ motion is the **same company as the brief** and uses denser language
277
+ deliberately (e.g. Superposition drafting for Superposition). Lowercase-casual
278
+ motions still read at a plain level and should not be flattened into
279
+ polished prose
280
+ - put a blank line between every sentence in the body, so the body renders
281
+ as a sequence of one-line paragraphs separated by blank lines — not as
282
+ multi-sentence paragraphs. Target 5-8 one-line paragraphs total
283
+ - split sentences that are above ~15 words or carry more than one comma
284
+ - do not stack three workflows into one mechanism line or one comma list.
285
+ Give each workflow its own one-sentence paragraph, or pick the most
286
+ painful one and drop the others
287
+ - **product clarity is non-negotiable**: a cold reader must be able to state
288
+ in one sentence what the product does. Require a crisp `Product is an X
289
+ that does Y` anchor sentence before any action breakdown, then one action
290
+ per one-line paragraph. Do not gesture at "that chain" / "the stack" /
291
+ "that work" in place of naming specific actions
292
+ - body flow must follow: opener → pain → what the product IS (one sentence)
293
+ → what it DOES (one action per line, up to three) → deployment ease →
294
+ CTA → optional PS
295
+ - the "what it DOES" action list may render as one-line paragraphs or as a
296
+ bullet list — whichever reads cleaner. Bullets are acceptable here (the
297
+ rule against bullet points applies to feature/benefit marketing lists,
298
+ not to enumerable product actions). Never comma-stack three actions into
299
+ one glued sentence
300
+ - **opener must use per-lead signal when any is present in `lead-sample.json`**
301
+ (post, hire, visible tool, topic engagement). Category-level openers are
302
+ acceptable only as fallback when the sample carries zero per-lead signal.
303
+ Fallback category openers must be flagged explicitly in Findings with a
304
+ recommendation to surface signals upstream
305
+ - keep the body tight: one proof sentence, one useful CTA, no vague meeting ask
306
+ - avoid generic walkthrough CTAs like `reply if you want a quick walkthrough`
307
+ unless the line names the useful thing the buyer will see or get
308
+ - the PS is optional. Only include it if it materially strengthens the fit
309
+ case for this buyer. Drop the PS if the best available proof would just
310
+ add noise
311
+ - when a PS is included, it must answer "why is this a fit for me?", not
312
+ "here are our credentials." Never drop a bare first name like "Chris"
313
+ with no anchor. Use "I" voice if the sender is the founder; "our CEO",
314
+ "our team", or "we" otherwise. At most two proof beats. No three-credential
315
+ resume lists
316
+ - samples in one set must read like siblings from one campaign, not three
317
+ experiments
318
+ - reject fallback drafts that lead with generic "most teams..." copy without a
319
+ concrete workflow anchor
320
+ - reject fallback drafts that open with synthesized "X means you're likely..."
321
+ or "at your stage" framing
322
+ - reject drafts that default to a lead magnet, report, brief, teardown,
323
+ video, or "send it over" CTA when the brief never said that asset exists
324
+ - reject drafts that use a `PS` above the CTA or in the middle of the body
325
+ - reject drafts that omit stronger safe proof already present in the brief
326
+ - avoid em dashes unless the chosen Primary Example uses them
327
+
328
+ When message validation fails because sourcing is weak:
329
+
330
+ - revise token fill rules
331
+ - or revise the filter
332
+ - or revise the message thesis
333
+
334
+ Do not invent a stronger signal just to save the copy.
335
+
336
+ `Template Used` must name the actual strategy being validated, not just a vague
337
+ theme:
338
+
339
+ - `event-led canonical template`
340
+ - `signal-led canonical template`
341
+ - `job-post-led canonical template`
342
+ - `proof-led specialist fallback`
343
+
344
+ If `brief.md` includes a message appendix, worked example, or interpolation
345
+ guidance, the sample messages should follow that structure closely. Otherwise,
346
+ the system should synthesize the structure from the closest archived winner.
347
+ Proof-led specialist fallback is only acceptable when no stronger validated
348
+ hook exists.
349
+
350
+ `Primary Example` should name the archived winner providing the structure.
351
+ `Secondary Influence` should be `none` or name one narrow influence only.
352
+
353
+ ## Stop Conditions
354
+
355
+ - `brief.md` missing
356
+ - provider/auth/quota error during preview
357
+ - zero usable leads from preview
358
+ - sample too small to judge
359
+ - required upstream artifact missing for the next step
360
+ - `lead-filter.md` missing required keep/exclude sections
361
+ - more than one optional/supporting rule proposed
362
+ - `message-validation.md` missing the dry-mode preamble
363
+ - any sample message contains unresolved or unsupported tokens
364
+ - any sample message contains invented proof or unsupported personalization
365
+
366
+ When a stop condition is hit, surface the reason in the relevant artifact and
367
+ do not attempt to mint live campaign state.