BUG-699 / BUG-704 的数据修补原先只存在于 frontend/scripts/repair-rectification-session-titles.mjs,需要 SCHEMA_DATABASE_URL 才能跑,产品负责人没有任何按钮能执行它,所以那批 错日期的会话标题一直没修。 脚本里本来就是纯 SQL,搬进一次性迁移即可复用现成的 `Migrate Staging Database` 按钮: - 新增 20260916020000_rectification_session_title_repair.sql,两段 update 逐字取自脚本(合同测试比对,改了哪边都会红);权限守卫沿用 20260915010000 的 schema_owner 写法。 - 幂等:改完之后两段 where 都不再匹配同一行,重复应用影响 0 行。 - 各自 get diagnostics + raise notice 打出行数;db-migrate.mjs 加 notice 转发,否则 node-postgres 会把 NOTICE 丢掉,迁移日志里一个数字都看不到。 - 脚本降级为只读核对工具:--apply 改为报错并指向迁移;导入不再连库。 生产停在 7b620c7a(没有 use-rectification-surface.ts,标题固定且不写库), 两段 where 自然匹配 0 行,是预期内的 no-op。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
119 lines
4.2 KiB
JavaScript
119 lines
4.2 KiB
JavaScript
// 只读核对工具。**修补本身已由迁移承担**:
|
|
// frontend/supabase/migrations/20260916020000_rectification_session_title_repair.sql
|
|
// 产品跑 Gitea → `Migrate Staging Database` 就会应用它,不需要 SSH 或库口令。
|
|
//
|
|
// 本文件保留下来有两个用处:
|
|
// 1. 排查时数一数还有多少行没对上(不写库,只 select count);
|
|
// 2. 下面三段 SQL 是那条迁移的**唯一出处**,迁移逐字抄它们。
|
|
// tests/rectification-session-title-repair-migration.test.ts 会比对两边,
|
|
// 改了这里而没同步迁移(或反过来)测试就红。
|
|
//
|
|
// 需要 SCHEMA_DATABASE_URL 才能连库;没有它的环境只能跑上面那个合同测试。
|
|
|
|
import { resolve } from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
|
|
import pg from "pg";
|
|
|
|
const { Client } = pg;
|
|
|
|
export const TITLE_MATCH_SQL = `
|
|
session_type = 'birth_time_rectification'
|
|
and title ~ '^[0-9]{1,2}月[0-9]{1,2}日[[:space:]]*·[[:space:]]*生时校正([[:space:]]+[0-9]{2}:[0-9]{2})?$'
|
|
and (
|
|
created_at is null
|
|
or (substring(title from '^([0-9]{1,2})月'))::int
|
|
is distinct from extract(month from created_at at time zone 'Asia/Shanghai')::int
|
|
or (substring(title from '月([0-9]{1,2})日'))::int
|
|
is distinct from extract(day from created_at at time zone 'Asia/Shanghai')::int
|
|
)
|
|
`;
|
|
|
|
const TITLE_COUNT_SQL = `select count(*)::int as n from public.chat_sessions where ${TITLE_MATCH_SQL}`;
|
|
|
|
export const TITLE_APPLY_SQL = `
|
|
update public.chat_sessions
|
|
set title = case
|
|
when created_at is null then '生时校正'
|
|
else to_char(created_at at time zone 'Asia/Shanghai', 'FMMM')
|
|
|| '月'
|
|
|| to_char(created_at at time zone 'Asia/Shanghai', 'FMDD')
|
|
|| '日 · 生时校正'
|
|
end
|
|
where ${TITLE_MATCH_SQL}
|
|
`;
|
|
|
|
const ACTIVITY_COUNT_SQL = `
|
|
select count(*)::int as n
|
|
from public.chat_sessions as session
|
|
join public.agentic_rectification_cases as case_row
|
|
on case_row.session_id = session.id
|
|
and case_row.user_id = session.user_id
|
|
left join lateral (
|
|
select max(turn.created_at) as last_turn_at
|
|
from public.agentic_rectification_turns as turn
|
|
where turn.case_id = case_row.id
|
|
) as turns on true
|
|
where session.session_type = 'birth_time_rectification'
|
|
and session.updated_at < coalesce(turns.last_turn_at, case_row.last_activity_at)
|
|
`;
|
|
|
|
export const ACTIVITY_APPLY_SQL = `
|
|
update public.chat_sessions as session
|
|
set updated_at = coalesce(turns.last_turn_at, case_row.last_activity_at)
|
|
from public.agentic_rectification_cases as case_row
|
|
left join lateral (
|
|
select max(turn.created_at) as last_turn_at
|
|
from public.agentic_rectification_turns as turn
|
|
where turn.case_id = case_row.id
|
|
) as turns on true
|
|
where case_row.session_id = session.id
|
|
and case_row.user_id = session.user_id
|
|
and session.session_type = 'birth_time_rectification'
|
|
and session.updated_at < coalesce(turns.last_turn_at, case_row.last_activity_at)
|
|
`;
|
|
|
|
function connectionString() {
|
|
const value = process.env.SCHEMA_DATABASE_URL?.trim();
|
|
if (!value) {
|
|
throw new Error("SCHEMA_DATABASE_URL is required");
|
|
}
|
|
if (!/^postgres(ql)?:\/\//.test(value)) {
|
|
throw new Error("SCHEMA_DATABASE_URL must be a PostgreSQL URL");
|
|
}
|
|
return value;
|
|
}
|
|
|
|
async function main() {
|
|
if (process.argv.includes("--apply")) {
|
|
throw new Error(
|
|
"--apply has been removed; the repair now ships as migration " +
|
|
"20260916020000_rectification_session_title_repair.sql. " +
|
|
"Apply it with Gitea -> Migrate Staging Database.",
|
|
);
|
|
}
|
|
const client = new Client({ connectionString: connectionString() });
|
|
await client.connect();
|
|
try {
|
|
const titles = await client.query(TITLE_COUNT_SQL);
|
|
const activity = await client.query(ACTIVITY_COUNT_SQL);
|
|
console.log(`mismatched_dated_rectification_titles=${titles.rows[0]?.n ?? 0}`);
|
|
console.log(`stale_rectification_updated_at=${activity.rows[0]?.n ?? 0}`);
|
|
} finally {
|
|
await client.end();
|
|
}
|
|
}
|
|
|
|
// Only connect when run as a command. Importing this file (the contract test
|
|
// does) must not open a database connection.
|
|
const invokedPath = process.argv[1]
|
|
? pathToFileURL(resolve(process.argv[1])).href
|
|
: undefined;
|
|
|
|
if (invokedPath === import.meta.url) {
|
|
main().catch((error) => {
|
|
console.error(error instanceof Error ? error.message : error);
|
|
process.exitCode = 1;
|
|
});
|
|
}
|