刘文博 liuwb

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • 8196dfd706 feat(s8): 启用规则前校验审批流程依赖,堵住带病上线 | server 1.0.570 S8 规则启用门禁此前不检查审批流程是否存在。规则声明了复核 / 超时升级, 但当前租户没有对应的已发布流程时,规则照样能启用成功 —— 失败推迟到运行时, 而且是静默的:复核链的 StartFlow 被 TryStartVerificationFlowAsync 的 catch 吞成一条 warning(建单与提交照常成功、流程没起来);升级链虽然会抛,但人工 升级只回一条错误消息、自动升级作业记一条 s8_timeout_auto_escalate_failed, 规则本身一路显示「已启用 / 最近结果:成功」。每一层都报成功,最难被发现。 在既有的 S8RuleReadinessGate 上补两条判据,与已有的账号池判据成对 —— 那两条查「有没有人」,这两条查「有没有流程」: policy.RequiresVerification → 必须有可用的 EXCEPTION_CLOSURE 流程 policy.SupportsTimeoutEscalation → 必须有可用的 EXCEPTION_ESCALATION 流程 「可用」的口径与 FlowEngineService.StartFlowCore 物理同源(含全局流程回落): IsPublished && !IsDelete && (TenantId == 当前租户 || TenantId IS NULL)。 门禁比运行时宽会放行起不了流的规则,比运行时严会把「有全局流程可回落」的租户 误判成不可用,两种偏差都比没有门禁更糟,所以判据不重写、只此一份实现。 取数走新增的 IS8ApprovalFlowScopeReader(接口为测试留 seam,正式实现只有一个, 写法对齐 S8DepartmentScopeValidator)。其中 ClearFilter() 是必需的:全局多租户 AOP 对 ApprovalFlow 生成的是 TenantId = @current、不含 OR IS NULL,不关掉就永远 看不见全局流程;关掉后由方法自己写死租户谓词,是显式声明边界而非绕过租户。 两条判据刻意追加在既有判据之后,不插在各自的池判据旁边 —— 插在中间会改变既有 失败场景先命中哪个 reason code,而运维手册与既有测试按当前顺序写。判据之间无 从属关系,顺序只影响报哪一条,不影响放不放行。 本门禁不创建流程,只回答「依赖满不满足」。自动补一条空壳流程只会把配置错误 变成审批空转。 三条真实执行入口(EnableAsync / RunNowAsync / SchedulerService.RunSingleRuleAsync) 本就共用该门禁,未新增接线;Preview 与 Disable 不接 —— 停用必须在流程配坏之后 仍然可用,否则规则会卡在启用态关不掉。 验证: - Admin.NET.sln 全 TFM build 0 Error,本批 3 文件 0 warning,总数与基线同为 674 - 新增 12 条契约测试,net10.0 / net8.0 各 84/84 通过(含既有 72 条) - 真机端到端(797 租户,逐条配齐前置后观察门禁逐级推进): 未配前置 → 400 [rule_required_parameter_missing] 配部门 → 400 [rule_handler_pool_empty] 配处理池 → 400 [rule_reviewer_pool_empty] 配复核池 → 400 [rule_escalation_pool_empty] 配升级池 → 400 [rule_escalation_flow_missing] ← 本批新增判据命中 被拦时 ado_s8_watch_rule.enabled 仍为 0,未写库;临时配置已撤回 - 流程齐备租户(838257186181189)enable / run-now 均 200,门禁不误拦 - disable 200,确认不过门禁

2 周之前

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • a6f3ac674f fix: enforce configurable S8 refreshed detection rate limit | server 1.0.567 REFRESHED 明细限频窗口由硬编码 10 分钟改为可配置,默认 60 分钟。 - S8WatchSchedulerService: RefreshedRateLimitMinutes 常量拆为 DefaultRefreshedRateLimitMinutes(60) + AIDOP_S8_REFRESHED_RATE_LIMIT_MINUTES env override,新增 ResolveRefreshedRateLimitMinutes(); env 缺失/空白→默认,解析失败或 <1→默认 + 单次 warn(不输出 raw 值)。 idiom 与既有 S8EvaluatorGuard.ResolveIntFromEnv 一致。 - 唯一调用点是 HasRecentRefreshedDetectionLogAsync 的 cutoff 计算; RefreshDetectionAsync / exception 状态更新 / CREATED / RECOVERED / RECOVERY_BLOCKED / 通知链路均未改动。 - docker-compose.yml: api 服务补 AIDOP_S8_LOG_CLEANUP_ENABLED=true 与 AIDOP_S8_REFRESHED_RATE_LIMIT_MINUTES=60 部署环境变量。 口径提醒:限频判据为闭区间,业务 tick 默认 5 分钟, 实际写入周期 = 窗口 + 一个 tick(60 → 实测约 65 分钟)。 注意:aidopdev 当前存在多个 Scheduler 实例混跑,旧实例仍使用 10 分钟常量, 本改动需所有 backend 节点统一部署后方可验收。

2 周之前

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • ff0ce53f8f fix(approval-flow): TenantId 改为 override,恢复租户归属护栏 | server 1.0.566 ApprovalFlow 用重新声明同名属性的方式隐藏了 EntityBaseTenantOrgDel.TenantId (CS0114)。两边类型同为 long?,所以不是可空性冲突,隐藏真正吃掉的是两样东西: 1. 接口分派。子类隐藏但未重新实现 ITenantIdFilter,((ITenantIdFilter)flow).TenantId 落到基类那个永不赋值的字段上。离线探针实测:flow.TenantId = 797403760988229 时, 经接口读回 null。任何按 ITenantIdFilter 做内存判断的泛型代码都会读错租户。 2. IsOnlyIgnoreUpdate。基类声明为 true(租户归属只在插入时写定),子类的 SugarColumn 未带该参数,实测 ORM 元数据被覆盖成 false —— 整实体 Updateable 会把 TenantId 带进 SET 子句,等于给「改写行的租户归属」开了口子。 两者当时都没有代码路径触发(现有写路径全是 UpdateColumns/SetColumns 白名单或 IgnoreColumns(null)),属 latent bug;但护栏已经没了,而编译器只给一条 warning。 改为 override 并补回 IsOnlyIgnoreUpdate,使 CLR、接口、SqlSugar 三处语义重新统一。 保留 IsNullable=true —— 表中 51 行 TenantId IS NULL 是在用的全局流程,不能推成 NOT NULL。 验证(离线只读探针 + 契约测试,未连库写入): - ORM: TenantId 列数 1、重复列 0、总列数 20 不变、IsNullable=True、IsOnlyIgnoreUpdate=True - 分派: 子类/接口/基类三路径在 value、null、反向写入三种场景下完全一致 - 更新: 整实体 Updateable 的 SET 不再含 TenantId;SetColumns/UpdateColumns/Insert 路径不受影响 - 新增 7 条契约测试,net10.0 与 net8.0 各 7/7 通过 - 反向验证: 还原成隐藏写法后 CS0114 复现且 4/7 测试失败,确认测试抓得住回归 - Admin.NET.sln 全 TFM build 0 Error,本批新增 warning 0 不在本批范围(已登记,未改动):全局 QueryFilter 缺 OR TenantId IS NULL 导致的 全局流程管理可见性问题。

