@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,368 @@
1
+ # Parallel Critique Protocol
2
+
3
+ This reference governs the optional Step 15 critique pass introduced in
4
+ Plan 85-03. Load this file whenever `messaging.critique.enabled === true`
5
+ in `core/auto-execute.yaml`. When the flag is `false`, the plain Step 15
6
+ path runs unchanged and none of the protocol below applies.
7
+
8
+ ## Principle
9
+
10
+ Step 15 (`auto-execute-messaging`) already generates messages under the
11
+ Phase 84 token contract. The critique pass is a bounded, opt-in quality
12
+ lens on top of that output. It is NOT a second drafting pass. It never
13
+ invents new tokens, new proof, or new personalization, and it never
14
+ ships a rewrite that violates the token contract.
15
+
16
+ The critique pass is:
17
+
18
+ - **Bounded.** It runs on `messaging.critique.sampleSize` rows, not
19
+ the full cohort. Cost is predictable whether the cohort is 50 or 500.
20
+ - **Parallel.** Three fixed critic groups (targeting, copy, voice)
21
+ run in parallel and return structured JSON.
22
+ - **Synthesized.** One synthesis step merges the three voices into a
23
+ single rewrite and re-runs the Phase 84 finalizer pass.
24
+ - **Budget-capped.** A trip on `budgetUsdCap` HALTS critique for the
25
+ remaining sample and continues the plain tail. Cost never explodes
26
+ silently.
27
+ - **Token-contract preserving.** Any rewrite that introduces an
28
+ unsupported token, an unresolved token, or invented proof is
29
+ rejected, and the plain generated message is persisted instead.
30
+
31
+ ## When the Protocol Loads
32
+
33
+ Load this file at the start of Step 15 if and only if
34
+ `messaging.critique.enabled === true`. Do not load it during Step 13,
35
+ Step 14, or Step 16. Do not load it when the flag is `false` — the
36
+ plain Step 15 path runs unchanged.
37
+
38
+ If the flag is `true` but any critique knob is unparseable (missing
39
+ `sampleSize`, missing `critics`, missing `synthesis`), skip the pass
40
+ and continue on the plain Step 15 path. Log the config error for
41
+ calibration.
42
+
43
+ ## Inputs
44
+
45
+ - The messages just drafted by the plain Generate Message cascade
46
+ call at the top of Step 15.
47
+ - `lead-sample.json`, `lead-filter.md`, `brief.md` (read-only — these
48
+ are the same artifacts the plain drafter used).
49
+ - `messaging.critique.*` config values from `auto-execute.yaml`.
50
+ - The Phase 84 token contract rules in
51
+ `references/thomas-revision-filters.md`.
52
+
53
+ ## Step Shape
54
+
55
+ ```text
56
+ if messaging.critique.enabled == false:
57
+ continue on the plain Step 15 path (no critique, no Opus)
58
+ return
59
+
60
+ loaded = load_auto_execute_config() # already parsed at Step 13
61
+ critique_cfg = loaded.messaging.critique
62
+
63
+ sample = first critique_cfg.sampleSize rows of the drafted messages
64
+ budget_used = 0.0
65
+ results = []
66
+
67
+ for row in sample:
68
+ if budget_used >= critique_cfg.budgetUsdCap:
69
+ log("budget_cap_tripped", row_id=row.id, budget_used=budget_used)
70
+ results.append(plain_message(row)) # fall back silently
71
+ continue
72
+
73
+ critics_out = run_parallel_critics(
74
+ row=row,
75
+ critics=critique_cfg.critics,
76
+ per_critic_timeout_s=critique_cfg.perCriticTimeoutSeconds,
77
+ total_timeout_s=critique_cfg.totalTimeoutSeconds,
78
+ )
79
+ # Timed-out critics are recorded as "no opinion" and skipped; the
80
+ # synthesis proceeds with the remaining voices. A total timeout
81
+ # falls back to the plain message for this row only.
82
+
83
+ rewrite = synthesize(
84
+ plain=row.plain_message,
85
+ critics=critics_out,
86
+ enforce_finalizer_pass=critique_cfg.synthesis.enforceFinalizerPass,
87
+ )
88
+
89
+ if rewrite.violates_token_contract:
90
+ log("rewrite_rejected_token_contract", row_id=row.id, reason=rewrite.reason)
91
+ results.append(plain_message(row)) # persist plain, not rewrite
92
+ continue
93
+
94
+ if critique_cfg.opus.enabled and is_highest_value(row, critics_out):
95
+ if opus_budget_left(critique_cfg.opus):
96
+ opus_rewrite = craft_message(row, rewrite, critics_out)
97
+ if not opus_rewrite.violates_token_contract:
98
+ rewrite = opus_rewrite
99
+ else:
100
+ log("opus_budget_cap_tripped", row_id=row.id)
101
+
102
+ results.append(rewrite)
103
+ budget_used += cost_of(critics_out, rewrite)
104
+
105
+ return results
106
+ ```
107
+
108
+ `is_highest_value(...)` and Opus eligibility live in
109
+ `references/thomas-variant-selection.md`. The synthesis step's
110
+ finalizer pass is the same Phase 84 finalizer pass the plain
111
+ `generate-messages` skill already runs.
112
+
113
+ ## Critic Groups
114
+
115
+ The three critic groups are fixed. Do NOT invent free-form critics.
116
+ Each critic runs independently, receives the same inputs, and returns
117
+ a strict JSON envelope.
118
+
119
+ ### Targeting critic
120
+
121
+ - **Focus:** `signal-lead-fit-and-buyer-intent`
122
+ - **Question it answers:** Is the signal or personalization in this
123
+ message actually supported by the lead row and the validated filter?
124
+ Does the opener reference something the buyer will recognize as
125
+ theirs?
126
+ - **Never answers:** sentence-level copy questions, voice questions,
127
+ motion alignment.
128
+
129
+ ### Copy critic
130
+
131
+ - **Focus:** `sentence-shape-proof-cta`
132
+ - **Question it answers:** Does the message follow the one-sentence-
133
+ per-line body shape? Is proof placed where the matched motion
134
+ expects it? Is the CTA proportional to the signal and useful, not a
135
+ naked meeting ask?
136
+ - **Never answers:** whether the lead is a good target, whether the
137
+ tone matches the motion.
138
+
139
+ ### Voice critic
140
+
141
+ - **Focus:** `tone-motion-alignment`
142
+ - **Question it answers:** Does this read like the Primary Example
143
+ motion (casual-lowercase if Superpower, dense-credentialed if
144
+ Superposition)? Does any sentence introduce jargon a 5th-grade
145
+ reader wouldn't parse? Does the PS carry the right shape (thin
146
+ narrative, at most two beats)?
147
+ - **Never answers:** targeting, proof math, CTA heaviness.
148
+
149
+ ## Critic JSON Envelope
150
+
151
+ Every critic returns exactly this envelope. Unknown fields are
152
+ rejected. Missing fields cause the critic to be dropped from synthesis
153
+ (treated as "no opinion") rather than blocking the tail.
154
+
155
+ ```json
156
+ {
157
+ "criticId": "targeting" | "copy" | "voice",
158
+ "verdict": "keep" | "revise" | "reject",
159
+ "revisions": [
160
+ {
161
+ "line": "<original sentence or block>",
162
+ "suggestion": "<rewritten sentence or block>",
163
+ "reason": "<one-line rationale grounded in brief / lead-sample>"
164
+ }
165
+ ],
166
+ "tokenNotes": [
167
+ {
168
+ "token": "{{example}}",
169
+ "status": "supported" | "unresolved" | "unsupported" | "proposed",
170
+ "evidence": "<field path in lead-sample.json or brief.md>"
171
+ }
172
+ ],
173
+ "confidence": 0.0
174
+ }
175
+ ```
176
+
177
+ Rules on the envelope:
178
+
179
+ - `verdict = reject` is reserved for rewrites the critic believes
180
+ cannot be saved (fake proof request, unsupported token the
181
+ synthesizer cannot drop, motion mismatch that needs brief revision).
182
+ A `reject` verdict short-circuits synthesis and falls back to the
183
+ plain message.
184
+ - `tokenNotes[*].status = "proposed"` is how a critic surfaces a NEW
185
+ token idea (e.g. a fresh field the brief doesn't yet document). The
186
+ synthesis step MUST NOT promote a proposed token into the persisted
187
+ rewrite. Proposed tokens appear in the optional operator review
188
+ surface only.
189
+ - `revisions[*].suggestion` must contain at most the same set of
190
+ tokens the plain message used, minus tokens the critic explicitly
191
+ asked to drop. It may never introduce a token that isn't supported
192
+ in `tokenNotes`.
193
+
194
+ ## Synthesis
195
+
196
+ One synthesis step takes the plain message plus the three critic
197
+ envelopes and returns a single rewrite. It runs on the
198
+ `synthesis.model` tier configured in `auto-execute.yaml` (kept
199
+ intentionally vague here so the tier can be retuned without touching
200
+ the protocol file).
201
+
202
+ Rules:
203
+
204
+ 1. **Token contract first.** The synthesizer enforces the Phase 84
205
+ token contract verbatim:
206
+
207
+ - Use only tokens documented in `brief.md` as supported fields.
208
+ - Reject any unresolved `{{token}}`.
209
+ - Reject any token flagged `unsupported` by any critic.
210
+ - Reject any rewrite that invents a metric, logo, customer,
211
+ funding detail, or product claim.
212
+ - A `proposed` token requires human approval and does NOT appear
213
+ in the persisted rewrite.
214
+
215
+ 2. **One primary example.** Preserve the matched archived motion's
216
+ voice. Do not blend two motions into one rewrite.
217
+
218
+ 3. **Finalizer pass last.** If `synthesis.enforceFinalizerPass` is
219
+ `true` (default), re-run the Phase 84 finalizer pass on the
220
+ synthesis output. The finalizer is the last guardrail against
221
+ critic-introduced token drift. A finalizer failure falls back to
222
+ the plain message for that row.
223
+
224
+ 4. **PS earns its place.** If the synthesizer adds or moves a PS, the
225
+ PS must pass the revision-filter shape rules (thin narrative, at
226
+ most two beats, no resume list, no fake credential).
227
+
228
+ 5. **Conservative merge.** When two critics disagree, prefer the
229
+ critic whose concern maps to the motion's weakest current link
230
+ (e.g. prefer the copy critic when the plain message is too dense;
231
+ prefer the voice critic when the plain message is tone-off).
232
+ Never synthesize toward a third option the critics didn't raise.
233
+
234
+ ## Budget Control
235
+
236
+ Two independent caps:
237
+
238
+ - **`budgetUsdCap`** — total for critics + synthesis across the whole
239
+ pass. Tripping it halts critique for the remaining sample rows and
240
+ persists the plain message for those rows. The trip is logged as
241
+ `budget_cap_tripped` with the row id and the running cost. The
242
+ autonomous tail continues; Step 15 does NOT stall.
243
+ - **`opus.budgetUsdCap`** — separate cap for the Opus subset. Opus
244
+ trip halts Opus escalation for the remaining rows of the current
245
+ pass only; non-Opus critique continues normally. Also logged as
246
+ `opus_budget_cap_tripped`.
247
+
248
+ Neither cap causes an escalation (in the `escalation-ladder.md`
249
+ sense). Budget trips are planned early exits of the critique path,
250
+ not skill-level failures.
251
+
252
+ ## Timeout Handling
253
+
254
+ - **`perCriticTimeoutSeconds`** (default 30) — a critic that misses
255
+ this cap is treated as "no opinion" for this row. Synthesis
256
+ proceeds with the remaining critic voices. Logged as
257
+ `critic_timeout` with `criticId` and `rowId`.
258
+ - **`totalTimeoutSeconds`** (default 120) — if the full parallel
259
+ group has not returned by this cap, the tail falls back to the
260
+ plain generated message for this row. Logged as
261
+ `critique_total_timeout` with `rowId`.
262
+
263
+ Timeouts never block the tail. A timed-out row is persisted as the
264
+ plain message and the loop continues to the next row.
265
+
266
+ ## Fake Proof Rejection
267
+
268
+ `rejectOnFakeProof: true` (default) means: any rewrite that
269
+ introduces a metric, logo, customer name, funding detail, or product
270
+ claim that is not supported by `brief.md` is rejected. The rejection
271
+ logs `rewrite_rejected_fake_proof` with the offending claim and the
272
+ row id. The plain generated message is persisted for that row.
273
+
274
+ Critics may ask for stronger proof; the synthesizer may only
275
+ surface stronger proof that is already in the brief. If the brief
276
+ cannot support the critic's ask, the rewrite falls back.
277
+
278
+ ## Unsupported Token Rejection
279
+
280
+ `rejectOnUnsupportedToken: true` (default) means: any rewrite that
281
+ introduces a `{{token}}` not declared in the brief is rejected. Two
282
+ sub-rules:
283
+
284
+ 1. **Unresolved tokens** (tokens that expand to empty) are rejected.
285
+ The plain message's unresolved-token safety rules still apply.
286
+ 2. **Proposed tokens** (new fields a critic believes should exist)
287
+ may surface for operator review but do NOT appear in the
288
+ persisted rewrite. The synthesizer strips them before finalization.
289
+
290
+ A rewrite that only differs from the plain message by an unsupported
291
+ token is a silent fallback to the plain message.
292
+
293
+ ## Opus / Craft-Message Subset
294
+
295
+ Opus is reserved for the highest-value subset only. Default config
296
+ leaves `opus.enabled = false`. When enabled, the rules from
297
+ `references/thomas-variant-selection.md` decide which rows earn Opus.
298
+ Hard caps:
299
+
300
+ - `opus.maxMessagesPerPass` (default 2) — never escalate more rows
301
+ than this per critique pass.
302
+ - `opus.budgetUsdCap` (default 1.5 USD) — independent of the overall
303
+ critique budget.
304
+
305
+ An Opus rewrite runs the same token-contract enforcement as the
306
+ non-Opus synthesis output. A fake-proof or unsupported-token Opus
307
+ rewrite is rejected the same way.
308
+
309
+ ## Fallback Behavior
310
+
311
+ Every critique-side failure mode falls back to the plain generated
312
+ message for that row. None of them escalate:
313
+
314
+ - Critique flag `false` → no critic calls, no Opus, plain Step 15
315
+ path runs.
316
+ - Config unparseable → no critic calls, plain Step 15 path runs.
317
+ - Per-critic timeout → synthesis proceeds without that critic.
318
+ - Total timeout → plain message persisted for that row.
319
+ - Budget cap tripped → plain message persisted for remaining rows.
320
+ - Opus budget tripped → rest of pass runs non-Opus.
321
+ - Token contract violation (fake proof, unsupported token, proposed
322
+ token leaked) → plain message persisted for that row.
323
+ - Synthesis finalizer pass failure → plain message persisted.
324
+
325
+ The tail continues. Step 15 does NOT stall on critique failures.
326
+
327
+ ## Logging
328
+
329
+ When `logging.logEveryThresholdTrip === true` (default), log each of
330
+ these events with the row id (when applicable) and the relevant
331
+ figures:
332
+
333
+ - `critique_pass_start` (row count, sample cap)
334
+ - `critic_result` (criticId, verdict, confidence)
335
+ - `critic_timeout` (criticId, rowId)
336
+ - `critique_total_timeout` (rowId)
337
+ - `rewrite_rejected_token_contract` (rowId, reason)
338
+ - `rewrite_rejected_fake_proof` (rowId, claim)
339
+ - `budget_cap_tripped` (rowId, budgetUsed)
340
+ - `opus_selected` (rowId, reason)
341
+ - `opus_budget_cap_tripped` (rowId)
342
+ - `critique_pass_end` (rowsCritiqued, rowsFallback, totalCost)
343
+
344
+ These logs drive `auto-execute.yaml` tuning after 3-5 real critique
345
+ passes.
346
+
347
+ ## Hard Rules
348
+
349
+ - Critique is OFF by default. Plan 85-02 never flips the flag.
350
+ - Sample size is bounded by `messaging.critique.sampleSize`. The
351
+ pass NEVER runs against the full cohort.
352
+ - Three critic groups only: targeting, copy, voice. Do NOT add a
353
+ fourth.
354
+ - Every critic returns the structured JSON envelope above. Free-form
355
+ critic output is discarded.
356
+ - The synthesizer enforces the Phase 84 token contract verbatim.
357
+ - The synthesis step MUST re-run the finalizer pass when
358
+ `enforceFinalizerPass === true` (default).
359
+ - Opus is reserved for the highest-value subset only and gated
360
+ independently from the main critique flag.
361
+ - Budget trips halt the pass gracefully and continue the tail on
362
+ plain messages.
363
+ - Fake proof and unsupported tokens ALWAYS fall back to the plain
364
+ message — never ship the rewrite.
365
+ - A proposed new token may surface for human approval but does NOT
366
+ appear in the persisted rewrite.
367
+ - No critique failure escalates in the `escalation-ladder.md` sense.
368
+ The plain path is always a valid fallback.
@@ -0,0 +1,289 @@
1
+ # Sample Validation Loop
2
+
3
+ This reference governs Step 14 (`validate-sample`) of the Plan 85-02
4
+ autonomous tail. Load this file before enriching + scoring the sample, and
5
+ on every revision round.
6
+
7
+ ## Principle
8
+
9
+ We spend a bounded review batch (default 25 rows) to prove fit before the
10
+ user spends credits on hundreds more leads. The sample loop has one job:
11
+ answer the question "do we have enough real passing examples for the user to
12
+ judge this campaign?"
13
+
14
+ If the answer is yes, proceed to Step 15 messaging for the review batch. If
15
+ the answer is no, diagnose whether the brief is wrong or the list is wrong,
16
+ and either revise brief autonomously OR escalate to the user — never
17
+ auto-revise leads.
18
+
19
+ ## Inputs
20
+
21
+ - `CampaignOffer.currentStep === "validate-sample"` (set at end of Step 13)
22
+ - Imported review batch from Step 13 (size: `importLimit`, default 25)
23
+ - Config from `auto-execute.yaml`: `sample.sampleSize`,
24
+ `sample.minProjectedPass`, `sample.maxRevisionRounds`
25
+ - Persisted counter `revisionRound` (starts at 0 on first entry; persists
26
+ across resume)
27
+
28
+ ## Loop Shape
29
+
30
+ ```text
31
+ 1. pick first sampleSize rows from the imported review batch
32
+
33
+ 2. paginate the campaign table to collect enrichCellIds where
34
+ enrichStatus = "pending" for the sample rows
35
+ - use get_rows_minimal (NOT get_rows); the default response embeds
36
+ full LinkedIn post content in carryData (~2KB/row)
37
+ - strip carryData from every row BEFORE retaining in tail context
38
+ (see §Known Tool Behaviors #4)
39
+
40
+ 3. queue_cells(enrichCellIds, batchSize <= 100)
41
+ - confirm_lead_list does NOT auto-enqueue enrichment on imports
42
+ into an existing workflow table; Step 14 must enqueue manually
43
+ (see §Known Tool Behaviors #1)
44
+
45
+ 4. wait for enrichment completion (poll via get_rows_minimal with
46
+ the enrichment batch ids; NOT by calling wait_for_rubric_results
47
+ before rubric was scheduled)
48
+
49
+ 5. check_rubric(sample)
50
+
51
+ 6. wait_for_rubric_results(sample, targetCount = <cohortSize>)
52
+ - cohortSize = stats.totalRows of the enrichment batch, or the
53
+ imported batch count
54
+ - default targetCount=25 matches the default review batch, but pass the
55
+ explicit batch count anyway so future larger expansion batches do not
56
+ accidentally stop early
57
+ (see §Known Tool Behaviors #3)
58
+
59
+ 7. read the result via a subagent; extract ONLY:
60
+ - ready: boolean
61
+ - passRate.completed: number
62
+ - stats: object
63
+ Never retain the full rows payload in tail context.
64
+ - 25 rows ≈ 67KB of payload (full post bodies in carryData)
65
+ - 100 rows ≈ 268KB; 500 rows ≈ 1.3MB — blows context on Opus
66
+ (see §Known Tool Behaviors #2)
67
+
68
+ 8. compute metrics:
69
+ passInSample = count of review-batch rows where rubric passed
70
+ projectedPass = round(passInSample / sampleSize * importLimit)
71
+
72
+ 9. branch:
73
+ if projectedPass >= minProjectedPass:
74
+ proceed to Step 15 (auto-execute-messaging) with this review batch
75
+ else:
76
+ diagnose (see Brief-vs-List Diagnosis below)
77
+ revisionRound += 1
78
+ if revisionRound > maxRevisionRounds:
79
+ ESCALATE (see references/escalation-ladder.md)
80
+ else if diagnosis == "brief":
81
+ autonomous update_campaign_brief with diagnostic
82
+ rerun sample from step 1 of this loop
83
+ else if diagnosis == "list":
84
+ ESCALATE — do NOT auto-revise leads
85
+ else: # unknown
86
+ ESCALATE — do not guess
87
+ ```
88
+
89
+ ## Known Tool Behaviors (work around these)
90
+
91
+ These are observed tool behaviors that Step 14 must compensate for.
92
+ They are documented here so the tail does not accidentally assume
93
+ "enrichment auto-runs" or "rubric wait is context-cheap."
94
+
95
+ ### 1. `confirm_lead_list` does NOT auto-enqueue enrichment
96
+
97
+ Observed: on imports into an existing workflow table (e.g. signal
98
+ discovery), `confirm_lead_list` returns `autoEnrichedCount=0`,
99
+ `enrichmentCellsCopied=0`, `enrichmentCellsPending=0`. Enrichment does
100
+ NOT kick off on its own.
101
+
102
+ Workaround: Step 14 must explicitly paginate the campaign table,
103
+ collect pending `enrichCellId`s, and call `queue_cells` before any
104
+ `wait_for_rubric_results` call. The plan text cannot assume
105
+ enrichment runs silently after import.
106
+
107
+ ### 2. `wait_for_rubric_results` returns full rows payload
108
+
109
+ Observed: default response includes `carryData.Post Content` with
110
+ whole LinkedIn post bodies. ~67KB per 25 rows. ~268KB per 100 rows.
111
+ Retaining the full payload across tail turns blows Opus context.
112
+
113
+ Workaround (short-term): invoke `wait_for_rubric_results` inside a
114
+ subagent turn and extract ONLY `ready`, `passRate.completed`, and
115
+ `stats`. Never put the raw rows payload back into the tail's own
116
+ context. Prefer `get_rows_minimal` (not `get_rows`) when row-level
117
+ inspection is required, and strip `carryData` before retention.
118
+
119
+ Workaround (long-term): add a `includeRows=false` / stats-only mode
120
+ to `wait_for_rubric_results` at the MCP tool level. Tracked as a
121
+ backlog item; until then the subagent-read pattern is the contract.
122
+
123
+ ### 3. `wait_for_rubric_results.targetCount` defaults to 25
124
+
125
+ Observed: default `targetCount=25` returns `ready=true` after the first 25
126
+ rubric completions. This matches the default review batch, but if a future
127
+ expansion batch is larger, pass the explicit batch size instead of relying on
128
+ the default. The completion semantic is `passRate.completed`, NOT "table
129
+ fully enriched and scored."
130
+
131
+ Workaround: always pass `targetCount = cohortSize` explicitly. Compute
132
+ `cohortSize` from `stats.totalRows` or the imported batch count.
133
+ Treat `needsEnrichCount > 0` after `ready=true` as "workers still
134
+ processing the queued batch," not "new cells need queuing" —
135
+ DO NOT re-call `queue_cells` in that case.
136
+
137
+ ### 4. `get_rows_minimal.carryData` includes full post content
138
+
139
+ Observed: cell-discovery pagination during the enrich-queue step
140
+ returns rows whose `carryData` includes full LinkedIn post content
141
+ (~2KB/row). Iterating the full cohort with default options and
142
+ keeping the result in the tail turn blows context the same way (2).
143
+
144
+ Workaround: strip `carryData` from each row IMMEDIATELY after reading
145
+ and BEFORE retaining. Prefer a minimal projection (name, title,
146
+ company, enrichCellId, enrichStatus) over the default shape.
147
+
148
+ ## Projected Pass Math
149
+
150
+ ```text
151
+ projectedPass = round(passInSample / sampleSize * importLimit)
152
+ ```
153
+
154
+ Worked examples with defaults (sampleSize=25, importLimit=25):
155
+
156
+ | passInSample | projectedPass | Handoff? (minProjectedPass=5) |
157
+ | ------------ | ------------- | ----------------------------- |
158
+ | 0 | 0 | No — escalate or revise |
159
+ | 4 | 4 | No — escalate or revise |
160
+ | 5 | 5 | Yes — exactly at floor |
161
+ | 10 | 10 | Yes |
162
+ | 15 | 15 | Yes |
163
+ | 25 | 25 | Yes |
164
+
165
+ When non-default importLimit/sampleSize are configured, the math scales
166
+ the same way. In the default review-batch mode, the sample size equals the
167
+ import limit, so the projection is the actual review-batch pass count.
168
+
169
+ ## Brief-vs-List Diagnosis
170
+
171
+ Before blindly revising, classify the failure. Three buckets:
172
+
173
+ ### Brief problem
174
+
175
+ Rubric fires cleanly but the passing sample fails voice/value-prop
176
+ alignment, OR messages repeatedly fail token contract, OR pass criteria
177
+ reflect a thesis the brief doesn't actually support.
178
+
179
+ Signal examples:
180
+
181
+ - Sample has ICP-matching leads, but drafted messages (if we drafted any)
182
+ fail token resolution because the brief's proof inventory is thin.
183
+ - Rubric pass rate is OK in absolute terms but the passing rows all fail
184
+ a downstream anchor (e.g. "mentions X pain point") that the brief
185
+ never supplied evidence for.
186
+ - Same token repeatedly can't resolve across multiple sample runs.
187
+
188
+ Action: autonomous `update_campaign_brief` with the specific diagnostic
189
+ (e.g. "add case-study proof for pain point Y"). Rerun sample. Increment
190
+ revisionRound.
191
+
192
+ #### Worked example — horizontal-SaaS seniority drift (Plan 95-03 Ambral)
193
+
194
+ Symptom observed during Plan 95-03 Ambral walkthrough: 14/30 (47%)
195
+ sample was FIT+MAYBE, but the MAYBE cluster was driven by sub-D-round
196
+ "VP Customer Success" titles. These rows match the title whitelist but
197
+ likely don't own budget at that stage.
198
+
199
+ Classification: **BRIEF problem**, not list.
200
+
201
+ Reasoning:
202
+
203
+ - Rubric fires cleanly (reasons match brief §5 ICP).
204
+ - Sample is NOT wrong-function or wrong-geo — titles are correct.
205
+ - The gap is in the brief's §5 seniority rule, which is too permissive
206
+ for horizontal-SaaS (where "VP CS" at a Series B can be a 2-person
207
+ team with no budget authority, vs "VP CS" at Series D which is a
208
+ proper budget owner).
209
+
210
+ Action: autonomous `update_campaign_brief` with diagnostic "tighten
211
+ §5 seniority to D-round and later; Series B-C VP CS titles do not
212
+ own expansion budget in horizontal-SaaS verticals." Rerun sample.
213
+ Expected: 2-3 rows graduate from MAYBE → FIT after the seniority
214
+ tightening + the corresponding rubric tightening. revisionRound = 1.
215
+
216
+ Do NOT classify this as a list problem. The list is sourced correctly;
217
+ the brief's buyer-specificity is under-tuned.
218
+
219
+ ### List problem
220
+
221
+ Rubric fires cleanly, but the sample is overwhelmingly outside ICP, OR
222
+ enrichment yields blanks (name/title/company missing), OR rubric pass
223
+ rate is zero with no pattern that brief editing could fix.
224
+
225
+ Signal examples:
226
+
227
+ - 10/10 rows are wrong function / wrong seniority / wrong geo.
228
+ - 8/10 rows enrich to empty strings for the tokens the brief needs.
229
+ - Rubric passes 0 rows and the failure reasons are all "not ICP."
230
+ - **Marketplace supply-side contamination:** >20% of sample rows match
231
+ the brief §14 forbidden side (e.g. Skillsync sample contains
232
+ supply-side GitHub contributors; the brief explicitly forbids
233
+ outbound to that side). This is a sourcing contamination, NOT a
234
+ brief tightening. Escalate for operator re-source. See
235
+ `references/filter-leads.md` §Marketplace safety rail.
236
+
237
+ Action: STOP. Escalate with the explicit diagnostic "the sourced list
238
+ looks bad — revise leads or start over." Do NOT call
239
+ `update_campaign_brief`. Do NOT silently rerun `import_leads`.
240
+
241
+ ### Unknown
242
+
243
+ Neither bucket is clean. Signals are mixed, or the rubric result is
244
+ uninterpretable (e.g. `save_rubrics` was bypassed upstream and the
245
+ rubric never actually fired).
246
+
247
+ Action: STOP. Escalate. Do not guess.
248
+
249
+ ## Token Contract Check
250
+
251
+ When `messaging.tokenContract === "strict"` (default), the sample
252
+ validation loop ALSO dry-checks token resolution on the enriched sample
253
+ before declaring success. If tokens fail to resolve on the sample, that's
254
+ a brief problem (the brief is asking for tokens the enrichment can't
255
+ provide), not a list problem — unless enrichment is returning blanks,
256
+ which is a list problem.
257
+
258
+ Prefer the brief diagnosis when a specific token is missing and the brief
259
+ could be edited to stop asking for it. Prefer the list diagnosis when
260
+ enrichment itself is incomplete.
261
+
262
+ ## Revision Round Counter
263
+
264
+ - Increments only on a failed round (projectedPass < minProjectedPass +
265
+ diagnosis acted on).
266
+ - Persists with the committed campaign metadata; does NOT live in
267
+ session memory.
268
+ - On skill resume, reads the persisted counter. Does NOT reset to 0.
269
+ - Trip: `revisionRound > maxRevisionRounds` ⇒ escalate.
270
+ - Logged on every increment so `auto-execute.yaml` tuning has data.
271
+
272
+ ## Logging
273
+
274
+ When `logging.logEveryThresholdTrip === true` (default), log:
275
+
276
+ - Every `projectedPass` computation (with inputs + result).
277
+ - Every diagnosis decision (brief / list / unknown) and the reason.
278
+ - Every `revisionRound` increment.
279
+ - Every escalation trigger (which condition fired).
280
+
281
+ ## Hard Rules
282
+
283
+ - A list problem NEVER triggers an autonomous `update_campaign_brief`.
284
+ - `projectedPass` math uses rounding, not floor/ceil.
285
+ - The counter persists across resume.
286
+ - The loop never retries the same sample silently — a rerun always goes
287
+ through diagnosis + brief revision first.
288
+ - The loop never calls `import_leads` again. Full-cohort import is a
289
+ Step 13 action, not a recovery action for a bad sample.