@sellable/mcp 0.1.742 → 0.1.744

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.
@@ -214,7 +214,7 @@ export type SchedulerChangedCounts = {
214
214
  }>;
215
215
  };
216
216
  export declare function normalizeSchedulerChangedCounts(value: unknown): SchedulerChangedCounts | null;
217
- export declare function schedulerEnvelopeIsTerminal(schedulerStatus: string): boolean;
217
+ export declare function schedulerEnvelopeIsTerminal(schedulerStatus: string, receipt?: Record<string, unknown> | null): boolean;
218
218
  export declare function normalizeSchedulerPrimitiveResult(value: unknown, expected?: {
219
219
  expectedTargets: SchedulerExpectedTargetInput[];
220
220
  expectedTargetsHash: string;
@@ -223,7 +223,7 @@ export declare function normalizeSchedulerPrimitiveResult(value: unknown, expect
223
223
  retryAfterMs: number;
224
224
  changedCounts: null;
225
225
  auditComplete: false;
226
- replayedReceipt?: Record<string, unknown> | undefined;
226
+ unsettledReceipt?: Record<string, unknown> | undefined;
227
227
  status: "matching_scheduler_active";
228
228
  schedulerStatus: string;
229
229
  terminal: false;
@@ -274,50 +274,71 @@ function completeExpectedTargetSummary(value, expectedTargets, maxPlacements) {
274
274
  JSON.stringify(accounted) === JSON.stringify(target?.senderIds));
275
275
  });
276
276
  }
277
- // fix(112ao-2): the scheduler's in-flight envelope statuses. A response with
278
- // one of these describes a job that is still running it never describes an
279
- // outcome of this dispatch, no matter what it carries alongside.
280
- const SCHEDULER_IN_FLIGHT_ENVELOPE_STATUSES = new Set(["accepted", "attached"]);
281
- // The envelope statuses that report a real outcome for this dispatch and are
282
- // therefore eligible for the receipt audit below.
277
+ // fix(112ao-2): the scheduler's ATTACHING envelope statuses. These describe a
278
+ // dispatch that joined an existing run rather than producing its own outcome.
279
+ // They are terminal ONLY when the attach actually yielded a terminal receipt.
280
+ const SCHEDULER_ATTACHING_ENVELOPE_STATUSES = new Set(["accepted", "attached"]);
281
+ // The envelope statuses that report an outcome of this dispatch directly.
283
282
  const SCHEDULER_TERMINAL_ENVELOPE_STATUSES = new Set([
284
283
  "ran",
285
284
  "window_closed_noop",
286
285
  ]);