2 周之前

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • d9e10f3ef1 fix(s8): close dashboard P2 semantics (filter/today/trend/denominator) | server 1.0.565 / web 2.4.408 P2-A 看板筛选参数被前端静默丢弃 kanban/s8.vue 写的是 fetchS8Alerts(1, 1, extra),而签名只有 (factoryId, extra): extra 绑到字面量 1,{factoryId, ...1} 展开数字等于 {},筛选一条都没上行, onQuery() 却照样弹「已查询」。改为调用新的汇总端点并正确传参。 P2-B 「今日预警数」实际只是 LIMIT N,没有今日时间过滤 该卡片数的是 s8-alerts(ORDER BY alert_time DESC LIMIT 6)返回的行数,SQL 里无今日谓词: 实库仅 2 行、时间是 2026-03-26,却被显示成「今日 2」;今日真超 6 条又会被封顶在 6。 新增 GET /api/AidopKanban/s8-alert-summary:显式半开窗口 + COUNT(*)、无 LIMIT, 未传日期 ⇒ [今日00:00, 明日00:00) 且 isToday=true;传了日期 ⇒ 区间窗口、标题切「区间预警数」。 既有 s8-alerts 列表端点保持不变(home.vue 仍依赖其最近 6 条),由测试钉住。 P2-C last_24h 趋势档恒 0 S8PeriodHelper 对 last_24h 返回 [Now-24h, Now)(非零点),而日桶按 CreatedAt.Date 分组; 原实现用 from.AddDays(i)(键上带 17:25:46)去 TryGetValue,永远 miss ⇒ 峰值/均值/今日全 0, 且只产 1 个桶、标签还是昨天。抽出 S8TrendBucketHelper 统一枚举「窗口跨越的自然日」, 保证 Σ桶 == 窗口内行数;窗口只收紧不放宽;超 90 桶保留最近 N 天并同步收紧窗口 (旧实现是砍尾巴、把已查出的行静默丢弃);首尾桶不完整时日环比置 null。 趋势摘要新增 windowFrom / windowToExclusive / windowTruncated / partialEdgeDays。 P2-D 「每百订单」缺少真实订单分母 ⇒ NO_DATA Frequency 的注释写「每百订单异常数」,实际是 Frequency = total(绝对数)。 算这个比率需要阶段级订单分母,S8 无任何 Authority 可提供:各 stage 异常对象类型不同 (实库 29 条全是 S4 / PURCHASE_DELIVERY_ABNORMAL,对象是采购单), mdp_std_so 只是 S1 的销售订单行,充当全阶段公共分母属于自造口径。 按「无 Authority 不得发明公式、NO_DATA ≠ 0」改为 double? 恒 null,前端渲染占位符。 验证 · dotnet build Admin.NET.sln(net8.0 + net10.0 全覆盖)0 Error · 新增 43 个测试(后端 31 / 前端 12)全绿;既有失败 5 条均为本批 diff 外的基线 · vue-tsc 583(基线 584,净 -1);vite build exit 0 · 运行时 API 与独立 SQL 逐窗口核对 Delta=0(9 个窗口);同请求连打 3 次结果一致 · 页面冒烟:last_24h 由 1 桶(标昨日) → 2 桶(09/22+09/23)、partialEdgeDays=true、环比 null; 看板筛选后 wire 上确有 dateStart/dateEnd;order-grid 全部 frequency=null;控制台无错误

2 周之前

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • 3330e50f0b fix(s8): remove unsupported numbers from production UI | server 1.0.563 / web 2.4.406 正式菜单页面上还有两处「没有任何来源的数字」在冒充业务事实,本次收口。 P1-1 kanban「闭环完成率」是写死的字面量 值为 `list.length ? '86%' : '100%'`,副标题「目标 95%」同样写死, 既无接口也无表。本页两张兄弟卡的数据源是 ado_s8_alert_record, 而该表全仓没有任何状态/关闭字段——告警本身就没有闭环状态机,这个比率不可算。 既有的 closureRate(S8DashboardService)建在另一张表 ado_s8_exception 上, 跨表挪用等于给本页自造业务语义,按「无 Authority 不得发明公式」一律不算。 改为显示 "—" + 「暂无数据(告警表无闭环状态)」,沿用本仓既有无数据语义。 P1-2 首页 S1–S7 状态灯与首屏报警是硬编码 状态灯原为写死数组(S2=critical / S4=warn / S6=orange),注释「与参考图一致」, 从不请求、从不更新——用户看到的是一组永不变化却长得像实时健康的红黄灯。 后端当前不存在任何模块健康 Authority,故不再给出健康断言:按钮保留其真正具备的 功能(跳转对应模块页),状态统一落 'unknown'(中性灰 + 「未监测」title)。 报警列表初始值原为 4 条编造数据(「机台 A-04 停机超时」「物料 P-99 缺料预警」, 连时间都写死为 14:28/14:25/…),会在请求返回前于首屏真实渲染,与随后的真实报警 外观一致。初始值改空;真实数据仍由 loadS8Data() → fetchS8Alerts 提供。 另两条原列为 P1 的,取证后确认前提不成立,故只补守卫、不改代码: P1-3 订单档案 KPI 并未基于 seed /orders 路由走 _realSvc.GetRealOrdersAsync(mdp_std_so + dwd_*), S8RealOrderArchiveService 不注入任何 SqlSugarRepository、纯 Ado 原生 SQL, 与 ado_s8_order_flow_* 无关。此前的看板盘点把它追到了 S8OrderFlowService,追错服务。 新增 S8OrderArchiveAuthorityTests 钉住「/orders 必须走 REAL 服务、不得回退 SEED 路径、 不得为去 seed 改成直连业务源表、租户一律服务端解析」。 P1-4 22 条 demo 异常在库中不存在 1.0.106 / 1.0.110 均已成功执行(2026-06-11),但其 INSERT...SELECT 硬编码 tenant_id = 1,而 ado_s8_order_flow_order 的种子落在 DemoTenantId=797403760988230, tenant 1 零行,故实际插入 0 条。实库 ado_s8_exception 仅有 29 条真实运行态行 (RULE_S4_PURCHASE_DELIVERY_DATE_DELAY / AUTO_WATCH)。 唯一会往 tenant 1 插 order_flow 的是 WIP-S8-DEMO-GOLDEN-DATASET.sql,从未应用。 因此不新增 cleanup migration:为不存在的行写 DELETE 只有风险没有收益, 也不改历史 migration(hash 不可变)。 验证:新增前端守卫 7 例(对修复前 6/7 失败,RED 验证)+ 后端守卫 5 例; dotnet build Admin.NET.sln 不带 --framework 全 TFM 覆盖 net8.0 + net10.0,0 Error; 插件 S8 833 passed / 1 failed、Admin.NET.Test S8 190/4,均为既有基线,净新增失败 0; vitest 16 文件 6 failed 同基线;vue-tsc 584 = 基线,逐项无新增;vite build exit=0。

2 周之前

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • ba87626d67 fix(s8): restore net8.0 build so teammates can start after pull | server 1.0.562 S8ApprovalFlowRepairService 把 root.ToJsonString() 直接写进了 SqlSugar SetColumns 的表达式树。ToJsonString 带可选参数,表达式树在 C# 12 下不允许 这种调用(CS0854);net10.0 用 C# 14 放行,net8.0 用 C# 12 直接编译失败。 插件是 net8.0;net10.0 双框架,而本地验证纪律只跑 --framework net10.0, net8.0 从未被编译,错误就这样推到了远端:同事用 VS 编译整个解决方案时 插件 net8.0 编不出 dll,引用它的 AiDOP.Tests 级联报 CS0006,无法启动。 改法是把调用提到表达式树外先求值,行为不变。 dotnet build Admin.NET.sln(不带 --framework,全 TFM)0 Error。

