fix(GOV-009): 完成脚手架审查整改

This commit is contained in:
2026-08-13 18:30:57 +08:00
parent dcdf634457
commit 3a8120a33a
47 changed files with 752 additions and 101 deletions
+10 -12
View File
@@ -73,8 +73,8 @@ export function assertSafeDevelopmentConfiguration(config) {
}
/**
* 客户端与服务端分别校验,防止用 MySQL 8 客户端生成带有 5.7 无法稳定恢复的语法,
* 同时核对数据库自身字符规则,而不是信任 env 中的声明值。
* 客户端保持 5.7 系列以生成兼容导出;服务端固定 5.7.42,避免开发、测试和生产
* 由不同补丁行为产生漂移。同时核对数据库真实字符规则,而不是信任 env 声明。
*/
export function assertMysql57Metadata(metadata) {
if (!/^5\.7\.\d+(?:[-+].*)?$/.test(metadata.mysqlClientVersion ?? '')) {
@@ -85,8 +85,8 @@ export function assertMysql57Metadata(metadata) {
`mysqldump 客户端必须为 5.7.x,实际 ${metadata.mysqldumpClientVersion || '(空)'}`,
);
}
if (!/^5\.7\.\d+(?:[-+].*)?$/.test(metadata.serverVersion ?? '')) {
throw new Error(`MySQL 服务端必须为 5.7.x,实际 ${metadata.serverVersion || '(空)'}`);
if (!/^5\.7\.42(?:[-+].*)?$/.test(metadata.serverVersion ?? '')) {
throw new Error(`MySQL 服务端必须为 5.7.42,实际 ${metadata.serverVersion || '(空)'}`);
}
requireExactValue(metadata.databaseCharset, REQUIRED_CHARSET, '开发数据库字符集不符合要求');
requireExactValue(
@@ -320,7 +320,9 @@ export function buildCapturedArtifacts(metadata, sql) {
throw new Error(`应用版本格式无效:${metadata.applicationVersion || '(空)'}`);
}
const normalizedSql = normalizeSchemaSql(sql);
if (!normalizedSql.trim()) {
const objectCounts = countSchemaObjects(normalizedSql);
// mysqldump 对空库仍会输出会话 SET 与头尾注释;结构对象计数为零才是空库判据。
if (Object.values(objectCounts).every((count) => count === 0)) {
return buildCapturedEmptyArtifacts(metadata);
}
const schemaSha256 = sha256(normalizedSql);
@@ -328,7 +330,7 @@ export function buildCapturedArtifacts(metadata, sql) {
...metadata,
status: 'captured',
captured: true,
objectCounts: countSchemaObjects(normalizedSql),
objectCounts,
schemaSha256,
});
return Object.freeze({
@@ -661,7 +663,7 @@ export function runBaselineGeneration(options) {
assertMysql57Metadata({
mysqlClientVersion,
mysqldumpClientVersion,
serverVersion: '5.7.0-pending-server-check',
serverVersion: '5.7.42-pending-server-check',
databaseCharset: REQUIRED_CHARSET,
databaseCollation: REQUIRED_COLLATION,
});
@@ -713,11 +715,7 @@ export function getCurrentReleasedVersion(repositoryRoot) {
};
}
/**
* not_applicable 清单只记录生成当时 MySQL 未启用,不能永久替代真实结构基线。
* 每次校验都重新读取固定开发环境配置;一旦开发库启用,旧清单立即失效,强制
* 下一版本开发开始前先从开发库生成 captured 基线。
*/
/** 已发布清单是不可变历史;当前开发配置只决定下一次基线生成结果。 */
export function assertNotApplicableMatchesDevelopmentConfiguration(repositoryRoot, result) {
// 已发布清单记录的是发布当时的数据库启用状态。当前开发配置只能决定下一份
// 基线如何生成,不能反向否定或改写已发布版本的历史事实。
+26 -7
View File
@@ -39,7 +39,7 @@ const validMetadata = Object.freeze({
gitCommit: '0a69aa1f6f5cc75d72f21c43d96f5ff648e2ec93',
mysqlClientVersion: '5.7.44',
mysqldumpClientVersion: '5.7.44',
serverVersion: '5.7.44-log',
serverVersion: '5.7.42-log',
databaseCharset: 'utf8mb4',
databaseCollation: 'utf8mb4_general_ci',
generatedAt: '2026-08-13T03:00:00.000Z',
@@ -132,11 +132,15 @@ test('拒绝错误字符集、排序规则和不完整的启用配置', () => {
);
});
test('只接受 MySQL 5.7 客户端、服务端和固定数据库字符规则', () => {
test('只接受 MySQL 5.7 客户端、5.7.42 服务端和固定数据库字符规则', () => {
assert.doesNotThrow(() => assertMysql57Metadata(validMetadata));
assert.throws(
() => assertMysql57Metadata({ ...validMetadata, serverVersion: '8.0.39' }),
/服务端必须为 5\.7/,
/服务端必须为 5\.7\.42/,
);
assert.throws(
() => assertMysql57Metadata({ ...validMetadata, serverVersion: '5.7.41' }),
/服务端必须为 5\.7\.42/,
);
assert.throws(
() => assertMysql57Metadata({ ...validMetadata, mysqlClientVersion: '8.0.39' }),
@@ -355,7 +359,7 @@ test('启用入口分别校验 mysql 与 mysqldump 客户端版本', () => {
: 'mysqldump Ver 10.13 Distrib 5.7.44, for macos';
}
if (command === 'mysql') {
return '5.7.44-log\tutf8mb4\tutf8mb4_general_ci\n';
return '5.7.42-log\tutf8mb4\tutf8mb4_general_ci\n';
}
return 'CREATE TABLE `sample` (`id` int);\n';
},
@@ -423,6 +427,21 @@ test('启用的空开发库生成 captured_empty format v2 清单', () => {
});
});
test('仅含 mysqldump 会话设置的真实空库仍生成 captured_empty', () => {
const emptyDump = `-- MySQL dump 10.13 Distrib 5.7.42, for Linux
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2026-08-13 18:00:00
`;
const artifacts = buildCapturedArtifacts(validMetadata, emptyDump);
assert.equal(artifacts.status, 'captured_empty');
assert.deepEqual(Object.keys(artifacts.files), ['manifest.yaml']);
});
test('启用入口把 mysqldump 空结构持久化为 captured_empty', () => {
const root = makeTemporaryDirectory();
const envFile = path.join(root, 'development.env');
@@ -444,7 +463,7 @@ test('启用入口把 mysqldump 空结构持久化为 captured_empty', () => {
return `${command} Ver 14.14 Distrib 5.7.44, for Linux`;
}
if (command === 'mysql') {
return '5.7.44-log\tutf8mb4\tutf8mb4_general_ci\n';
return '5.7.42-log\tutf8mb4\tutf8mb4_general_ci\n';
}
return '';
},
@@ -482,9 +501,9 @@ test('校验拒绝篡改成错误 MySQL 版本或伪造对象范围的真实清
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
manifest.mysql_server_version = '8.0.39';
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
assert.throws(() => verifyBaselineDirectory(target, 'v0.0.5'), /服务端必须为 5\.7/);
assert.throws(() => verifyBaselineDirectory(target, 'v0.0.5'), /服务端必须为 5\.7\.42/);
manifest.mysql_server_version = '5.7.44-log';
manifest.mysql_server_version = '5.7.42-log';
manifest.objects.events = false;
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
assert.throws(() => verifyBaselineDirectory(target, 'v0.0.5'), /events/);