287
- export function schedulerEnvelopeIsTerminal(schedulerStatus) {
288
- return SCHEDULER_TERMINAL_ENVELOPE_STATUSES.has(schedulerStatus);
286
+ // The only receipt statuses that constitute a settled outcome. A receipt
287
+ // without one of these is not evidence that anything finished.
288
+ const SCHEDULER_TERMINAL_RECEIPT_STATUSES = new Set([
289
+ "ran",
290
+ "window_closed_noop",
291
+ "failed",
292
+ ]);
293
+ function hasTerminalSchedulerReceipt(receipt) {
294
+ const status = receipt ? stringValue(receipt.status) : null;
295
+ return Boolean(status && SCHEDULER_TERMINAL_RECEIPT_STATUSES.has(status));
296
+ }
297
+ export function schedulerEnvelopeIsTerminal(schedulerStatus, receipt = null) {
298
+ if (SCHEDULER_TERMINAL_ENVELOPE_STATUSES.has(schedulerStatus))
299
+ return true;
300
+ return (SCHEDULER_ATTACHING_ENVELOPE_STATUSES.has(schedulerStatus) &&
301
+ hasTerminalSchedulerReceipt(receipt));
289
302
  }
290
303
  export function normalizeSchedulerPrimitiveResult(value, expected) {
291
304
  const response = recordValue(value);
292
305
  const receipt = recordValue(response?.receipt);
293
306
  const schedulerStatus = stringValue(response?.status) ?? stringValue(receipt?.status) ?? "unknown";
294
307
  const errorCode = stringValue(recordValue(response?.error)?.code);
295
- // fix(112ao-2): whether this dispatch finished is a total function of the
296
- // ENVELOPE status, decided here before any receipt is read. The previous
297
- // guard was `!receipt && (accepted || attached)`, which made completion
298
- // depend on whether a field happened to be present: the scheduler may attach
299
- // an in-flight response to an already-running job and replay THAT job's
300
- // receipt, so an `attached` envelope arrived carrying complete, v3-auditable
301
- // counters from a prior run. It skipped this branch, passed the audit on
302
- // that stale evidence, and returned auditComplete: true — the coordinator
303
- // read a finished sweep and redispatched the run it should have awaited
304
- // (Dotwork 2026-07-29: 21 exact runs, coverage frozen at sent:0/scheduled:0).
308
+ // fix(112ao-2): whether this dispatch finished is decided explicitly from the
309
+ // envelope status plus the RECEIPT'S OWN STATUS never from the bare
310
+ // presence of a receipt field.
311
+ //
312
+ // The previous guard was `!receipt && (accepted || attached)`. Truthiness is
313
+ // the wrong test: an attach that carries a receipt with no terminal status is
314
+ // not evidence that anything settled, yet it skipped this branch, ran the v3
315
+ // audit on that non-evidence, and could return auditComplete: true — a
316
+ // completion claim for a run still in flight, which the coordinator then
317
+ // treats as done rather than awaiting (Dotwork 2026-07-29: 21 exact runs,
318
+ // coverage frozen at sent:0/scheduled:0).
319
+ //
320
+ // The attach is NOT always in flight, and this deliberately does not treat it
321
+ // so: scheduler-run.ts:466-483 only returns a receipt from
322
+ // waitForSchedulerRunReceipt when it is scope-matched to THIS request, so an
323
+ // attach carrying a terminal receipt is a legitimate idempotent completion of
324
+ // this exact operation and must keep its counters. Discarding it would report
325
+ // zero placements for a sweep that really placed cells.
305
326
  //
306
- // `accepted`/`attached` are IN-FLIGHT; `ran`/`window_closed_noop` are
307
- // TERMINAL and continue to the audit below. `backoff` and `failed` keep their
327
+ // `ran`/`window_closed_noop` remain terminal. `backoff`/`failed` keep their
308
328
  // existing handling — the run loop already unwraps a backoff envelope's
309
329
  // replayed receipt deliberately (schedulerRunReceiptIsFreshZeroScheduled).
310
- if (SCHEDULER_IN_FLIGHT_ENVELOPE_STATUSES.has(schedulerStatus)) {
330
+ if (SCHEDULER_ATTACHING_ENVELOPE_STATUSES.has(schedulerStatus) &&
331
+ !hasTerminalSchedulerReceipt(receipt)) {
311
332
  return {
312
333
  status: "matching_scheduler_active",
313
334
  schedulerStatus,
314
335
  terminal: false,
315
336
  disposition: "bounded_reread_wait",
316
337
  receipt: null,
317
- // A prior run's counters are never this dispatch's effect. Surfaced
318
- // under a distinct name so it can be read for diagnostics but can never
319
- // be mistaken for a fresh outcome.
320
- ...(receipt ? { replayedReceipt: receipt } : {}),
338
+ // A non-terminal receipt is not this dispatch's effect. Surfaced under a
339
+ // distinct name so it stays readable for diagnostics but can never be
340
+ // mistaken for a settled outcome.
341
+ ...(receipt ? { unsettledReceipt: receipt } : {}),
321
342
  retryAfterMs: numberValue(response?.retryAfterMs) ?? 30_000,
322
343
  changedCounts: null,
323
344
  auditComplete: false,
@@ -363,7 +384,7 @@ export function normalizeSchedulerPrimitiveResult(value, expected) {
363
384
  status: schedulerStatus,
364
385
  // fix(112ao-2): the completion decision, carried explicitly instead of
365
386
  // leaving each consumer to re-infer it from the status string.
366
- terminal: schedulerEnvelopeIsTerminal(schedulerStatus),
387
+ terminal: schedulerEnvelopeIsTerminal(schedulerStatus, receipt),
367
388
  receipt: response?.receipt ?? null,
368
389
  retryAfterMs: numberValue(response?.retryAfterMs),
369
390
  changedCounts,
@@ -2967,11 +2988,26 @@ export async function executeOneYoloPrimitive(action, workspaceId) {
2967
2988
  refusalReason: "run_scheduler_sweep action is missing exact workspace/date/revision scope or complete receipt requirements",
2968
2989
  };
2969
2990
  }
2991
+ // fix(112ao-1): poll by RUN IDENTITY. requestKey is already unique per
2992
+ // sweep, so requestKey + targetDate names this exact dispatch. Sending
2993
+ // targetShapeRevision/expectedTargetsHash/maxPlacements here made the
2994
+ // probe assert that the PLANNER's revision had not moved — but it is
2995
+ // state-fresh by design (112q D2), so it always had, scopeMatches came
2996
+ // back false, and every wait branch below fell through to the
2997
+ // action:"run" redispatch at the end of this block. The dispatch below
2998
+ // still carries the full write scope.
2970
2999
  const statusResult = recordValue(await runSchedulerSweep({
2971
3000
  workspaceId,
2972
3001
  action: "status",
2973
3002
  targetDate,
2974
3003
  requestKey,
3004
+ // fix(112ao-1-revert): the identity-only probe REQUIRES the FIX 1
3005
+ // server half (route isStatusReadScope). It shipped in 0.1.743 while
3006
+ // the deployed route still demanded the full triple, so every probe
3007
+ // 400'd invalid_scheduler_scope, api.ts threw, and the sweep primitive
3008
+ // died before its action:"run" dispatch — no sweep could place in ANY
3009
+ // workspace. Restored to the full scope until the server half is
3010
+ // deployed. Re-remove these three ONLY after that deploy is live.
2975
3011
  targetShapeRevision,
2976
3012
  expectedTargetsHash,
2977
3013
  maxPlacements,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.742",
3
+ "version": "0.1.744",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
6
6
  "main": "dist/index.js",