@sellable/mcp 0.1.112 → 0.1.113

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.
@@ -355,7 +355,7 @@ export const rubricToolDefinitions = [
355
355
  },
356
356
  minPassedCount: {
357
357
  type: "number",
358
- description: "Optional pass floor for bounded create-campaign samples. When this floor is met and remaining target rows are resolved as failed/not processing, the tool returns ready:true with partial:true instead of forcing a timeout.",
358
+ description: "Optional pass floor for bounded create-campaign samples. When this floor is met, the tool returns ready:true with partial:true instead of waiting for every target row to finish.",
359
359
  },
360
360
  timeoutMs: {
361
361
  type: "number",
@@ -686,11 +686,13 @@ export async function waitForRubricResults(input) {
686
686
  stats,
687
687
  };
688
688
  }
689
- if (minPassFloorMet && noActiveProcessing) {
689
+ if (minPassFloorMet) {
690
690
  const percent = completed > 0 ? Math.round((passed / completed) * 100) : 0;
691
- const reason = unresolvedRowsResolvedAsFailures
692
- ? "min_passed_count_met_with_resolved_failures"
693
- : "min_passed_count_met_no_active_processing";
691
+ const reason = !noActiveProcessing
692
+ ? "min_passed_count_met_with_active_processing"
693
+ : unresolvedRowsResolvedAsFailures
694
+ ? "min_passed_count_met_with_resolved_failures"
695
+ : "min_passed_count_met_no_active_processing";
694
696
  const rowSnapshot = includeRows
695
697
  ? await getTableRowsMinimal(tableId, {
696
698
  limit: effectiveTarget,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.112",
3
+ "version": "0.1.113",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -186,8 +186,10 @@ Optional debug/UAT draft directory, disabled in normal customer runs:
186
186
  `references/watch-guide-narration.md` for the compact screen contract. Do not
187
187
  send separate guide headline/body args. In Find Leads, the static stage label
188
188
  is not enough: name the active lane/provider, what search or sample you are
189
- trying, and why it fits this campaign. Do not promise time estimates in guide
190
- copy.
189
+ trying, and why it fits this campaign. When the source recommendation and
190
+ counts/sample quality are ready in chat, update the guide to tell the user to
191
+ review and approve the source in Codex/Claude instead of saying `I'll show`
192
+ the recommendation later. Do not promise time estimates in guide copy.
191
193
  - Every approval gate must include live campaign access after the readable inline
192
194
  content. Show a short `Watch link:` line with the campaign link when a
193
195
  campaign shell exists. In normal customer runs, do not show `Open artifact:`
@@ -580,6 +582,21 @@ workstreams`, `in parallel`, or `background` unless parallel branches were
580
582
  `attach_recommended_sequence`, `attach_sequence`, and `start_campaign`.
581
583
  `create_campaign`, `update_campaign`, and `save_rubrics` are allowed only when
582
584
  the active `flow.v2.json` step allows them.
585
+ - When source approval moves into import, keep chat and watch narration in the
586
+ same moment. If chat says import is starting, send `watchNarration` with
587
+ `stage: "review-batch"`, current-tense copy such as `Importing the review
588
+ batch`, and a no-launch safety note. Do not leave the guide saying
589
+ `source approved` or `I'll show the review-batch outcome` once import is
590
+ starting.
591
+ - After Lead Fit Builder saves rubrics, move the watched browser to Filter
592
+ Leads before waiting for message work to finish. Persist
593
+ `enableICPFilters: true`, `currentStep: "apply-icp-rubric"`, and
594
+ `watchNarration.stage: "fit-message"` so the user can see fit filtering
595
+ happen while the first message sample finishes. After message approval,
596
+ persist `useMessagingTemplate: true` and keep `enableICPFilters: true`;
597
+ sample validation then runs the review-batch cascade, and the user should be
598
+ walked through fit results, generated message results, and
599
+ Settings/sender/sequence handoff.
583
600
  - During pre-import validation, do not call `check_rubric`; use the lead-filter
584
601
  artifacts and only use campaign-backed scoring after Step 13 imports the
585
602
  15-lead test batch.
@@ -744,6 +744,7 @@
744
744
  "artifactLinkTiming": "before_next_step_or_revision_question",
745
745
  "doNotCompressToSummaryOnly": false,
746
746
  "doNotRenderArtifactLinksOnly": true,
747
+ "sourceRecommendationReadyWatchRule": "When the source recommendation decision card is ready in chat with counts and sample quality, update watchNarration to a find-leads chat-handoff frame. Use a headline like `Review the source in Codex`, body copy that says the browser is showing the evaluated source/results, and nextAction like `Approve in Codex`. Do not keep future-tense copy like `I'll show a source recommendation` after the decision is visible. Include a safety note that no leads import until the user approves the source.",
747
748
  "chatRenderRule": "Show a slim rendered-Markdown decision summary only, never a fenced code block. The first sentence must make the decision explicit: 'I recommend {primary source} using {exact filter/source recipe}. The runner-up is {source} because {reason}.' Use indexed sections and short bullets: recommendation, Primary source and filters, Runner-up sources, why it won, Quick numbers with one provider/source angle per bullet, raw volume, sampled fit count as n/N only (no percentages), estimated good-fit range after cleanup, activity/warmth basis, confidence note, 3-5 representative sample leads, and one tradeoff. Do not forecast connection acceptance rates, reply rates, meetings, pipeline, revenue, or ROI unless the user supplied verified benchmark data for this exact workspace/sender. If Signals was searched or considered, include two compact inline Markdown tables before the recommendation is treated as final: Signal keyword lanes with keyword lane, timeframe, posts found, and finalist posts reviewed; and LinkedIn posts sampled with post URL/title, author/topic, age, engagers, sampled engagers, good fits as n/N only, estimated usable prospects per post, and use/discard decision. Default to selecting a few promising Signals posts for the first sample instead of trying to prove full Signals scale up front; if the sample is good but volume is low, say how many more posts to add/scrape next. Do not skip or discard Signals based only on raw post count or vibes; show the post-level math first, or explicitly say no engagers could be fetched and lower confidence. Keep discarded paths, full sample rows, and lead-sample.json details in lead-review.md. Do not show plain filesystem paths unless links cannot be created."
748
749
  },
749
750
  {
@@ -760,6 +761,7 @@
760
761
  "selectedLeadListId stays the source list and workflowTableId is the campaign table"
761
762
  ],
762
763
  "watchNarrationRule": "Do not include time estimates. Say what review-batch work is happening and what the user will approve next.",
764
+ "sourceApprovedImportRule": "After source approval, if chat says import is starting, send review-batch watchNarration with current-tense import copy. Headline should be like `Importing the review batch`; body should say the browser is still showing the approved source leads while only the bounded 15-row review batch is imported. Do not use source-approved or future-tense outcome copy once import is starting.",
763
765
  "chatRenderRule": "Lead source is set. Next import and confirm only the first 15-row review batch into the campaign table. After workflowTableId exists, run the fit-filter and message-generation workstreams from the campaign table sample. If real parallel MCP/tool branches or host subagents were actually launched, say so; otherwise say the branches will run sequentially. Never claim parallelism unless parallel execution actually started."
764
766
  },
765
767
  {
@@ -790,6 +792,7 @@
790
792
  "leadSourceProvider",
791
793
  "providerSearchAssociation",
792
794
  "currentStep: primary provider step (signal-discovery, sales-nav, prospeo/contact-search, saved-lists, or leads)",
795
+ "watchNarration: find-leads source recommendation ready, or review-batch import starting when source approval already happened",
793
796
  "selectedLeadListId as source list id only for existing-list or supplied-list preview"
794
797
  ],
795
798
  "fallback": "Stop if campaignId is missing; the source must be attached to the existing CampaignOffer before import.",
@@ -870,7 +873,8 @@
870
873
  "onMissingCampaignAttachedSource": "stop_before_import_and_route_to_find_leads; source scouts must attach searches/selections with campaignOfferId before Step 13"
871
874
  },
872
875
  {
873
- "action": "watch_mode_orient"
876
+ "action": "watch_mode_orient",
877
+ "watchNarrationRule": "Before import_leads or confirm_lead_list starts, align the guide with chat by setting review-batch watchNarration to current-tense import copy. Use a headline like `Importing the review batch`; explain that the browser may still show the approved source leads while Codex imports only the bounded 15-row review batch; include a no-launch safety note."
874
878
  },
875
879
  {
876
880
  "tool": "import_leads",
@@ -1039,17 +1043,17 @@
1039
1043
  "fallback": "If real parallel branches are unavailable or the named agents are absent, run filter-leads and then message-generation in the parent thread with product MCP tools/assets. Do not customer-surface agent install status, and do not claim background or parallel work in that fallback."
1040
1044
  },
1041
1045
  {
1042
- "action": "wait_for_post_lead_artifacts",
1046
+ "action": "wait_for_lead_filter_artifact",
1043
1047
  "requiredArtifacts": [
1044
- "lead-filter.md",
1045
- "message-validation.md"
1048
+ "lead-filter.md"
1046
1049
  ],
1047
1050
  "optionalArtifacts": [
1048
1051
  "rubric.json",
1052
+ "message-validation.md",
1049
1053
  "message-prep.md",
1050
1054
  "message-candidate-drafts.md"
1051
1055
  ],
1052
- "reconciliationRule": "Before entering message-review, verify lead-filter.md and message-validation.md both came from the same brief.md, lead-review.md, and lead-sample.json. lead-filter.md gates the sample rows; lead-sample.json remains the message sample source."
1056
+ "rule": "Do not wait for message-validation.md before saving rubrics and moving the watched browser to Filter Leads. Message work may still be running while the user watches fit filtering."
1053
1057
  },
1054
1058
  {
1055
1059
  "action": "save_filter_rubrics_to_campaign",
@@ -1069,6 +1073,39 @@
1069
1073
  ],
1070
1074
  "writesCampaignState": "leadScoringRubrics",
1071
1075
  "requiredBeforeCascade": true
1076
+ },
1077
+ {
1078
+ "action": "advance_watch_to_filter_leads_after_rubrics_saved",
1079
+ "tool": "update_campaign",
1080
+ "requires": [
1081
+ "campaignId",
1082
+ "workflowTableId",
1083
+ "leadScoringRubrics"
1084
+ ],
1085
+ "requiredValues": {
1086
+ "currentStep": "apply-icp-rubric",
1087
+ "enableICPFilters": true,
1088
+ "watchNarration.stage": "fit-message"
1089
+ },
1090
+ "watchNarrationRule": "Headline should be like `Filtering the review batch`. Body should say the browser is on Filter Leads, Codex saved the fit rules, and the review rows are being checked while the first message sample finishes. Next should point to message review.",
1091
+ "when": "after_save_rubrics_succeeds_before_waiting_for_message_validation",
1092
+ "writesCampaignState": "currentStep:apply-icp-rubric"
1093
+ },
1094
+ {
1095
+ "tool": "get_campaign_navigation_state",
1096
+ "purpose": "confirm the watched UI moved to Filter Leads after rubrics saved",
1097
+ "optional": true
1098
+ },
1099
+ {
1100
+ "action": "wait_for_message_validation_artifact",
1101
+ "requiredArtifacts": [
1102
+ "message-validation.md"
1103
+ ],
1104
+ "optionalArtifacts": [
1105
+ "message-prep.md",
1106
+ "message-candidate-drafts.md"
1107
+ ],
1108
+ "reconciliationRule": "Before entering message-review, verify message-validation.md came from the same brief.md, lead-review.md, lead-sample.json, and saved lead-filter.md. lead-filter.md gates the sample rows; lead-sample.json remains the message sample source."
1072
1109
  }
