Commit Graph
499 Commits
Author SHA1 Message Date
Hermes CI Fix 75a48f55d2 feat(strategy-map): live board reading map_objectives + kpi_targets (read-only)
Task: finance-20261011-strategy-map-frontend-fullstack (P2, read-only integration)

- backend/app/utils/strategy_board.py: single source of truth for the level
  caliber (layer colors, inverse-kpi explicit whitelist, status class by
  unit, zero guard, period ranking, latest-actual picking). Pure functions.
- backend/app/api/maps.py: GET /api/cma/maps/{map_id}/strategy-board
  - nodes straight from map_objectives (sorted by sort_order, id)
  - node<->kpi binding with 4-step fallback, binding_source echoed back
  - targets from kpi_targets (target_type compatible: yearly + annual),
    deduped per (kpi_id, period) with yearly preferred
  - actuals from kpi_values actual-only scope, latest period
  - causality edges from kpi_causality, out-of-map endpoints dropped+counted
  - entity_id taken from the token only (no cross-tenant reads)
  - zero writes; nothing is persisted by this endpoint
- frontend/src/views/StrategyMap.vue: /strategy-map live board (4 swim lanes
  bottom-up, node cards, 20 causality arrows in SVG, period switch, A3 print).
  No emoji, null guards everywhere (no toFixed on null).
- api/index.ts + router/index.ts + permission.ts: route + menu registration
  (/strategy-map, zone A read-only, roles ceo/finance/business/it).
- backend/tests/test_strategy_board_20261011.py: 33 cases (spec section 4 /
  4.1 / 4.2 / 4.3 + section 7 fixtures + threshold boundaries + period rank).
- backend/tests/test_map_objectives_kpis_form_20261011.py: 14 cases for the
  four kpis column shapes (list, json string, object list, NULL/[]/weird).

Not included: push + backend restart (production write, separate gate).

(cherry picked from commit c675bde6b4beaf1d8a9d02a80f5434203a53f348)
2026-10-18 16:31:52 +08:00
Hermes CI Fix 2cff58ca6a feat(docs-center): report reading section (type=report) on /api/cma/docs
Increment #2 over PoC ede0524 (task yanxue-20260924-cma-report-source).

- backend derives reports from existing drop points (wisdom/_reports + every profile pending-tasks/_reports), read live per request, no cache, no rebuild

- gate = front-matter plus non-empty conclusion; missing either means silently out of the list (receipts/audits never enter); title/date/domain/sources missing are marked incomplete instead of rejected

- entity + role partitioning (ceo/it cross-entity; others own entity plus group/generic); kpi_refs resolve to a one-hop metric link

- new GET /api/cma/docs/report/{rid} detail endpoint; knowledge branch unchanged (type defaults to knowledge)

- frontend: CMAKnowledge adds a report reading pane (filters + detail dialog), reuses the existing markdown renderer, no new deps

(cherry picked from commit 7c6e0857e68133f5ba2a539fd3569aeb82547911)
2026-10-18 16:31:52 +08:00
Hermes CI Fix 57f9dbae15 fix(action-plans): 「已逾期」改日期粒度(口径 B,当天到期不算逾期)
- _apply_overdue():`ActionPlan.due_date < datetime.now()` → `... < 今天0点`
  (唯一实现,plan_stats 与 list_plans 共用,故一处改即两端点同改)
- 口径同步注释(口径 A→B 的沿革与分界式);行为改动仅此一处
- 测试夹具改到日粒度(整天偏移),新增「今日到期不算逾期」鉴别行与
  `test_overdue_caliber_is_date_granularity` 口径锁定用例(防回退 A)
- 依据:任总 2026-09-22 10:10 拍板口径 B;前端角标 ActionPlanLibrary.vue::isOverdue
  与 cash.py::_plan_dict 本就同粒度,本单未动
- 只改码:未 push / 未部署 / 未重启(发布须放行)

(cherry picked from commit 2da5b6da018013f860430913b5b0e0436f966685)
2026-10-18 16:31:52 +08:00
wecom-project 5fa83e2532 fix(knowledge): related_page 过滤精确化(子串包含 -> 归一候选键等值)
KBFF-v11c(派单 KBFF-v11c-relatedpage-exact-match-filter · A 侧 wecom-yanxue · 执行人 wecom-project)
本提交=代码面切片:未 push、未部署;生产部署另走发布单 + approval-gate(回滚 = git revert <本 sha>)。

- backend/app/api/knowledge_articles.py:related_page 过滤由 LIKE '%<v>%'(子串包含,短键/前缀超集命中)
  收敛为「归一候选键 IN ∪ 模板子路由 <键>/:<占位符>」;不可归一入参 -> 空集(不退化为全量)
- backend/app/utils/route_key.py(新增):匹配口径唯一实现处(归一化 / 候选键派生 / 模板模式)
- backend/tests/test_kbff_v11c_related_page_exact_match_20261012.py(新增):42 用例
  —— 生产 seed 7 值逐值回归(各中 1 行)、短键 /maps->0 行 /kpi->0 行、带参路由与尾斜杠/query/大小写兼容、
  改造前 contains 语义负对照、账套分区与 category 组合语义保持

(cherry picked from commit 2a5cb182c237ae724d4b506b68eb48810ba27b16)
2026-10-18 16:31:52 +08:00
fullstackandHermes CI Fix 60b6ef3bfe fix(alert-rules): dynamic-thresholds 迭代 Column 当行取值致 500
GET /api/cma/alert-rules/dynamic-thresholds 在 {c.name: getattr(c, c.name) for c in
DynamicThresholdCache.__table__.columns} 里把 Column 对象当 ORM 行取属性,触发
AttributeError: 'Comparator' object has no attribute 'id'。改为列名取自表元数据、
值取自 query 结果行;entity_id 过滤与 limit 50/kpi_id 分支不变。

(cherry picked from commit 4130726be411a74a3318d9dc6bbc518a6c60d0ff)
2026-10-18 16:31:52 +08:00
wecom-fullstackandHermes CI Fix a9dcbf22bc chore(security): 摘除 knowledge_service 硬编码默认 DeepSeek Key(缺 env 抛明确错误/503)
- knowledge_service: 移除 os.getenv 默认值占位,改 _require_deepseek_key() 必填,缺 env 抛 DeepSeekKeyMissing
- knowledge API: generate/{daily,weekly,monthly} 缺 Key 返回 503(可观测,不再静默走模板兜底)
- finance_bot / verify_zxt_vouchers_json / bot_bridge_push: 凭据改 env 优先(CMA_BOT_KEY_* / CMA_BOT_API_KEYS / CMA_BRIDGE_TOKEN),历史共享键仅作兼容期兜底并告警
- tests: 新增 5 例回归(缺 Key 服务层抛错、API 503;有 Key 正常 200)

