docs(release): close production-inspection-fixes branch and add standalone repair script
Memind CI / Test, build, and release guards (push) Successful in 3m8s
Memind CI / Test, build, and release guards (push) Successful in 3m8s
Register branch disposition after 103 deploy/repair, and add a lightweight 103 repair script that avoids heavy runtime module imports. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,6 +3,31 @@
|
||||
本文件记录已经完成迁移、但仍可能因为 Git 拓扑或遗留 worktree 被误判为“尚未进入 `main`”的分支。
|
||||
它是分支复用、合并、cherry-pick 和清理前的必查清单。
|
||||
|
||||
## `feature/production-inspection-fixes`
|
||||
|
||||
**状态:禁止再次引用。改动已提交并进入 `origin/main`,该分支保留仅用于只读追溯,不是待合并开发分支。**
|
||||
|
||||
审计日期:2026-08-16
|
||||
分支 HEAD:`843e4d10`
|
||||
`origin/main` 对应提交:`843e4d10`
|
||||
|
||||
### 原始用途
|
||||
|
||||
103 生产巡检后续修复:定时任务去重、orphan 交付契约 reconcile(HTML 缺失 mark failed)、MindSpace remote 超时 30s、103 数据/Goosed harness 修复脚本。
|
||||
|
||||
### 验证摘要
|
||||
|
||||
- `node --test scheduled-task-executor.test.mjs scheduled-task-service.test.mjs`:29 passed
|
||||
- 103 快速发布 `memind-portal-runtime-20260816-085224-843e4d10`,Portal 8081 健康
|
||||
- 103 数据修复:取消唐用户 4 个重复/废弃定时任务,保留 5:30 新闻 + 8:00 天气各 1 个
|
||||
- Goosed `run-goosed-native.sh` PATH 已修补,18007–18014 plist PATH 已更新
|
||||
- memind_adm LaunchAgent 脚本已部署,8085/5174 健康
|
||||
|
||||
### 最终处置
|
||||
|
||||
- 保留本地分支名用于审计追溯。
|
||||
- 不要从该分支继续开发、merge、cherry-pick 或构建 runtime/artifact。
|
||||
|
||||
## `feature/scheduled-task-page-delivery-fix`
|
||||
|
||||
**状态:禁止再次引用。改动已提交并进入 `origin/main`,该分支保留仅用于只读追溯,不是待合并开发分支。**
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Standalone 103 production repair (no heavy module imports).
|
||||
* Dry-run by default; pass --apply to mutate database rows.
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
|
||||
const TANG_USER_ID = 'a70ff537-8908-486e-9b6c-042e07cc25db';
|
||||
const TASKS_TO_CANCEL = [
|
||||
'781a77e5-a787-4f37-b864-e21d7e918855',
|
||||
'9df3f89a-3a06-434d-8b4c-17b1aeb8911f',
|
||||
'7f953d5f-6fc3-4191-b52a-bffcef48e77f',
|
||||
'9b537fe8-eea2-4e39-8bcb-e8f169986327',
|
||||
];
|
||||
|
||||
function normalizeDeliveryRelativePath(value) {
|
||||
const raw = String(value ?? '').trim().replace(/^\/+/, '');
|
||||
if (!raw) return '';
|
||||
return raw.startsWith('public/') ? raw : `public/${raw}`;
|
||||
}
|
||||
|
||||
async function loadPool() {
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
if (!databaseUrl) throw new Error('缺少 DATABASE_URL');
|
||||
const { default: mysql } = await import('mysql2/promise');
|
||||
return mysql.createPool({ uri: databaseUrl, connectionLimit: 2 });
|
||||
}
|
||||
|
||||
async function markContractFailed(pool, { userId, relativePath, reason }) {
|
||||
const now = Date.now();
|
||||
await pool.query(
|
||||
`UPDATE h5_page_delivery_contracts
|
||||
SET status = 'failed', updated_at = ?, last_error = ?
|
||||
WHERE user_id = ? AND workspace_relative_path = ? AND status = 'preparing'`,
|
||||
[now, reason, userId, relativePath],
|
||||
);
|
||||
}
|
||||
|
||||
async function markContractReady(pool, { userId, relativePath }) {
|
||||
const now = Date.now();
|
||||
await pool.query(
|
||||
`UPDATE h5_page_delivery_contracts
|
||||
SET status = 'ready', updated_at = ?, last_error = NULL
|
||||
WHERE user_id = ? AND workspace_relative_path = ? AND status = 'preparing'`,
|
||||
[now, userId, relativePath],
|
||||
);
|
||||
}
|
||||
|
||||
async function reconcileStuckContracts(pool, h5Root, apply, limit = 50) {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT user_id, workspace_relative_path
|
||||
FROM h5_page_delivery_contracts
|
||||
WHERE status = 'preparing' AND data_mode = 'static'
|
||||
ORDER BY updated_at ASC
|
||||
LIMIT ?`,
|
||||
[limit],
|
||||
);
|
||||
const summary = [];
|
||||
for (const row of rows ?? []) {
|
||||
const relativePath = normalizeDeliveryRelativePath(row.workspace_relative_path);
|
||||
if (!relativePath) continue;
|
||||
const filePath = path.join(h5Root, 'MindSpace', row.user_id, relativePath);
|
||||
const exists = fs.existsSync(filePath);
|
||||
const action = exists ? 'ready' : 'failed';
|
||||
summary.push({ userId: row.user_id, relativePath, fileExists: exists, action: apply ? action : `would_${action}` });
|
||||
if (apply) {
|
||||
if (exists) {
|
||||
await markContractReady(pool, { userId: row.user_id, relativePath });
|
||||
} else {
|
||||
await markContractFailed(pool, {
|
||||
userId: row.user_id,
|
||||
relativePath,
|
||||
reason: 'reconcile: static html missing',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const apply = process.argv.includes('--apply');
|
||||
const h5Root = process.env.H5_ROOT || '/Users/john/Project/Memind';
|
||||
const pool = await loadPool();
|
||||
const now = Date.now();
|
||||
const actions = [];
|
||||
|
||||
for (const taskId of TASKS_TO_CANCEL) {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT id, user_id, title, status, hour, minute
|
||||
FROM h5_scheduled_tasks WHERE id = ? LIMIT 1`,
|
||||
[taskId],
|
||||
);
|
||||
const row = rows?.[0];
|
||||
if (!row) {
|
||||
actions.push({ taskId, action: 'skip', reason: 'not_found' });
|
||||
continue;
|
||||
}
|
||||
if (row.status === 'cancelled') {
|
||||
actions.push({ taskId, action: 'skip', reason: 'already_cancelled', title: row.title });
|
||||
continue;
|
||||
}
|
||||
actions.push({
|
||||
taskId,
|
||||
action: apply ? 'cancel' : 'would_cancel',
|
||||
title: row.title,
|
||||
status: row.status,
|
||||
schedule: `${row.hour ?? '?'}:${String(row.minute ?? 0).padStart(2, '0')}`,
|
||||
});
|
||||
if (apply) {
|
||||
await pool.query(
|
||||
`UPDATE h5_scheduled_tasks
|
||||
SET status = 'cancelled', updated_at = ?, last_error = NULL
|
||||
WHERE id = ? AND user_id = ?`,
|
||||
[now, taskId, TANG_USER_ID],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const [activeWeather] = await pool.query(
|
||||
`SELECT id, title, hour, minute, status, last_run_at
|
||||
FROM h5_scheduled_tasks
|
||||
WHERE user_id = ? AND title LIKE '%天气预报%' AND status = 'active'
|
||||
ORDER BY hour, created_at`,
|
||||
[TANG_USER_ID],
|
||||
);
|
||||
|
||||
const reconcileSummary = await reconcileStuckContracts(pool, h5Root, apply);
|
||||
|
||||
console.log(JSON.stringify({
|
||||
mode: apply ? 'apply' : 'dry-run',
|
||||
cancelledTasks: actions,
|
||||
remainingActiveWeatherTasks: activeWeather,
|
||||
deliveryContracts: reconcileSummary,
|
||||
}, null, 2));
|
||||
|
||||
await pool.end();
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user