1073
1110
  ],
1074
1111
  "requiredArtifacts": [
@@ -1090,6 +1127,8 @@
1090
1127
  "get_subskill_asset",
1091
1128
  "get_post_find_leads_scout_registry",
1092
1129
  "save_rubrics",
1130
+ "update_campaign",
1131
+ "get_campaign_navigation_state",
1093
1132
  "Task",
1094
1133
  "spawn_agent",
1095
1134
  "AskUserQuestion",
@@ -1106,7 +1145,6 @@
1106
1145
  "create_campaign",
1107
1146
  "import_leads",
1108
1147
  "confirm_lead_list",
1109
- "update_campaign",
1110
1148
  "queue_cells",
1111
1149
  "start_campaign",
1112
1150
  "check_rubric",
@@ -1114,7 +1152,7 @@
1114
1152
  "enrich_with_prospeo",
1115
1153
  "bulk_enrich_with_prospeo"
1116
1154
  ],
1117
- "watchRequired": false,
1155
+ "watchRequired": true,
1118
1156
  "waitFor": [
1119
1157
  "post_lead_workstreams_ready",
1120
1158
  "revise_leads",
@@ -1496,6 +1534,8 @@
1496
1534
  ],
1497
1535
  "requiredValues": {
1498
1536
  "currentStep": "validate-sample",
1537
+ "enableICPFilters": true,
1538
+ "useMessagingTemplate": true,
1499
1539
  "watchNarration.stage": "review-ready"
1500
1540
  },