(cherry picked from commit 859bd8276d7c5aad4bf10ee3399b6cccc188f064)
2026-10-18 16:31:52 +08:00
wecom-fullstack 885cfddbff feat(warning-p0a): 资金缺口阈值外置 + 复算对拍 + 讲人话(数字守恒) + 数据就绪核验
ci/woodpecker/push/main Pipeline failed
P0-A(任务 fullstack-20261017-warning-capability-build-p0):
- cash_thresholds.py:阈值迁 per-entity(复用 alert_rules.rule_type=cash_gap_threshold,
  默认 20/10 万不动;无配置/非法/异常一律回落默认,fail-safe 不改预警行为)
- cash_forecast_engine.py:阈值改从配置读取,响应新增 threshold_source 明写来源
- verify_cash_gap_replay.py:同输入双跑逐字段 diff=0 + 手算 3/3 + 端点逐字段对拍(--live)
- cash_gap_narrative.py:复用 ai_brief.call_deepseek,只写原因/建议;
  断言生成文本数字集合 ⊆ 规则层数字集合,违例即不带文案上线
- audit_warning_data_readiness.py:G0 只读核验(cma_reader SELECT only),
  行数/期间/枚举 + 数据质量缺口(期初全 0、负期末、单批次、无员工维度表)

未 push(push 即部署,须项目侧书面放行);未改生产库/未动 cron。
2026-10-17 23:33:51 +08:00
Hermes FullstackandHermes CI Fix a52faa90a6 fix(dashboard): bep-card terms 分支补字段别名映射(修 289cc8f 引入的 e2 500)
ci/woodpecker/push/main Pipeline was successful
289cc8f(§4-1 博海固定成本仅借方)在 dashboard._bep_sum_field 新增 terms 分支,
把 bep_caliber 的 terms 记法字段名(debit/credit)原样传给 getattr(VoucherDetail, field)
⇒ AttributeError: type object 'VoucherDetail' has no attribute 'debit'
⇒ GET /api/cma/dashboard/bep-card(entity=2 博海)稳定 500(entity=1 走 filters 分支不受影响)。

修复:terms 分支内做字段名映射 debit→debit_amount / credit→credit_amount(与
profit_lines.FIELD_MAP、kpi_bep_deriver._TERM_FIELD 同式),再递归聚合。

回归测试:tests/test_profit_summary_real_caliber_20261014.py 新增
test_dashboard_card_e2_terms_mode_field_alias(修复前 RED:复现同一 AttributeError;
修复后 GREEN:e2 固定成本 98,000 元 / 卡面 9.80 万)。
2026-10-16 18:28:50 +08:00
wecom-fullstack 68ca27109e feat(report): 管理利润表「变动成本」行加负值标注(脚注+tooltip,纯展示)
ci/woodpecker/push/main Pipeline was successful
依据 e1 变动成本负值口径核查(口径无误,设计使然):变动成本含贷方冲减项(厂补),
个别期间(2026-08 / 2025-07)可为负。本单仅在报表层加说明,防读者误读为取数/口径错误。

- 项目列「变动成本」行加 InfoFilled 悬停 tooltip(按 line_key === 'variable_cost' 精确命中)
- 表格下方加脚注一行
- 未改取数逻辑、未改口径、未改任何数值(diff:1 file, 11 insertions(+), 0 deletions(-))
2026-10-16 18:13:50 +08:00
Hermes CI Fix 289cc8f2cc fix(profit-caliber): 博海口径 §4-1/§4-2/§4-5 落地 —— 固定成本仅借方 + 6403 进变动 + 财务收益单列 + 批次备注
ci/woodpecker/push/main Pipeline was successful
ASK-20261014-04(任总 2026-10-14 11:20「放行4」)批后口径:
- §4-1 博海固定成本弃净额:6601/6602/6603 仅借方(133.98 万,2026-01..09);2026-03 单月由 -9.02 万转正;
  6603 贷方(现金折扣收益 64.40 万)改 finance_income_terms 单列备查,不进固定成本/末行/勾稽。
- §4-2 博海变动成本追加 6403 营业税金及附加(530.17 万)。
- §4-3/§4-4 维持现行(酣客收入/边际贡献基数不动)。
- §4-5 管理利润表页加「数据批次/导入时刻」纯展示备注(import_logs)。
- 口径唯一源仍为 bep_caliber.ENTITY_CALIBERS;报表/卡面/派生器一律经 fixed_cost_spec() 解析(读口写口一致)。
2026-10-16 12:32:52 +08:00
wecom-fullstackandHermes CI Fix ff2d92ba54 ci(woodpecker): 部署通道收口为单链 —— CI 只做门禁,部署恒走宿主 Gitea hook
ci/woodpecker/push/main Pipeline was successful
② 现状:push origin main 同时触发两条写生产链 ——
   (a) 宿主 Gitea post-receive hook → /usr/local/sbin/gitea-cma-deploy-hook.sh → deploy.sh(铺站 + 重启 + 冒烟)
   (b) Woodpecker pipeline 的 frontend-deploy(rsync --delete dist → /var/www/cma + nginx reload)
       与 backend-deploy(在开发区 git pull + systemctl restart cma-backend)
   两条链并发 ⇒ 两套在途部署判定、重复劳动与证据歧义(2026-10-13 事故即此形态)。
本提交复用 ci/wp-v3-fix-20260916 已写就的 .woodpecker/main.yml(wecom-project 2026-09-16 8ddcbe1):
   删除 variables(ssh_setup) 与 frontend-deploy/backend-deploy 两步,只留门禁
   (frontend-install/typecheck/build + backend-test),并补国内镜像与 pytest 安装。
零生产写:候选仅落本 worktree 分支,未 push。
2026-10-16 03:05:57 +08:00
wecom-ops d2cb812bfa chore(deploy): 触发部署链复跑(真修复 · 无内容变更)
ci/woodpecker/push/main Pipeline failed
2026-10-16 00:19:31 +08:00
wecom-fullstackandHermes CI Fix 32f368a846 fix(infra): pnpm 工具链去脆弱化 —— frontend/package.json 落 packageManager pin + deploy.sh pnpm 版本断言(fail-closed)
ci/woodpecker/push/main Pipeline failed
① frontend/package.json 补 "packageManager": "pnpm@10.33.2+sha512.a90faf6f…":
   corepack 在项目未声明该字段时按全局 lastKnownGood.json 解析版本,而该文件会被任何跑过
   corepack 的项目改写(2026-10-13 → 12.11.2 ⇒ MODULE_NOT_FOUND;2026-10-14 → 9.15.9 ⇒
   packages field missing)。pin 落库后 corepack 只认 pin,宿主机状态漂移不再影响部署树。
   选 10.33.2 的依据:pnpm-workspace.yaml 的 allowBuilds 仅 pnpm 10 识别;部署树 node_modules
   .modules.yaml packageManager=pnpm@10.33.2(store/v11/v10);lockfileVersion 9.0;2026-10-13
   22:53 该版本下部署成功。
