@sellable/mcp 0.1.191 → 0.1.193

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.
@@ -27,8 +27,8 @@ Re-cascade runs whenever ALL of the following are true:
27
27
  to continue processing more campaign rows.
28
28
  2. A subsequent check of rubric state shows rows that were pending at
29
29
  first message-generation pass are now passed.
30
- 3. Those newly-passed rows do NOT yet have generated messages
31
- (`messagesCount` flat relative to pre-cascade).
30
+ 3. Those newly-passed rows do NOT yet have current-revision generated messages
31
+ (`currentRevisionGeneratedMessagesCount` flat relative to pre-cascade).
32
32
  4. Step 15 has NOT yet transitioned to `awaiting-user-greenlight`.
33
33
 
34
34
  Before that first generated-message approval, do not run this loop just to wait
@@ -41,25 +41,31 @@ rows graduated.
41
41
  ## Re-Cascade Loop Shape
42
42
 
43
43
  ```text
44
- 1. paginate campaign table (get_rows_minimal, strip carryData) to
45
- collect rowIds where:
46
- - rubricStatus = "passed"
47
- - messageStatus = "pending" OR messagesCount = 0
44
+ 1. use selector dry-run only if debugging:
45
+ `select_campaign_cells({ columnRole: "generateMessage", rowSelector: { type: "needsGeneratedMessage" } })`
46
+ The normal path can skip this and queue directly.
48
47
 
49
- 2. if newlyPassedRowIds.length == 0:
48
+ 2. if selectedCellCount == 0:
50
49
  proceed to awaiting-user-greenlight
51
50
  EXIT re-cascade loop
52
51
 
53
52
  3. else:
54
- queue_cells({
53
+ queue_campaign_cells({
55
54
  tableId,
56
- cellIds: newlyPassedGenerateMessageCellIds
55
+ columnRole: "generateMessage",
56
+ rowSelector: { type: "needsGeneratedMessage" }
57
57
  })
58
58
 
59
- 4. re-check token contract on the cascaded subset (strict mode default)
59
+ 4. wait_for_campaign_processing({
60
+ tableId,
61
+ minGeneratedMessages: 1,
62
+ templateRevision: "current"
63
+ })
64
+
65
+ 5. re-check token contract on the cascaded subset (strict mode default)
60
66
  — see references/thomas-revision-filters.md + gold-standard-message-patterns.md
61
67
 
62
- 5. go back to step 1 until newlyPassedRowIds == 0
68
+ 6. go back to step 1 until selectedCellCount == 0
63
69
  ```
64
70
 
65
71
  ## Cap
@@ -86,9 +92,10 @@ silently skipped.
86
92
  ## Hard Rules
87
93
 
88
94
  - Generate Message cells do not always auto-cascade on late-passed rows —
89
- Step 15 must explicitly queue newly-pending Generate Message cells.
95
+ Step 15 must explicitly queue `needsGeneratedMessage` cells.
90
96
  - Re-cascade iteration cap = 3. Trip escalates.
91
- - `messagesCount` flat + rubric graduating rows ⇒ re-cascade required.
97
+ - `currentRevisionGeneratedMessagesCount` flat + rubric graduating rows ⇒
98
+ re-cascade required.
92
99
  - Token contract enforcement applies per-cascade, not once-for-all.
93
100
  - Re-cascade NEVER re-runs `import_leads`, `check_rubric`, or
94
101
  `enrich_with_prospeo`. Those are Step 13 / Step 14 actions.
@@ -30,9 +30,8 @@ Step 13 — materialize source list + confirm initial campaign slice
30
30
  materialize/reuse approved source with campaignOfferId
31
31
  import_leads(source-list target; SalesNav/Prospeo default ~1000, Signal Discovery default ~1500 engagers)
32
32
  wait_for_lead_list_ready
33
- confirm_lead_list(reviewBatchLimit=15)
33
+ confirm_lead_list(reviewBatchLimit=15) # compact reviewBatch metadata is stored on the campaign table
34
34
  wait_for_campaign_table_ready # campaign table exists
35
- get_rows_minimal # read initial campaign-table execution slice
36
35
  update_campaign(currentStep=filter-choice)
37
36
 
38
37
  Post-import main thread
@@ -45,22 +44,17 @@ Post-import main thread
45
44
  only then start the initial-slice cascade
46
45
 
47
46
  Step 14 — kick initial-slice cascade + observe campaign rows
