|
|
@@ -0,0 +1,318 @@
|
|
|
+# 启动期数据库连接竞态根治执行任务书
|
|
|
+
|
|
|
+> **文档用途**:给**其他大模型 / 编码代理**照着执行。根因已于 2026-09-26 复现并三层取证完毕,**禁止再把它当成「MySQL 权限不足」或「SqlSugar 连接泄漏」去排查**。
|
|
|
+> **状态**:**全部完成(2026-09-26 23:49)**。火忘 `Task.Run` 已撤回为同步调用;5 次冷启动全部成功且竞态报错归零;`JobSchedule:Enabled` 已恢复 `true` 并复验一次仍干净(246.3s,竞态 0,未处理异常 0);守卫测试已补且验证过能拦住回归。后端版本 `1.0.581`。端口开放延迟实测约 160~200 秒(不含编译),超过建议阈值,是否改走方案 A1 仍待负责人定,但不阻塞本次收口。
|
|
|
+> **问题背景与完整证据**:[`20260926问题描述.md`](20260926问题描述.md),尤其第七节「复核结论与证据」。
|
|
|
+> **创建日期**:2026-09-26
|
|
|
+> **适用范围**:本机 `dotnet run` 连远程库 `123.60.180.165:3306/aidopdev`。本问题**只存在于当前工作区**,HEAD 主线不受影响(见 §1.2)。
|
|
|
+> **前置规则**:`best-solution-default.mdc`、`capability-claims-evidence.mdc`、`impact-backtrace.mdc`、`collaboration-scope.mdc`、`version-bump-on-commit.mdc`、`approval-flow-integration.mdc`。
|
|
|
+
|
|
|
+## 进度表
|
|
|
+
|
|
|
+| 包 | 状态 | 说明 |
|
|
|
+|---|---|---|
|
|
|
+| R 主修:撤回火忘任务 | **已完成(2026-09-26 22:24)** | `AiDOP/Startup.cs` 的火忘 `Task.Run` 已换回同步调用;三处必须保留的未提交改动(`AidopOutboundOptions`、两个 `AddHostedService`)未动 |
|
|
|
+| V 验证:连起 5 次 + 竞态归零 | **已完成(2026-09-26 22:46)** | 5/5 `listen=True`,`race=0`,`fatal=0`。耗时 232.4 / 242.2 / 206.1 / 204.6 / 201.0 秒。对齐结果与修复前一致:`mdp_source_table_registry` 53 行;`mdp_std_inv_trans` DB_SYNC 594230;`mdp_std_inventory` DB_SYNC 103600 / SEED 5;`mdp_std_item` DB_SYNC 49803 / PLATFORM_FORM 22091 |
|
|
|
+| M 实测:端口开放延迟 | **已完成,但口径要读批注** | 上面 5 个数字**含编译**(脚本按任务书原文每次都带编译,且重定向把输出缓冲到进程退出,无法从日志里拆出编译段)。编译本身约 40 秒(宿主进程在 `dotnet run` 之后约 40 秒才出现),故纯启动延迟约 **160~200 秒**,**超过 §4 建议的 60 秒阈值**。是否改走方案 A1 由负责人定 |
|
|
|
+| S 守卫:补回归测试 | **已完成** | `Infrastructure/StartupConcurrencyGuardTests.cs` 通过;临时贴回 `Task.Run` 探针后该测试确实变红,探针已撤回 |
|
|
|
+| X 收口:App.json 提交风险 | **已完成(2026-09-26 23:49)** | `JobSchedule:Enabled` 恢复为 `true`。同文件的 `AiDOP:Jobs:Enabled=false`、`Outbound:Enabled=false`、`MdpRebuild` 各项属 165 治理既定决策,未动。恢复后复验一次:listen=True,246.3s,竞态 0,未处理异常 0 |
|
|
|
+| P 可选:ApprovalFlow 容错 | **不需要** | 包 V 已全绿且竞态归零,按 §7 第 1 条本包不做 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 0. 新会话复制提示词
|
|
|
+
|
|
|
+把下面整段贴进新对话,不要改口径。
|
|
|
+
|
|
|
+```
|
|
|
+请按 doc/plan/天缘对接/20260926启动期连接竞态根治执行任务书.md 执行。
|
|
|
+
|
|
|
+已核实、禁止重查或改写的结论:
|
|
|
+1. 崩溃点是 Admin.NET.Plugin.ApprovalFlow/Startup.cs:26 的 CodeFirst.InitTables 抛未处理异常,
|
|
|
+ 进程退出,5005 从未监听。不是 MySQL 权限问题,不是连接泄漏,不是网络问题。
|
|
|
+ 服务端已排除:threads_connected 13/500,无挂起 DDL,无 MDL 锁等待。
|
|
|
+2. 机理:ISqlSugarClient 注册为单例 SqlSugarScope(SqlSugarSetup.cs:74),其 AsyncLocal 上下文
|
|
|
+ 在启动期被 Configure 管道与火忘 Task.Run 共享,两个执行流拿到同一个 MySqlConnection。
|
|
|
+3. 主凶是 AiDOP/Startup.cs:152 的 `_ = Task.Run(...)`(来源登记 MdpSchemaAligner)。
|
|
|
+ 这一处是**未提交的工作区回归**,HEAD 上是同步调用。不要去改 1.0.565 的提交。
|
|
|
+4. 崩溃**非必现**(本机实测 2/4)。禁止用「这次起来了」判定修复成功,验收按 §3 的 5 次口径。
|
|
|
+5. 禁止用 git checkout HEAD -- 整个 AiDOP/Startup.cs:该文件还有别的必须保留的未提交改动。
|
|
|
+6. 禁止修改 Admin.NET.Plugin.ApprovalFlow 插件代码,除非先按 §7 登记 P4-XX 并得到确认。
|
|
|
+7. 禁止把 App.json 的 JobSchedule:Enabled=false 提交进去。
|
|
|
+8. 动手前先按 §2 列出本轮文件清单,等我确认后再改代码。
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 1. 根因(一句话版 + 不要重查的取证)
|
|
|
+
|
|
|
+### 1.1 致死链条
|
|
|
+
|
|
|
+`AiDOP(900).Configure` 一进来就用 `_ = Task.Run(...)` 火忘启动来源登记(扫全部 `mdp_stg_*` 贴源表,远程库上拉开数秒量级的窗口);Furion 按 `Order` 降序执行插件 `Configure`,轮到 `ApprovalFlow(100)` 同步 `InitTables` 时,火忘任务还在跑,两者共用同一个底层连接 → `ApprovalFlow` 没有任何 try-catch → 未处理异常 → 进程死亡。
|
|
|
+
|
|
|
+`AiDOP.Configure` 自己的同步对账块也在同时被撞,但它每块都有 try-catch,只记日志,所以看不见。
|
|
|
+
|
|
|
+### 1.2 为什么主线没事(关键,决定修法)
|
|
|
+
|
|
|
+| 断言 | 取证命令 | 结果 |
|
|
|
+|---|---|---|
|
|
|
+| 火忘 `Task.Run` 在 HEAD 不存在 | `git show HEAD:server/Plugins/Admin.NET.Plugin.AiDOP/Startup.cs` | HEAD 为 `.GetAwaiter().GetResult()`,try-catch 包着 |
|
|
|
+| `EtlInstanceRegistrar` / `MdpSourceConnectionImportService` 的 `AddHostedService` 在 HEAD 不存在 | 同上文件内检索 | 零匹配 |
|
|
|
+| 1.0.565 引入的是同步调用 | `git log -S"EnsureSourceIdentityAsync(sourceDb)" --oneline -- server/Plugins/Admin.NET.Plugin.AiDOP/Startup.cs` | 仅 `c6a6d96db` |
|
|
|
+| `EnsureSourceIdentityAsync` 全仓唯一调用点 | 全仓检索 | `AiDOP/Startup.cs:158` |
|
|
|
+
|
|
|
+**结论**:这是一处只存在于工作区的回归。所以最优修法是**撤回它**,不是在它之上再加设计。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 2. 包 R:主修 —— 把火忘任务撤回为同步调用
|
|
|
+
|
|
|
+### 2.1 改动清单(先列给负责人确认)
|
|
|
+
|
|
|
+| 文件 | 改动 | 行数级别 |
|
|
|
+|---|---|---|
|
|
|
+| `server/Plugins/Admin.NET.Plugin.AiDOP/Startup.cs` | 把 `Configure` 里的 `_ = Task.Run(...)` 块换回 HEAD 的同步版本 | 一个 hunk,约 −16/+10 |
|
|
|
+
|
|
|
+本包**不动**:`ApprovalFlow` 插件、`SqlSugarSetup`、`Web.Core/Startup.cs`、任何配置文件、任何数据库。
|
|
|
+
|
|
|
+### 2.2 ⚠ 禁止的做法
|
|
|
+
|
|
|
+**不要** `git checkout HEAD -- server/Plugins/Admin.NET.Plugin.AiDOP/Startup.cs`。
|
|
|
+
|
|
|
+该文件还有三处必须保留的未提交改动,整文件回退会把它们一起抹掉:
|
|
|
+
|
|
|
+```csharp
|
|
|
+services.AddConfigurableOptions<AidopOutboundOptions>();
|
|
|
+services.AddHostedService<Admin.NET.Plugin.AiDOP.Infrastructure.EtlInstanceRegistrar>();
|
|
|
+services.AddHostedService<Admin.NET.Plugin.AiDOP.DataPlatform.MdpSourceConnectionImportService>();
|
|
|
+```
|
|
|
+
|
|
|
+只能手工替换下面这一个 hunk。
|
|
|
+
|
|
|
+### 2.3 把这一段(当前工作区,约 `Startup.cs:149-164`)
|
|
|
+
|
|
|
+```csharp
|
|
|
+ // 来源登记会扫全部贴源表。放在 Configure 里同步执行时,端口要等它结束才开。
|
|
|
+ // 失败只记日志,不阻断启动(与本方法里其它对账相同)。
|
|
|
+ var sourceServices = app.ApplicationServices;
|
|
|
+ _ = Task.Run(async () =>
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ using var sourceScope = sourceServices.CreateScope();
|
|
|
+ var sourceDb = sourceScope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
|
|
|
+ await Admin.NET.Plugin.AiDOP.DataPlatform.Schema.MdpSchemaAligner.EnsureSourceIdentityAsync(sourceDb);
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ Trace.TraceError("Ai-DOP 来源登记与 written_by 对齐失败: " + ex);
|
|
|
+ }
|
|
|
+ });
|
|
|
+```
|
|
|
+
|
|
|
+### 2.4 换成这一段(HEAD 原样 + 说明为什么不能改回火忘)
|
|
|
+
|
|
|
+```csharp
|
|
|
+ // 来源登记扫全部贴源表,同步执行会让端口晚开数秒。**不要改成 Task.Run 火忘**:
|
|
|
+ // ISqlSugarClient 是单例 SqlSugarScope,其 AsyncLocal 上下文在启动期会被 Configure 管道
|
|
|
+ // 与火忘任务共享,两个执行流拿到同一个 MySqlConnection;后执行的 ApprovalFlow(100) 插件
|
|
|
+ // 同步 InitTables 时没有任何容错,撞上即未处理异常、进程退出。
|
|
|
+ // 2026-09-26 的启动崩溃就是这么来的,见 doc/plan/天缘对接/20260926问题描述.md。
|
|
|
+ try
|
|
|
+ {
|
|
|
+ using var sourceScope = app.ApplicationServices.CreateScope();
|
|
|
+ var sourceDb = sourceScope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
|
|
|
+ Admin.NET.Plugin.AiDOP.DataPlatform.Schema.MdpSchemaAligner.EnsureSourceIdentityAsync(sourceDb)
|
|
|
+ .GetAwaiter().GetResult();
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ Trace.TraceError("Ai-DOP 来源登记与 written_by 对齐失败: " + ex);
|
|
|
+ }
|
|
|
+```
|
|
|
+
|
|
|
+改完确认 `git diff` 里该文件只剩 `AddConfigurableOptions` / 两个 `AddHostedService` 三处新增,来源登记那段回到与 HEAD 一致。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 3. 包 V:验证(崩溃非必现,这是唯一有效验收)
|
|
|
+
|
|
|
+### 3.1 为什么不能只起一次
|
|
|
+
|
|
|
+当日 4 次启动里 2 次成功,**且两次成功的日志中同样有 3~5 条相同的连接竞态报错**,只是恰好落在带 try-catch 的一方被吞掉。「起来了」完全可能是运气。
|
|
|
+
|
|
|
+### 3.2 验收标准(三条全中才算通过)
|
|
|
+
|
|
|
+1. **连续冷启动 5 次**(每次都带编译,模拟最坏时序),5 次全部成功监听 5005;
|
|
|
+2. 5 次日志里 `already in use` 与 `Cannot Open when State is Connecting` **合计归零**。被吞掉的竞态仍是缺陷,不算通过;
|
|
|
+3. 对齐结果不变:`mdp_source_table_registry` 行数与 `mdp_std_*` 的 `written_by` 分布与修复前一致(该逻辑幂等,不应有差异)。
|
|
|
+
|
|
|
+### 3.3 可照抄的验证脚本
|
|
|
+
|
|
|
+> ⚠ 脚本里有两处**不能省**的细节,省了会得到假绿:
|
|
|
+> 1. `dotnet run` 会把真正的服务进程作为**子进程**拉起。只 `Stop-Process` 掉 `dotnet run` 本体,服务仍在跑并占着 5005,下一轮会瞬间判定 `listen=True`——五轮全绿其实一轮都没测。必须**按进程树杀**(`taskkill /T /F`)。
|
|
|
+> 2. 每轮开始前必须确认 5005 **已经空出来**,否则同样是上一轮的残留在冒充本轮。
|
|
|
+
|
|
|
+```powershell
|
|
|
+$proj = "D:\Projects\Ai-DOP\SourceCode\ZZYDOP\server\Admin.NET.Web.Entry\Admin.NET.Web.Entry.csproj"
|
|
|
+$log = "$env:TEMP\startup_trials"
|
|
|
+New-Item -ItemType Directory -Force -Path $log | Out-Null
|
|
|
+
|
|
|
+function Wait-PortFree([int]$port, [int]$sec = 60) {
|
|
|
+ $sw = [Diagnostics.Stopwatch]::StartNew()
|
|
|
+ while ($sw.Elapsed.TotalSeconds -lt $sec) {
|
|
|
+ if (-not (Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue)) { return $true }
|
|
|
+ Start-Sleep -Milliseconds 500
|
|
|
+ }
|
|
|
+ return $false
|
|
|
+}
|
|
|
+
|
|
|
+for ($i = 1; $i -le 5; $i++) {
|
|
|
+ if (-not (Wait-PortFree 5005)) { "trial $i: 跳过——5005 仍被占用,先清理残留进程"; break }
|
|
|
+
|
|
|
+ $out = "$log\trial$i.txt"; $err = "$log\trial$i.err.txt"
|
|
|
+ $p = Start-Process "C:\Program Files\dotnet\dotnet.exe" `
|
|
|
+ -ArgumentList "run --project `"$proj`" --framework net10.0 --no-launch-profile" `
|
|
|
+ -RedirectStandardOutput $out -RedirectStandardError $err -PassThru -NoNewWindow
|
|
|
+
|
|
|
+ $sw = [Diagnostics.Stopwatch]::StartNew(); $ok = $false
|
|
|
+ while ($sw.Elapsed.TotalSeconds -lt 600 -and -not $p.HasExited) {
|
|
|
+ if (Get-NetTCPConnection -LocalPort 5005 -State Listen -ErrorAction SilentlyContinue) { $ok = $true; break }
|
|
|
+ Start-Sleep -Milliseconds 500
|
|
|
+ }
|
|
|
+
|
|
|
+ $race = @(Select-String -Path $out, $err -Pattern 'already in use|State is Connecting' -ErrorAction SilentlyContinue).Count
|
|
|
+ $fatal = @(Select-String -Path $out, $err -Pattern 'Unhandled exception' -ErrorAction SilentlyContinue).Count
|
|
|
+ "trial {0}: listen={1} 用时={2,6:N1}s 竞态={3} 未处理异常={4}" -f $i, $ok, $sw.Elapsed.TotalSeconds, $race, $fatal
|
|
|
+
|
|
|
+ # 按进程树杀,否则 dotnet run 的子进程会继续占着 5005
|
|
|
+ taskkill /T /F /PID $p.Id 2>&1 | Out-Null
|
|
|
+ Start-Sleep -Seconds 3
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+期望输出:5 行全部 `listen=True`、`竞态=0`、`未处理异常=0`。三者缺一不算通过。
|
|
|
+
|
|
|
+**核对第 3 条(对齐结果未变)**,修复前后各跑一次,结果必须一致:
|
|
|
+
|
|
|
+```sql
|
|
|
+SELECT (SELECT COUNT(*) FROM mdp_source_table_registry) AS registry_rows;
|
|
|
+SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS
|
|
|
+ WHERE TABLE_SCHEMA = DATABASE() AND COLUMN_NAME = 'written_by' AND TABLE_NAME LIKE 'mdp_std%'
|
|
|
+ ORDER BY TABLE_NAME;
|
|
|
+-- 再挑 2~3 张行数较大的 mdp_std_* 表逐张比对 written_by 分布:
|
|
|
+-- SELECT written_by, COUNT(*) FROM `<表名>` GROUP BY written_by;
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 4. 包 M:实测端口开放延迟(决定 R 是否定稿)
|
|
|
+
|
|
|
+原作者把它改成火忘,注释写的理由是「放在 Configure 里同步执行时,端口要等它结束才开」。这个代价必须量化后再定稿。
|
|
|
+
|
|
|
+- 数据来源:直接用 §3.3 脚本输出的「用时」列(首次带编译,后 4 次可加 `--no-build` 去掉编译噪声)。
|
|
|
+- 参考基准:2026-09-26 03:06 那次成功启动里,可观测到的对齐活动集中在 03:06:39,宿主 03:06:46 进入稳态轮询,相差 7 秒。**但这是稀疏日志的推断,不是实测总耗时**,必须以本包实测为准。
|
|
|
+- 判定:
|
|
|
+ - 延迟可接受(由负责人定阈值,建议 ≤ 60s)→ **包 R 定稿**,任务书收口;
|
|
|
+ - 延迟不可接受 → 退回问题描述文档的**方案 A1**:把这段改为订阅 `IHostApplicationLifetime.ApplicationStarted` 后再执行。注意 A1 仍需过一遍包 V 的 5 次验证,因为它只是把窗口挪开,没有消除共享上下文。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 5. 包 S:补守卫测试(当前零防护)
|
|
|
+
|
|
|
+这类回归能悄悄进工作区,就是因为没有任何测试能拦住它。
|
|
|
+
|
|
|
+- 文件:`server/Plugins/Admin.NET.Plugin.AiDOP.Tests/Infrastructure/StartupConcurrencyGuardTests.cs`(新建)
|
|
|
+- **放 `Infrastructure/` 而不是新建 `Startup/`**:该测试项目的命名空间跟随目录名,`...Tests.Startup` 这一段会和插件里的 `Startup` 类型产生解析歧义,没必要惹。
|
|
|
+- 测试项目 `<ImplicitUsings>enable</ImplicitUsings>`,`System` / `System.IO` 已隐式引入,下面只需显式 `using Xunit;`。
|
|
|
+- 手法沿用仓库既有的源文本扫描测试(参考 `DataPlatform/MdpMonitorAdminGuardTests.cs` 的路径解析写法)。
|
|
|
+
|
|
|
+```csharp
|
|
|
+using Xunit;
|
|
|
+
|
|
|
+namespace Admin.NET.Plugin.AiDOP.Tests.Infrastructure;
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// 启动期并发守卫。ISqlSugarClient 是单例 SqlSugarScope,其 AsyncLocal 上下文在 Host 启动阶段
|
|
|
+/// 会被 Configure 管道与任何火忘任务共享,共享即同一个 MySqlConnection;后执行的 ApprovalFlow
|
|
|
+/// 插件同步 InitTables 没有容错,撞上就是进程退出。
|
|
|
+/// 2026-09-26 事故复盘:doc/plan/天缘对接/20260926问题描述.md
|
|
|
+/// </summary>
|
|
|
+public class StartupConcurrencyGuardTests
|
|
|
+{
|
|
|
+ private static string PluginStartupSource()
|
|
|
+ {
|
|
|
+ var dir = new DirectoryInfo(AppContext.BaseDirectory);
|
|
|
+ while (dir != null && !File.Exists(Path.Combine(dir.FullName, "AGENTS.md")))
|
|
|
+ dir = dir.Parent;
|
|
|
+ Assert.NotNull(dir);
|
|
|
+ return File.ReadAllText(Path.Combine(
|
|
|
+ dir!.FullName, "server", "Plugins", "Admin.NET.Plugin.AiDOP", "Startup.cs"));
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void AiDopStartup_DoesNotFireAndForgetDatabaseWork()
|
|
|
+ {
|
|
|
+ var source = PluginStartupSource();
|
|
|
+ Assert.DoesNotContain("Task.Run", source);
|
|
|
+ Assert.DoesNotContain("Task.Factory.StartNew", source);
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+验收:该测试通过;把火忘代码临时贴回去,该测试必须失败(**验证守卫真的能拦住**,不要只看它绿)。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 6. 包 X:收口 App.json 的提交风险
|
|
|
+
|
|
|
+`server/Admin.NET.Application/Configuration/App.json` 里 `JobSchedule:Enabled` 当前是 `false`,注释写着「本地UAT测试临时关闭,测完恢复 true」。它在 git 跟踪范围内。
|
|
|
+
|
|
|
+风险:一旦被 `git add .` 带进提交,**所有部署环境的定时任务会被整体关闭**;且缺省为 `true`、改前行为一致,代码评审几乎看不出来。
|
|
|
+
|
|
|
+处置(二选一,须明确执行其中一条):
|
|
|
+
|
|
|
+- **恢复为 `true`**,本机改用环境变量或 `dotnet run` 的 `--JobSchedule:Enabled=false` 临时覆盖;
|
|
|
+- 或在本任务书收口时确认该文件**不纳入任何提交**,并在提交前用 `git diff --cached -- server/Admin.NET.Application/Configuration/App.json` 复查为空。
|
|
|
+
|
|
|
+> 注:同文件里 `AiDOP:Jobs:Enabled=false`、`AiDOP:Outbound:Enabled=false`、`MdpRebuild` 的几项,是 165 连接耗尽治理的既有决策(见 [`165-aidopdev连接耗尽根治执行任务书.md`](../165-aidopdev连接耗尽根治执行任务书.md)),**不在本包处置范围**,不要顺手改回去。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 7. 包 P(可选,且必须先登记):给 ApprovalFlow 加容错
|
|
|
+
|
|
|
+想法本身合理:给 `ApprovalFlow/Startup.cs` 的 8 个 `InitTables` + 2 个模板种子包有限重试(如 5 次 × 3s),把启动期偶发竞态从「进程崩溃」降级为「稍后成功」,兜底防并发方④⑤。
|
|
|
+
|
|
|
+**但不能直接动手。** `.cursor/rules/approval-flow-integration.mdc` 明令:
|
|
|
+
|
|
|
+> ❌ 不得在未登记为后期待办的前提下修改 `Admin.NET.Plugin.ApprovalFlow` 插件自身代码;若业务强需求要动插件,先在 `doc/plan/审批流-综合优化方案.md` 登记新编号(P4-XX),再按「动手前须列清单、等确认」规则操作。
|
|
|
+
|
|
|
+执行顺序:
|
|
|
+
|
|
|
+1. 先做包 R + V。**如果 5 次验证全绿且竞态归零,本包不需要做** —— 日志里被实证互撞的只有①↔②,④⑤ 目前只是推断,没有证据。
|
|
|
+2. 只有在包 R 之后仍能观测到竞态报错,才在 `doc/plan/审批流-综合优化方案.md` 登记 P4-XX,说明触发场景与证据,等确认后再改。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 8. 反向影响推演(针对包 R)
|
|
|
+
|
|
|
+| 面 | 核查方式 | 结论 |
|
|
|
+|---|---|---|
|
|
|
+| 代码调用面 | 全仓检索 `EnsureSourceIdentityAsync`,唯一调用点 `AiDOP/Startup.cs:158` | **无影响** |
|
|
|
+| 数据契约面 | 只改调用时机,不改 `mdp_source_table_registry` / `written_by` 写入逻辑,且该逻辑幂等 | **无影响** |
|
|
|
+| 多数据源面 | 对齐按表名规则登记,认不出的只记日志不猜测,来源缺失行为不变 | **无影响** |
|
|
|
+| 多租户 / Domain 面 | 该段不涉及 `tenant_id` / `domain` 过滤 | **无影响** |
|
|
|
+| 运行面 | 唯一可见变化是端口开放时间后移;`AutoVersionUpdate` 在此之前已完成,不受影响 | **需实测(包 M)** |
|
|
|
+| 验证面 | 目前无任何测试能拦住「Configure 里起火忘 DB 任务」 | **需补(包 S)** |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 9. 版本号与提交
|
|
|
+
|
|
|
+按 `version-bump-on-commit.mdc`:
|
|
|
+
|
|
|
+- 包 R / S 改的是 `server/` 下的运行代码与测试 → 同一提交内把 `server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj` 的 `<Version>` / `<AssemblyVersion>` / `<FileVersion>` **三处同步 patch +1**(当前 `1.0.580` → `1.0.581`)。
|
|
|
+- 本次不动前端 → **不要**递增 `Web/package.json`。
|
|
|
+- 本任务书与问题描述文档属纯文档 → 单独提交时不递增任何版本号。
|
|
|
+- 提交前按包 X 复查 `App.json` 未被误纳入。
|
|
|
+- 本仓库「提交」= commit + push(`git-submit-intent.mdc`),收尾须汇报远端可达的 commit 哈希。
|