② deploy.sh 新增 assert_pnpm_version()(fail-closed,两处调用:1.2a 首次 pnpm 调用前、[2] 构建前):
   期望版本只取自 package.json 的 packageManager(单一源),与 pnpm -v 实读逐字比对,缺失/不一致
   即中止;两次调用都先于 [3] 铺站与 [5] 重启 ⇒ 任何生产写之前失败关闭。

零生产写:候选仅落本 worktree 分支,未 push、未动 /srv/hermes/shared/cma-deploy 与 dev 树。
2026-10-15 08:35:20 +08:00
wecom-fullstack e3e6b3ba8a fix(reports): 全表真实取数——清零示例兜底(BS_DEMO/CF_DEMO/_get_demo_block_total/0.50) + 上期同比/勾稽/预测标注 + CF官方口径单位收口
ci/woodpecker/push/main Pipeline failed
2026-10-15 04:13:40 +08:00
hermes 2244cd79b7 fix(reports): profit-summary 真实科目取数重建(去 KPI 比率倒推/硬编码 0.50 + 勾稽自检 + entity 隔离)
ci/woodpecker/push/main Pipeline was canceled
2026-10-14 23:14:41 +08:00
wecom-fullstack 6d8ef0d443 fix(infra): pnpm-workspace.yaml 补 packages 字段(pnpm 9.15.9 强制)+ vue-demi 占位符改 true
ci/woodpecker/push/main Pipeline failed
根因:corepack 修到 pnpm 9.15.9 后,pnpm 见 pnpm-workspace.yaml 即进 workspace 模式,
强制要求 packages 字段;缺失 ⇒ 'packages field missing or empty' ⇒ 部署树 pnpm build 失败。
本地 pnpm build 验证通过(16.05s, exit 0)。
2026-10-14 11:35:05 +08:00
wecom-fullstack e55f0ecdf9 fix(ui): 侧栏「资金内控」图标缺失——MainLayout 补 Lock 到 import 与 iconMap
ci/woodpecker/push/main Pipeline failed
任总 2026-10-14 10:49 报「资金内控:导航栏缺少图标」。
根因:permission.ts:40 icon:"Lock",而 layouts/MainLayout.vue 的
@element-plus/icons-vue import 与 iconMap 均无 Lock ⇒ iconMap["Lock"]=undefined
⇒ <component :is="undefined"> 渲染为空。全量核:菜单 40 项 / 19 个不同 icon 值,
仅 Lock 真缺。本提交仅该 1 文件 2 行(import + iconMap 各补 Lock),
不改其它图标、菜单顺序、分组、角色可见性。
2026-10-14 10:54:11 +08:00
wecom-fullstackandHermes CI Fix 26db15e6d3 fix(ui): 系统级设置归位(KPI对齐/预算模式切换入口收归系统设置)+ 业务页右下浮窗提醒
ci/woodpecker/push/main Pipeline failed
- KPIAlignment.vue: 移除页头切换按钮与模式选择器,去 saveAlignConfig;新增右下浮窗
- BudgetManagement.vue: 预算模式 el-switch -> 只读标签,去 onBudgetModeChange;新增右下浮窗
- RolePermissions.vue: 保留目标对齐正本,新增「预算模式」Tab(系统级切换入口)
- 仅 frontend/ 三件;build 通过
2026-10-13 23:04:07 +08:00
wecom-fullstack 424c33c085 feat(kpi,review): KPI direction 列 + KPITarget 模型 + 复盘产出方入库(R1 收口)
ci/woodpecker/push/main Pipeline failed
R1 收口批次(A 侧裁定 p12-ruling-input-r1-batch.RULING.md 主路径)· 单个快进提交:
- backend/app/models/__init__.py(+42/−2):CMA-KPI-20261003 阶段1 `direction`(KPIDefinition/KPITemplate)
  + ② KPITarget(kpi_targets,11 列 + uk_entity_kpi_period_type + 2 索引)
- 复盘产出方三件(review_builder 含 getattr(direction) 硬化 / review_run / 测试件),原始提交件 26dc242 的重铸件

收口边界申报:本提交**不含** D-1 批次(VoucherDetail.row_uid / ImportLog uq_import_logs_entity_batch_type)——
只读补证显示 prod `cma` 无该列/该唯一键(证据 out/r1-t1-batches.json + out/r1-t1-d1-placement.json),
携行会导致 deploy 后 voucher_details 整实体 SELECT/INSERT 报 1054;D-1 另单(先 DDL 后 ORM)。
2026-10-13 20:33:42 +08:00
Hermes CI Fix 4cdd30e321 feat(lead): 网站线索额外推送任总DM(经 ai-cs 实盘分流脚本)
ci/woodpecker/push/main Pipeline failed
- 新增 notify_boss():POST /api/cma/lead 时,除既有全员群 notify_wecom 外,
  额外经 ai-cs 实盘分流脚本(routing-live.py)通知 wecom:200653946(加性,不改动既有逻辑)
- 跨 profile 调用必须显式传 HOME/HERMES_HOME=/var/lib/wecom-ai-cs/profile:
  cma-backend.service Environment 为空,缺省会解析到非 ai-cs profile(实测无 wecom 目标)
- 失败静默(capture_output + except pass),不影响主流程
任务: ai-cs-20261013-website-lead-to-boss-and-routing-hook-ops
2026-10-13 17:48:00 +08:00
Hermes CI Fix 501322a8b9 feat(maps): 战略地图按 objective 粒度合并(granular 通道),不同 objective 并发不再互相 409
ci/woodpecker/push/main Pipeline failed
2026-10-13 11:37:24 +08:00
wecom-fullstackandwecom-yanxue f786ff9ae3 test(docs-center): 金丝雀分类断言改「必备分类在场+相对顺序」(分类扩展不再留假红)
ci/woodpecker/push/main Pipeline failed
- test_prod_content_readable 原硬编码 [term,formula,practice,faq],与生产 categories.json 全等比较;
  2026-10-12 14:05 内容目录新增 manual(模块手册,order 5) 后期望值失鲜 ⇒ 基线/改后两棵树同红。