48
- queue_cells(cellIds=<first 15 campaign-table execution-slice Enrich Prospect cells only>) <-- starts bounded chain
49
- wait_for_campaign_table_ready # wait until sample cascade starts returning filter results
50
- get_rows_minimal # read passesRubric + message cell status per row
51
- wait_for_rubric_results(minPassedCount=1, includeRows=false)
47
+ queue_campaign_cells(columnRole=enrich, rowSelector=reviewBatch) <-- starts bounded chain
48
+ wait_for_campaign_processing(minPassedCount=1)
52
49
  if at least one row passes: update_campaign(currentStep=auto-execute-messaging)
53
50
  compute projectedPass for later reporting / revision decisions
54
- if zero rows pass: diagnose brief-vs-list; if brief: update_campaign_brief + re-queue + wait
51
+ if zero rows pass: diagnose brief-vs-list; if brief: update_campaign_brief + re-queue + wait with changed args
55
52
  (check_rubric / bulk_enrich_with_prospeo are NOT called here —
56
- cascade already did them. wait_for_rubric_results is OK as a
57
- read-only observation helper if you need to block until the first
58
- passing filtered row exists.)
53
+ cascade already did them.)
59
54
 
60
55
  Step 15 — observe messaging
61
- wait_for_rubric_results({ minPassedCount: 1, minMessagesCount: 1, includeRows: false })
62
- get_rows_minimal # confirm the first passing generated message exists
63
- (rare) queue_cells on any pending Generate Message cells
56
+ queue_campaign_cells(columnRole=generateMessage, rowSelector=needsGeneratedMessage)
57
+ wait_for_campaign_processing({ minGeneratedMessages: 1, templateRevision: "current" })
64
58
  token-contract spot check via get_rows
65
59
  update_campaign(currentStep=auto-execute-messaging) with review-ready narration
66
60
  ask the user to approve the generated message before Settings
@@ -99,19 +93,14 @@ Message` column's http_request writes those cells via the cascade.
99
93
  - Do NOT call `check_rubric`, `enrich_with_prospeo`, or
100
94
  `bulk_enrich_with_prospeo` in the tail. Those are direct-API
101
95
  mutation tools that fetch data to the caller without writing to
102
- the workflow table cells. Step 13's `queue_cells` already
96
+ the workflow table cells. Step 14's `queue_campaign_cells` call
103
97
  triggers the column-level enrichment that populates those cells.
104
98
  Running these tools in the tail produces duplicate cost with no
105
99
  cell side effect.
106
- - `wait_for_rubric_results` is read-only and OK to use as an
107
- observation helper when you need to block on rubric completion
108
- it does not mutate cells. Prefer `get_rows_minimal` +
109
- `wait_for_campaign_table_ready` for the primary cascade-observation
110
- path, but use `wait_for_rubric_results({ targetCount: cohortSize })`
111
- when the table-level wait returns before rubric cells finish. In
112
- create-campaign-v2 Step 14, pass `minPassedCount: 1`; one passing
113
- filtered row is enough to start observing Generate Message, even if
114
- other sample rows are still processing.
100
+ - `wait_for_campaign_processing` is the stats-only observation helper for the
101
+ tail. In Step 14 pass `minPassedCount: 1`; in Step 15 pass
102
+ `minGeneratedMessages: 1` and `templateRevision: "current"`. Do not repoll
103
+ identical args after a timeout.
115
104
  - `start_campaign` is FORBIDDEN in the autonomous tail. It belongs only
116
105
  in the Claude-greenlight path, AFTER the user signals "start". See
117
106
  `references/final-handoff-contract.md`.
@@ -121,15 +110,16 @@ Message` column's http_request writes those cells via the cascade.
121
110
  - You MAY NOT call `start_campaign` while ANY passing row has an
122
111
  empty `Generate Message` cell. If you discover empty message cells in
123
112
  the greenlight turn, ABORT greenlight and return to Step 15 first.
124
- - You MAY NOT call `attach_sequence` while ANY passing row has an
125
- empty `Generate Message` cell. Before `attach_sequence`, call
126
- `get_rows_minimal` and confirm every row where `passesRubric=true`
127
- has a `completed` Generate Message cell with non-empty `result`. If
128
- any are pending, call `queue_cells` on those generateMessageCellIds
129
- and wait. If rows truly won't message, ESCALATE.
130
- - You MAY NOT advance past Step 13 without calling `queue_cells` on
131
- the initial-slice Enrich Prospect cells. Without it, every downstream
132
- cell stays `pending` and the campaign ships empty.
113
+ - You MAY NOT call `attach_sequence` while ANY reviewed passing row has an
114
+ empty or stale `Generate Message` cell. Before `attach_sequence`, call
115
+ `select_campaign_cells({ tableId, columnRole: "generateMessage",
116
+ rowSelector: { type: "needsGeneratedMessage" } })`. If any cells are
117
+ selected, call `queue_campaign_cells` with the same selector and wait with
118
+ `wait_for_campaign_processing({ minGeneratedMessages: 1,
119
+ templateRevision: "current" })`. If rows truly won't message, ESCALATE.
120
+ - You MAY NOT advance past Step 14 without calling `queue_campaign_cells` for
121
+ `{ columnRole: "enrich", rowSelector: { type: "reviewBatch" } }`. Without it,
122
+ every downstream cell stays `pending` and the campaign ships empty.
133
123
  - You MAY NOT queue enrichment for rows outside the configured internal
