Files
memind/mindspace-delivery-contract.mjs
T
john 843e4d1002
Memind CI / Test, build, and release guards (push) Successful in 2m58s
fix(prod): dedupe scheduled tasks and reconcile orphan delivery contracts
Prevent duplicate active scheduled tasks, fail static preparing contracts
when HTML is missing, raise MindSpace remote timeout default to 30s, and
add 103 repair scripts for inspection follow-ups.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-16 08:52:16 +08:00

98 lines
3.8 KiB
JavaScript

import crypto from 'node:crypto';
export function normalizeDeliveryRelativePath(value) {
const path = String(value ?? '').replace(/\\/g, '/').replace(/^\/+/, '');
if (!path.startsWith('public/') || !path.toLowerCase().endsWith('.html')) return null;
if (path.split('/').some((part) => !part || part === '.' || part === '..')) return null;
return path;
}
export async function preparePageDeliveryContract({ pool, userId, requestId, relativePath, pgRequired = false }) {
const workspaceRelativePath = normalizeDeliveryRelativePath(relativePath);
if (!pool || !userId || !requestId || !workspaceRelativePath) return null;
const now = Date.now();
const id = crypto.randomUUID();
await pool.query(
`INSERT INTO h5_page_delivery_contracts
(id, user_id, request_id, workspace_relative_path, data_mode, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, 'preparing', ?, ?)
ON DUPLICATE KEY UPDATE data_mode = VALUES(data_mode), status = 'preparing', failure_reason = NULL, updated_at = VALUES(updated_at)`,
[id, userId, requestId, workspaceRelativePath, pgRequired ? 'pg_required' : 'static', now, now],
);
return { id, userId, requestId, workspaceRelativePath, dataMode: pgRequired ? 'pg_required' : 'static', status: 'preparing' };
}
export async function getPageDeliveryContract({ pool, userId, relativePath }) {
const workspaceRelativePath = normalizeDeliveryRelativePath(relativePath);
if (!pool || !userId || !workspaceRelativePath) return null;
const [rows] = await pool.query(
`SELECT id, data_mode, status, failure_reason FROM h5_page_delivery_contracts
WHERE user_id = ? AND workspace_relative_path = ?
ORDER BY updated_at DESC LIMIT 1`,
[userId, workspaceRelativePath],
);
return rows?.[0] ?? null;
}
export async function markPageDeliveryContractReady({ pool, userId, relativePath }) {
const workspaceRelativePath = normalizeDeliveryRelativePath(relativePath);
if (!pool || !userId || !workspaceRelativePath) return false;
const now = Date.now();
const [result] = await pool.query(
`UPDATE h5_page_delivery_contracts
SET status = 'ready', ready_at = ?, failure_reason = NULL, updated_at = ?
WHERE user_id = ? AND workspace_relative_path = ? AND status = 'preparing'`,
[now, now, userId, workspaceRelativePath],
);
return Number(result?.affectedRows ?? 0) > 0;
}
export async function markPageDeliveryContractFailed({
pool,
userId,
relativePath,
failureReason = 'delivery_material_missing',
} = {}) {
const workspaceRelativePath = normalizeDeliveryRelativePath(relativePath);
if (!pool || !userId || !workspaceRelativePath) return false;
const now = Date.now();
const [result] = await pool.query(
`UPDATE h5_page_delivery_contracts
SET status = 'failed', failure_reason = ?, updated_at = ?
WHERE user_id = ? AND workspace_relative_path = ? AND status = 'preparing'`,
[String(failureReason ?? 'delivery_material_missing'), now, userId, workspaceRelativePath],
);
return Number(result?.affectedRows ?? 0) > 0;
}
export async function releaseMaterializedPageDeliveryContracts({
pool,
userId,
relativePaths = [],
allowPgRequired = false,
} = {}) {
if (!pool || !userId) return [];
const released = [];
for (const rawPath of relativePaths) {
const workspaceRelativePath = normalizeDeliveryRelativePath(rawPath);
if (!workspaceRelativePath) continue;
const contract = await getPageDeliveryContract({
pool,
userId,
relativePath: workspaceRelativePath,
});
if (!contract || contract.status === 'ready') continue;
if (contract.data_mode === 'pg_required' && !allowPgRequired) continue;
if (
await markPageDeliveryContractReady({
pool,
userId,
relativePath: workspaceRelativePath,
})
) {
released.push(workspaceRelativePath);
}
}
return released;
}