- 改为子集式断言(必备分类在场 + 过滤后相对顺序),不再硬编码全集;分类扩展不再红。
- 同族断言盘点:本文件其余分类断言走 tmp content_dir 自持 categories.json,不依赖生产内容目录,无需改。
2026-10-13 07:48:18 +08:00
Hermes CI Fix cfd6a13ca4 fix(budget): 年度行(budget_month=0)不回落 1 月 + 同族 R1/R2/R3
ci/woodpecker/push/main Pipeline failed
- R-A2 sync-cash-plans: 年度行形态判定(_is_annual_budget_row)跳过 + skipped_annual 披露
- R2 POST /plans 期间形态校验(YYYY-MM / YYYY-00 显式接受)
- R3 budget_roll_forward 排除年度行
- R1 cash_classify 归类路径年度行判据 + 披露
- 新增 2 个回归件(14+10 例)

仅含本族 hunk(禁整文件提交): 开发区其余 14 hunk 属他会话在途件,未纳入。
2026-10-13 03:41:39 +08:00
Hermes CI Fix d08a8490eb fix(maps): 覆写通道扩面清点——同契约调用点补基准(3 例静默降级 + 2 例新增失败)
ci/woodpecker/push/main Pipeline failed
缺陷 cma-strategy-map-autosave-stale-overwrite 的护栏(PUT /api/cma/maps/{id} 覆写
dimensions/canvas_data 须带 base_updated_at,否则 409 fail-closed)改变了同一写通道的
契约 ⇒ 全仓清点调用点,在调用点取基准(写前读 updated_at),**不放宽护栏**:

- tests/test_map_versions.py: 新增 _base_updated_at(db, map_id) 助手;3 处覆写 dimensions 的
  PUT 补基准(test_rollback_backs_up_current_and_restores_content 会直接失败;
  test_rollback_again_uses_next_free_backup_version 两处此前已静默降级——PUT 被拒但用例仍绿,
  即「改了内容再回滚」的前提实际未发生)。
- tests/test_kr_name_pollution_20260916.py: 2 处 KR 透传护栏用例补基准。

改前/改后全量对拍(同树 pythonpath):
  baseline 22 failed / fixed2 18 failed;diff 仅剩 tests/test_doc_center_20260924.py::
  TestProdContentCanary::test_prod_content_readable —— 基线树同样失败(/srv/hermes/shared/
  cma-content 于 14:05 被写入 manual 分类,共享内容目录变化),与本单无关。
  本单新增的 5 例在基线树全红、改后全绿。

任务: yanxue-20261012-cma-map-autosave-stale-overwrite-defect-fullstack
2026-10-12 17:06:07 +08:00
wecom-fullstack 03254bd4c7 fix(maps): 战略地图写通道乐观并发 + 画布自动保存仅脏时写
生产缺陷 cma-strategy-map-autosave-stale-overwrite(D4 批次 19 行 map_objectives 被过期快照覆写):
- 后端 update_map: 覆写 dimensions/canvas_data 须带 base_updated_at,与服务端 updated_at 不符 → 409;
  校验在任何写入之前完成(校验不过时一行都不删/不改)
- 连线写通道(POST/DELETE connections)响应补 updated_at,供前端采纳新基准、避免自伤 409
- 前端 MapCanvas: 自动保存仅在 draft + isDirty 时触发;请求体带 base_updated_at;收到 409 停自动保存不重试
- 修复 async onMounted 竞态:await 期间被卸载后其续段仍注册定时器(清理钩子先跑完、无人回收)
  → 泄漏写者永久在后台用过期快照覆写(生产 referer=/maps 仍每 30–60s 写)。加 disposed 守卫 + stopAutoSave
- 测试: backend/tests/test_map_concurrency_20261012.py(8 例,改前 5 败);前端行为台架 11/11(改前 3/11)

任务: yanxue-20261012-cma-map-autosave-stale-overwrite-defect-fullstack
2026-10-12 11:42:56 +08:00
Hermes CI Fix 1613f7f961 fix(cpa): sales bill-type whitelist for customer revenue caliber (P0)
ci/woodpecker/push/main Pipeline failed
Customer revenue/concentration now filters bill_type IN (1,4,14) (XSD/XSTHD/XSHHD).

Non-sales docs (JHD purchase, YFKD prepay, YSKD accept, QTRK, SKD, YBFYD) carry SUPPLIER unit_id and were polluting 'customer concentration' (old Top1/Top2 were our biggest suppliers; root cause of 6.04x base gap).

Unify sign filter to (sum_money+sum_tax_money)>0 (equivalent to money_only on current data, diff 0 rows) and correct the withdrawn 407-row/17.2% wording in cpa_caliber docstring.

Deriver: anchors from cpa_caliber single source; new --concentration overwrite and --only concentration (minimal 9-row write face) with pre-overwrite backup.

Tests: fixture gains bill_type/sum_tax_money; +5 whitelist cases (44 passed targeted).
2026-10-09 17:00:37 +08:00
Hermes CI Fix 451c994a86 feat(cpa): customer profitability MVP P0-a/b - revenue caliber single source + snapshot table + 9-card tri-state
Task: finance-20261003-cpa-customer-profitability-mvp-fullstack (P0-a / P0-b)

- backend/app/utils/cpa_caliber.py (new): single source of truth for
  customer-level revenue aggregation from zxt_masterbill. Reused by
  alert_rules._customer_revenue_shares (dedupe: no second implementation).
- backend/scripts/migrate_cpa_snapshot_20261004.py (new): idempotent DDL for
  customer_profitability_snapshot (UNIQUE entity_id+period+unit_id),
  --check / --apply / --rollback / --status / --show-create + journal.
- backend/scripts/kpi_customer_profitability_deriver.py (new): monthly
  snapshot + kpi_values upsert (single transaction, idempotent).
  fail-closed: cogs_alloc / chan_sub / contrib / contrib_rate stay NULL and
  are never written as 0 or as an apportioned value.
- backend/scripts/sql/cpa_kpi_defs_20261004.sql (new): C_CUST_REVENUE and
  C_CUST_COUNT active; 5 P1 codes registered inactive with fail-closed reason.
- backend/app/api/customer_dashboard.py: new read-only endpoint
  GET /api/cma/customer-dashboard/standard-indicators returning the 9-card
  tri-state (value / no_data / undefined). Reads kpi_definitions via raw SQL
  to stay decoupled from the in-flight KPI direction DDL.
