@sellable/mcp 0.1.756 → 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
|
@@ -61,6 +61,14 @@ const DEFAULT_BUDGETS = {
|
|
|
61
61
|
// refuses to promote unversioned legacy creditTrust packets into scheduling
|
|
62
62
|
// authority, even when they contain a fresh provider response.
|
|
63
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
|
+
]);
|
|
64
72
|
// Mirrors RefillDoneReason in src/lib/workflow-tables/refill-target-plan.ts,
|
|
65
73
|
// plus loop-authored terminals the planner never emits:
|
|
66
74
|
// - abandoned_stale_checkpoint (fix 110-20d): a resumed wait the loop declares
|
|
@@ -2274,6 +2282,116 @@ async function startOrResume(input, deps) {
|
|
|
2274
2282
|
}
|
|
2275
2283
|
return ctx;
|
|
2276
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
|
+
}
|
|
2277
2395
|
async function gateBootstrap(input, deps, ctx) {
|
|
2278
2396
|
const plan = await deps.readPlan(refillPlanReadInput(input, { ctx, journal: false }));
|
|
2279
2397
|
const issuedReportingContext = sanitizeRefillReportingContextProjection(plan.reportingContext);
|
|
@@ -2294,46 +2412,127 @@ async function gateBootstrap(input, deps, ctx) {
|
|
|
2294
2412
|
if (issuedReportingContext) {
|
|
2295
2413
|
input.reportingContext = issuedReportingContext;
|
|
2296
2414
|
}
|
|
2297
|
-
const
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
const
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
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
|
+
};
|
|
2312
2476
|
}
|
|
2313
|
-
const
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
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
|
+
};
|
|
2319
2506
|
}
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
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;
|
|
2331
2526
|
await safeJournalAppend(ctx, deps, deps.journal.renderBootstrapSection({
|
|
2332
2527
|
summary: "Refill v2 execution bootstrap",
|
|
2333
2528
|
senderSummary: safeJson(recordValue(plan.bootstrap)?.senders ?? []),
|
|
2334
2529
|
laneSummary: safeJson(recordValue(plan.bootstrap)?.laneOrder ?? []),
|
|
2335
2530
|
targetSummary: safeJson(recordValue(plan.bootstrap)?.target ?? []),
|
|
2336
|
-
creditSummary: safeJson(
|
|
2531
|
+
creditSummary: safeJson({
|
|
2532
|
+
refreshActionCount: paidCreditActions.length,
|
|
2533
|
+
persistedReceiptCount: contextualReceipts.length,
|
|
2534
|
+
refreshedSenderIds: [...new Set(refreshedSenderIds)],
|
|
2535
|
+
}),
|
|
2337
2536
|
}));
|
|
2338
2537
|
const advanced = await deps.runClient.advanceRefillRunGateRemote({
|
|
2339
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);
|