2 周之前

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • ee4dbd4355 fix(s8): remove seed-derived headline kpi claims | server 1.0.561 四个结果 KPI 以「订单链路计算」的名义对外宣称当前业务实算,实际算的是种子数据。 ORDER_DELIVERY_RATE / PO_DELIVERY_RATE / WO_COMPLETION_RATE / ORDER_FLOW_CYCLE_RATIO 分别读 ado_s8_order_flow_order / _procurement_pivot / _manufacturing_process / _stage, 发射 Source="ORDER_FLOW_CALC",前端 S9QdcSummaryCard 渲染为「订单链路计算」。 但这四张表唯一的写入方是 SeedData(全仓无任何 Insertable/Updateable 命中), 实库 25 / 1152 / 110 / 126 行全部 data_source='SEED',四个查询也从不过滤 SEED。 数字不是业务事实,那句 provenance 是错的。 逐项核对可替代的真实中台 Authority,四项均不成立,因此不做「改读中台」: · ORDER_DELIVERY_RATE —— dwd_t8_order_shipment_fulfillment 是行级发运及时率/按 biz_date, 与「订单周期 actual<=target」口径不同,换算须自行发明时间窗并改分母粒度; · PO_DELIVERY_RATE —— mdp_std_purchase_order_completion 既无承诺到期日也无实际到货日, 其 observed_at / source_update_time 在表注释中被明确标注「禁止冒充业务完成时间」, 按期到货率在现有中台里根本不可算; · WO_COMPLETION_RATE —— dwd_t8_work_order_mfg_fulfillment 粒度为 (biz_date,工单,物料) 的窗口完成率,与「工序 achievement_rate 算术平均」不同,换算须自行选定 biz_date; · ORDER_FLOW_CYCLE_RATIO —— 中台没有任何对象承载 S8 五阶段 planned/actual days。 按「不得为了保住卡片而发明公式」的口径,四项一律不再出当前值,落既有 PENDING_REAL (前端既有文案「待真实接入」),与 IQC_PASS_RATE 的 DEMO_BASELINE/「当前基线」同范式。 四个计算方法与四个 SEED 仓储依赖一并移除——S8MonitoringService 从此与 ado_s8_order_flow_* 无任何运行期事实依赖(不删表、不删 seed,仅断开事实链路)。 同时修 envelope:Source 原无条件写死 "MIXED_BASELINE"(前端「当前基线 · 部分订单链路计算」), items 为空时照样宣称「部分算了」——而字典 5 行当前全部 enabled=0,items 恒为空, 这正是当前唯一真正显示给用户的那句假陈述。改为按 items 真实构成推出。 另修正一处长期与实现不符的文档注释(原写「4 个 → DEMO_BASELINE / 1 个 → ORDER_FLOW_CALC」, 实际相反),避免后续据旧文案推断行为。 验证:新增 S8KpiProvenanceTests 8 例(含反射元数据层守卫:构造函数不得再注入 SEED 仓储), 对修复前实现 5/8 失败(RED 验证);插件 S8 用例 828 passed / 1 failed, 该失败为既有基线 MutationActions_DoNotUseReadOnlyCapabilities,净新增失败 0; Admin.NET.Test S8 190/4 与基线一致;dotnet build 0 error。
  • 9c1ca274c6 fix(s8): stop baseline data from masquerading as order facts | web 2.4.405 订单执行档案/订单链路全景上有三类「缺数被兜底成事实」,本次逐条收口。 1) 每个真实订单都被涂成绿色「正常」 REAL 路径后端把 CurrentStatus 下发为空串(S8RealOrderArchiveService.cs:272)—— S8 从未对真实订单做过红黄绿评级。而 asNodeStatus 把「空 / 不认识」一律兜底成 'green',于是列表里每一单都显示「正常」。这是凭空造出来的评级结论。 改为无评级即 null:只显示节点名、不着色;严重度筛选项同步过滤掉 null (与相邻的 productLines / regions 同一 .filter(Boolean) 写法)。 同文件的 asCustomerType / asPriority 早已按这条规矩修过,本次把 asNodeStatus 补齐。 2) 三张时间 KPI 卡在毫无 Authority 时打印「0.0 分钟」 getAverage 本就对空集合返回 null,是 computeOrderExecutionKpis 的 `?? 0` 把它吃掉了。0.0 分钟读起来是「响应极快」,属于把缺数说成业绩。 改为保持 null,由卡片沿用既有 "--" 无数据语义呈现(不新造文案)。 3) chain(SEED) 整体覆盖 detail(REAL) 的阶段事实 detail(orders/by-id/{id},读 mdp_std_so + dwd_*)才是五阶段事实 Authority; chain(orders/{code}/chain,读 ado_s8_order_flow_*)只应补 L2/L3 子步骤。 旧实现反了:拿 chain 阶段整体覆盖,再把 4 个 DataState 字段救回来,其余 targetDate / actualStartedAt / actualReachedAt / actualDays / actualHours / calculationStatus / 偏差 / status 全被 SEED 值顶替,唯一防线只是「订单号恰好不撞」。 改为真实阶段整体保留、只叠加 subSteps;detail 未取到阶段时不再由 chain 单独拼 lifecycle。这同时让两个页面对同一订单的阶段事实来自同一 authority。 4) 零订单租户被渲染成 105 单基线矩阵 isUnfiltered 原为纯长度相等,allOrders 为 0 时 `0 === 0` 恒真,于是一张真实订单 都没有(或列表加载失败)时反而进入基线态,标题还印着「全部 0 单」。 补 allOrders 非空条件;标题改为「参考基线(非本租户实算) · 基线样本 N 单」, N 取快照自身样本量,不再把真实订单数印在基线矩阵上方。 Stage-3 的 DataState/DataScope/KnownItemCount/CompletedItemCount 契约未改, 并新增回归守卫钉住「DataState 优先于任何时间形态推断、三态均不含已完成」。 验证:新增 15 例(provenance 11 + store 4),对修复前实现分别 3/11 与 2/4 失败(RED 验证); vitest 55 passed / 6 failed,6 例为既有基线(s8MonitoringAnomalyApi.spec.ts),净新增失败 0; vue-tsc 584 → 584,逐行 diff 无新增;vite build exit=0。
  • 对比 2 次代码提交 »

3 周之前

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • ff6f8e2bba fix(s8): enforce debug tenant boundary | server 1.0.560 AdoS8WatchDebugController 是 S8 中唯一把 query 上的 tenantId 当执行上下文的入口: run-once 直接把它传给 S8WatchSchedulerService.CreateExceptionsAsync(tenantId), 可在任意租户建单,并从出参 createdExceptionId / relatedObjectCode / matchedExceptionId 读回该租户的业务事实;test-cleanup 同理可软删任意租户的 TEST_G09_ 数据。此前唯一阻挡是 WatchScheduler:DebugEndpointEnabled —— 那是功能开关,不是信任边界。 现在租户一律由 S8TrustedScopeResolver 从认证身份解析(最终落到 AidopTenantScope.ResolveOrThrow,只信 JWT),与 S8 其余 Controller 同一个 authority,不新增第二套租户解析。query 上的 tenantId / factoryId 仅保留形参 以兼容既有调用方,不承担任何安全边界作用,显式丢弃。 设计目标:即使 DebugEndpointEnabled 被误开到生产,本入口也不可能跨租户 —— 开关只决定“能不能用”,不再决定“能动谁的数据”。 超管口径沿用平台既有策略,不在此另开全租户调试权:ResolveOrThrow 对 “超管 + 未选目标租户”直接抛异常,超管须先切到目标租户后以该租户身份执行。 新增 S8DebugTenantGuardTests(9 例)钉住该边界:去注释后的源码契约 + 反射元数据, 覆盖作用域解析、兼容形参必须丢弃、读写均限于已解析租户、未启用时 404 早于任何 作用域解析与 service 调用、超管不得在此获得跨租户能力、不得引入第二套租户 authority。 对修复前的实现 9 例中 6 例失败(RED 验证),确保不是空守卫。 验证:build 0 error;插件 S8 用例 820/821 通过,唯一失败为既有基线 MutationActions_DoNotUseReadOnlyCapabilities(AdoS8OrderReviewKpiController.RefreshAsync → s8:dashboard:read),已确认在 origin/master 原样复现,净新增失败 0。

3 周之前

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • 106ca8758b docs(ops): record winsw stop-window gap and windows verification steps Linux/WSL 侧的关停链路已闭环(restart_aidop.sh 改为 TERM → 等待 → 必要时才 KILL, 应用侧 TransformRunLogFinalizer 把在飞的转换收口成 ABORTED)。Windows/WinSW 这一侧 还没确认:服务管理器如果等不到应用自己退出就强制结束进程,收尾不会执行, mdp_transform_run_log 会重新开始攒 RUNNING 残留。 本次只记录审计结果,**没有改 AiDOPBackendService.xml**: 已确证(应用侧) · 全仓未显式配置 HostOptions.ShutdownTimeout(Program.cs 只设了 BackgroundServiceExceptionBehavior,配置文件与 WinSW <env> 均未注入) · 实际生效窗口 = 30 秒 —— 本机实测 new HostOptions().ShutdownTimeout 在运行时 10.0.12 / net10.0 下为 00:00:30,不是查文档也不是凭记忆 未确证(WinSW 侧) · 部署机实际使用的 WinSW 版本(文档未锁版本,仓库无二进制,本机无该部署) · 其 stop 的默认等待窗口 · 其 stop 的语义:投递可被 .NET 捕获的控制事件,还是直接终止进程 —— 若是后者,加多少 timeout 都没用 · stoptimeout 的字段名与单位/格式在该版本中的确切写法 之所以不顺手加一行配置:字段名或单位写错会让 WinSW 解析失败、服务起不来, 等于拿生产可用性换一个未验证的猜测。同文件已有的 <onfailure delay="10 sec" /> 只能说明该配置用「数字 + 单位」的时长串, 那是 delay 的写法,不构成 stoptimeout 的证据。 文档新增一节,给出:确认版本与字段的命令、判定停止语义的方法 (看关停日志是否出现)、取值原则(明显大于 30 秒并留余量)、 九步验收流程,以及禁止用 taskkill /F 代替正常 stop。 按项目规约,纯文档改动不递增版本号。