- frontend/src/views/CustomerDashboard.vue: 9-card panel rendering the three
  states (never renders an empty value as 0).

Anchor check 2026-01..09 (offline sqlite rehearsal, ALL_PASS=true):
73 customers / 4883.52 wan / top1 25.3835 / top2 23.4905 / top4 63.3249 /
top10 81.7675 - all six match the task acceptance anchors.

No production write and no deploy in this commit; release is gated.
2026-10-09 17:00:37 +08:00
wecom-fullstack a48a116e06 fix(dupont): insight.detail 叙述段字面 None 收口(表现层 _fmt_val,仅文案)
ci/woodpecker/push/main Pipeline failed
- 恒等式叙述段 5 处裸 f-string 插值(三因子 value / ROE / identity.diff)改走 _fmt_val:
  None → 「-」(与同段四列 _fmt 同族),有值原样输出(不经 :.2f,保证有值路径逐字节不变)
- 结构化字段 roe / factors[*].value / identity.diff 仍为 null;check_identity 语义、dupont_basis.py 未改
- 基线 origin/main acf179a;本单零生产写(不推远端/不同步部署树/不重启/不连库)
2026-10-06 23:36:06 +08:00
Hermes CI Fix acf179a199 fix(subjects): migrate 脚本口径乙 fail-closed(排除白名单逐码 + 双轨锚点)
ci/woodpecker/push/main Pipeline failed
2026-10-06 17:03:44 +08:00
wecom-fullstack b58513c48d docs(ddl): runbook 首步强制「迁移前像 dump 哈希」(--apply 之前)
ci/woodpecker/push/main Pipeline was successful
CMA-DDL-RUNBOOK-20261005 / parent: CMA-KPI-20261003-D-T1
- docs/db-migration-runbook.md 新增 §3.0:任何 --apply 之前对每张受影响表落
  全表有序 (PK, 受影响列) dump,记录 sha256 + 行数 + 字节数,归档 backups/pre-ddl-<task>/
- 含可直接复制的一行命令(ORDER BY 主键保证字节级确定性)
- §4 发布 checklist 第 4 条(列级 ALTER)指向 §3.0

仅文档;零生产库/零部署/零重启。
2026-10-05 13:02:02 +08:00
wecom-fullstack 4f9bab6bc0 fix(dupont): reject out-of-domain window + bidirectional clip + suffix discipline (R1-R4/M2)
ci/woodpecker/push/main Pipeline failed
Per finance ruling 2026-10-05 (project-20261005-dupont-oob-window-fix):

- R1: no intersection with available month domain => explicit error (endpoint 400), no window fields

- R2: with intersection => intersection window, months>=1 (kills inversion and months<=0)

- R3: quarter/half/... suffix kept only when returned window == nominal window (clipped flag)

- R4: symmetric lower-bound clip note; M2: TTM nominal window = [anchor-11, anchor] (12 months)

- M1: docstring correction (lower-bound overflow is clipped, not 400)

No push / no deploy / no restart / no DB write.
2026-10-05 02:02:21 +08:00
Hermes CI Fix 3ed9fa0611 fix(cvp): O1 residue - e2 rebate_policy_pct 置 null(口径单一源 = bep_caliber)
ci/woodpecker/push/main Pipeline failed
问题:/bep-card 端点对 e2(博海)回落模块内硬编码常量 BEP_REBATE_POLICY_PCT=84.61,
而 bep_caliber.ENTITY_CALIBERS[2]["policy_pct"] 为 None(博海无渠补科目)⇒ 下游 API
消费者会把 payload 里的 rebate_policy_pct=84.61 误读为「博海有 84.61% 政策渠补率」。

改动(严格限定 dashboard.py 一个模块 + 1 条守护用例):
- 删除模块级常量 BEP_REBATE_POLICY_PCT;四处回落分支改为直接取口径值
  (未注册口径分支置 None;其余取 cal.get("policy_pct") / 已算出的 policy_pct)。
- 口径单一事实源维持 app/utils/bep_caliber.py,端点不做任何回落。
- 新增 tests/test_bep_rebate_actual_caliber.py::test_o1_policy_pct_single_source_no_hardcoded_fallback
  (改前必红 / 改后必绿,e1=84.61 作同端点负对照)。

改前/改后(端点函数直调,生产库只读,e1/e2 同批):
  改前 e2 inputs.rebate_policy_pct = 84.61 → 改后 null;e1 恒为 84.61(负对照不变)。
2026-10-04 22:37:20 +08:00
wecom-fullstackandHermes CI Fix 587d5b3983 fix(cvp): BEP ⑤ 口径统一落地(变动成本率=完整口径)
ci/woodpecker/push/main Pipeline failed
- 派生器/口径:F_BEP_VC_RATE 新增;卡面/公式行文案改「月固定成本 ÷ 边际贡献率」
- 保本额 = 月固定成本 ÷ (1 − 变动成本率);实测渠补率(F_BEP_REBATE)降为 e1 专用 chip
- 报告卡口径说明文字同步(固定成本=固定费用合计)

任务: finance-20261003-cvp-fix-and-bohai-coverage-fullstack ⑤
2026-10-04 19:53:12 +08:00
Hermes CI Fix 4fe956fcf9 fix(dupont): resolve YYYY-Qn / YYYY-Hn period windows (500 on quarter/half)
ci/woodpecker/push/main Pipeline failed
Root cause: resolve_window() matched quarter/half with p[4] == "q" / "h"
(always '-' for "2026-q2"/"2026-h1"), and the branches sat after the
single-month branch -> dead code. Real input fell into the month branch,
so months_between() raised ValueError on int('q2') -> HTTP 500 for all
18 quarter/half options in the period selector.

Fix: judge on p[5] in ("q","h") with a digit guard on p[6], evaluated
before the single-month branch; month branch tightened to p[5:].isdigit().
Existing error-branch wording unchanged.

Tests: backend/tests/test_dupont_period_options.py (18 defect options,
case equivalence, month/year/ytd/ttm/custom non-regression, malformed
input returns error dict instead of raising).
2026-10-04 18:34:05 +08:00
Hermes CI Fix 40d2ffe0f7 fix(dupont): None guard for raw formatting in /reports/dupont insight detail
ci/woodpecker/push/main Pipeline failed
Non-ending stock windows (e.g. ?period=2026-Q2 / 2026-05 / custom) return raw total_assets/equity as None when the window has no subject_balances level-1 rows; identity guard reports status 'unavailable' (not 'identity_broken') and execution continues into the insight detail f-string, where .2f on None raised TypeError -> HTTP 500. Add explicit fmt() ('-' for None) for the four raw columns in detail.

