docs: add shadow v3 field operations package

This commit is contained in:
zgw
2026-09-03 17:08:13 +08:00
parent 169b64850d
commit 47190451ff
8 changed files with 748 additions and 249 deletions

View File

@@ -0,0 +1,143 @@
package com.ruoyi.algorithm.shadow;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Locale;
import static org.assertj.core.api.Assertions.assertThat;
class ShadowV3OperationsPackageTest {
private final Path root = projectRoot();
@Test
void packageContainsAllCmdOnlyOperationsFiles() {
assertThat(root.resolve("scripts/shadow-v3-preflight.cmd")).isRegularFile();
assertThat(root.resolve("scripts/shadow-v3-install.cmd")).isRegularFile();
assertThat(root.resolve("scripts/shadow-v3-uninstall.cmd")).isRegularFile();
assertThat(root.resolve("scripts/shadow-v3-export.cmd")).isRegularFile();
assertThat(root.resolve("scripts/shadow-v3-export.sql")).isRegularFile();
assertThat(root.resolve("doc/algorithm-shadow/SHADOW_V3_SERVER_AI_EXPORT_PROMPT.md"))
.isRegularFile();
}
@Test
void preflightIsReadOnlyAndChecksExactSourceAndSingleProcess() throws IOException {
String script = text("scripts/shadow-v3-preflight.cmd");
assertThat(script).startsWith("@echo off").contains(
"setlocal",
"git status --porcelain",
"git merge-base --is-ancestor",
"git branch --show-current",
"git rev-parse HEAD",
"jps -lv",
"sqlcmd",
"shadow_snapshot_state",
"FT546WMS_SHADOW_ONLY",
"2026-09-03-3",
"exit /b 1");
assertThat(script.toLowerCase(Locale.ROOT)).doesNotContain(
"git checkout", "git switch", "git reset", "git clean", "powershell");
}
@Test
void installAndUninstallAreFailFastAndStrictlyGuarded() throws IOException {
String installer = text("scripts/shadow-v3-install.cmd");
String uninstaller = text("scripts/shadow-v3-uninstall.cmd");
assertThat(installer).startsWith("@echo off").contains(
"if /I \"%~3\"==\"upgrade\"",
"if /I \"%~3\"==\"clean\"",
"upgrade-shadow-v2-to-v3.sql",
"install-shadow.sql",
"sqlcmd", "-E", "-b", "-r1",
"if errorlevel 1 exit /b 1");
assertThat(uninstaller).startsWith("@echo off").contains(
"CONFIRM_UNINSTALL",
"uninstall-shadow.sql",
"sqlcmd", "-E", "-b", "-r1",
"if errorlevel 1 exit /b 1");
assertThat(installer.toLowerCase(Locale.ROOT)).doesNotContain("powershell");
assertThat(uninstaller.toLowerCase(Locale.ROOT)).doesNotContain("powershell");
}
@Test
void exportCollectsSourceRuntimeSqlAndWarningEvidence() throws IOException {
String exporter = text("scripts/shadow-v3-export.cmd");
String exportSql = text("scripts/shadow-v3-export.sql").toLowerCase(Locale.ROOT);
assertThat(exporter).startsWith("@echo off").contains(
"git rev-parse HEAD",
"git branch --show-current",
"git status --porcelain",
"jps -lv",
"shadow-v3-export.sql",
"sqlcmd", "-E", "-b", "-r1",
"FORTUCKY_SHADOW_",
"shadow_failure");
assertThat(exportSql).contains(
"$(warehouseid)",
"$(startutc)",
"from alg.shadow_package_manifest",
"from alg.shadow_snapshot_state",
"from alg.shadow_instance_heartbeat",
"from alg.shadow_runtime_metric",
"from alg.decision_log",
"from sys.dm_exec_requests");
assertThat(exportSql).doesNotContain(
"insert ", "update ", "delete ", "merge ", "truncate ", "drop ", "alter ", "create ", "exec ");
}
@Test
void runbookAndServerAiPromptPreserveTheSafeFieldOrder() throws IOException {
String runbook = text("doc/algorithm-shadow/README.md");
String aiPrompt = text("doc/algorithm-shadow/SHADOW_V3_SERVER_AI_EXPORT_PROMPT.md");
assertThat(runbook).contains(
"test/ft546wms-shadow-v3-20260903",
"短暂停机",
"正常业务请求只做固定大小的内存登记,不等待影子线程",
"git fetch origin",
"shadow-v3-preflight.cmd",
"shadow-v3-install.cmd \"<SQL_SERVER>\" \"<DATABASE>\" upgrade",
"FORTUCKY_SHADOW_ENABLED=true",
"FORTUCKY_SHADOW_SNAPSHOT_MAX_AGE_SECONDS=60",
"FORTUCKY_SHADOW_QUIET_INTERVAL_MILLIS=1000",
"至少连续观察 7 天",
"FORTUCKY_SHADOW_ENABLED=false",
"CONFIRM_UNINSTALL");
assertThat(runbook.indexOf("停止 IDEA 中的 v2 稳定实例"))
.isLessThan(runbook.indexOf("git fetch origin"));
assertThat(runbook.indexOf("shadow-v3-preflight.cmd"))
.isLessThan(runbook.lastIndexOf("shadow-v3-install.cmd"));
assertThat(runbook.indexOf("至少连续观察 7 天"))
.isLessThan(runbook.indexOf("## 7. 回滚"));
assertThat(runbook.indexOf("## 7. 回滚"))
.isLessThan(runbook.indexOf("## 8. 可选卸载"));
assertThat(aiPrompt).contains(
"你的任务只导出证据",
"不修改数据库",
"不切换分支",
"不重启服务",
"shadow-v3-export.cmd",
"EXPORT_OK");
}
private String text(String relativePath) throws IOException {
return Files.readString(root.resolve(relativePath), StandardCharsets.UTF_8);
}
private static Path projectRoot() {
Path current = Path.of(System.getProperty("user.dir")).toAbsolutePath().normalize();
while (current != null && !Files.isDirectory(current.resolve("scripts"))) {
current = current.getParent();
}
assertThat(current).as("project root containing scripts").isNotNull();
return current;
}
}