3 周之前

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • fb8bb5609f fix(mdp): finalize in-flight transform runs as ABORTED on host shutdown 宿主关停时,mdp_transform_run_log 里在飞的那一行没有归宿:走得完就落终态, 走不完就永远停在 RUNNING。而 RUNNING + end_time IS NULL 正是 S8 Authority 判定「有更新的一轮在飞」的依据,一条收不了口的行会把该 (job_code, tenant_id) 的健康判定长期按住。实库现存 399 条这样的残留。 新增 TransformRunLogFinalizer,接入全部 17 个 producer、39 处 run-log 生命周期: · 判据是 IHostApplicationLifetime.ApplicationStopping,不是传进来的 CancellationToken —— controller 上的 token 会被模型绑定到 RequestAborted, 客户端断开时同样取消,拿它当判据会把用户关浏览器记成宿主关停。 · 收口写 ABORTED 而非 FAILED:关停不是算错,混进去会稀释「转换失败」这个信号。 也不复用 CANCELED —— 它已被 S0DimRefreshService 占用为业务级取消。 · 条件 UPDATE(id + status='RUNNING' + end_time IS NULL)保证只改没落终态的行, 正常跑完的一轮命中 0 行,重复调用幂等。 · 收口走 CopyNew() 的新连接并显式清 Ado.CancellationToken。这是实测教训: 首版只做到「不传 token」,冒烟时 finalizer 确实触发了,UPDATE 却抛 OperationCanceledException、一行没落 —— SqlSugar 把 token 挂在 Ado provider 上,关停时它已取消,后续每条 SQL 连坐。删掉这两句收口会静默失效。 · finalizer 从不抛异常:它只在 finally / catch 里被调用,抛出会顶掉真正的业务异常。 契约测试 13 条,含两条反回归:凡写 INSERT ... RUNNING 的文件必须接收口器、 凡 MarkXxxRunFailedAsync 必须被 IsHostStopping 挡过 —— 接线是 39 处机械改动, 将来新增 producer 的人不会读到本批结论,交给测试守。 冒烟(隔离实例 5025 + SIGTERM):在飞的 S2 轮次收口为 ABORTED, error_message=HOST_SHUTDOWN;同期 241 轮正常完成(237 SKIPPED_NO_CHANGE + 4 SUCCESS)无一被误改。 本批不清理历史残留、不动调度、不改 schema(status 已是 varchar(30))。

3 周之前

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • dcb80f0662 chore(s7): close server version for FQC generic flow mutation guard | server 1.0.558 本提交只做版本收口,不含任何功能改动。 两笔 S7 功能提交(block generic flow mutation entries / consolidate dedicated entry policy)此前在落后 4 个 commit 的本地 master 上完成,其 csproj 位停在 1.0.553,无法作为发布身份。本批从最新 origin/master(69c46a66c,server 1.0.557) 新建干净隔离 worktree 后 cherry-pick 两笔(patch-id 逐一相同、零冲突), 在此基础上取下一版本号 1.0.558。 取号依据(均为执行时现场实测,非沿用历史快照): origin/master csproj = 1.0.557 origin 最高 UpdateScript = 1.0.555 生产 sys_db_migration_log = max 1.0.555,556/557/558 无记录 (556/557 为纯代码提交、本无 SQL) 全部 worktree csproj = 551/553/554/555/556/557,无 558 本地分支 = 无 558 相关 peer 交叉确认 = eager-pudding 明确 558 无在途占用, 其后续批次主动改取 559 本批无 schema 变化,故不创建 UpdateScript —— 版本号仅作 server release identity,不制造空 migration。前端未改动,Web/package.json 不动。
  • fdaebfd4d5 refactor(flow): consolidate dedicated entry policy 上一批(c6935407e)为封堵 S7 的四个变更型通用入口,新增了 FlowMutationDedicatedOnlyBizTypes,与 DedicatedEntryOnlyBizTypes 形成两份 BizType policy source。行为正确,但两套名单不接受长期保留。本批只修这个 设计问题,不改变任何已验证的功能行为。 收敛方式:字典值由 string 升级为 DedicatedEntryPolicy(Message, BlockFlowMutation), 把「是否只能走专用入口」与「封堵范围是否延伸到变更型入口」放进同一条记录: S5_IQC_INSPBILL BlockFlowMutation = false S6_PROCESS_INSPECTION BlockFlowMutation = false IPQC_INSPECTION BlockFlowMutation = false S7_FQC_INSPBILL BlockFlowMutation = true Approve / Reject / BatchApprove / BatchReject 仍按「BizType 在名单内即拒绝」 的原语义走 EnsureDedicatedEntryOnlyAsync,行为不变(仅取文案改为 hint.Message)。 四个变更型入口改为读同一条记录的 BlockFlowMutation 显式字段,不再查第二个集合; 判定不从文案内容、字典顺序或 BizType 字面量反推,也未引入 if/switch 分支—— S7 字面量在 FlowTaskService 中现只出现 1 次(policy 键)。 Guard placement 未变:四个入口的守卫仍是方法第一条语句,仍在进入 FlowEngine 前 fail-closed。FlowEngineService 一行未改。S5/S6/IPQC 的 Generic Mutation 行为 完全保持(BlockFlowMutation=false)。Escalate / Urge 未动。 S5IqcGenericApprovalGuardContractTests 的一处断言由 Oops.Oh(hint) 改为 Oops.Oh(hint.Message):TEST ONLY COMPATIBILITY UPDATE,仅适配源码形状, 业务契约不变(文案仍来自字典查表、仍不取自入参),S5 生产代码未改。 该适配无法通过生产侧兼容写法避免——S5/S6/IPQC 共同要求 TryGetValue(..., out var hint) 字面量,而 S5 又要求 hint 可直传 Oops.Oh, 两者同时成立只能让字典值退回 string,与本批目标矛盾。 验证:ApprovalFlow 与 AiDOP 插件编译 0 Error;S7 守卫契约 57/57; S5 11/11、S6 15/15、IPQC 22/22、S7TenantAuthority 36/36、 S7FqcInitiatorTenantGuard 30/30;全量 2912 passed / 1 failed / 2 skipped, 唯一失败为既有 S8 用例(AdoS8OrderReviewKpiController.RefreshAsync → s8:dashboard:read),错误内容与上一批逐字相同,与本批无关。 无 schema / migration / 版本号改动。Runtime 本批未执行,仍为 RUNTIME ACCEPTANCE PENDING。
  • 84236267a2 fix(s7): block generic flow mutation entries for FQC S7 成品检验单的 DedicatedEntryOnly 守卫此前只挂在 approve / reject / batchApprove / batchReject 四处,而 ReturnToPrev / Withdraw / Transfer / AddSign 四个会改流程状态的通用入口全部直通引擎、零守卫。因 S7 的 IFlowBizHandler 三个回调全是 no-op,业务副作用一律由 FqcInspBillFlowService 在引擎之外同事务完成,故这四个入口只改流程、不改业务: - ReturnToPrev 绕过专用 supervisor-reject 的强制退回意见、同事务复位 qms_fqcbj.FINSPECTSTATUS='检验中'、同步 mdp_std_fqc_task.inspect_progress; 从 N3 调用还会退到 N2,产生专用设计从不产生的状态。 - Withdraw 把实例置 Cancelled 而 OnFlowCompleted 为 no-op,报检单永久停在 「检验中」:重提结果被「流程已结束」拒、重新认领被「负责人已锁定」拒、 重新生成检验单被幂等唯一约束拒,三条恢复路径全部关闭。 - Transfer / AddSign 把 AssigneeId 原样写入(不校验存在性/租户/角色), 而专用 N2/N3 门禁是「持有本节点待办」而非「持有角色」,于是无 ROLE_S7_FQC_SUPERVISOR / ROLE_S7_FQC_QE 的用户可直接完成主管审核或 QE 处置。 实现复用既有机制,不新建第二套守卫:名单与文案的唯一来源仍是 DedicatedEntryOnlyBizTypes;新增 FlowMutationDedicatedOnlyBizTypes 仅表达 「封堵范围是否延伸到变更型入口」这一作用域,且必须是主名单的子集(测试断言)。 只有 S7 纳入——其余三条目前仅封堵同意/拒绝,一并纳入会改变 S5/S6/IPQC 的现有行为,超出本批范围。FlowEngineService 一行未改。 守卫是四个动作方法的第一条语句。这一点是硬要求:引擎侧这些动作都没有 [UnitOfWork],进入后按「取消同节点待办 → 改 task 状态 → 改 CurrentNodeId → 建新任务」逐步提交,中途抛错无法回滚,守卫晚一步就不再是 ZERO MUTATION。 Escalate / Urge 本批未动(前者因 S7 定义未配 manual escalation 在变更前即 抛错;后者不改流程状态,其权限问题属 ApprovalFlow 通用治理),并有测试冻结。 验证:ApprovalFlow 与 AiDOP 插件编译 0 Error;S7 守卫契约测试 52 项全过; S5/S6/IPQC 守卫与 S7 租户授权、S7 Initiator 守卫四套回归全过;插件全量 2907 passed / 1 failed / 2 skipped,唯一失败为既有 S8 用例 (AdoS8OrderReviewKpiController.RefreshAsync → s8:dashboard:read), 该测试对 FlowTaskService / ApprovalFlow / S7 的引用均为 0,与本批无关。 无 schema 变化,未创建 UpdateScript。版本号未随本次提交递增:csproj 在 HEAD..origin/master 区间已被他方改动(origin 已至 1.0.556,本地基线 1.0.553), 按共享工作区规约不覆盖 FOREIGN 改动,版本收口另行处理。
  • 对比 3 次代码提交 »

