@sellable/mcp 0.1.755 → 0.1.757
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/dist/refill-run-loop.js
CHANGED
|
@@ -56,6 +56,19 @@ const DEFAULT_BUDGETS = {
|
|
|
56
56
|
schedulerWaitRunBudgetMs: 300_000,
|
|
57
57
|
maxInvocationWallClockMs: 220_000,
|
|
58
58
|
};
|
|
59
|
+
// Keep this transport marker aligned with
|
|
60
|
+
// src/lib/workflow-tables/refill-run-state.ts. The backend intentionally
|
|
61
|
+
// refuses to promote unversioned legacy creditTrust packets into scheduling
|
|
62
|
+
// authority, even when they contain a fresh provider response.
|
|
63
|
+
const REFILL_CREDIT_TRUST_VERSION = 2;
|
|
64
|
+
const PAID_INMAIL_CREDIT_RECEIPT_VERSION = "paid_inmail_credit_evaluation.v2";
|
|
65
|
+
const PAID_INMAIL_CREDIT_POLICY_VERSION = 1;
|
|
66
|
+
const PAID_INMAIL_CREDIT_POLICY_TTL_MS = 6 * 60 * 60 * 1000;
|
|
67
|
+
const USABLE_CONTEXTUAL_CREDIT_RECEIPT_STATUSES = new Set([
|
|
68
|
+
"fresh",
|
|
69
|
+
"refreshed",
|
|
70
|
+
"below_threshold",
|
|
71
|
+
]);
|
|
59
72
|
// Mirrors RefillDoneReason in src/lib/workflow-tables/refill-target-plan.ts,
|
|
60
73
|
// plus loop-authored terminals the planner never emits:
|
|
61
74
|
// - abandoned_stale_checkpoint (fix 110-20d): a resumed wait the loop declares
|
|
@@ -2269,6 +2282,116 @@ async function startOrResume(input, deps) {
|
|
|
2269
2282
|
}
|
|
2270
2283
|
return ctx;
|
|
2271
2284
|
}
|
|
2285
|
+
function sha256ReceiptParts(parts) {
|
|
2286
|
+
return createHash("sha256").update(JSON.stringify(parts)).digest("hex");
|
|
2287
|
+
}
|
|
2288
|
+
function sameReceiptContext(value, expected) {
|
|
2289
|
+
return (stringValue(value.workspaceId) === expected.workspaceId &&
|
|
2290
|
+
stringValue(value.senderId) === expected.senderId &&
|
|
2291
|
+
stringValue(value.runId) === expected.runId &&
|
|
2292
|
+
stringValue(value.campaignId) === expected.campaignId &&
|
|
2293
|
+
stringValue(value.tableId) === expected.tableId &&
|
|
2294
|
+
stringValue(value.columnId) === expected.columnId &&
|
|
2295
|
+
(stringValue(value.cellId) ?? null) === expected.cellId &&
|
|
2296
|
+
stringValue(value.actionType) === expected.actionType &&
|
|
2297
|
+
numberValue(value.threshold) === expected.threshold);
|
|
2298
|
+
}
|
|
2299
|
+
/**
|
|
2300
|
+
* Decode the backend-owned Phase 123 receipt enough to prove that the client
|
|
2301
|
+
* is handing the exact fact/context identity back to the planner. The backend
|
|
2302
|
+
* performs the same validation again when parsing run state; this client-side
|
|
2303
|
+
* check prevents advancing g0 with bytes that are already known to fail there.
|
|
2304
|
+
*/
|
|
2305
|
+
function decodeContextualPaidInmailReceipt(value, expected, now) {
|
|
2306
|
+
const response = recordValue(value);
|
|
2307
|
+
const receipt = recordValue(response?.receipt) ??
|
|
2308
|
+
(stringValue(response?.receiptVersion) ===
|
|
2309
|
+
PAID_INMAIL_CREDIT_RECEIPT_VERSION
|
|
2310
|
+
? response
|
|
2311
|
+
: null);
|
|
2312
|
+
if (!receipt ||
|
|
2313
|
+
stringValue(receipt.receiptVersion) !==
|
|
2314
|
+
PAID_INMAIL_CREDIT_RECEIPT_VERSION ||
|
|
2315
|
+
numberValue(receipt.policyVersion) !== PAID_INMAIL_CREDIT_POLICY_VERSION ||
|
|
2316
|
+
numberValue(receipt.policyTtlMs) !== PAID_INMAIL_CREDIT_POLICY_TTL_MS ||
|
|
2317
|
+
numberValue(receipt.maxStalenessMs) !== PAID_INMAIL_CREDIT_POLICY_TTL_MS ||
|
|
2318
|
+
!USABLE_CONTEXTUAL_CREDIT_RECEIPT_STATUSES.has(stringValue(receipt.status) ?? "")) {
|
|
2319
|
+
return null;
|
|
2320
|
+
}
|
|
2321
|
+
const evaluationContext = recordValue(receipt.evaluationContext);
|
|
2322
|
+
if (!evaluationContext ||
|
|
2323
|
+
!sameReceiptContext(evaluationContext, expected) ||
|
|
2324
|
+
!sameReceiptContext(receipt, expected)) {
|
|
2325
|
+
return null;
|
|
2326
|
+
}
|
|
2327
|
+
const evaluatedAt = new Date(stringValue(receipt.evaluatedAt) ?? "");
|
|
2328
|
+
const after = recordValue(receipt.after);
|
|
2329
|
+
const checkedAt = new Date(stringValue(after?.checkedAt) ?? "");
|
|
2330
|
+
const balance = numberValue(after?.balance);
|
|
2331
|
+
const sentSince = numberValue(after?.sentSince);
|
|
2332
|
+
const available = numberValue(after?.available);
|
|
2333
|
+
const logicalRefreshId = stringValue(receipt.logicalRefreshId);
|
|
2334
|
+
const transportAttempt = recordValue(receipt.transportAttempt);
|
|
2335
|
+
if (!Number.isFinite(evaluatedAt.getTime()) ||
|
|
2336
|
+
!Number.isFinite(checkedAt.getTime()) ||
|
|
2337
|
+
evaluatedAt.getTime() > now.getTime() ||
|
|
2338
|
+
checkedAt.getTime() > now.getTime() ||
|
|
2339
|
+
balance === null ||
|
|
2340
|
+
!Number.isInteger(balance) ||
|
|
2341
|
+
balance < 0 ||
|
|
2342
|
+
sentSince === null ||
|
|
2343
|
+
!Number.isInteger(sentSince) ||
|
|
2344
|
+
sentSince < 0 ||
|
|
2345
|
+
available === null ||
|
|
2346
|
+
!Number.isInteger(available) ||
|
|
2347
|
+
available < 0 ||
|
|
2348
|
+
!logicalRefreshId ||
|
|
2349
|
+
stringValue(transportAttempt?.logicalRefreshId) !== logicalRefreshId) {
|
|
2350
|
+
return null;
|
|
2351
|
+
}
|
|
2352
|
+
const factIdentity = sha256ReceiptParts([
|
|
2353
|
+
"paid-inmail-credit-fact",
|
|
2354
|
+
PAID_INMAIL_CREDIT_POLICY_VERSION,
|
|
2355
|
+
expected.senderId,
|
|
2356
|
+
checkedAt.toISOString(),
|
|
2357
|
+
balance,
|
|
2358
|
+
]);
|
|
2359
|
+
if (stringValue(receipt.factIdentity) !== factIdentity ||
|
|
2360
|
+
stringValue(after?.factIdentity) !== factIdentity) {
|
|
2361
|
+
return null;
|
|
2362
|
+
}
|
|
2363
|
+
const snapshotIdentity = sha256ReceiptParts([
|
|
2364
|
+
"paid-inmail-credit-evaluation-snapshot",
|
|
2365
|
+
factIdentity,
|
|
2366
|
+
sentSince,
|
|
2367
|
+
available,
|
|
2368
|
+
evaluatedAt.toISOString(),
|
|
2369
|
+
]);
|
|
2370
|
+
if (stringValue(receipt.evaluationSnapshotIdentity) !== snapshotIdentity) {
|
|
2371
|
+
return null;
|
|
2372
|
+
}
|
|
2373
|
+
const contextIdentity = sha256ReceiptParts([
|
|
2374
|
+
expected.workspaceId,
|
|
2375
|
+
expected.senderId,
|
|
2376
|
+
expected.runId,
|
|
2377
|
+
expected.campaignId,
|
|
2378
|
+
expected.tableId,
|
|
2379
|
+
expected.columnId,
|
|
2380
|
+
expected.cellId,
|
|
2381
|
+
expected.actionType,
|
|
2382
|
+
expected.threshold,
|
|
2383
|
+
]);
|
|
2384
|
+
const receiptId = sha256ReceiptParts([
|
|
2385
|
+
PAID_INMAIL_CREDIT_RECEIPT_VERSION,
|
|
2386
|
+
PAID_INMAIL_CREDIT_POLICY_VERSION,
|
|
2387
|
+
contextIdentity,
|
|
2388
|
+
factIdentity,
|
|
2389
|
+
snapshotIdentity,
|
|
2390
|
+
logicalRefreshId,
|
|
2391
|
+
stringValue(receipt.status),
|
|
2392
|
+
]);
|
|
2393
|
+
return stringValue(receipt.receiptId) === receiptId ? receipt : null;
|
|
2394
|
+
}
|
|
2272
2395
|
async function gateBootstrap(input, deps, ctx) {
|
|
2273
2396
|
const plan = await deps.readPlan(refillPlanReadInput(input, { ctx, journal: false }));
|
|
2274
2397
|
const issuedReportingContext = sanitizeRefillReportingContextProjection(plan.reportingContext);
|
|
@@ -2289,45 +2412,127 @@ async function gateBootstrap(input, deps, ctx) {
|
|
|
2289
2412
|
if (issuedReportingContext) {
|
|
2290
2413
|
input.reportingContext = issuedReportingContext;
|
|
2291
2414
|
}
|
|
2292
|
-
const
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
const
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2415
|
+
const pinnedRunState = pinInitialSenderLaneChains(plan, ctx.runState);
|
|
2416
|
+
// Exact fenced plans are projected by projectedV2Packet(), whose legacy
|
|
2417
|
+
// bootstrap.paidCredit array is intentionally empty. The canonical refresh
|
|
2418
|
+
// authority is packet.target.globalActionQueue; reading the bootstrap array
|
|
2419
|
+
// minted `{ version: 2, receipts: [] }` on every run, which made the next
|
|
2420
|
+
// exact planner read receipt-aware without giving it a receipt to validate.
|
|
2421
|
+
const packet = recordValue(plan.packet);
|
|
2422
|
+
const packetTarget = recordValue(packet?.target);
|
|
2423
|
+
const paidCreditActions = arrayValue(packetTarget?.globalActionQueue)
|
|
2424
|
+
.filter(isRecord)
|
|
2425
|
+
.filter((action) => stringValue(action.type) === "refresh_paid_inmail_credits");
|
|
2426
|
+
const contextualReceipts = [];
|
|
2427
|
+
const refreshedSenderIds = [];
|
|
2428
|
+
const now = deps.now?.() ?? new Date();
|
|
2429
|
+
for (const action of paidCreditActions) {
|
|
2430
|
+
const toolInput = actionToolInput(action);
|
|
2431
|
+
const ids = actionIds(action);
|
|
2432
|
+
const senderId = actionSenderId(action);
|
|
2433
|
+
const campaignId = actionCampaignId(action);
|
|
2434
|
+
const tableId = actionTableId(action);
|
|
2435
|
+
const columnId = stringValue(toolInput.columnId) ??
|
|
2436
|
+
stringValue(ids.columnId) ??
|
|
2437
|
+
stringValue(action.columnId);
|
|
2438
|
+
const actionType = stringValue(action.actionType) ?? stringValue(toolInput.actionType);
|
|
2439
|
+
const threshold = numberValue(toolInput.threshold) ??
|
|
2440
|
+
numberValue(toolInput.oldThreshold) ??
|
|
2441
|
+
numberValue(action.threshold) ??
|
|
2442
|
+
numberValue(action.oldThreshold);
|
|
2443
|
+
const completeContext = senderId &&
|
|
2444
|
+
campaignId &&
|
|
2445
|
+
tableId &&
|
|
2446
|
+
columnId &&
|
|
2447
|
+
actionType === "send_inmail_closed" &&
|
|
2448
|
+
threshold !== null &&
|
|
2449
|
+
Number.isInteger(threshold) &&
|
|
2450
|
+
threshold >= 0
|
|
2451
|
+
? {
|
|
2452
|
+
workspaceId: input.workspaceId,
|
|
2453
|
+
senderId,
|
|
2454
|
+
runId: ctx.runId,
|
|
2455
|
+
campaignId,
|
|
2456
|
+
tableId,
|
|
2457
|
+
columnId,
|
|
2458
|
+
cellId: null,
|
|
2459
|
+
actionType: "INMAIL_CLOSED",
|
|
2460
|
+
threshold,
|
|
2461
|
+
}
|
|
2462
|
+
: null;
|
|
2463
|
+
if (!completeContext) {
|
|
2464
|
+
return {
|
|
2465
|
+
status: "blocked",
|
|
2466
|
+
blocker: "paid_inmail_refresh_failed",
|
|
2467
|
+
runId: ctx.runId,
|
|
2468
|
+
fence: ctx.fence,
|
|
2469
|
+
gate: ctx.gate,
|
|
2470
|
+
guidance: "The exact paid InMail refresh action did not carry a complete sender/campaign/table/column/action/threshold context. The run stayed at bootstrap and no refill primitive executed.",
|
|
2471
|
+
report: { action },
|
|
2472
|
+
journalPath: ctx.journalPath,
|
|
2473
|
+
targetConfig: ctx.targetConfig ?? undefined,
|
|
2474
|
+
runHandle: ctx.runHandle,
|
|
2475
|
+
};
|
|
2307
2476
|
}
|
|
2308
|
-
const
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2477
|
+
const refresh = await deps.executors.refreshPaidInmailCreditsWithRetry(completeContext.senderId, completeContext.workspaceId, {
|
|
2478
|
+
runId: completeContext.runId,
|
|
2479
|
+
actionType: "send_inmail_closed",
|
|
2480
|
+
campaignId: completeContext.campaignId,
|
|
2481
|
+
tableId: completeContext.tableId,
|
|
2482
|
+
columnId: completeContext.columnId,
|
|
2483
|
+
threshold: completeContext.threshold,
|
|
2484
|
+
});
|
|
2485
|
+
const classification = classifyPaidInmailRefreshReceipt(refresh.receipt);
|
|
2486
|
+
const decodedReceipt = decodeContextualPaidInmailReceipt(refresh.receipt, completeContext, now);
|
|
2487
|
+
if (!classification.usableCurrentFacts || !decodedReceipt) {
|
|
2488
|
+
return {
|
|
2489
|
+
status: "blocked",
|
|
2490
|
+
blocker: "paid_inmail_refresh_failed",
|
|
2491
|
+
runId: ctx.runId,
|
|
2492
|
+
fence: ctx.fence,
|
|
2493
|
+
gate: ctx.gate,
|
|
2494
|
+
guidance: "Paid InMail credit refresh did not return one usable receipt matching the exact fenced context. The run stayed at bootstrap and did not advance with invalid authority.",
|
|
2495
|
+
report: {
|
|
2496
|
+
senderId: completeContext.senderId,
|
|
2497
|
+
receiptStatus: classification.status,
|
|
2498
|
+
error: classification.error,
|
|
2499
|
+
attempts: refresh.attempts,
|
|
2500
|
+
errors: refresh.errors,
|
|
2501
|
+
},
|
|
2502
|
+
journalPath: ctx.journalPath,
|
|
2503
|
+
targetConfig: ctx.targetConfig ?? undefined,
|
|
2504
|
+
runHandle: ctx.runHandle,
|
|
2505
|
+
};
|
|
2314
2506
|
}
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2507
|
+
contextualReceipts.push(decodedReceipt);
|
|
2508
|
+
refreshedSenderIds.push(completeContext.senderId);
|
|
2509
|
+
}
|
|
2510
|
+
// Never promote an empty or legacy packet into receipt authority. With no
|
|
2511
|
+
// refresh head, the exact planner remains cache-fact based and can execute
|
|
2512
|
+
// the source/preparation action it already selected.
|
|
2513
|
+
const { creditTrust: _discardedCreditTrust, ...runStateWithoutCreditTrust } = pinnedRunState;
|
|
2514
|
+
const nextRunState = contextualReceipts.length > 0
|
|
2515
|
+
? mergeRunState(runStateWithoutCreditTrust, {
|
|
2516
|
+
creditTrust: {
|
|
2517
|
+
version: REFILL_CREDIT_TRUST_VERSION,
|
|
2518
|
+
receiptId: stringValue(contextualReceipts[0]?.receiptId),
|
|
2519
|
+
refreshedAt: now.toISOString(),
|
|
2520
|
+
senderIds: [...new Set(refreshedSenderIds)],
|
|
2521
|
+
attemptedSenderIds: [...new Set(refreshedSenderIds)],
|
|
2522
|
+
receipts: contextualReceipts,
|
|
2523
|
+
},
|
|
2524
|
+
})
|
|
2525
|
+
: runStateWithoutCreditTrust;
|
|
2325
2526
|
await safeJournalAppend(ctx, deps, deps.journal.renderBootstrapSection({
|
|
2326
2527
|
summary: "Refill v2 execution bootstrap",
|
|
2327
2528
|
senderSummary: safeJson(recordValue(plan.bootstrap)?.senders ?? []),
|
|
2328
2529
|
laneSummary: safeJson(recordValue(plan.bootstrap)?.laneOrder ?? []),
|
|
2329
2530
|
targetSummary: safeJson(recordValue(plan.bootstrap)?.target ?? []),
|
|
2330
|
-
creditSummary: safeJson(
|
|
2531
|
+
creditSummary: safeJson({
|
|
2532
|
+
refreshActionCount: paidCreditActions.length,
|
|
2533
|
+
persistedReceiptCount: contextualReceipts.length,
|
|
2534
|
+
refreshedSenderIds: [...new Set(refreshedSenderIds)],
|
|
2535
|
+
}),
|
|
2331
2536
|
}));
|
|
2332
2537
|
const advanced = await deps.runClient.advanceRefillRunGateRemote({
|
|
2333
2538
|
workspaceId: input.workspaceId,
|
|
@@ -17,6 +17,40 @@ type GetRefillPlanV2Input = {
|
|
|
17
17
|
journalNote?: string;
|
|
18
18
|
};
|
|
19
19
|
export declare function sanitizeEvergreenRefillPlanResult(value: unknown): Record<string, unknown>;
|
|
20
|
+
export declare function projectedV2Packet(packet: Record<string, unknown>): {
|
|
21
|
+
sideEffects: {
|
|
22
|
+
scheduled: boolean;
|
|
23
|
+
mutated: boolean;
|
|
24
|
+
sent: boolean;
|
|
25
|
+
refreshedPaidInmailCredits: boolean;
|
|
26
|
+
};
|
|
27
|
+
warnings: unknown;
|
|
28
|
+
reporting?: Record<string, unknown> | undefined;
|
|
29
|
+
refillReporting?: Record<string, unknown> | undefined;
|
|
30
|
+
reportingContext?: RefillReportingContextV2 | undefined;
|
|
31
|
+
readOnly: boolean;
|
|
32
|
+
generatedAt: unknown;
|
|
33
|
+
bootstrap: {
|
|
34
|
+
senders: never[];
|
|
35
|
+
laneOrder: never[];
|
|
36
|
+
target: never[];
|
|
37
|
+
paidCredit: never[];
|
|
38
|
+
laneMemoryHints: {
|
|
39
|
+
appliedFromRunState: boolean;
|
|
40
|
+
cooldownsApplied: never[];
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
plans: {};
|
|
44
|
+
globalActionQueue: {};
|
|
45
|
+
evergreen: unknown;
|
|
46
|
+
planRevision: unknown;
|
|
47
|
+
stateRevision: unknown;
|
|
48
|
+
packet: Record<string, unknown>;
|
|
49
|
+
targetConfig: unknown;
|
|
50
|
+
targetConfigBytes: unknown;
|
|
51
|
+
targetConfigDigest: unknown;
|
|
52
|
+
runHandle: {} | null;
|
|
53
|
+
};
|
|
20
54
|
export declare function buildRunStateFromLocalHints(workspaceId: string): {
|
|
21
55
|
version: number;
|
|
22
56
|
senderCursors: never[];
|
|
@@ -76,7 +76,7 @@ export function sanitizeEvergreenRefillPlanResult(value) {
|
|
|
76
76
|
}
|
|
77
77
|
return sanitized;
|
|
78
78
|
}
|
|
79
|
-
function projectedV2Packet(packet) {
|
|
79
|
+
export function projectedV2Packet(packet) {
|
|
80
80
|
const target = isRecord(packet.target) ? packet.target : {};
|
|
81
81
|
const reporting = sanitizeRefillReportingProjection(packet.reporting);
|
|
82
82
|
const reportingContext = sanitizeRefillReportingContextProjection(packet.reportingContext);
|