134
124
  execution slice before the user approves expansion. Full-list enrichment/message
135
125
  generation is a credit-spend decision and must happen after the user
@@ -141,9 +131,8 @@ All subsequent steps read the already-parsed config. Do not re-load mid-run,
141
131
  and do not read repo-local config files; packaged Claude Code and Codex runs
142
132
  must use the MCP asset loader.
143
133
  Load each subskill prompt (`create-campaign-v2`, `research-sender`,
144
- `generate-messages`) at most once per run. A multi-call chunk sequence counts
145
- as one load. If a tool result already told you to load a prompt, load all
146
- chunks once and remember; do not restart the same prompt from offset 0 later.
134
+ `generate-messages`) at most once per run. If a tool result already told
135
+ you to load a prompt, remember it; do not restart the same prompt later.
147
136
 
148
137
  After every `update_campaign({ currentStep: ... })` in the tail, narrate
149
138
  what changed and orient the user to what the already-open app will show next —
@@ -265,16 +254,17 @@ searchId, targetLeadCount: 1000 })`.
265
254
  the source list and `workflowTableId` is the campaign table.
266
255
  4. `wait_for_campaign_table_ready` until the initial campaign-table execution slice rows are
267
256
  available in the campaign table.
268
- 5. Call `get_rows_minimal({ tableId: workflowTableId })` and confirm the
269
- 15-row internal execution slice is present. Do not queue cells in Step 13.
257
+ 5. Confirm the compact `reviewBatch` returned by `confirm_lead_list` has the
258
+ expected 15-row internal execution slice metadata. Do not fetch row payloads
259
+ or queue cells in Step 13.
270
260
  6. If the import returns zero usable leads, ESCALATE per
271
261
  `references/escalation-ladder.md` (hard fail).
272
262
  7. `update_campaign({ campaignId, currentStep: "filter-choice" })`.
273
263
  8. Describe the filter-choice screen now visible in the already-open app. Do not
274
264
  repeat the watch URL.
275
265
 
276
- **Do NOT call `check_rubric`, `wait_for_rubric_results`,
277
- `queue_cells`, `enrich_with_prospeo`, or `bulk_enrich_with_prospeo` in Step 13.**
266
+ **Do NOT call `check_rubric`, `wait_for_campaign_processing`,
267
+ `queue_campaign_cells`, `enrich_with_prospeo`, or `bulk_enrich_with_prospeo` in Step 13.**
278
268
  Those are direct-API tools that fetch enrichment/scoring data to the
279
269
  caller or start the workflow-table cascade too early. The cascade starts in
280
270
  Step 14 only after `save_rubrics` and `update_campaign_brief` have both
@@ -299,18 +289,16 @@ message is ready. It does NOT call `check_rubric`,
299
289
  Shape:
300
290
 
301
291
  ```text
302
- queue_cells({ tableId: workflowTableId, cellIds: reviewBatchEnrichCellIds })
303
- wait_for_campaign_table_ready({ tableId: workflowTableId })
304
- get_rows_minimal({ tableId: workflowTableId })
305
- wait_for_rubric_results({ tableId: workflowTableId, targetCount: cohortSize, minPassedCount: 1, includeRows: false })
292
+ queue_campaign_cells({ tableId: workflowTableId, columnRole: "enrich", rowSelector: { type: "reviewBatch" } })
293
+ wait_for_campaign_processing({ tableId: workflowTableId, minPassedCount: 1 })
306
294
  passInSample = count of first sampleSize review/process sample rows with passesRubric === true
307
295
  projectedPass = round(passInSample / sampleSize * importLimit)
308
296
 
309
- if wait_for_rubric_results.ready === true and passRate.passed >= 1:
297
+ if wait_for_campaign_processing.ready === true and passRate.passed >= 1:
310
298
  advance to Step 15 to observe or queue Generate Message for currently passing rows
311
299
  do not wait for every sample row to finish before message generation starts
312
300
  stop for review as soon as one passing generated message is ready
313
- else if wait_for_rubric_results.ready === false and reason === "timeout":
301
+ else if wait_for_campaign_processing.ready === false and reason === "timeout":
314
302
  use the partial passRate/stats as the sample diagnostic
315
303
  if passRate.passed >= 1:
316
304
  advance to Step 15 to observe/queue Generate Message for passing rows