3 周之前

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • 69c46a66c3 fix(s8): ignore superseded transform runs in authority health | server 1.0.557

3 周之前

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • 77a4ce7180 feat(s8): align stage3 procurement state across order views | server 1.0.556 / web 2.4.404

3 周之前

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • e6ad49a378 fix(mdp): restore mdp_entity inbound defaults under CodeFirst | server 1.0.555 1.0.554 把 inbound_enabled / stale_guard_enabled 收口为 TINYINT + DEFAULT, migration 与 verify 均 Success;但下一次启动时 SqlSugar CodeFirst (EnableInitTable=true)按实体声明 `public int` 把它们改回 INT,其 ALTER 形态为 `alter table t change column c c int NOT NULL`——不带 DEFAULT 子句, 于是连带抹掉 DEFAULT,S1/S2 的 Error 1364 复现。 对照组证明了正确解法:inbound_max_rows 的类型本就等于实体声明的 INT, CodeFirst 无差异可纠正、不生成 ALTER,其 DEFAULT 500 完整穿越了 CodeFirst 遍历。即:只要列类型与实体声明一致,CodeFirst 不重写该列,DEFAULT 存活。 故本批类型保持 INT,只补 DEFAULT。类型漂移(INT vs 契约 TINYINT)留 R2-B, 待 CodeFirst schema authority 定案后统一处理。 1.0.554 保持历史不变:不修改、不改 hash、不改 migration log、不重放。 其 inbound_max_rows 的 DEFAULT 500 成果保留,本脚本只对它做前置断言(G5), 若 500 丢失即判 SCOPE CHANGED 并中止,不顺手补。 实现要点: - 逐列 target-state 比对,CONCAT_WS 拼子句,已正确的列真 no-op - G6 类型守卫:两列必须已是 INT NOT NULL,否则中止,本脚本不自动改类型 - COLUMN_DEFAULT 比较用 NULL-safe <=> - ALGORITHM=INSTANT(纯元数据,不重建表,无写阻塞),3307 实测 58ms - 门禁前附可读诊断 SELECT(SIGNAL 不支持 prepared statement,条件成立时 实际抛 1295 而非预期消息) 演练补上了上一轮的盲区——这次跑了真实 backend: SQL 执行 → 三列 int/0、int/500、int/1 CodeFirst 完整遍历 285/284 → 三个 DEFAULT 全部存活 重启再跑一次完整遍历 → schema checksum 逐位不变 裸 INSERT 省略三列 → 落值 0/500/1

3 周之前

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • a293498d4d fix(mdp): reconcile mdp_entity inbound defaults | server 1.0.554 S1/S2 的实体注册用裸 SQL INSERT INTO mdp_entity (...) ON DUPLICATE KEY UPDATE,显式列清单不含 inbound_enabled / inbound_max_rows / stale_guard_enabled。生产这三列 NOT NULL 且缺 DEFAULT,sql_mode 含 STRICT_TRANS_TABLES,于是持续报 Error 1364。MySQL 每次只报第一个缺失 列,故三列必须同批收口,否则要三次部署才收敛。 漂移成因不是 migration 写错:1.0.536 对三列都写了正确的 TYPE + DEFAULT (:118/:124/:130),但 mdp_inbound_* 六表的 create_time 实测为 2026-09-15 23:03:31~33,由 SqlSugar CodeFirst 抢先创建;1.0.536 正式执行 是 2026-09-16 20:36:37,晚 21 小时,其 IF(@exist=0, ADD COLUMN ...) 守卫 整段 no-op。CodeFirst 按实体生成 int / 无 DEFAULT。 本脚本采用 TARGET-STATE RECONCILIATION:判定「定义是否等于目标」而非 「列是否存在」——后者正是 1.0.536 的失败模式。COLUMN_DEFAULT 比较用 NULL-safe 的 <=>:COLUMN_DEFAULT 为 NULL 时 `COLUMN_DEFAULT = '500'` 求值为 NULL 而非 FALSE,被 WHERE 按假丢弃导致漏检。实证:仅 inbound_max_rows 缺 DEFAULT 的环境下,= 谓词 drift=0(migration 报 Success 然后静默跳过),<=> 谓词 drift=1,而该环境实际 INSERT 确实报 1364。 算法在本地 3307 同构演练库实测:INSTANT 被拒(Need to rebuild the table)、INPLACE 被拒(Cannot change column type INPLACE)、LOCK=NONE 被拒(COPY algorithm requires a lock)。故显式声明 ALGORITHM=COPY, LOCK=SHARED,让锁行为在脚本内可见可审计。202 行实测 72ms,写阻塞约 72ms,读不受影响。 门禁前均附一条可读诊断 SELECT:SIGNAL 不支持 prepared statement,条件 成立时实际抛 ERROR 1295 而非预期消息(1.0.551/1.0.553 同 idiom,只因 门禁从未触发而未暴露)。fail-closed 仍成立,但需让日志先打印是哪道门禁。 范围:只动 mdp_entity 三列。不碰 uk_inbound_grant(1.0.553 已建,仅回归 断言)、不碰 R2-B 的 7 项 dormant drift、不碰任何 P0 对象。 演练:完整 fixture(六表同构、mdp_entity 202 行真实漂移态)下 migration 无错、verify ok=1;连跑三次幂等;人为退回单列后仍一次收敛。

3 周之前

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • aa4fb187c1 fix(mdp): enforce inbound grant uniqueness | server 1.0.553
  • 368afef6b7 fix(s7): enforce tenant-safe FQC initiator creation | server 1.0.552 S7 FQC 曾把不属于单据租户的用户写成 jyfzr,起流后被引擎照实 捕获为 ApprovalFlowInstance.InitiatorId;而 ResolveApprovers 的 Initiator 分支按环境租户过滤,导致该实例退回 N1 时永久解析不出 审批人。本次只封堵新增,不改动任何既有实例。 不变量:BusinessTenant == jyfzr 用户.TenantId == Initiator 用户.TenantId 三道防线(全部 fail closed,且都在业务写入/起流之前): - Assign:校验 InspectorUserId,原实现完全不查用户 - Claim:校验 _userManager.UserId —— 超管切租户只改内存 TenantId 并重签 token、不持久化,故 JWT 租户与 SysUser.TenantId 会背离 - GenerateInspBill / SubmitResult:校验业务行上已存的 jyfzr, 拦住先于本守卫写入的存量坏数据再次起流 守卫落在 S7 业务边界,不改 FlowEngineService:引擎照实记录操作者 是正确行为,「谁可以成为该租户单据的负责人」是 S7 的业务规则。 平台权限不等于业务身份——超管除非真实属于该租户,不能作为其 FQC 负责人与发起人。 Runtime(R1-R10)未验证:后端受 sys_db_migration_log 版本簿记 阻塞无法启动,Chrome DevTools MCP 同时断链,两者均超本批授权, 不自行绕过。已验证项:plugin 编译 0 Error;30 条针对性契约测试 全过;插件全量 2872 passed / 1 failed(既有 S8 用例,与本批无关); S7 域 ApprovalFlowInstance/Task/Log 16/42/49 与操作前基线一致; 特殊实例 840240525086789 四项快照未变。
  • 对比 2 次代码提交 »

