@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.
- package/.claude-plugin/plugin.json +12 -0
- package/.mcp.json +9 -0
- package/README.md +355 -0
- package/dist/api.d.ts +21 -0
- package/dist/api.js +73 -0
- package/dist/auth.d.ts +60 -0
- package/dist/auth.js +246 -0
- package/dist/engage-memory.d.ts +63 -0
- package/dist/engage-memory.js +354 -0
- package/dist/index-dev.d.ts +2 -0
- package/dist/index-dev.js +17 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +499 -0
- package/dist/skills.d.ts +11 -0
- package/dist/skills.js +97 -0
- package/dist/tools/auth.d.ts +30 -0
- package/dist/tools/auth.js +124 -0
- package/dist/tools/blueprint-commit.d.ts +174 -0
- package/dist/tools/blueprint-commit.js +286 -0
- package/dist/tools/bootstrap.d.ts +64 -0
- package/dist/tools/bootstrap.js +246 -0
- package/dist/tools/campaigns.d.ts +589 -0
- package/dist/tools/campaigns.js +892 -0
- package/dist/tools/cells.d.ts +58 -0
- package/dist/tools/cells.js +48 -0
- package/dist/tools/context.d.ts +88 -0
- package/dist/tools/context.js +271 -0
- package/dist/tools/csv-domains.d.ts +73 -0
- package/dist/tools/csv-domains.js +464 -0
- package/dist/tools/csv-linkedin.d.ts +102 -0
- package/dist/tools/csv-linkedin.js +712 -0
- package/dist/tools/direct-campaigns.d.ts +240 -0
- package/dist/tools/direct-campaigns.js +250 -0
- package/dist/tools/engage-bootstrap.d.ts +94 -0
- package/dist/tools/engage-bootstrap.js +205 -0
- package/dist/tools/engage-discovery.d.ts +78 -0
- package/dist/tools/engage-discovery.js +150 -0
- package/dist/tools/engage-memory.d.ts +181 -0
- package/dist/tools/engage-memory.js +143 -0
- package/dist/tools/engage-state.d.ts +72 -0
- package/dist/tools/engage-state.js +62 -0
- package/dist/tools/enrichment.d.ts +167 -0
- package/dist/tools/enrichment.js +174 -0
- package/dist/tools/flow-preflight.d.ts +68 -0
- package/dist/tools/flow-preflight.js +138 -0
- package/dist/tools/framework.d.ts +44 -0
- package/dist/tools/framework.js +153 -0
- package/dist/tools/interaction-mode.d.ts +27 -0
- package/dist/tools/interaction-mode.js +102 -0
- package/dist/tools/leads.d.ts +2417 -0
- package/dist/tools/leads.js +2307 -0
- package/dist/tools/linkedin.d.ts +210 -0
- package/dist/tools/linkedin.js +229 -0
- package/dist/tools/navigation.d.ts +91 -0
- package/dist/tools/navigation.js +381 -0
- package/dist/tools/one-off.d.ts +229 -0
- package/dist/tools/one-off.js +273 -0
- package/dist/tools/processing.d.ts +70 -0
- package/dist/tools/processing.js +56 -0
- package/dist/tools/prompts.d.ts +211 -0
- package/dist/tools/prompts.js +210 -0
- package/dist/tools/provider-preflight.d.ts +21 -0
- package/dist/tools/provider-preflight.js +59 -0
- package/dist/tools/readiness.d.ts +261 -0
- package/dist/tools/readiness.js +510 -0
- package/dist/tools/rows.d.ts +126 -0
- package/dist/tools/rows.js +105 -0
- package/dist/tools/rubrics.d.ts +497 -0
- package/dist/tools/rubrics.js +681 -0
- package/dist/tools/senders.d.ts +44 -0
- package/dist/tools/senders.js +69 -0
- package/dist/tools/sequencer.d.ts +127 -0
- package/dist/tools/sequencer.js +194 -0
- package/dist/tools/tables.d.ts +35 -0
- package/dist/tools/tables.js +36 -0
- package/dist/tools/verify-row.d.ts +36 -0
- package/dist/tools/verify-row.js +38 -0
- package/dist/tools/workspaces.d.ts +140 -0
- package/dist/tools/workspaces.js +139 -0
- package/dist/utils/workspace-root.d.ts +1 -0
- package/dist/utils/workspace-root.js +39 -0
- package/package.json +46 -0
- package/skills/building-gtm-tables/SKILL.md +216 -0
- package/skills/building-gtm-tables/core/auto-execute.yaml +19 -0
- package/skills/building-gtm-tables/core/blueprint-schema.json +72 -0
- package/skills/building-gtm-tables/references/brief-to-blueprint.md +334 -0
- package/skills/building-gtm-tables/references/column-type-catalog.md +318 -0
- package/skills/building-gtm-tables/references/common-blueprints.fixtures.ts +199 -0
- package/skills/building-gtm-tables/references/common-blueprints.md +44 -0
- package/skills/building-gtm-tables/references/failure-taxonomy.md +197 -0
- package/skills/building-gtm-tables/references/uat-seed-prompts.md +37 -0
- package/skills/building-gtm-tables/references/verify-loop.md +74 -0
- package/skills/campaign-messages/SKILL.md +173 -0
- package/skills/campaign-messages/flow.v1.json +75 -0
- package/skills/craft-message/SKILL.md +401 -0
- package/skills/create-campaign/ARCHITECTURE.md +232 -0
- package/skills/create-campaign/DISCUSS.md +296 -0
- package/skills/create-campaign/FLOW_ASCII.md +240 -0
- package/skills/create-campaign/HOST-PARITY-CHECKLIST.md +49 -0
- package/skills/create-campaign/README.md +142 -0
- package/skills/create-campaign/SKILL.md +286 -0
- package/skills/create-campaign/context/README.md +67 -0
- package/skills/create-campaign/context/_TEMPLATE.md +12 -0
- package/skills/create-campaign/context/context.md +35 -0
- package/skills/create-campaign/context/learnings.md +16 -0
- package/skills/create-campaign/context/registry.json +19 -0
- package/skills/create-campaign/core/flow.v1.json +217 -0
- package/skills/create-campaign/core/policy.md +191 -0
- package/skills/create-campaign/core/providers/apollo.json +35 -0
- package/skills/create-campaign/core/providers/prospeo.json +34 -0
- package/skills/create-campaign/core/providers/registry.json +31 -0
- package/skills/create-campaign/core/providers/sales-nav.json +37 -0
- package/skills/create-campaign/core/providers/signal-discovery.json +42 -0
- package/skills/create-campaign/references/brief-template.md +64 -0
- package/skills/create-campaign/references/campaign-quality.md +84 -0
- package/skills/create-campaign/references/copy-calibration-examples.md +120 -0
- package/skills/create-campaign/references/offer-patterns.md +108 -0
- package/skills/create-campaign/references/provider-selection-strategy.md +212 -0
- package/skills/create-campaign/references/question-examples.md +167 -0
- package/skills/create-campaign/references/token-fill-examples.md +81 -0
- package/skills/create-campaign-brief/ARCHITECTURE.md +72 -0
- package/skills/create-campaign-brief/DISCUSS.md +64 -0
- package/skills/create-campaign-brief/README.md +176 -0
- package/skills/create-campaign-brief/SKILL.md +537 -0
- package/skills/create-campaign-brief/references/brief-synthesis-rules.md +100 -0
- package/skills/create-campaign-brief/references/brief-template.md +220 -0
- package/skills/create-campaign-brief/references/campaign-idea-options.md +30 -0
- package/skills/create-campaign-brief/references/copy-appendix-template.md +62 -0
- package/skills/create-campaign-brief/references/draft-lifecycle.md +23 -0
- package/skills/create-campaign-brief/references/examples/MANIFEST.json +89 -0
- package/skills/create-campaign-brief/references/examples/briefs/clover.md +223 -0
- package/skills/create-campaign-brief/references/examples/briefs/galley.md +222 -0
- package/skills/create-campaign-brief/references/examples/briefs/gelee.md +220 -0
- package/skills/create-campaign-brief/references/examples/briefs/hey-digital.md +234 -0
- package/skills/create-campaign-brief/references/examples/briefs/persona.md +231 -0
- package/skills/create-campaign-brief/references/examples/briefs/revvix.md +220 -0
- package/skills/create-campaign-brief/references/examples/briefs/sellable-dev.md +220 -0
- package/skills/create-campaign-brief/references/examples/briefs/superposition.md +233 -0
- package/skills/create-campaign-brief/references/examples/briefs/superpower.md +219 -0
- package/skills/create-campaign-brief/references/examples/briefs/westpark-villas.md +220 -0
- package/skills/create-campaign-brief/references/icp-lock-question-bank.md +43 -0
- package/skills/create-campaign-brief/references/messaging-inputs.md +58 -0
- package/skills/create-campaign-brief/references/output-acceptance-rubric.md +62 -0
- package/skills/create-campaign-brief/references/phase75-active-runtime-message-pack.md +248 -0
- package/skills/create-campaign-brief/references/phase75-canonical-brief-template.md +319 -0
- package/skills/create-campaign-brief/references/phase75-good-brief-and-messaging-examples.md +445 -0
- package/skills/create-campaign-brief/references/quick-research-protocol.md +39 -0
- package/skills/create-campaign-brief/references/reference-sheet-protocol.md +60 -0
- package/skills/create-campaign-brief/references/zero-shot-iteration-rules.md +66 -0
- package/skills/create-campaign-v2/SKILL.md +1619 -0
- package/skills/create-campaign-v2/core/auto-execute.README.md +219 -0
- package/skills/create-campaign-v2/core/auto-execute.yaml +121 -0
- package/skills/create-campaign-v2/core/flow.v2.json +1643 -0
- package/skills/create-campaign-v2/core/policy.md +82 -0
- package/skills/create-campaign-v2/references/ai-tells.md +253 -0
- package/skills/create-campaign-v2/references/approval-gate-framing.md +346 -0
- package/skills/create-campaign-v2/references/draft-lifecycle.md +110 -0
- package/skills/create-campaign-v2/references/escalation-ladder.md +119 -0
- package/skills/create-campaign-v2/references/filter-leads.md +495 -0
- package/skills/create-campaign-v2/references/final-handoff-contract.md +176 -0
- package/skills/create-campaign-v2/references/gold-standard-message-examples.md +394 -0
- package/skills/create-campaign-v2/references/gold-standard-message-patterns.md +314 -0
- package/skills/create-campaign-v2/references/gold-standard-message-validation-example.md +212 -0
- package/skills/create-campaign-v2/references/lead-validation-preview.md +172 -0
- package/skills/create-campaign-v2/references/parallel-critique-protocol.md +368 -0
- package/skills/create-campaign-v2/references/sample-validation-loop.md +289 -0
- package/skills/create-campaign-v2/references/step-13-import-leads.md +151 -0
- package/skills/create-campaign-v2/references/step-15-re-cascade.md +90 -0
- package/skills/create-campaign-v2/references/thomas-revision-filters.md +521 -0
- package/skills/create-campaign-v2/references/thomas-variant-selection.md +202 -0
- package/skills/create-campaign-v2/references/tier-routing-matrix.md +66 -0
- package/skills/create-campaign-v2/references/validation-criteria.md +367 -0
- package/skills/create-campaign-v2/references/watch-link-handoff.md +106 -0
- package/skills/create-campaign-v2-validation/SKILL.md +296 -0
- package/skills/create-post/SKILL.md +1308 -0
- package/skills/create-rubric/SKILL.md +251 -0
- package/skills/engage/SKILL.md +549 -0
- package/skills/engage/core/README.md +23 -0
- package/skills/engage/core/proven-searches.json +11 -0
- package/skills/engage/core/style-guide.template.md +47 -0
- package/skills/engage/core/tracked-people.json +10 -0
- package/skills/enrich-prospects/SKILL.md +97 -0
- package/skills/find-leads/SKILL.md +467 -0
- package/skills/generate-messages/SKILL.md +2361 -0
- package/skills/interview/SKILL.md +132 -0
- package/skills/interview/core/ENGAGE_STYLE_GUIDE.template.md +54 -0
- package/skills/interview/core/ICP.template.md +54 -0
- package/skills/interview/core/VOICE_PROFILE.template.md +101 -0
- package/skills/providers/apollo.md +520 -0
- package/skills/providers/prospeo.md +398 -0
- package/skills/providers/sales-nav.md +372 -0
- package/skills/providers/signal-discovery.md +495 -0
- package/skills/research/SKILL.md +258 -0
- package/skills/research/config.json +9 -0
- package/skills/research/override.md +13 -0
- package/skills/research-prospect/SKILL.md +99 -0
- package/skills/research-sender/SKILL.md +158 -0
- package/skills/workflow-sequences/SKILL.md +85 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
# Filter Leads
|
|
2
|
+
|
|
3
|
+
The middle step is a lightweight lane audit and decision gate. It should add
|
|
4
|
+
fidelity to the find-leads estimate, not re-run sourcing or produce a large
|
|
5
|
+
scoring framework.
|
|
6
|
+
|
|
7
|
+
The business goal is not "find people who might reply." It is "every reply the
|
|
8
|
+
user gets should come from someone who is in the ICP and has a realistic path
|
|
9
|
+
to buying." A lead who may reply but cannot buy, lacks budget, sits at the wrong
|
|
10
|
+
account type, is a vendor/competitor, or is on the wrong side of a marketplace
|
|
11
|
+
is not a good lead.
|
|
12
|
+
|
|
13
|
+
Inputs:
|
|
14
|
+
|
|
15
|
+
- `brief.md`
|
|
16
|
+
- `lead-review.md`
|
|
17
|
+
- `lead-sample.json`
|
|
18
|
+
|
|
19
|
+
Outputs:
|
|
20
|
+
|
|
21
|
+
- `lead-filter.md`
|
|
22
|
+
- optional `rubric.json`
|
|
23
|
+
|
|
24
|
+
Core constraint:
|
|
25
|
+
|
|
26
|
+
- Every accepted filter in `lead-filter.md` must be directly translatable into
|
|
27
|
+
the production `LeadScoringRubric` shape. If a filter cannot become a
|
|
28
|
+
runnable rubric criterion from available row data or public research, it is
|
|
29
|
+
not an accepted filter; convert it to an optional enrichment note, a user
|
|
30
|
+
tradeoff, or a lead-source revision reason.
|
|
31
|
+
|
|
32
|
+
## Evaluation Data Scope
|
|
33
|
+
|
|
34
|
+
Do not write final filters that depend on the limited fields visible in the
|
|
35
|
+
source-provider preview card. Sales Nav cards, Prospeo rows, and Signals rows
|
|
36
|
+
are sampling evidence, not the full data contract.
|
|
37
|
+
|
|
38
|
+
Production rubrics should describe what we will decide after normal enrichment
|
|
39
|
+
and public research have run. For example:
|
|
40
|
+
|
|
41
|
+
- Good: "Keep US-licensed MD/DO dermatologists; verify credentials from the
|
|
42
|
+
enriched LinkedIn profile, professional bio, NPI/state-license page, or other
|
|
43
|
+
public source."
|
|
44
|
+
- Bad: "Exclude rows missing MD/DO on the Sales Nav card."
|
|
45
|
+
|
|
46
|
+
If a preview row lacks enough evidence to prove a required rule, mark it as
|
|
47
|
+
`needs enrichment` or `borderline pending verification` in sample findings. Do
|
|
48
|
+
not turn preview-card missingness into a hard production exclusion unless the
|
|
49
|
+
same missing field will remain missing after enrichment.
|
|
50
|
+
|
|
51
|
+
## Primary Goal
|
|
52
|
+
|
|
53
|
+
Use the actual sample to answer:
|
|
54
|
+
|
|
55
|
+
- was the Step 2 projected-good-fit estimate basically right or wrong
|
|
56
|
+
- which leads clearly match the buyer hypothesis
|
|
57
|
+
- which leads are noise
|
|
58
|
+
- which repeated false positives must become explicit exclusions
|
|
59
|
+
- whether filters are enough, or whether the source lane would make the
|
|
60
|
+
campaign miss if launched as-is
|
|
61
|
+
- whether the filtered leads are economically worth sending to, using the best
|
|
62
|
+
campaign-native proxy available from row data, enrichment, or Googleable facts
|
|
63
|
+
|
|
64
|
+
## Standard Competitor / Vendor Exclusion
|
|
65
|
+
|
|
66
|
+
Every confirmed filter should include a competitor / vendor / wrong-side
|
|
67
|
+
exclusion unless the brief and sample make it clearly irrelevant. This is a
|
|
68
|
+
standard production safety rule, not an optional nice-to-have.
|
|
69
|
+
|
|
70
|
+
Use this simple test:
|
|
71
|
+
|
|
72
|
+
- Would this company, product, service provider, agency, consultant, reseller,
|
|
73
|
+
or marketplace participant appear on the same comparison page as the client?
|
|
74
|
+
- Would they compete for the same customer budget, sell into the same buyer
|
|
75
|
+
workflow, or sit on the wrong side of the marketplace?
|
|
76
|
+
- Did the sample show adjacent companies that a client would likely reject only
|
|
77
|
+
after seeing the list?
|
|
78
|
+
|
|
79
|
+
If yes, add an explicit exclude rule. Include named competitors from the brief,
|
|
80
|
+
sample, or user context when available, plus the general category exclusion so
|
|
81
|
+
new lookalike competitors are also blocked. If a competitor/vendor exclusion is
|
|
82
|
+
omitted, `lead-filter.md` must say why it is not applicable.
|
|
83
|
+
|
|
84
|
+
Treat this as part of the manual review loop. When the user or client sees a
|
|
85
|
+
sample and says "these companies are not a fit," update the exclusion watchlist
|
|
86
|
+
and filter draft before Step 4. Use `confirm-with-user` when the sample contains
|
|
87
|
+
ambiguous adjacent vendors, competitors, agencies, marketplace-side conflicts,
|
|
88
|
+
or reseller/consultant roles where excluding them would materially shrink the
|
|
89
|
+
lane.
|
|
90
|
+
|
|
91
|
+
## `lead-filter.md` Shape
|
|
92
|
+
|
|
93
|
+
````md
|
|
94
|
+
# Lead Filter
|
|
95
|
+
|
|
96
|
+
Status: confirmed | confirm-with-user | revise-find-leads
|
|
97
|
+
|
|
98
|
+
## Decision
|
|
99
|
+
|
|
100
|
+
- continue | confirm with user | revise find-leads
|
|
101
|
+
- one plain-English sentence saying why
|
|
102
|
+
|
|
103
|
+
## Who We'll Keep
|
|
104
|
+
|
|
105
|
+
- ...
|
|
106
|
+
|
|
107
|
+
## Who We'll Exclude
|
|
108
|
+
|
|
109
|
+
- ...
|
|
110
|
+
|
|
111
|
+
## Sample False Positives
|
|
112
|
+
|
|
113
|
+
- ...
|
|
114
|
+
|
|
115
|
+
## Optional Supporting Rule
|
|
116
|
+
|
|
117
|
+
- omit this section unless exactly one supporting rule materially helps messaging or prioritization
|
|
118
|
+
|
|
119
|
+
## Pass Rate
|
|
120
|
+
|
|
121
|
+
- ...
|
|
122
|
+
|
|
123
|
+
## Recommendation
|
|
124
|
+
|
|
125
|
+
- proceed to generate message
|
|
126
|
+
- or revise leads / revise filter
|
|
127
|
+
|
|
128
|
+
## Implementation Details
|
|
129
|
+
|
|
130
|
+
If `Status: confirmed`, include production rubrics:
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"leadScoringRubrics": [
|
|
135
|
+
{
|
|
136
|
+
"checkName": "role_fit",
|
|
137
|
+
"description": "Lead holds one of the validated buyer titles.",
|
|
138
|
+
"criterion": "Yes if the current title matches one of the accepted buyer titles. No if the title is a wrong-function, junior, advisory-only, or service-provider title.",
|
|
139
|
+
"reason": "The campaign only works when the recipient owns the buyer problem and can act on the offer.",
|
|
140
|
+
"isRequiredCheck": true,
|
|
141
|
+
"allowPartialCredit": false,
|
|
142
|
+
"strictMatching": false
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"checkName": "company_fit",
|
|
146
|
+
"description": "Lead works at a company type validated by the sample.",
|
|
147
|
+
"criterion": "Yes if the current company matches the accepted account type from the brief and sample. No if the company is a vendor, agency, competitor, or structurally wrong account type.",
|
|
148
|
+
"reason": "The message thesis depends on this company context being true.",
|
|
149
|
+
"isRequiredCheck": true,
|
|
150
|
+
"allowPartialCredit": false,
|
|
151
|
+
"strictMatching": false
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
If `Status: confirm-with-user` or `Status: revise-find-leads`, do not include
|
|
158
|
+
production rubrics yet. Explain why the draft filter is not locked downstream.
|
|
159
|
+
|
|
160
|
+
## Required Keep Rules
|
|
161
|
+
|
|
162
|
+
- technical source rules, if different from the plain-English keep list
|
|
163
|
+
|
|
164
|
+
## Required Exclude Rules
|
|
165
|
+
|
|
166
|
+
- technical source rules, if different from the plain-English exclude list
|
|
167
|
+
````
|
|
168
|
+
|
|
169
|
+
Rules:
|
|
170
|
+
|
|
171
|
+
- `Decision` is mandatory and must appear before filter details
|
|
172
|
+
- default to `Status: confirmed` when the source lane is viable and filters are
|
|
173
|
+
enough to protect the campaign
|
|
174
|
+
- use `confirm-with-user` only when the lane is viable but there is a real
|
|
175
|
+
strategic tradeoff the user must choose before launch
|
|
176
|
+
- use `revise-find-leads` only when filters would be so heavy, low-volume, or
|
|
177
|
+
thesis-distorting that launching from this lane would likely miss
|
|
178
|
+
- always emit a readable draft filter, even when status is `confirm-with-user`
|
|
179
|
+
or `revise-find-leads`, so the user can inspect and edit the AI's definition
|
|
180
|
+
of a good fit
|
|
181
|
+
- `Who We'll Keep` and `Who We'll Exclude` are mandatory user-facing sections
|
|
182
|
+
- `Required Keep Rules` and `Required Exclude Rules` are allowed as technical
|
|
183
|
+
source sections, but should appear after `Implementation Details` if present
|
|
184
|
+
- `Sample False Positives` must be grounded in observed sample leads
|
|
185
|
+
- `Optional Supporting Rule` is allowed only when one rule clearly helps later
|
|
186
|
+
messaging or prioritization
|
|
187
|
+
- never emit more than one optional/non-required rule
|
|
188
|
+
- `Implementation Details` is mandatory for every status
|
|
189
|
+
- `Implementation Details` must contain one fenced JSON object with
|
|
190
|
+
`leadScoringRubrics` only when `Status: confirmed`
|
|
191
|
+
- non-confirmed filters are draft/user-review artifacts, not official Step 4
|
|
192
|
+
inputs
|
|
193
|
+
- `leadScoringRubrics` must contain 2-5 production-shaped rubric items, not one
|
|
194
|
+
item per bullet
|
|
195
|
+
- do not put source-path mechanics or send-priority hints into
|
|
196
|
+
`leadScoringRubrics`. Examples: recently posted on LinkedIn, came from a
|
|
197
|
+
Signals search, appeared in Sales Nav, was found by Prospeo, first-send wave,
|
|
198
|
+
or priority cohort. If that signal was already used in the search, mention it
|
|
199
|
+
in `Pass Rate` or `Recommendation`; if it is useful but not required, keep it
|
|
200
|
+
as prose under `Optional Supporting Rule`, not as a production rubric.
|
|
201
|
+
- do not put DNC or one-off relationship-safety notes into
|
|
202
|
+
`leadScoringRubrics` unless the sample shows that family is likely to leak at
|
|
203
|
+
meaningful volume and normal DNC/domain suppression will not catch it. Former
|
|
204
|
+
employers, existing customers, investors, partner lists, and "do not contact"
|
|
205
|
+
domains usually belong in `Recommendation` or a DNC note, not in ICP scoring.
|
|
206
|
+
- confirmed `leadScoringRubrics` must protect sales quality, not just surface
|
|
207
|
+
similarity. They should usually cover:
|
|
208
|
+
- buyer authority / role fit
|
|
209
|
+
- account or company-type fit
|
|
210
|
+
- economic capacity / ability to buy, using a campaign-native proxy such as
|
|
211
|
+
headcount, revenue, funding, practice size, location count, patient volume,
|
|
212
|
+
department maturity, existing tooling, or budget-owner title
|
|
213
|
+
- geography / market, when relevant
|
|
214
|
+
- active-current-role safety
|
|
215
|
+
- hard exclusions for competitors, vendors, agencies, job seekers,
|
|
216
|
+
wrong-function leads, and wrong-side marketplace participants
|
|
217
|
+
- confirmed `leadScoringRubrics` should include a competitor / vendor /
|
|
218
|
+
wrong-side exclusion by default. It can be bundled with account-fit only when
|
|
219
|
+
the criterion still names both the accepted account type and the excluded
|
|
220
|
+
competitor/vendor/wrong-side categories.
|
|
221
|
+
- when one production rubric bundles multiple families, the criterion must say
|
|
222
|
+
exactly which buyer-quality gates it covers
|
|
223
|
+
- if the filter cannot be compiled into 2-5 production rubric items, do not
|
|
224
|
+
confirm; return `confirm-with-user` or `revise-find-leads`
|
|
225
|
+
- hide raw rubric flags from the top user-facing sections; keep
|
|
226
|
+
`isRequiredCheck`, `allowPartialCredit`, and `strictMatching` inside
|
|
227
|
+
`Implementation Details`
|
|
228
|
+
|
|
229
|
+
## Rule Families To Preserve
|
|
230
|
+
|
|
231
|
+
When relevant to the brief and sample, preserve these recurring families:
|
|
232
|
+
|
|
233
|
+
- buyer role / seniority
|
|
234
|
+
- wrong-function exclusions
|
|
235
|
+
- company-type exclusions
|
|
236
|
+
- competitor / vendor / intermediary exclusions
|
|
237
|
+
- geography
|
|
238
|
+
- company size
|
|
239
|
+
- active current-role status
|
|
240
|
+
|
|
241
|
+
## Sample-Judging Method
|
|
242
|
+
|
|
243
|
+
For each proposed rule:
|
|
244
|
+
|
|
245
|
+
1. inspect matching and failing sample leads
|
|
246
|
+
2. decide whether the rule removes real noise or blocks real buyers
|
|
247
|
+
3. mark the rule as necessary, loosen, or remove
|
|
248
|
+
4. record the resulting pass rate
|
|
249
|
+
|
|
250
|
+
Repeated false positives should become explicit exclude rules whenever the same
|
|
251
|
+
pattern appears more than once in the sample.
|
|
252
|
+
|
|
253
|
+
## `rubric.json` Sidecar
|
|
254
|
+
|
|
255
|
+
Only emit a sidecar when a downstream step needs a separate file. The same
|
|
256
|
+
production-shaped items must always appear as fenced JSON in `lead-filter.md`
|
|
257
|
+
under `## Implementation Details`, even when `rubric.json` is omitted.
|
|
258
|
+
|
|
259
|
+
Shape:
|
|
260
|
+
|
|
261
|
+
```json
|
|
262
|
+
{
|
|
263
|
+
"leadScoringRubrics": [
|
|
264
|
+
{
|
|
265
|
+
"checkName": "role_fit",
|
|
266
|
+
"description": "Lead holds one of the validated buyer titles.",
|
|
267
|
+
"criterion": "Yes if the current title matches one of the accepted buyer titles. No if the title is a wrong-function, junior, advisory-only, or service-provider title.",
|
|
268
|
+
"reason": "The campaign only works when the recipient owns the buyer problem and can act on the offer.",
|
|
269
|
+
"isRequiredCheck": true,
|
|
270
|
+
"allowPartialCredit": false,
|
|
271
|
+
"strictMatching": false
|
|
272
|
+
}
|
|
273
|
+
]
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Rules:
|
|
278
|
+
|
|
279
|
+
- derive every sidecar rule from `lead-filter.md`
|
|
280
|
+
- default every rule to `isRequiredCheck: true`
|
|
281
|
+
- allow at most one `isRequiredCheck: false` rule
|
|
282
|
+
- do not add a rule that is not present in `lead-filter.md`
|
|
283
|
+
- do not duplicate two rules that restate the same keep/exclude idea
|
|
284
|
+
- use the production fields exactly: `checkName`, `description`, `criterion`,
|
|
285
|
+
`reason`, `isRequiredCheck`, `allowPartialCredit`, and `strictMatching`
|
|
286
|
+
- prefer 2-5 rubric criteria total; do not map every keep/exclude bullet into
|
|
287
|
+
its own scoring row
|
|
288
|
+
- production rubric criteria should be buyer-quality gates, not execution
|
|
289
|
+
metadata. Do not create rubric rows for provider source, recently-posted
|
|
290
|
+
priority, first-send ordering, or other search mechanics unless the user
|
|
291
|
+
explicitly asks to score that as part of ICP fit.
|
|
292
|
+
- production rubric criteria should not become one-off DNC lists. Named-company
|
|
293
|
+
exclusions are appropriate when they represent a repeated false-positive
|
|
294
|
+
category, a true competitor class, or a material lane risk; otherwise preserve
|
|
295
|
+
them as DNC/suppression instructions outside the rubric JSON.
|
|
296
|
+
- do not write "these rules are expressible as rubrics" as a substitute for the
|
|
297
|
+
actual 2-5 item draft
|
|
298
|
+
- bundle repeated false positives into a single exclusion criterion when they
|
|
299
|
+
are the same kind of failure, for example vendors / competitors /
|
|
300
|
+
wrong-side providers
|
|
301
|
+
- keep title / role fit as its own explicit criterion with concrete pass and
|
|
302
|
+
fail title patterns
|
|
303
|
+
- use `allowPartialCredit: false` for hard gates where ambiguity should fail
|
|
304
|
+
- use `strictMatching: true` only when the rule depends on exact named-account
|
|
305
|
+
or exact-token matching
|
|
306
|
+
|
|
307
|
+
Recommended rubric grouping:
|
|
308
|
+
|
|
309
|
+
- buyer authority / role fit
|
|
310
|
+
- company / industry / provider-lane fit
|
|
311
|
+
- economic capacity / ability to buy
|
|
312
|
+
- geography / size / stage fit, when relevant
|
|
313
|
+
- competitor / vendor / wrong-side / repeated false-positive exclusion
|
|
314
|
+
- one optional buyer-quality signal, at most, only when it changes fit scoring
|
|
315
|
+
rather than send ordering
|
|
316
|
+
|
|
317
|
+
Rubric-readiness checklist:
|
|
318
|
+
|
|
319
|
+
- Can this rule be evaluated from `lead-sample.json`, provider row fields,
|
|
320
|
+
enrichment, or normal public research?
|
|
321
|
+
- Does the criterion say both what passes and what fails?
|
|
322
|
+
- Is the reason a business rationale, not a restatement of the criterion?
|
|
323
|
+
- Would `save_rubrics` accept this without adding missing fields?
|
|
324
|
+
- Would `evaluate-icp-fit-enriched` have enough prospect-side data or
|
|
325
|
+
Googleable facts to score it?
|
|
326
|
+
|
|
327
|
+
## Prohibited During Phase 84
|
|
328
|
+
|
|
329
|
+
- calling `check_rubric`
|
|
330
|
+
- calling `save_rubrics`
|
|
331
|
+
- using campaign-backed rubric tools
|
|
332
|
+
- creating a second independent rubric design
|
|
333
|
+
- blocking Phase 84 when only `rubric.json` fails and `lead-filter.md` is valid
|
|
334
|
+
|
|
335
|
+
## Marketplace safety rail (two-sided archetypes)
|
|
336
|
+
|
|
337
|
+
When brief §14 declares a marketplace with an explicit forbidden
|
|
338
|
+
side-of-market (e.g. Skillsync forbids cold-outbound to supply-side
|
|
339
|
+
GitHub contributors; the indexed pool is NEVER the outbound target),
|
|
340
|
+
the lead-filter MUST include a forbidden-role exclude rule naming the
|
|
341
|
+
supply-side title family.
|
|
342
|
+
|
|
343
|
+
### Enforcement at filter-write time
|
|
344
|
+
|
|
345
|
+
- `lead-filter.md` Required Exclude Rules MUST contain at least one
|
|
346
|
+
rule pattern-matching the forbidden side's title family (e.g.
|
|
347
|
+
"exclude 'Software Engineer' / 'Staff Engineer' / GitHub
|
|
348
|
+
contributor profile signal for Skillsync-like marketplaces").
|
|
349
|
+
- If brief §14 declares the marketplace but the filter does NOT include
|
|
350
|
+
the forbidden-side exclude rule, surface this as a `revise-rubric`
|
|
351
|
+
precondition failure. Do NOT proceed to the commit gate.
|
|
352
|
+
|
|
353
|
+
### Enforcement at validate-sample time
|
|
354
|
+
|
|
355
|
+
- The Plan 85-02 validate-sample loop treats `>20% of sample rows
|
|
356
|
+
matching the brief §14 forbidden side` as a **LIST problem** (not a
|
|
357
|
+
brief problem). Rationale: the sourced list is contaminated with
|
|
358
|
+
the wrong side of the marketplace; operator must re-source.
|
|
359
|
+
- Detection signal: row title/profile matches the supply-side family
|
|
360
|
+
declared in brief §14, even if the title whitelist at §5 ICP
|
|
361
|
+
accidentally allows it.
|
|
362
|
+
|
|
363
|
+
### Why this matters
|
|
364
|
+
|
|
365
|
+
Marketplaces are the only B2B archetype where the same LinkedIn pool
|
|
366
|
+
contains both the intended buyer and the forbidden counterparty. A
|
|
367
|
+
marketplace operator who accidentally drafts InMail to a supply-side
|
|
368
|
+
contributor has simultaneously:
|
|
369
|
+
|
|
370
|
+
1. Violated their own TOS with that contributor (cold InMail for a
|
|
371
|
+
non-applied-to "hiring" pitch).
|
|
372
|
+
2. Leaked their own product positioning (we target engineers,
|
|
373
|
+
implying we sell you as a vendor).
|
|
374
|
+
3. Poisoned the cohort's recall rate for real demand-side buyers.
|
|
375
|
+
|
|
376
|
+
A filter-spec safety rail catches the drift BEFORE Step 13 import.
|
|
377
|
+
A validate-sample signal catches the drift if it still slips through.
|
|
378
|
+
|
|
379
|
+
Plan 95-07 surfaced this residual on the Skillsync walkthrough; Plan
|
|
380
|
+
95-08 ships the rail.
|
|
381
|
+
|
|
382
|
+
## Post-Filter Signal Enrichment Pass (Sales Nav lane)
|
|
383
|
+
|
|
384
|
+
After `lead-filter.md` lands and the Pass Rate is recorded, run a bounded
|
|
385
|
+
signal-enrichment pass on FIT rows whose primary lane is Sales Nav and
|
|
386
|
+
whose `lead-sample.json` entry has `recent_posts.length === 0` (the
|
|
387
|
+
Sales Nav lane does not carry post data natively).
|
|
388
|
+
|
|
389
|
+
This pass directly addresses the Earned-right + Read-as-1:1 substance
|
|
390
|
+
filters in Phase 84. Without row-level post data, the generator has
|
|
391
|
+
nothing to quote back, forcing either (a) category-level openers that
|
|
392
|
+
trip Read-as-1:1, or (b) presumption-led openers that trip Earned-right.
|
|
393
|
+
|
|
394
|
+
Contract:
|
|
395
|
+
|
|
396
|
+
- **Scope:** only FIT rows (or the top K rows when FIT count > K).
|
|
397
|
+
- **Budget cap K:** top 10 FIT rows by score. Do NOT enrich MAYBE or
|
|
398
|
+
REJECT rows in Phase 84 — they are not headed to messaging.
|
|
399
|
+
- **Tool:** `mcp__sellable__fetch_linkedin_posts` (per-row call). If the
|
|
400
|
+
tool is unavailable in the current harness, skip the pass entirely
|
|
401
|
+
and document in Findings.
|
|
402
|
+
- **Fetch:** the most recent 3-5 posts per row.
|
|
403
|
+
- **Append to `lead-sample.json`:** each enriched row gets a
|
|
404
|
+
`recent_posts[]` array with `{ url, posted_at, excerpt }` entries
|
|
405
|
+
(200 char excerpt cap per post).
|
|
406
|
+
- **Rows with zero public posts:** keep `recent_posts: []`. This is
|
|
407
|
+
valid data — the absence signals "quiet-profile row, use trigger or
|
|
408
|
+
peer-observation angle instead".
|
|
409
|
+
- **Order of operations:** run AFTER `lead-filter.md` is written, BEFORE
|
|
410
|
+
`message-validation.md` starts drafting. The enriched
|
|
411
|
+
`lead-sample.json` is the input to the per-row signal extraction pass
|
|
412
|
+
in the generator.
|
|
413
|
+
|
|
414
|
+
Findings update:
|
|
415
|
+
|
|
416
|
+
- Record enrichment outcome at the bottom of `lead-filter.md` under a
|
|
417
|
+
new `## Signal Enrichment` heading: rows attempted, rows with posts,
|
|
418
|
+
rows with zero posts, tool failures.
|
|
419
|
+
- If more than 50% of enriched rows have zero posts, flag the sample as
|
|
420
|
+
"low signal density" and recommend the generator's peer-observation
|
|
421
|
+
fallback angle with the Findings-surface warning.
|
|
422
|
+
|
|
423
|
+
This pass does NOT apply to:
|
|
424
|
+
|
|
425
|
+
- Signals-lane rows (already carry post data natively)
|
|
426
|
+
- Prospeo-lane rows (messaging is usually event/job-post-led, post
|
|
427
|
+
data is secondary)
|
|
428
|
+
- live-campaign mode (enrichment happens per-row during Research)
|
|
429
|
+
|
|
430
|
+
## Brief-Validated Rewrite Authority
|
|
431
|
+
|
|
432
|
+
When `lead-review.md` shows that the Phase-83 primary lead-source picked an
|
|
433
|
+
UNVALIDATED lane (no source-material support) AND the alternative source
|
|
434
|
+
yields ≥ 5× FIT leads in the same sample, `brief-validated.md` may rewrite
|
|
435
|
+
exactly two Phase-83 brief sections:
|
|
436
|
+
|
|
437
|
+
- §3 Campaign Thesis — update the source-hypothesis line to reflect the
|
|
438
|
+
higher-yielding lane as primary.
|
|
439
|
+
- §12 Next Steps — update the first lead-search step to start on the
|
|
440
|
+
higher-yielding lane.
|
|
441
|
+
|
|
442
|
+
Every rewrite must include an evidence citation block quoting
|
|
443
|
+
`lead-review.md`:
|
|
444
|
+
|
|
445
|
+
```md
|
|
446
|
+
> Evidence (from lead-review.md):
|
|
447
|
+
>
|
|
448
|
+
> - Signals sample: 2 rows, 0 FIT
|
|
449
|
+
> - Sales Nav sample: 25 rows, 3 FIT + 7 MAYBE
|
|
450
|
+
> - Primary-lane reversal: Sales Nav ≥ 5× Signals FIT yield in the same
|
|
451
|
+
> preview.
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
All OTHER Phase-83 brief sections remain read-only during Phase-84. This
|
|
455
|
+
authority is narrowly scoped to one specific failure mode — UNVALIDATED
|
|
456
|
+
primary-source picks contradicted by real preview data. It is NOT a general
|
|
457
|
+
brief-rewrite license. Do not use it to rewrite ICP, Buyer Pain, Offer
|
|
458
|
+
Strategy, Social Proof, Differentiators, Message Thesis, or any other
|
|
459
|
+
section. Those deltas continue to flow through `lead-filter.md` and
|
|
460
|
+
`message-validation.md` as today.
|
|
461
|
+
|
|
462
|
+
If the evidence is ambiguous (e.g. 2 FIT Signals vs 4 FIT Sales Nav — under
|
|
463
|
+
5× ratio), DO NOT rewrite. Flag the weak preference in `lead-filter.md`
|
|
464
|
+
§ Recommendation instead.
|
|
465
|
+
|
|
466
|
+
## Provider-lane expected FIT density
|
|
467
|
+
|
|
468
|
+
Observed during Phase 94 + Phase 95 UAT. Use as anchor for judging whether
|
|
469
|
+
a lane is "lane exhausted" (see `references/step-13-import-leads.md`) or
|
|
470
|
+
genuinely weak.
|
|
471
|
+
|
|
472
|
+
| Lane | Typical FIT density | Archetype examples |
|
|
473
|
+
| ------------------------------------------ | ------------------- | ------------------------------------------------------------ |
|
|
474
|
+
| **Prospeo** (vertical SMB) | 60-80% | Patientdesk (dental practice owners); vertical B2C-style SMB |
|
|
475
|
+
| **Sales Nav** (horizontal enterprise) | 40-55% | Ambral (enterprise CS); Skillsync (eng-led companies) |
|
|
476
|
+
| **Sales Nav** (dev tools / specific infra) | 30-45% | IncidentFox (SRE/Platform leaders at Series B-D SaaS) |
|
|
477
|
+
| **Signals** (overlay — warm-scored) | 20-35% | Overlay added to any lane; rate depends on topical density |
|
|
478
|
+
|
|
479
|
+
Interpretation:
|
|
480
|
+
|
|
481
|
+
- **Density at the high end of the band** — fresh lane; import cap should
|
|
482
|
+
be aggressive (bump importLimit from 100 default to 200+ if needed).
|
|
483
|
+
- **Density below the band** — either the filter is too tight OR the lane
|
|
484
|
+
is approaching exhaustion (most good rows already imported in prior runs).
|
|
485
|
+
Check dedup ratios to distinguish.
|
|
486
|
+
- **Density dramatically below band** — likely a brief problem (ICP too
|
|
487
|
+
narrow) or a provider-side issue. Diagnose before reruning.
|
|
488
|
+
|
|
489
|
+
Plan 95-05 Patientdesk observed ~70% FIT density on Prospeo (top of band).
|
|
490
|
+
Plan 95-03 Ambral observed ~47% on Sales Nav horizontal enterprise (mid band).
|
|
491
|
+
Plan 95-01 IncidentFox observed ~36% on Sales Nav dev-infra (mid-low band).
|
|
492
|
+
Plan 94-07 Skillsync observed ~32% on Signals (mid band for overlay).
|
|
493
|
+
|
|
494
|
+
Use these as regression anchors: a 10%+ drop from the archetype's
|
|
495
|
+
historical density on a lane = investigate before escalating to operator.
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Final Handoff Contract
|
|
2
|
+
|
|
3
|
+
This reference governs Step 16 (`awaiting-user-greenlight`) of the Plan
|
|
4
|
+
85-02 autonomous tail — the moment where the skill stops running
|
|
5
|
+
autonomously and waits for the user to decide whether to start the
|
|
6
|
+
campaign. Load this file before entering Step 16 and on every resume into
|
|
7
|
+
`awaiting-user-greenlight`.
|
|
8
|
+
|
|
9
|
+
## Principle
|
|
10
|
+
|
|
11
|
+
The autonomous tail NEVER calls `start_campaign` on its own. The final
|
|
12
|
+
start is always a user decision. There are two equally valid channels for
|
|
13
|
+
that decision — UI and Claude greenlight — and the skill must handle both
|
|
14
|
+
cleanly, including the case where one channel has already started the
|
|
15
|
+
campaign before the other arrives.
|
|
16
|
+
|
|
17
|
+
## Step 16 Setup
|
|
18
|
+
|
|
19
|
+
On entry to `awaiting-user-greenlight`, the skill does the following in
|
|
20
|
+
order (all before waiting for the user):
|
|
21
|
+
|
|
22
|
+
1. Attach the sequence to the campaign (`attach_sequence`).
|
|
23
|
+
2. Re-surface the `watchUrl` using the exact block in
|
|
24
|
+
`references/watch-link-handoff.md`.
|
|
25
|
+
3. Surface the `handoff.orientation` string from `auto-execute.yaml`
|
|
26
|
+
alongside the watch link so the user sees both the link and the
|
|
27
|
+
"what to do next" sentence.
|
|
28
|
+
4. STOP. Do not call `start_campaign`. Do not advance `currentStep`.
|
|
29
|
+
|
|
30
|
+
The final handoff must answer five customer questions in plain language:
|
|
31
|
+
|
|
32
|
+
- where to inspect the campaign (`watchUrl`)
|
|
33
|
+
- what is ready now (brief, filters/rubrics, review-batch leads, messages, sequence)
|
|
34
|
+
- what clicking Start does (approves/starts the campaign send path)
|
|
35
|
+
- how to revise (reply with what to change, or use the campaign UI)
|
|
36
|
+
- whether anything has started yet (nothing sends until the user starts)
|
|
37
|
+
|
|
38
|
+
When `lead-source-intake.json` exists, also answer:
|
|
39
|
+
|
|
40
|
+
- which source was materialized (`supplied-linkedin-profiles`,
|
|
41
|
+
`supplied-domains`, or `existing-lead-list`)
|
|
42
|
+
- how many rows/accounts were supplied
|
|
43
|
+
- how many rows were imported into the first review batch
|
|
44
|
+
- that only the first review batch was enriched and messaged before
|
|
45
|
+
greenlight, not the full supplied list
|
|
46
|
+
- for `existing-lead-list`, that the source list was reused, not recreated
|
|
47
|
+
|
|
48
|
+
Use the exact state name `awaiting-user-greenlight` so logs and UX artifacts
|
|
49
|
+
can verify the stop point, but explain it in normal words: the campaign is
|
|
50
|
+
built and waiting for the user's final start decision.
|
|
51
|
+
|
|
52
|
+
The skill waits. It does not poll. A fresh continuation turn arrives
|
|
53
|
+
either because the user said something in chat (Claude greenlight path)
|
|
54
|
+
or because `get_campaign` / polling determined the user clicked Start in
|
|
55
|
+
the UI.
|
|
56
|
+
|
|
57
|
+
## Dual Greenlight Channels
|
|
58
|
+
|
|
59
|
+
### UI path
|
|
60
|
+
|
|
61
|
+
The user reviews messages in the watch link, bulk-approves messages in
|
|
62
|
+
the workflow table UI (the existing "Approve all" button that wraps
|
|
63
|
+
`POST /api/v3/workflow-tables/cells/approve-batch`), and clicks "Start
|
|
64
|
+
Campaign" in the campaign-builder UI.
|
|
65
|
+
|
|
66
|
+
No skill involvement. The skill remains idle in
|
|
67
|
+
`awaiting-user-greenlight`. On the next resume, the skill observes
|
|
68
|
+
`CampaignOffer.status === "running"` (or equivalent) and shifts to a
|
|
69
|
+
"campaign is live" confirmation without calling `start_campaign` again.
|
|
70
|
+
|
|
71
|
+
### Claude greenlight path
|
|
72
|
+
|
|
73
|
+
The user replies in chat with an affirmative intent: "yeah start",
|
|
74
|
+
"looks good, start", "ship it", "go". The skill then performs, in
|
|
75
|
+
order, atomically:
|
|
76
|
+
|
|
77
|
+
1. **Bulk-approve queued messages** via the existing endpoint
|
|
78
|
+
`POST /api/v3/workflow-tables/cells/approve-batch` with
|
|
79
|
+
`{ tableId }`.
|
|
80
|
+
- This is the SAME server action the UI "Approve all" button uses
|
|
81
|
+
(`src/components/outbound-builder/canvas/WorkflowTablesCanvas.tsx`
|
|
82
|
+
calls `/api/v3/workflow-tables/cells/approve-batch`).
|
|
83
|
+
- Do NOT invent a new tool or endpoint. Do NOT bypass this call by
|
|
84
|
+
directly updating cells — the endpoint handles downstream
|
|
85
|
+
scheduling via `CellOrchestrator.reprocessColumn`.
|
|
86
|
+
- If no MCP tool wrapper for approve-batch is available, the skill
|
|
87
|
+
invokes it via the same authenticated API client path that other
|
|
88
|
+
v3 routes use.
|
|
89
|
+
2. **`start_campaign({ campaignId })`** — flips the campaign into the
|
|
90
|
+
running state. If this call fails, surface the error; do NOT leave
|
|
91
|
+
the approvals in an "approved but not started" half-state silently
|
|
92
|
+
— either retry or escalate with the explicit state.
|
|
93
|
+
3. **`update_campaign({ campaignId, currentStep: "running" })`** — moves
|
|
94
|
+
the skill past `awaiting-user-greenlight` so resume lands in a
|
|
95
|
+
"campaign live" confirmation state.
|
|
96
|
+
4. **Re-surface the watch link** plus a short "campaign is live"
|
|
97
|
+
confirmation sentence.
|
|
98
|
+
|
|
99
|
+
All three mutating steps (bulk-approve, start_campaign, update_campaign)
|
|
100
|
+
must succeed together. If step 1 succeeds but step 2 fails, the skill
|
|
101
|
+
surfaces the recoverable state and stops — it does NOT attempt to
|
|
102
|
+
unapprove (that would be destructive) and it does NOT silently retry
|
|
103
|
+
forever.
|
|
104
|
+
|
|
105
|
+
## Already-Running No-Op
|
|
106
|
+
|
|
107
|
+
A Claude greenlight on an already-running campaign is a no-op
|
|
108
|
+
confirmation, NOT a duplicate start. Detect this at the top of the
|
|
109
|
+
greenlight path:
|
|
110
|
+
|
|
111
|
+
1. Call `get_campaign({ campaignId })` (or whatever lightweight state
|
|
112
|
+
fetch is available).
|
|
113
|
+
2. If the campaign is already running (status === "running" /
|
|
114
|
+
`currentStep === "running"`), skip all three mutating steps and
|
|
115
|
+
return a confirmation message: "Campaign is already live — here's
|
|
116
|
+
the watch link again."
|
|
117
|
+
3. Do NOT call `start_campaign` again. Do NOT call approve-batch
|
|
118
|
+
again. Do NOT update `currentStep`.
|
|
119
|
+
|
|
120
|
+
The symmetric UI case is handled by the UI itself — the UI button
|
|
121
|
+
disables once the campaign is running.
|
|
122
|
+
|
|
123
|
+
## Workspace + Sender Invariant
|
|
124
|
+
|
|
125
|
+
Before calling `start_campaign` via the Claude greenlight path,
|
|
126
|
+
re-validate the workspace/sender invariants Phase 84 committed against.
|
|
127
|
+
If the current auth context no longer matches (workspace switched mid
|
|
128
|
+
session, sender removed, etc.), abort the start with the same invariant
|
|
129
|
+
error Phase 84 uses. Do NOT start a campaign from the wrong workspace.
|
|
130
|
+
|
|
131
|
+
## Pre-Start State Guards
|
|
132
|
+
|
|
133
|
+
Refuse the start and surface the reason if any of the following are
|
|
134
|
+
true:
|
|
135
|
+
|
|
136
|
+
- Campaign is in a failed/paused state that needs operator attention
|
|
137
|
+
(e.g. upstream lead import failed post-mint and never recovered).
|
|
138
|
+
- Workspace mismatch between skill context and campaign row.
|
|
139
|
+
- Sender is missing or disabled.
|
|
140
|
+
- `attach_sequence` never succeeded (no sequence bound to the
|
|
141
|
+
campaign).
|
|
142
|
+
|
|
143
|
+
A refusal is NOT an escalation in the ladder sense — it's a pre-check
|
|
144
|
+
failure. Surface the specific reason so the user can fix it and say
|
|
145
|
+
"start" again.
|
|
146
|
+
|
|
147
|
+
## Atomicity Contract
|
|
148
|
+
|
|
149
|
+
"Atomic" here means: from the user's perspective, saying "yeah start"
|
|
150
|
+
either produces a running campaign with approved messages, OR produces
|
|
151
|
+
an explicit error state with NO silent half-state. It does NOT mean a
|
|
152
|
+
database transaction — approve-batch and start_campaign are separate
|
|
153
|
+
calls — it means the skill treats the trio as a single unit and does
|
|
154
|
+
not leave the user guessing which step half-succeeded.
|
|
155
|
+
|
|
156
|
+
If approve-batch partially succeeds (e.g. `approved: 50, failed: 3`),
|
|
157
|
+
the skill surfaces the partial result and asks the user whether to
|
|
158
|
+
proceed with start_campaign anyway or investigate the 3 failures. It
|
|
159
|
+
does NOT silently proceed.
|
|
160
|
+
|
|
161
|
+
## Hard Rules
|
|
162
|
+
|
|
163
|
+
- The autonomous tail NEVER calls `start_campaign` on its own.
|
|
164
|
+
- Step 16 NEVER auto-advances past `awaiting-user-greenlight` without a
|
|
165
|
+
user signal.
|
|
166
|
+
- Bulk-approve in the Claude greenlight path uses the EXISTING
|
|
167
|
+
`POST /api/v3/workflow-tables/cells/approve-batch` endpoint. Do NOT
|
|
168
|
+
invent a new tool / endpoint / mutation path.
|
|
169
|
+
- A Claude greenlight on an already-running campaign is a no-op
|
|
170
|
+
confirmation, not a duplicate start.
|
|
171
|
+
- Workspace/sender mismatch at greenlight time aborts the start with
|
|
172
|
+
the Phase 84 invariant.
|
|
173
|
+
- A pre-check refusal is NOT an escalation — it's a fixable state. The
|
|
174
|
+
skill stays in `awaiting-user-greenlight`.
|
|
175
|
+
- Never print the watch link in a half-started state (approved but not
|
|
176
|
+
started, or started but `currentStep` not updated).
|