Task: fullstack-20261004-1544 (P1; proposer wecom-fullstack; ASK-20261004-07 follow-up). No DDL. Single file.
2026-10-04 18:03:34 +08:00
Hermes CI Fix 38a6de9325 feat(dupont): period-basis design (cumulative flow x average stock, optional annualize, identity guard)
ci/woodpecker/push/main Pipeline failed
Task: finance-20261003-dupont-period-basis-design-fullstack (P1; proposer wecom-finance)
Basis ruling: finance 2026-10-03 (Boss 09:49 CST DM approval of the design task)

- /reports/dupont accepts period/start/end/annualize (default YTD), returns
  period_start/period_end/basis/annualized metadata; identity guard
  |ROE - npm x turnover x multiplier| <= 0.05 pp, else identity_broken (no values).
- new app/utils/dupont_basis.py = single source of truth (window resolve, flow
  cumulative via voucher_details, stock average via subject_balances level=1,
  fail-closed basis_fallback, pure calc + identity check).
- scripts/kpi_dupont_deriver.py (idempotent derived writer, dry-run/check/write),
  scripts/apply_dupont_kpi_defs.py + sql/dupont_kpi_defs_20261003.sql (seed:
  F_ASSET_TOTAL/F_EQUITY_TOTAL/F_EQUITY_MULTIPLIER as derived; F_ASSET_TURNOVER
  manual -> derived). Seed already applied under approval 296cc1fd.
- scripts/verify_dupont_period_basis.py: read-only acceptance battery A1-A9
  (independent SQL recompute). BEFORE (base tree) 1/10 PASS -> AFTER 10/13 PASS;
  remaining 3 need the deriver value write (approval-gated).
- frontend DupontAnalysis.vue: period selector (YTD default) + annualize switch
  + caliber metadata bar + identity_broken alert.

No DDL. No ERP direct connection (old register_dupont_kpis.py caliber dropped).
2026-10-04 13:35:25 +08:00
Hermes CI Fix 1f1385ebbf feat(cvp): CM≤0 fail-closed 禁写0.0 + 博海BEP扩面(新口径) + 影子CVP标注is_mock + 前端NULL守卫/不适用
ci/woodpecker/push/main Pipeline failed
- kpi_bep_deriver.py: 实体口径注册表(app/utils/bep_caliber.py 单一事实源);博海(6001/6401/6601-03/1602);
  fail-closed② 边际贡献非正/固定成本退化 ⇒ 五条保本值 NULL+pending+remark'不适用'(禁回落0.0)
- dashboard.py /bep-card: 多实体口径 + not_applicable 态返回 label/reason
- predict.py /cvp-detailed: 显式 source=scenario_simulation + is_mock=true(影子CVP标注)
- 前端 BEPBreakEvenCard.vue: 全量数值判空 + '不适用'态;PredictDashboard.vue: 情景模拟·非实测标签

验收 accept-bep-cvp-20261003.py: BEFORE 2/11 → AFTER 11/11 PASS
任务 finance-20261003-cvp-fix-and-bohai-coverage-fullstack
2026-10-03 16:03:12 +08:00
Hermes CI Fix 8631ce718f feat(profitability): real ROI (op profit / invested capital), enable EVA, add RI
ci/woodpecker/push/main Pipeline failed
Kaliber: wecom-finance 2026-10-02 ruling (Boss 11:59 CST "li; but (1) add real ROI").

- kpi_definitions seed scripts/sql/profitability_kpi_defs_20261003.sql:
  F_ROI renamed to F_ROA (same kpi_id=20, all 8 historical values kept), new F_ROI
  (real IMA caliber = operating profit / invested capital), F_EVA activated with unit
  and thresholds aligned to 万元, new F_RI (residual income).
- parameters are NOT hardcoded in formula text: they live in
  kpi_definitions.data_source_config.params (tax_rate / wacc / min_return_rate /
  include_factory_subsidy / include_channel_subsidy / include_sales_discount /
  annualize / capital_basis); single reader = app/utils/profitability_params.py.
- deriver scripts/kpi_profitability_deriver.py: YTD operating profit from
  voucher_details (carry_forward=0 net), invested capital from subject_balances
  level-1 rows only, idempotent upsert into kpi_values (source_type=derived, no
  data_status downgrade, fail-closed when invested capital is 0).
- read-only endpoint GET /reports/value-management reads the derived rows only
  (single source of truth, no recomputation) and exposes the parameter block.