3 周之前

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • 941ea73482 fix(core): reconcile known migration collision before update 2026-09-15 22:58 一次重启从尚未更新到目标 revision 的工作区启动,其 UpdateScripts/1.0.536.sql 当时仍是旧 Stage-3 内容(与 1.0.539 同一份 SQL、 仅注释里版本号不同),该内容被执行并记成 version=1.0.536 / hash=970E5A8F / Success。而同一份内容早已由 version=1.0.539 / hash=9B627E41 合法记录。 结果真正的 1.0.536(API_INBOUND,hash=5A18A15A)永远无法执行—— ShouldSkipScript 在计算 pending 时就抛 hash 不符,任何带目标 revision 的实例都起不来。 新增 MigrationCollisionPolicy:纯函数、版本精确登记、七道闸门。 G1-G4 识别事故签名,任一不成立返回 None(全新环境、已升级完成的环境、 失败重试中的环境都必须放行给既有流程;尤其「已升级完成」与事故环境 只差 file_hash 一个字段,误判会打死正常库)。G5-G7 是安全前提, 签名确认后任一不成立一律 Reject 并停机。 不删除任何历史行:sys_db_migration_log 对 version 有唯一键,且 UpsertMigrationLog 按 version 原地 UPDATE,因此只把误记行 status 置为 Superseded,随后由原有正常流程执行真正的脚本并覆写同一行—— 「这条迁移到底成没成功」始终是脚本自己跑出来的结论。 reconcile 必须在计算 pending 之前调用;放在执行循环里(像 TryRecoverKnownFailedMigration 那样)永远够不到这类事故。 本策略不能感知同库上的其它实例:若重整后、正式脚本执行前有仍持旧脚本 的实例启动,它会重跑旧内容并把 hash 改回去。部署时必须先停掉所有连接 该库的旧 backend,这是流程前提,代码无法兜底。 隔离库演练:First Run 完成 reconcile → 1.0.536/537/538/551 全部 Success 且 verify Success;Second Run reconcile 次数 0、pending 0、history 与 schema checksum 逐行不变。单测 24/24,并经变异验证(闸门 4 改 Reject 触发 3 个失败、删除闸门 6 触发 4 个失败)。