1501
1541
  "when": "after_update_campaign_brief_succeeds",
@@ -1939,7 +1979,7 @@
1939
1979
  },
1940
1980
  {
1941
1981
  "tool": "wait_for_campaign_table_ready",
1942
- "purpose": "wait_for_bounded_review_batch_cascade_to_drain"
1982
+ "purpose": "wait_for_review_batch_cascade_to_start_returning_filter_results"
1943
1983
  },
1944
1984
  {
1945
1985
  "tool": "get_rows_minimal",
@@ -1954,11 +1994,12 @@
1954
1994
  "minPassedCount"
1955
1995
  ],
1956
1996
  "targetCountSource": "stats.totalRows_or_imported_batch_count",
1957
- "minPassedCountSource": "sample.minProjectedPass (3)",
1997
+ "minPassedCountSource": "firstPassingRowForMessageStart (1)",
1958
1998
  "requiredValues": {
1959
- "includeRows": false
1999
+ "includeRows": false,
2000
+ "minPassedCount": 1
1960
2001
  },
1961
- "note": "The shell-first flow tests 15 leads first; always pass cohortSize explicitly instead of relying on default 25 behavior. Pass minPassedCount so a 15-row batch with resolved failures can advance when the 3-pass floor is already met.",
2002
+ "note": "The shell-first flow tests 15 leads first; always pass cohortSize explicitly instead of relying on default 25 behavior. Pass minPassedCount=1 so the first passing filtered row unblocks Generate Message observation instead of waiting for every sample row to finish.",
1962
2003
  "readVia": "stats_only_tool_result",
