@sellable/mcp 0.1.908 → 0.1.909

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.
@@ -135,19 +135,9 @@ export function normalizeRefillV3SourceOwnerReceipt(params) {
135
135
  const signalExcludedPostIds = stringArray(rawRecord.excludedPostIds);
136
136
  const signalImportResult = recordValue(rawRecord.importResult);
137
137
  const signalImportJobId = stringValue(signalImportResult?.jobId);
138
- if (isSignalOwner &&
138
+ const signalImportPending = isSignalOwner &&
139
139
  rawRecord.rowsGainedPending === true &&
140
- signalImportJobId) {
141
- // Signal import is asynchronous. Starting its scrape is not a zero-row
142
- // terminal: return a retained effect identity so the coordinator reobserves
143
- // the live source job instead of recording a dry round and spending the
144
- // keyword avenue before any rows can arrive.
145
- return {
146
- kind: "pending",
147
- effectIdentity: signalImportJobId,
148
- detail: `Signal import ${signalImportJobId} started and rows are pending; reobserve the source job before classifying yield.`,
149
- };
150
- }
140
+ signalImportJobId !== null;
151
141
  const signalRowsGained = Math.max(0, numberValue(rawRecord.rowsGained) ?? 0);
152
142
  const signalProjection = recordValue(rawRecord.yieldProjection);
153
143
  const signalYieldRecord = recordValue(rawRecord.yieldEvidence);
@@ -266,6 +256,19 @@ export function normalizeRefillV3SourceOwnerReceipt(params) {
266
256
  }
267
257
  : null,
268
258
  };
259
+ if (signalImportPending) {
260
+ // Search and per-post qualification have already completed at this point;
261
+ // only row materialization is asynchronous. Preserve that completed search
262
+ // receipt while returning the retained import identity. Otherwise the live
263
+ // job disappears after settlement with no negative-set entry and the outer
264
+ // coordinator offers the identical broadening round again.
265
+ return {
266
+ kind: "pending",
267
+ effectIdentity: signalImportJobId,
268
+ detail: `Signal import ${signalImportJobId} started and rows are pending; reobserve the source job before classifying yield.`,
269
+ receipt,
270
+ };
271
+ }
269
272
  return importedCount > 0
270
273
  ? { kind: "receipt", receipt }
271
274
  : { kind: "no_effect", receipt };
@@ -860,6 +860,36 @@ export async function runRefillV3CampaignWorkWithProductionOwners(input, owners
860
860
  after: sourceOutcome.receipt,
861
861
  });
862
862
  }
863
+ if (sourceOutcome.kind === "scoped_failure" &&
864
+ sourceOutcome.pendingReceipt &&
865
+ owners.recordSourceCycle) {
866
+ // A Signal search has completed once the bounded posts were sampled
867
+ // and its async import was launched. Record that round before
868
+ // returning the retained job wait. Previously the pending branch
869
+ // wrote nothing, so the job could settle and disappear while the
870
+ // next advance still advertised the identical round as untried.
871
+ await owners.recordSourceCycle({
872
+ ...sourceCycleScope,
873
+ ...sourceCycleRunFact,
874
+ receipt: {
875
+ schemaVersion: 1,
876
+ kind: "search",
877
+ campaignId: request.scope.campaignId,
878
+ family: request.sourceCommand.family,
879
+ protectedFitDigest,
880
+ requestFingerprint: sourceOutcome.pendingReceipt.requestFingerprint,
881
+ sourceFingerprint: sourceOutcome.pendingReceipt.sourceAfterFingerprint,
882
+ keywordBatch: request.sourceCommand.keywordBatch,
883
+ postIds: request.sourceCommand.selectedPostIds,
884
+ // Row materialization is still pending. Zero here describes the
885
+ // completed search receipt, not the eventual import yield; the
886
+ // live job and fresh campaign/source observations own that.
887
+ importedCount: 0,
888
+ yieldEvidence: sourceOutcome.pendingReceipt.yieldEvidence,
889
+ observedAt: request.scope.date,
890
+ },
891
+ });
892
+ }
863
893
  const closedProviderFrontierSpent = sourceOutcome.kind === "source_terminal";
864
894
  const qualitySafeProviderFrontierSpent = request.action === "broaden_source_family" &&
865
895
  request.sourceCommand.family === "cold" &&
@@ -1323,12 +1353,18 @@ async function executeRefillV3Continuation(input, dependencies) {
1323
1353
  scopeFingerprint,
1324
1354
  }),
1325
1355
  });
1326
- if (!outcome.ok && outcome.correctiveContext !== undefined) {
1356
+ // Source-owner refusals are valid, reobserved no-effect receipts. Their one
1357
+ // correction still belongs to this exact packet and must return before the
1358
+ // ordinary completed-work path releases the retained campaign.
1359
+ const correctiveContext = outcome.ok
1360
+ ? outcome.receipt?.correctiveContext
1361
+ : outcome.correctiveContext;
1362
+ if (correctiveContext !== undefined) {
1327
1363
  return {
1328
1364
  kind: "attention_correction_required",
1329
1365
  runToken,
1330
1366
  packet: advanceResult.packet,
1331
- correction: outcome.correctiveContext,
1367
+ correction: correctiveContext,
1332
1368
  outcome,
1333
1369
  };
1334
1370
  }
@@ -144,10 +144,16 @@ export type RefillV3SourceOwnerOutcome = {
144
144
  kind: "no_effect";
145
145
  receipt: RefillV3SourceOwnerReceipt;
146
146
  } | {
147
- /** The source mutation started an asynchronous import that has not settled. */
147
+ /**
148
+ * The search completed and the source mutation started an asynchronous
149
+ * import that has not settled. The completed-search receipt must be
150
+ * recorded before waiting so the same keyword/post round cannot reopen
151
+ * after the import job disappears from world state.
152
+ */
148
153
  kind: "pending";
149
154
  effectIdentity: string;
150
155
  detail: string;
156
+ receipt: RefillV3SourceOwnerReceipt;
151
157
  } | {
152
158
  /** A closed provider-source business terminal, never infrastructure. */
153
159
  kind: "source_terminal";
@@ -220,6 +226,8 @@ export type RefillV3SourceDispatchResult = {
220
226
  failureClass: RefillV3SourceFailureClass | "uncertain";
221
227
  retryable: true;
222
228
  effectIdentity: string | null;
229
+ /** Completed search evidence retained while its async import settles. */
230
+ pendingReceipt?: RefillV3SourceOwnerReceipt;
223
231
  detail: string;
224
232
  correctiveContext?: RefillV3AttentionCorrection;
225
233
  exhausted: false;
@@ -457,6 +457,7 @@ export async function dispatchRefillV3SourceFamily(input) {
457
457
  failureClass: "uncertain",
458
458
  retryable: true,
459
459
  effectIdentity: outcome.effectIdentity,
460
+ pendingReceipt: outcome.receipt,
460
461
  detail: `${ownerKey}: ${outcome.detail}`,
461
462
  exhausted: false,
462
463
  reobserveRequired: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.908",
3
+ "version": "0.1.909",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
6
6
  "main": "dist/index.js",