3 周之前

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • 792fa006d2 chore(aidop): assign official migration number to P0-A cleanup | server 1.0.551 rebase 到 origin/master 后给 P0-A 数据纠偏 migration 取正式号。 WIP-P0A-CLEANUP.sql -> 1.0.551.sql WIP-P0A-CLEANUP.verify.sql -> 1.0.551.verify.sql aidop_migration_derived_cleanup_log 的 migration_version 'WIP-P0A' -> '1.0.551' csproj 注册 Copy 条目,版本 1.0.550 -> 1.0.551(§二·7:SQL 号与 csproj 版本一致) 取号经过(两次让号): 1) origin/master 的 1.0.536(API_INBOUND WP-A)与本地 1.0.536(S8 Stage-3 身份桥) 同号不同内容,rebase 时把本地那份重编为 1.0.539,远端原号不动。 2) 本批先取 1.0.550,随后与另一实例刚提交的 1.0.550(S8 订单评审耗时菜单退役) 实时撞号;对方已在共享 master 上,故本批让号顺延为 1.0.551。 契约测试改为按内容锚点(P0A-G1 / uk_po_line_stat)定位脚本而非按文件名, 避免再次重编号时测试失效。 **脚本仍未在任何库执行(MIGRATION NOT EXECUTED)** —— Stop-Write Gate 未通过,S3 写入方仍在活跃跑批。执行条件与门禁见脚本头部注释。
  • 711dd23956 fix(s8): retire obsolete order review hours menu and stale route | server 1.0.550 / web 2.4.403
  • 803fdc96f0 fix(s7): migrate active FQC flow authority to role codes | server 1.0.549 把原 7 条仍在运行的 S7 成品检验实例快照里冻结的默认租户物理 RoleId (1329915020002 / 1329915020003)结构化改写为 RoleCode (ROLE_S7_FQC_SUPERVISOR / ROLE_S7_FQC_QE),使它们在租户本地化模型下继续流转、 且不再依赖 9 条跨租户 SysUserRole 绑定。 为什么必须迁数据而不是改引擎:StartFlowCore 落实例时把 ApprovalFlow.FlowJson 整体冻结进 FlowJsonSnapshot,此后所有推进路径一律读快照、再不看定义。因此上一批 (1.0.547)的定义 RoleCode 化只对新实例生效,存量实例仍靠跨租户绑定解析。 运行时隐式把 A 角色当 B 角色执行会让「执行的」与「快照里展示的」永久分离, 故本批是显式、一次性、可审计、可回滚的数据迁移,不是 resolver 兼容层。 设计镜像已验证的 S6LegacyFlowAuthorityMigrationService(未复用其本体:它的 TargetBizTypes 与租户反查硬绑 S6 两条链,泛化就要改 S6 已关闭的逻辑)。S7 侧额外做了: - DryRunAsync 与 MigrateAsync 共用同一个 EvaluateAsync,预演与实改在代码上无法分叉; - 改写路径门禁:数字引用只允许出现在 N2/N3,改写后再独立复核一次落点; - 目标 Code 白名单:Code 由 SysRole.Code 反查而非硬编码,不在白名单即挡下; - 留证表按要求补 changed_paths / applied_by / migration_key。 安全边界:批次边界 2026-09-14 22:00(RoleCode 定义发布前),之后发起的实例一律不碰; 终态实例不动;同租户数字 RoleId 不动;N1 是 Initiator 节点,服务只看 approverType==Role 故天然不碰;业务租户由 qms_qcpp_inspbill 反查,不取登录/定义/发起人/待办人租户; UPDATE 带 Id + Running + 改前 MD5 三条件且只改 FlowJsonSnapshot 一列; 留证 INSERT 与快照 UPDATE 同一事务;幂等判据取自快照内容而非留证表。 Runtime: - Dry Run(只读、留证表 0 行):candidates=7 applicable=7 blocked=0,每条只改 2 条路径 且全落 nodes[id=N2_SUP_REVIEW|N3_QE_DISPOSITION].properties.approverIds; 目标角色 797→848821564117061/848821564616773、838→848821570179141/848821570670661 (均 tenant-local、各 1 名同租户成员);回滚来源各命中恰好 1 条 ApprovalFlowVersion。 - Apply:selected=7 applied=7,blockedTargetAuthority/RollbackSource/ChangedPath/Concurrent 全 0; 留证 7 行全 APPLIED、1 个批次、7 个不同实例。 - 迁移后:原 7 条 legacy RoleId 命中 0/7,RoleCode 命中 7/7(N2 与 N3 各 7)。 - 零副作用:Active 状态指纹(Id|FlowId|FlowVersion|Status|CurrentNodeId|InitiatorId|任务 id:assignee:status)前后相同 075ef325…;新建健康 2 条快照指纹相同 629ae11f…; 终态 7 条指纹相同 6ab93ac0…;业务行 pd/hgsl/bhgsl/clfs/FBILLSTATUS 指纹相同 702860f2…; ApprovalFlowLog 49 条、CompletedNode 48 条均未变;9 条跨租户绑定按设计保留。 - 幂等:第二次启动 candidates=0 selected=0 applied=0,留证仍 7 行 1 批次。 - Resolver 证明(不推进流程):797 与 838 的 N2/N3 均解析到本租户 TENANT_LOCAL 角色, 成员 AIDOPDemo@797 / UATQualityA@838,满足 User.TenantId == Role.TenantId == BusinessTenant。 - 特殊实例 840240525086789:Role authority 已修复,但 InitiatorId 仍为 1300000000101 (默认租户)、N1 仍是 Initiator、CurrentNode 仍 N2、原待办未变 —— 其 Initiator tenant mismatch 仍 OPEN,Reject→N1 依旧不可用,留待独立批次。 本批未开启 TenantStrictRoleBizTypes、未清理跨租户绑定、未改通用审批守卫与流程定义。 测试:全量 2842 passed / 1 failed(既有 S8AuthorizationGuardTests,与本批无关)。
  • 07420e31b1 fix(aidop): rebuild polluted outsource delivery DWD (MIGRATION NOT EXECUTED) 新增 P0-A 数据纠偏 migration 与配套 verify、契约测试。**脚本尚未在任何库执行。** 未执行原因:Stop-Write Gate FAIL。实测后端进程存活、 job_smart_ops_kpi_mdp_bootstrap 于 20:07:13 刚触发、 job_s3_mdp_sync_transform NextRunTime=21:07:14,写入必然恢复。 按规约不带着活跃写入方做 DELETE / TRUNCATE / ALTER。 文件名用占位 WIP-P0A-CLEANUP.sql(CLAUDE.md §二·7):本仓与 origin/master 分叉, 且 1.0.536~538 已撞号(同号不同内容),此刻无法取正式版本号。 占位文件未注册 csproj Copy 条目,不会被 AutoVersionUpdate 拾取执行。 待分叉解决后 rename 为 1.0.<next>.sql 并补注册。 migration 步骤(顺序不可颠倒): Step 0 门禁 G1-G5,任一不成立即 SIGNAL 中止 G1 集合完备:population_a + survivors == std_total(断言集合关系而非硬编码行数) G2 血缘双向差集为 0(EXISTS 回溯 stg 的 RoutingOpDetail 分区) G3 幸存者全部是真实 PW G4 overlap=0 G5 待保全子集非空 Step 1 保全 DWD 真实 PO 子集 + 元数据留证(复用 1.0.350 的 cleanup_log 范式) Step 2 精准清 std 种群 A(范式 A:CREATE TABLE LIKE + INSERT IGNORE + DELETE) Step 3 TRUNCATE DWD(非分批 DELETE:后者不归还磁盘,却多付约 5-8GB binlog 与数十 GB undo) Step 4 空表期 schema 收口:po_no/po_line NOT NULL DEFAULT ''、 唯一键换为 (tenant_id,factory_id,stat_date,po_no,po_line)、 租户守卫 CHECK 按 CHECK_CLAUSE 动态定位后收敛到稳定名 Step 5 回插保全历史,显式排除 tenant_id IN (0,1,1300000000001) 两处实测校正(否则会静默出错): - 保全谓词必须是 IFNULL(po_no,'')<>'' 而非 po_no IS NOT NULL。 代码侧 IFNULL 补丁上线后种群 A 的 po_no 变成空串而非 NULL, 用 IS NOT NULL 当时会多保 56 行垃圾。 - 血缘回溯不得附加 source_system 等值条件。stg 侧两代分属 AIDOP(colon 形态) 与 AIDOPDEV_MYSQL(digits 形态),std 侧被硬编码为 'AIDOP', 加上该条件会假阴 75939 行。 只读 dry-run(aidopdev,仅 SELECT): G1 117154 + 4 = 117158 ✓ G2 双向差集 0/0 ✓ G3 0 ✓ G4 0 ✓ G5 保全 31 行 / 1 grain / 2026-08-16~09-15 ✓ 列类型核对:po_no varchar(100) / po_line varchar(50),与 MODIFY 语句一致 ✓ 测试:新增 11 条结构契约测试,mutation 验证有效 (保全谓词退回 IS NOT NULL → T6 红;schema 收口提前到清数据之前 → T7 红)。
  • 177e890e4c fix(aidop): stop P0 data-platform write amplification | server 1.0.548 P0-A: 切断 RoutingOpDetail 错误入站链 - 移除 S3MdpEntityConfig.All 中的 S3_ROUTING_OUTSOURCE 注册。RoutingOpDetail 是 「S0 标准工艺路线明细」主数据,已由 S0 维度层建模为 bridge_item_routing_op, S3 这份属重复建模。 - 移除 BuildStandardCommands 里 RoutingOpDetail -> mdp_std_process_outsource_order 的 STD INSERT。该语句驱动集是 WHERE source_table='RoutingOpDetail',只服务这一个 来源;其中 po_no / po_line 因源侧无对应字段被硬编码为 NULL。 - BuildDwdCommands 投影补 IFNULL(o.po_no,''),与既有的 IFNULL(o.po_line,'') 对称。 uk_work_op_stat 含这两列,MySQL 唯一索引 NULL-distinct 语义使 ODKU 永不命中, 每轮跑批全量追加,实测膨胀至 3262 万行。 注意顺序:该保护必须在切断入站之后才安全,否则会把大量不同主数据压进少数键。 - mdp_std_process_outsource_order 的写入方由 2 个收敛为 1 个 (Supply/ProcessOutsourceOrderService.cs,PurOrdMaster Potype='PW'),未改动。 P0-B: S0Dim 写前 checksum 闸门 - S0DimMaterializer.RunAsync 在 source_count 之后、staging purge 之前插入纯只读闸门: source checksum 与 dim checksum 一致则置 SKIPPED_NO_CHANGE 并返回,不 purge、 不 pull、不删 dim、不跑写后对账;不一致则原 FULL 路径逐字不变。 - 正确性依据:dim 事务内的 AssertBlockingAsync 已断言 source == dim,故任一轮 SUCCESS 之后两者必然相等;下一轮仍相等即证明源侧无变化。不依赖任何持久化状态。 - S0DimReconciler 把 Layer 3 的 checksum 读取抽为 public ReadChecksumPairAsync, 闸门与写后对账共用同一条路径,杜绝口径漂移(纯重构,判据德摩根等价)。 - 保留 FULL,未引入 INCR/ROLLING/watermark;未改 mdp_entity 配置; 未改 MdpStagingWriter / MdpDbPullExecutor / MdpStdFullReplace / MdpSyncWindowResolver。 测试: 新增契约测试 33 条 (8 + 25),S3/DataPlatform/S0Dim 回归 362 条全绿。
  • 对比 22 次代码提交 »