@@ -322,9 +310,10 @@ else if wait_for_rubric_results.ready === false and reason === "timeout":
322
310
  ask whether to revise source, revise filter/rubric, or wait once only if still processing
323
311
  else:
324
312
  diagnose brief-vs-list per sample-validation-loop.md
325
- - brief: autonomous update_campaign_brief, then re-kick cascade
326
- (queue_cells on enrichCellIds) and wait for the new
327
- results
313
+ - brief: autonomous update_campaign_brief, then re-kick cascade with
314
+ queue_campaign_cells({ columnRole: "enrich", rowSelector:
315
+ { type: "reviewBatch" }, forceRerun: true }) and wait for the
316
+ new results
328
317
  - list: ESCALATE (no auto-revision of leads)
329
318
  - unknown: ESCALATE
330
319
  revisionRound += 1
@@ -361,8 +350,8 @@ Entered on `CampaignOffer.currentStep === "auto-execute-messaging"`.
361
350
 
362
351
  **Messages are produced by the `Generate Message` column cascade, not
363
352
  by a separate tool.** Do NOT call a `generate_messages` MCP tool —
364
- that tool does not exist; `generate-messages` is a subskill prompt
365
- loaded during live messaging drafts. In the autonomous tail, messages
353
+ that tool does not exist; the full `generate-messages` subskill prompt is the
354
+ Message Draft Builder contract for the approved template. In the autonomous tail, messages
366
355
  flow through the column pipeline: `Enrich Prospect` →
367
356
  `DNC Check` → `ICP Score` → `Passes Rubric` → `Generate Message`.
368
357
  Each column's http_request auto-fires when its upstream dependency
@@ -373,21 +362,19 @@ the output — not to generate messages manually.
373
362
 
374
363
  **What Step 15 does:**
375
364
 
376
- 1. `get_rows_minimal` to read `passesRubric` + `generateMessageCellId`
377
- - the Generate Message cell's `status` and `result` per row.
378
- Before queueing or waiting on Generate Message cells, confirm the
379
- minted campaign brief still contains `{{...}}` in `## Approved Message
380
- Template`. If it does not, fail before the cascade runs. Do not repair
381
- after mint; the template must be present during mint so Step 15 never
382
- starts in freeform generation mode.
383
- 2. For every row where `passesRubric === true`:
384
- - The Generate Message cell should be `running` or `completed`
385
- already (cascade auto-fired it). If it is still `pending`, queue
386
- it explicitly: `queue_cells({ tableId, cellIds:
387
- <generateMessageCellIds> })`.
388
- 3. `wait_for_rubric_results({ tableId, targetCount: reviewBatchSize,
389
- minPassedCount: 1, minMessagesCount: 1, includeRows: false })` until the
390
- first passing generated message is ready. Do not wait for every passing row's
365
+ 1. Before queueing or waiting on Generate Message cells, confirm the minted
366
+ campaign brief still contains `{{...}}` in `## Approved Message Template`.
367
+ If it does not, fail before the cascade runs. Do not repair after mint; the
368
+ template must be present during mint so Step 15 never starts in freeform
369
+ generation mode.
370
+ 2. Use `select_campaign_cells({ tableId, columnRole: "generateMessage",
371
+ rowSelector: { type: "needsGeneratedMessage" } })` only if you need a dry-run
372
+ count. If any passing row has a pending, empty, or stale Generate Message
373
+ cell, queue it explicitly with `queue_campaign_cells({ tableId, columnRole:
374
+ "generateMessage", rowSelector: { type: "needsGeneratedMessage" } })`.
375
+ 3. `wait_for_campaign_processing({ tableId, minGeneratedMessages: 1,
376
+ templateRevision: "current" })` until the first current-revision generated
377
+ message is ready. Do not wait for every passing row's
391
378
  Generate Message cell, and do not add "one more wait" for a stronger sample.
392
379
  Remaining review/process sample rows can continue processing in the background.
393
380
  4. Read the first ready generated message back via `get_rows` (full) and
@@ -46,8 +46,8 @@ Reject the branch as blocked if the campaign id, selected lead list, workflow
46
46
  table, or review-batch row ids/hash do not match. Do not reconstruct state from
47
47
  stale local markdown artifacts or direct database reads.
48
48
 
49
- Return compact output only: template recommendation, token fill rules, rendered
50
- sample, concerns, status, basis token, output timestamp/hash, and error or
49
+ Return message-draft output only: template recommendation, token fill rules,
50
+ rendered sample, concerns, status, basis token, output timestamp/hash, and error or
51
51
  retry detail. Do not write message cells, enrich rows, update the campaign
52
52
  brief, attach sequence, or imply send readiness from this branch.
53
53