- DupontAnalysis.vue: value management section (ROI / ROA / ROE / EVA / RI).
- caliber_units.py: RATIO_CALIBER_TABLE key F_ROI renamed to F_ROA, new F_ROI row.
- tests: backend/tests/test_profitability_indicators.py (21 cases).
2026-10-03 02:10:47 +08:00
Hermes CI Fix 1da0a74568 fix(h2-forecast): 预估区残余读路径 P2 收口(3 类 6 处)
任总 2026-10-02 11:35 直令「对外 /api/cma/** 读 KPIValue 默认只取实测区」同族残余
(ac4bbc6 范围外的 3 类 6 处)。6 处一律引 app.utils.kpi_value_scope.actual_only_clause(),
零新写字面量口径:
- customer_dashboard.py:76    年视图汇总(预估禁与实测相加)
- budget.py:260               按去年实绩加权派生基数(批量 auto-decompose)
- budget.py:333               同上(单 KPI auto-decompose)
- cash_forecast_engine.py:204 get_entity_kpi_history 历史/趋势序列
- cash_forecast_engine.py:259 get_current_cash_balance 最新值
- cash_forecast_engine.py:471 calculate_accuracy 实际值序列(预测输入)

证据:同脚本双树 A/B 15/15(改前 5/15,真库 kpi-forecast history 去掉 2026-H2、
next_target 336.32→250.99);分类计数 33/41/9/3 → 39 covered/38 period_guarded/9 write/0 unguarded;
pytest 3 failed/1529 passed,失败集与基线逐条同 ID。不 push / 不部署 / 不重启 / 不写库。
Task: project-20261002-h2-forecast-residual-reads-p2-fullstack
2026-10-02 23:41:17 +08:00
Hermes CI Fix 5d7b3cc728 feat(h2-forecast): 实测区/预估区口径分离 —— 对外读路径默认只取实测区
ci/woodpecker/push/main Pipeline failed
- 新增 app/utils/kpi_value_scope.py(口径单一事实源:actual_only_clause / forecast_only_clause / is_forecast_row)
- dashboard.py: /kpis /my-kpis /finance-analysis /predict /my-dashboard / latest_kpi_value 默认剔预估,include_forecast=true 单列返回
- alert_rules.py: 动态阈值白名单去 estimated + 4 处读路径加实测区判据
- 前端 ForecastTag.vue 新增;Dashboard.vue / MyDashboard.vue 预估单列 + 标签(不与实测相加)

Task: finance-20261002-h2-forecast-separation-deploy-fullstack
2026-10-02 23:33:15 +08:00
wecom-fullstack f32f6e7430 feat(receivables): 催收页只认客户级行 — 月度汇总行不计入卡片/账龄/明细/owners
- 口径实现逐字节取自既有已批准件 475f78b(ASK-20261001-33, wt/hanke-ar-customer-level):
  _is_customer_level_row(plan) = counterparty 非空且非空白(.strip() 覆盖制表符/全角空格)
  用在两处且仅两处:明细循环 + owners 查询 ⇒ 四条消费面同一判据
- 数据零变更:cash_plans 一行不删不改(source='zxt-sync' 的 67 行月度汇总行=正确通道数据,保留)
- 不新写第二份实现;不改 frontend/src/api/index.ts 全局拦截器;无 DDL、无数据迁移
2026-10-02 21:15:42 +08:00
Hermes CI Fix 29dc5c0bdc fix(receivables): unwrap response envelope once + align card/column field names
- Receivables.vue: const d = r (was r.data || r) -> cards/table no longer blank

- cards: pending_balance(待收余额) / overdue / completed_amount / completed_count

- columns: counterparty(+description fallback) / owner(未分配) / settled_amount / plan_date / aging_bucket

- cash.py: summary.completed_count + _plan_dict.settled_amount (existing keys untouched)

- api/index.ts untouched (global interceptor)

(cherry picked from commit d60557622441b971970fb9dc86ef7491c3a9d72b)
2026-10-02 21:12:20 +08:00
Hermes CI Fix ac4bbc6891 fix(h2-forecast): 对外 KPIValue 残余读路径统一实测区口径(7 处)
任总 2026-10-02 11:35 直令「对外 /api/cma/** 读 KPIValue 默认只取实测区;
预估禁进趋势序列 / 禁作预警与预算依据」的同族残余读路径收口。

7 处逐处改(均引 app/utils/kpi_value_scope.actual_only_clause,不新写字面量口径):
- kpis.py:604            GET /kpis/{id} 详情历史值(预估禁进趋势序列)
- ai_analysis.py:363     GET /ai/kpi-analysis/{id} 趋势序列/LLM 上下文
- thresholds.py:132      GET /thresholds/suggest/{id} 阈值推荐依据
- budget_generate.py:94  GET /budget/kpi-budget-candidates 预算候选最新值
- budget.py:1380         POST /budget/apply-method 派生基数(预算依据)
- maps.py:657            GET /maps/{id}/review 地图节点最新值
- data_quality.py:363    GET /data-quality/stats 缺失判定(仅预估行不算有实际值)

证据:同脚本双树 A/B 11/11(改前 3/11 FAIL)+ pytest 3 failed/1529 passed 与基线逐条同 ID。
不 push / 不部署 / 不重启 / 不写库。
2026-10-02 21:01:25 +08:00
Hermes CI Fix 3521cce01e fix(bep-caliber): seed target_value NULL -> 0.00 (kpi_definitions NOT NULL, 1048)
ci/woodpecker/push/main Pipeline failed
首发串 [4/8] 以 pymysql 1048 失败: kpi_definitions.target_value 为 NOT NULL(decimal(15,2) default 0.00),种子显式写 NULL 覆盖默认值。

本提交只改 backend/scripts/sql/bep_kpi_defs_rebate_20261002.sql 一行。
2026-10-02 15:55:30 +08:00
Hermes CI Fix 14ed29bcbf fix(bep-caliber): rebate rate uses YTD actual voucher cumulative (571103 debit / 5001 credit), book+cash card read derived, fail-closed
ci/woodpecker/push/main Pipeline was canceled
2026-10-02 12:20:39 +08:00
Hermes CI Fix 6be903fbc9 fix(bep-caliber): expense and profit sums use net (debit minus credit) under carry_forward=0
ci/woodpecker/push/main Pipeline failed
ASK-20261002-06 fix04u: admin(5602%) and depreciation(560213) take net amount in both the dashboard card helper and the BEP deriver; sales (5001) keeps credit_amount. Verbatim per proposal section 2. No DB writes, no kpi_values backfill.
2026-10-02 11:22:38 +08:00
Hermes CI Fix 6c1ea85d71 fix(alignment): /tree map_id filter via map_objectives.kpis union (v2)
ci/woodpecker/push/main Pipeline failed
- add missing 'or_' import (NameError on map_id calls)

- union of map_objectives.kpis codes and KPIDefinition.map_id, intersect with entity active KPIs

- cross-entity / unknown map_id -> empty tree (no leak)

- no map_id -> unchanged behaviour
2026-10-01 21:12:02 +08:00
wecom-fullstack 22ec40052d fix(alignment): /tree accept and apply map_id filter for KPI selection
GET /api/cma/alignment/tree ignored the map_id query param (FastAPI silently dropped it) so the board always rendered the full active-KPI set of the account. Add map_id: Optional[int] = Query(None) and filter KPIDefinition.map_id when provided; no param keeps the previous behaviour. Entity scoping unchanged (entity_id from token) - cross-account map_id yields an empty tree, no other-account KPI is returned. Only the KPI selection set is filtered; vertical/horizontal/bsc_chain builders untouched.
2026-10-01 15:58:40 +08:00
wecom-fullstack 0824efc8ed feat(data-quality): 规则配置可编辑入口 + 经营口径排除接线(ASK-20260928-05 尾项)
ci/woodpecker/push/main Pipeline failed
依据:任总 2026-09-29 08:49「P1和P2批」放行 P1-cma-dq-rule-config-approval-20260923 ①③
     (派单件 0-P1-project-20260929-qrc-write-entry-and-demo-exclusion-fullstack)

三处尾项(本单):

1) 前端可编辑入口(补拍板件 ① 未被上轮覆盖的部分)
   - frontend/src/views/DataQuality.vue:规则配置 Tab 由「只读」改为
     ceo/it 可编辑、finance/business 只读(角色口径与 router/index.ts:45 及
     系统设置页一致);新增「操作/编辑」列(平台级项仅 it 可点、演示账套禁用);
     编辑走 MyDialog(业务弹窗铁律),必填变更理由(>=10 字),取值区间/枚举
     由后端返回字段呈现,越界/非法一律如实回显后端 detail,前端不自造校验通过。

2) scope.excluded_entities 接线 + 出厂默认 [3,4,5,6](拍板件 ③-2「排除」)
   - app/dq_rule_config.py:默认由 [] 改为 [3,4,5,6];新增单一事实源
     EXCLUDED_ENTITIES_KEY / EXCLUDED_ENTITIES_FIELD、normalize_entity_ids()、
     excluded_entities_from_resolution()、excluded_entity_ids()、
     is_excluded_from_business_caliber()。
   - app/api/data_quality.py:新增 _excluded_clause()(仅全账套口径 eid=0 生效,
     COALESCE(col,1) NOT IN (...) 参数化,NULL 归属不吞行)与 _business_excluded();
     七条规则的明细查询与 _detail_count 全部叠加排除子句;_plan_id_set /
     _pending_alert_plan_links 支持 excluded(播报口径同源)。
   - 响应新增 business_caliber{caliber,exclusion_applied,excluded_entities,included}
     与 scope_excluded_entities;单账套查询保持展示、仅标注不计入经营口径。
   - PUT /rule-config:平台级项改写全账套模板行(entity_id=0),审计 detail 与响应
     增 target_entity_id;GET 增 scope_note 与 write_contract{roles,platform_roles,
     min_reason_len,entity_config_entities,bot_channel_write,endpoint}。

3) 修复两处实现缺陷(本单测试新增用例的 RED 驱动)
   - excluded_entities_from_resolution() 取键与 resolve()["scope"] 的归组形态不一致
     (配了 scope. 全名去取已归组后的短名字典)⇒ 恒返回空集,接线静默失效;
     改为短名优先、全名兼容(EXCLUDED_ENTITIES_FIELD)。
   - upsert_param() 全局项 before 取值多一层 rule_key 查找 ⇒ before 恒 None;
     改为全局项直接以 param_key 为键。

4) 测试:tests/test_dq_rule_config_20260928.py 3 处既有断言按新口径更新 +
   新增 E 类 8 例(排除默认/单一事实源、全账套与单账套两种口径面、A/B 聚合差异
   只来自被排除账套、明细计数同源、平台级项改名单即时生效、平台级写落模板行)。

不改:判定表达式本体 / 级别归属 / 评分函数 / 规则集(DAMA §4 锁死 10 类)、
      unit_check.amount_threshold(保持 10000)、virtual_pollution 关键词表与来源白名单。

边界:本提交只含 4 文件(backend/app/dq_rule_config.py、
      backend/app/api/data_quality.py、backend/tests/test_dq_rule_config_20260928.py、
      frontend/src/views/DataQuality.vue);未裹挟他人在制件
      (audit_log.py / migrate_subjects_entity_id_20260923.py / package.json)。

证据:pending-tasks/_evidence/ask5-tail-20260929/
      (pytest 全量 1 failed/1516 passed + 失败项归因 A/B、vue-tsc rc=0、
       ab-exclusion-pre-write.json 只读 A/B 原始输出、gate dry-run 等)
2026-09-29 13:00:48 +08:00
wecom-fullstack b8493f8d04 feat(db): 新表授权面收敛机制落地(白名单驱动 · 增量锚定 · fail-closed · 只增不减)
依据:A 侧(wecom-yanxue)裁定 编号 2「准予但需调整」+ R1(裁定件
yanxue-20260928-newtable-grant-surface-impl-plan-ruling.md,md5 272d1a8b4db0db0e9f35f5e62695ae50)
三条开口 (a) 空锚=合法零集 / (b) --anchor-file 载体等价 / (c) drill WARN 不阻断(两条独立分支)

改动面(3 件,全部开发工作树):
- 新增 backend/scripts/grant_surface_sync.py:项目侧原型逐字复制 + Δ1/Δ2/Δ3/Δ4 + R1
  Δ1 锚语义(未给=缺锚 rc=2 / 空集=合法零集)、Δ2 连接身份与 db_migrate 同口径并拒绝 cma_api、
  Δ3 --anchor-file(读 db-migrate-journal 指针件)、Δ4 anchor_source 入 JSON
  R1-a 同源自证(journal 在册 + status=applied + finished_at 一致 + 目录内最新 -applied)
  R1-b 未收敛锚累积由 db_migrate.write_anchor_pointer 实现、R1-c 收敛回写 converged_at
- db_migrate.py 加性 +2 处:新增 write_anchor_pointer()(原子写 journal/last-anchor.json);
  cmd_apply 成功/失败两路径各调一次;零变更面未动(列级差异只告警 / create_all 幂等 /
  --check 不写指针 / --rollback 与 resolve_journal 语义 / JSON 键集)
- deploy.sh 加性 1 步 [4b-2/5] 授权面收敛(插在「✓ 结构迁移完成」与「# 5. 重启后端服务」之间):
  apply 跑 --apply --anchor-file ... --drift-policy warn,rc!=0 ⇒ 打印缺裁定清单 + exit 1
  (后端不重启、旧码继续服务);drill 跑 --check,读数原样打印、WARN 不阻断

证据(全部零生产面):
- py_compile x3 通过;bash -n deploy.sh 通过(PYTHONPYCACHEPREFIX 隔离,仓内零新增 pyc)
- 纯函数用例 11/11(零连库):语句均为 GRANT / 无 REVOKE / 语句表 ⊆ write_tables / 域外权限 SystemExit
- 沙箱 S1–S9 回归 9/9 一致(SUMMARY.txt 与项目侧基线逐字节相同 md5 1a697e002a2cba8ae9b5ad9cc71b022d)
- 锚/R1 新例 18/18:缺锚/缺件/status=failed/R1-a 四负例均 rc=2 零写入;空锚 rc=0 executed=[]
- R1 验收链:发布1 建表 X(∉白名单) ⇒ rc=3 零写入 + 片段 exit 1 ⇒ 不修白名单重跑 ⇒ 仍 rc=3 ⇒
  补白名单 ⇒ rc=0 + X 获授权 + converged_at 落定 ⇒ 三次重跑 executed=[](幂等)
- 既有 backend/tests:1509 passed / 0 failed,改前(HEAD 导出树)与改后失败集零差异

边界:本地 commit,零 push / 零部署 / 零重启 / 零连生产库 / 零 DDL-DML-GRANT-REVOKE;
上线(push + 部署 ⇒ 授权面生效)另单另铸 approval ID 且必附 R2 预演 diff(本单不为任何动作铸 ID)
2026-09-28 17:01:39 +08:00