3 周之前

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • 8ce9bca054 fix(flow): block generic approval bypass for S5 IQC tasks 审批中心的通用「同意/拒绝」可绕过 S5 IQC 专用审批链。实测(Batch 5A B2): POST /api/flowTask/approve 携带 S5 的 N2 pending taskId 返回 HTTP 200 并真实把 流程从 N2 推进到 N3、新建 N3 任务、写入审批日志——完全绕过 IqcInspBillFlowService 的四层 authority(JWT 租户解析 → 业务单 tenant 谓词 → 当前节点守卫 → UseTranAsync)。 根因:FlowTaskService.Approve/Reject 零校验裸调引擎,而引擎 GetPendingTask 只校验 「任务存在 / Pending / 指派人==当前用户」,不校验业务类型与租户。 后端(网关层 fail-closed,未改 FlowEngine) - FlowTaskService 新增 DedicatedEntryOnlyBizTypes 名单与 EnsureDedicatedEntryOnlyAsync, 按 taskId 从库中反查权威 BizType(不信任前端入参,TaskActionInput 本就只有 TaskId/Comment) - 守卫插在 Approve / Reject / BatchApprove 循环 / BatchReject 循环四个调用点的引擎调用之前; 批量端点直接调引擎、不经单条方法体,故必须各自带守卫 - 任务或实例不存在时不抢先抛错,沿用引擎既有错误语义(「审批任务不存在」) - 只做拒绝不做放行,因此不引入新的租户判定(Flow 两表继承 EntityBaseOrg,无 TenantId 列) - BizType 字符串硬编码:ApprovalFlow 插件不引用 AiDOP 插件(反向依赖会成环) 前端(与后端一致的引导,纯新增 18 行、0 删除) - PendingList 新增 S5_IQC_INSPBILL 分支,按 nodeId 跳 /aidop/s5/iqc/detail/${row.bizId}; bizId 即 qms_qcp_inspbill.id(StartFlow 以检验单主键写入 BizId) - 未知节点 fail-closed(只读文案),与 S7 一致;S6/S7/异常/通用分支逐字未动 - 节点码用 N3_SQE_DISPOSITION,注意与 S7 的 N3_QE_DISPOSITION 不同 测试 - 新增 S5IqcGenericApprovalGuardContractTests(11 条源码契约断言):守卫必须在引擎调用之前、 批量端点各自带守卫、按精确 BizType 匹配不得宽泛误伤、前端 fail-closed 且不 emit approve/reject 实测:通用 approve/reject/batchApprove/batchReject 对 S5 全部被拒且 0 Flow/Task/History/业务单 变更;非 S5 通用审批仍落到引擎自身错误、未被误伤;S5 专用 submit-result / supervisor-approve / supervisor-reject 全部正常。
  • 6aee5cd2e7 feat(s5): migrate IQC inspection detail to independent page 将 S5 IQC 检验单详情由「列表页内嵌 el-drawer」收敛为与 S7 FQC 同类的独立整页 /aidop/s5/iqc/detail/:id(隐藏 SysMenu 路由 + 独立 Tab),旧 Drawer 完全删除, 不保留两套详情入口。server 1.0.535 / Web 2.4.397。 前端 - 新增 iqcInspBillDetail.vue:AidopDemoShell 整页,顶部「返回 / 单号 / 流程状态」, 区块顺序 基本信息 → 物料与供应商 → 检规信息(自物料块拆出)→ 检验数量 → 检验执行 → 不合格与偏差 → 检验项目明细 → 审核信息 → 流程信息 → 节点操作区; descriptions 由 2 列改 4 列以利用宽屏 - N1 提交 / N2 审核 / N3 SQE 三段操作自 Drawer 平移,校验逻辑逐字不变, emit 中转改为页面内直接调用 API - 项目级保存链路由三层缩短为两层:ItemPanel → 页面 → save-detail → 重取 detail - 表单重置由「可见性 watch」改为「加载函数内联重置」(对齐 S7 resetForms 机制) - iqcTaskList.vue:三个详情入口(待检任务 Tab / 检验单 Tab / 生成后自动进入) 统一 router.push;移除全部详情态 state 与流程 handler 路由与授权 - SysMenuSeedData 局部新增一条隐藏菜单 1329015010004,显式 IsKeepAlive=false (实体默认 true)——详情页是强状态页面,缓存会残留旧 canEdit - UpdateScripts/1.0.535.sql 三段幂等:SysMenu / SysTenantMenu / SysRoleMenu; 授权口径 = 直接继承 IQC 任务列表入口菜单的授权面(子查询继承而非硬编码 Id), 实测 10 租户 / 23 角色,与入口页严格相等 - 配套 1.0.535.verify.sql 14 条断言,含「入口可见必详情可见」与「详情不得越权」双向校验 保持不变 - IqcInspBillItemPanel.vue 零改动复用:NUMERIC/NON_NUMERIC、不定长 Sample、 AUTO/MANUAL、INVALID、类型切换确认、LEGACY 空态全部保留,未引入 FQC 的 j1..j50 - 后端 S5 业务代码、DTO、Flow、MDP、DB schema 均未改动 例外说明 - api/iqcInspBill.ts 与 iqcInspBillFlow.ts 的 id 类签名由 number 放宽为 number | string: 路由 path param 为字符串,且库中存在 101991258985070726 这类超出 Number.MAX_SAFE_INTEGER 的检验单 id,转 number 会丢精度
  • f2dc6b9ea2 chore(server): close S5 IQC item result API version Batch 3(23cb5126c)落地时 csproj 被其他实例在途占用,未做 server 版本递增。 本次仅补版本闭环:1.0.533 → 1.0.534,不含任何业务代码与 migration。
  • 33765dd817 feat(s8): integrate product design stage KPI 把 1.0.532 建立的 Stage-2 Start / End Authority 沉淀为 dwd_product_design_kpi, 并让订单执行档案的 PRODUCT_DESIGN 第一次显示真实 Start / End / ActualHours。 dwd_order_review_kpi.review_end_time (Start) + mdp_std_action_event (S2_DAILY_PLAN_RELEASE, FIRST SUCCESS)(End) + mdp_std_operation_schedule.status='Y' (历史"已下达"旁证) → dwd_product_design_kpi → S8 订单执行档案 PRODUCT_DESIGN Start 直接消费 Stage-1 已有的 DWD,不重新实现一遍评审配对逻辑。 End 取首个成功释放事件的 action_end_time,后续再次下达不改写历史里程碑, 因此 transform 是确定性且幂等的(连跑三次指纹恒为 2b9057750536c62f0a617a4a14733a17,行数恒为 10)。 END_UNKNOWN 是本表存在的核心理由。1.0.532 之前系统不写下达事件,历史上已下达的 订单只能证明"已经下达"、无法证明"何时下达"。这类订单必须与"确实还没下达"的 IN_PROGRESS 分开,否则会把历史数据缺口误判成业务未完成。判别依据是中台里是否存在 该订单 status='Y' 的日计划行——只取这个布尔事实,绝不取它的时间。 END_UNKNOWN / IN_PROGRESS / NOT_STARTED / INVALID_* 的 end 与 actual_hours 一律 NULL; End <= Start 判 INVALID_TIME_SEQUENCE,不做 abs()/swap() 硬算正数。 订单全集以 mdp_std_so 驱动而非释放事件驱动,否则"尚未开始评审"的订单会从档案里 整个消失。归因全程 MDP ONLY,不 JOIN 任何源业务表。 前端只改语义不扩 UI:沿用既有五阶段矩阵的"实际耗时"行,并为 END_UNKNOWN 单独给 "已完成(历史时间未知)"与"历史时间未知"文案——它没有 end 时间,但绝不是"进行中"。 实测(租户 838257186181189): SO202608240001 08-25 15:05 → 09-11 23:07 416.03h OK SO202609030004 09-03 09:54 → 09-11 23:07 205.22h OK IN_PROGRESS 4 单 / NOT_STARTED 4 单,Stage 3~5 全 --,Stage 1 无回归。 跨租户 order_id 返回 204,伪造 tenantId 无效。 同批修复 1.0.532.verify.sql 的一个会导致全新库启动失败的缺陷:AutoVersionUpdate 按分号切分 verify 并把每一片都当断言,尾部的 informational 查询与纯注释块都会被 切成多余语句而判迁移失败。仅做 parser 安全清理,14 条断言逐字未动、未放宽任何判据; 1.0.532.sql 未改。 已知风险:END_UNKNOWN 分支当前无真实运行态样本(全库不存在"有评审结束 + 已下达 日计划 + 无释放事件"的订单),逻辑由 verify 第 9/10/11 条结构性守住,不伪造历史事件补测。 本批不含 target_hours / PI / 红黄绿 / Stage 3~5 / 子节点;未退役独立评审耗时页。 版本:server 1.0.533 / Web 2.4.396
  • a049b539d5 feat(s5): add IQC inspection detail editing UI 在既有「来料检验单详情」抽屉内新增「检验项目明细」区块,接通 Batch 3 的项目级 读写契约(detail.lines / lineSummary / canEdit + S5IqcInspection/save-detail)。 不新增路由、不新增菜单、不改后端。 - 汇总计数条 + 项目表(序号/检验项目/检验标准/检验方法·工具/限值/录入类型/判定/ 来源/结果·测量/结果备注/操作),展开行内含检规快照与录入区 - NUMERIC 走逐样本实测值(新增/删除/全量替换),NON_NUMERIC 走项目级合格/不合格数, 两者互斥渲染,杜绝双真源入参 - 人工判定仅「自动判定/人工判合格/人工判不合格」,不提供 INVALID; 判定与样本判定一律展示后端结果,前端不复算 - 录入类型切换双向确认,取消即回退到库中现值 - canEdit 为唯一编辑 authority:false 时全区只读、无保存/新增/删除入口 - itemCount=0(历史单/未命中检规/检规无明细)显示 LEGACY 空态说明 - 保存成功后重新 GET detail;失败由响应拦截器原样透出后端消息,不叠加技术文案 - 快照字段空值统一显示 '-',含脏写入的 'null'/'undefined' 字面量 - id 类字段按 number | string 声明(后端 long 序列化为 JSON 字符串),消除 prop 类型校验告警与隐式转换风险
  • 对比 11 次代码提交 »

1 月之前

liuwb 推送了 master 分支的代码到 ZZYDOP/AiDOPWarehouse

  • 69e87d10b4 docs: 将根目录截图归档至文档图片目录

1 月之前