1963
2004
  "extractFields": [
1964
2005
  "ready",
@@ -1975,7 +2016,7 @@
1975
2016
  {
1976
2017
  "action": "handle_partial_or_timeout_sample",
1977
2018
  "when": "wait_for_rubric_results.ready_false_or_reason_timeout",
1978
- "rule": "Do not repeat waits indefinitely. If active processing is visible, wait once more at most. Otherwise treat returned passRate/stats as a partial sample and stop before Settings with sample_revision_required when passed rows are below minProjectedPass or generated messages are incomplete.",
2019
+ "rule": "Do not repeat waits indefinitely. If at least one row has passed, move to Step 15 to observe or queue Generate Message for passing rows even when other sample rows are still processing. If zero rows have passed and active processing is visible, wait once more at most. Otherwise treat returned passRate/stats as a partial sample and stop before Settings with sample_revision_required.",
1979
2020
  "customerStatus": "sample-needs-revision",
1980
2021
  "showFields": [
1981
2022
  "passRate.completed",
@@ -2024,6 +2065,7 @@
2024
2065
  "revision_round_persists_across_resume",
2025
2066
  "wait_for_rubric_results_never_retain_rows_payload_in_tail_context",
2026
2067
  "wait_for_rubric_results_targetCount_always_explicit",
2068
+ "first_passing_row_unblocks_generate_message_observation",
2027
2069
  "timeout_never_repeats_without_customer_handoff",
2028
2070
  "timeout_or_underfloor_sample_never_advances_to_settings",
2029
2071
  "review_batch_cascade_waits_for_saved_rubrics_and_approved_template"
@@ -9,7 +9,9 @@ on every revision round.
9
9
  We spend a bounded review batch (default 15 rows) to prove fit before the
10
10
  user spends credits on hundreds more leads. The sample loop has one job:
11
11
  answer the question "do we have enough real passing examples for the user to
12
- judge this campaign?"
12
+ judge this campaign?" Message generation starts earlier: the first row that
13
+ passes filters is enough to begin observing or queueing Generate Message for
14
+ that passing row.
13
15
 
14
16
  If the answer is yes, proceed to Step 15 messaging for the review batch. If
15
17
  the answer is no, diagnose whether the brief is wrong or the list is wrong,
@@ -48,13 +50,16 @@ auto-revise leads.
48
50
 
49
51
  5. check_rubric(sample)
50
52
 
51
- 6. wait_for_rubric_results(sample, targetCount = <cohortSize>)
53
+ 6. wait_for_rubric_results(sample, targetCount = <cohortSize>, minPassedCount = 1)
52
54
  - cohortSize = stats.totalRows of the enrichment batch, or the
53
55
  imported batch count
54
56
  - default targetCount=15 matches the default review batch, but pass the
55
57
  explicit batch count anyway so future larger expansion batches do not
56
58
  accidentally stop early
57
59
  (see §Known Tool Behaviors #3)
60
+ - minPassedCount=1 means one passing filtered row unblocks Step 15
61
+ Generate Message observation. Do not wait for all sample rows to finish
62
+ before messages start.
58
63
 
59
64
  7. call `wait_for_rubric_results` with `includeRows=false`; extract ONLY:
60
65
  - ready: boolean
@@ -71,11 +76,13 @@ auto-revise leads.
71
76
  8. if `ready=false` and `reason="timeout"`:
72
77
  - do NOT keep polling indefinitely
73
78
  - treat the returned passRate/stats as a partial sample
79
+ - if at least one row has passed filters, advance to Step 15 to observe or
80
+ queue Generate Message for currently passing rows
74
81
  - if active processing is visible (`processingCount > 0` or a similar
75
82
  running-cell stat), one additional wait is allowed
76
83
  - otherwise diagnose the partial sample now
77
- - if passed rows are below `sample.minProjectedPass` or generated messages
78
- are missing for passing rows, stop before Settings with:
84
+ - if zero rows have passed and there is no active processing, stop before
85
+ Settings with:
79
86
  `Status: sample-needs-revision`
80
87
  - show the concrete numbers: completed, passed, pending, pass percent, and
81
88
  message count when available
@@ -89,8 +96,9 @@ auto-revise leads.
89
96
  projectedPass = round(passInSample / sampleSize * importLimit)
90
97
 
91
98
  10. branch:
92
- if projectedPass >= minProjectedPass:
93
- proceed to Step 15 (auto-execute-messaging) with this review batch
99
+ if passInSample >= 1:
100
+ proceed to Step 15 (auto-execute-messaging) with currently passing rows
101
+ so Generate Message can start without waiting for the full sample
94
102
  else:
95
103
  diagnose (see Brief-vs-List Diagnosis below)
96
104
  revisionRound += 1
@@ -166,11 +174,12 @@ and partial stats such as 13/15 scored, 2 passing, 2 messages generated. That is
166
174
  enough to diagnose an underperforming sample. Waiting again without active
167
175
  processing makes the experience feel frozen.
168
176
 
169
- Workaround: treat timeout stats as a partial sample. If the pass count is below
170
- the configured floor or message generation has not covered the passing rows,
171
- stop at `Status: sample-needs-revision` before Settings. Show the completed /
172
- passed / pending counts and ask whether to revise source, revise filter/rubric,
173
- or wait once only if active processing is still visible.
177
+ Workaround: treat timeout stats as a partial sample. If at least one row has
178
+ passed, move to Step 15 and observe or queue Generate Message for the passing
179
+ rows. If zero rows have passed and no active processing is visible, stop at
180
+ `Status: sample-needs-revision` before Settings. Show the completed / passed /
181
+ pending counts and ask whether to revise source, revise filter/rubric, or wait
182
+ once only if active processing is still visible.
174
183
 
175
184
  ## Projected Pass Math
176
185
 
@@ -86,6 +86,24 @@ Search iteration:
86
86
  }
87
87
  ```
88
88
 
89
+ Source recommendation ready:
90
+
91
+ ```json
92
+ {
93
+ "stage": "find-leads",
94
+ "headline": "Review the source in Codex",
95
+ "visibleState": "The browser is showing the evaluated Signal Discovery source with counts and sample quality.",
96
+ "agentIntent": "Approve LinkedIn Engagement or ask for a source change in chat before any leads import.",
97
+ "nextAction": "Approve in Codex",
98
+ "safety": "No leads import until you approve the source."
99
+ }
100
+ ```
101
+
102
+ When the source recommendation, counts, and sample-quality decision are ready in
103
+ chat, update the guide to route the user back to Codex/Claude for approval. Do
104
+ not keep future-tense copy such as `I'll show a source recommendation` once the
105
+ decision card is visible.
106
+
89
107
  Review batch:
90
108
 
91
109
  ```json
@@ -98,6 +116,24 @@ Review batch:
98
116
  }
99
117
  ```
100
118
 
119
+ Source approved and import starting:
120
+
121
+ ```json
122
+ {
123
+ "stage": "review-batch",
124
+ "headline": "Importing the review batch",
125
+ "visibleState": "The browser is still showing the approved LinkedIn Engagement source leads.",
126
+ "agentIntent": "Codex is importing only the bounded 15-row review batch into the campaign now.",
127
+ "nextAction": "Review batch ready",
128
+ "safety": "This is still a review step; nothing launches or sends."
129
+ }
130
+ ```
131
+
132
+ When chat says the source is approved and import is starting, the guide must use
133
+ current-tense import copy. Do not leave the guide in source-approved or
134
+ future-tense copy such as `I'll show the review-batch outcome`; that makes the
135
+ browser guide describe a different moment than chat.
136
+
101
137
  Fit + message:
102
138
 
103
139
  ```json
@@ -20,8 +20,8 @@ Every tail run MUST call these tools in this exact order. The tail is
20
20
  **review-batch cascade-driven**: you kick off Enrich Prospect only for
21
21
  the imported review batch, and the workflow engine chains DNC Check →
22
22
  ICP Score → Passes Rubric → Generate Message automatically. Your job is
23
- to START the bounded cascade, WAIT for it to drain, OBSERVE the results,
24
- and stop for review.
23
+ to START the bounded cascade, WAIT until filter results land, OBSERVE message
24
+ generation as soon as one row passes, and stop for review.
25
25
  Do NOT manually run rubric-check, enrich, or message-generation
26
26
  tools — the cascade already does them.
27
27
 
@@ -43,14 +43,16 @@ Post-import main thread
43
43
 
44
44
  Step 14 — kick bounded cascade + observe sample
45
45
  queue_cells(cellIds=<review-batch Enrich Prospect cells only>) <-- starts bounded chain
46
- wait_for_campaign_table_ready # review-batch cascade drains here
46
+ wait_for_campaign_table_ready # wait until review-batch cascade starts returning filter results
47
47
  get_rows_minimal # read passesRubric + message cell status per row
48
- compute projectedPass
49
- if OK: update_campaign(currentStep=auto-execute-messaging)
50
- else: diagnose brief-vs-list; if brief: update_campaign_brief + re-queue + wait
48
+ wait_for_rubric_results(minPassedCount=1, includeRows=false)
49
+ if at least one row passes: update_campaign(currentStep=auto-execute-messaging)
50
+ compute projectedPass for later reporting / revision decisions
51
+ if zero rows pass: diagnose brief-vs-list; if brief: update_campaign_brief + re-queue + wait
51
52
  (check_rubric / bulk_enrich_with_prospeo are NOT called here —
52
53
  cascade already did them. wait_for_rubric_results is OK as a
53
- read-only observation helper if you need to block on completion.)
54
+ read-only observation helper if you need to block until the first
55
+ passing filtered row exists.)
54
56
 
55
57
  Step 15 — observe messaging
56
58
  get_rows_minimal # confirm passing rows have completed Generate Message cells
@@ -98,9 +100,12 @@ Message` column's http_request writes those cells via the cascade.
98
100
  - `wait_for_rubric_results` is read-only and OK to use as an
99
101
  observation helper when you need to block on rubric completion —
100
102
  it does not mutate cells. Prefer `get_rows_minimal` +
101
- `wait_for_campaign_table_ready` for the primary cascade-drain
103
+ `wait_for_campaign_table_ready` for the primary cascade-observation
102
104
  path, but use `wait_for_rubric_results({ targetCount: cohortSize })`
103
- when the table-level wait returns before rubric cells finish.
105
+ when the table-level wait returns before rubric cells finish. In
106
+ create-campaign-v2 Step 14, pass `minPassedCount: 1`; one passing
107
+ filtered row is enough to start observing Generate Message, even if
108
+ other sample rows are still processing.
104
109
  - `start_campaign` is FORBIDDEN in the autonomous tail. It belongs only
105
110
  in the Claude-greenlight path, AFTER the user signals "start". See
106
111
  `references/final-handoff-contract.md`.
@@ -275,9 +280,9 @@ decision tree lives in `references/sample-validation-loop.md`.
275
280
 
276
281
  **Step 14 starts the bounded cascade, then observes it.** Step 13 imported the
277
282
  review batch only. After `save_rubrics` and the approved message template are
278
- persisted, Step 14 queues the review-batch Enrich Prospect cells, waits for the
279
- table cascade to drain, then reads the resulting ICP scores and Passes Rubric
280
- values. It does NOT call `check_rubric`, `bulk_enrich_with_prospeo`, or any
283
+ persisted, Step 14 queues the review-batch Enrich Prospect cells, waits until
284
+ filter results start landing, then moves to message observation as soon as one
285
+ row passes. It does NOT call `check_rubric`, `bulk_enrich_with_prospeo`, or any
281
286
  other direct enrichment/scoring tool.
282
287
 
283
288
  Shape:
@@ -286,23 +291,23 @@ Shape:
286
291
  queue_cells({ tableId: workflowTableId, cellIds: reviewBatchEnrichCellIds })
287
292
  wait_for_campaign_table_ready({ tableId: workflowTableId })
288
293
  get_rows_minimal({ tableId: workflowTableId })
289
- wait_for_rubric_results({ tableId: workflowTableId, targetCount: cohortSize, minPassedCount: minProjectedPass, includeRows: false })
294
+ wait_for_rubric_results({ tableId: workflowTableId, targetCount: cohortSize, minPassedCount: 1, includeRows: false })
290
295
  passInSample = count of first sampleSize review-batch rows with passesRubric === true
291
296
  projectedPass = round(passInSample / sampleSize * importLimit)
292
297
 
293
- if wait_for_rubric_results.ready === true and partial === true and reason === "min_passed_count_met_with_resolved_failures":
294
- use the partial passRate/stats as a valid sample diagnostic
295
- continue if projectedPass >= minProjectedPass and generated messages are present for the passing rows
298
+ if wait_for_rubric_results.ready === true and passRate.passed >= 1:
299
+ advance to Step 15 to observe or queue Generate Message for currently passing rows
300
+ do not wait for every sample row to finish before message generation starts
296
301
  else if wait_for_rubric_results.ready === false and reason === "timeout":
297
302
  use the partial passRate/stats as the sample diagnostic
298
- if active processing is visible:
303
+ if passRate.passed >= 1:
304
+ advance to Step 15 to observe/queue Generate Message for passing rows
305
+ else if active processing is visible:
299
306
  wait one more time at most
300
307
  else:
301
308
  stop before Settings with Status: sample-needs-revision
302
309
  show completed, passed, pending, pass percent, and message count when available
303
310
  ask whether to revise source, revise filter/rubric, or wait once only if still processing
304
- else if projectedPass >= minProjectedPass:
305
- advance to Step 15
306
311
  else:
307
312
  diagnose brief-vs-list per sample-validation-loop.md
308
313
  - brief: autonomous update_campaign_brief, then re-kick cascade