@whyshu/argent-engine 0.0.0-stage → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/MANIFEST.json +307 -0
  2. package/engine/VERSION +1 -0
  3. package/engine/agents/catalog.json +454 -0
  4. package/engine/configs/_aliases.proposed.json +651 -0
  5. package/engine/configs/_migration-plan.proposed.json +899 -0
  6. package/engine/configs/account.json +272 -0
  7. package/engine/configs/advanced.json +205 -0
  8. package/engine/configs/host.json +149 -0
  9. package/engine/configs/hr.json +27 -0
  10. package/engine/configs/notify.json +264 -0
  11. package/engine/core/cli/argent.mjs +277 -0
  12. package/engine/core/contracts/argent.contracts.json +1065 -0
  13. package/engine/core/contracts/verbs.schema.json +2539 -0
  14. package/engine/core/lib/argent-datadir.mjs +223 -0
  15. package/engine/core/lib/audit.mjs +187 -0
  16. package/engine/core/lib/claim.mjs +258 -0
  17. package/engine/core/lib/config-catalog.mjs +1172 -0
  18. package/engine/core/lib/doctor-customer.mjs +577 -0
  19. package/engine/core/lib/flow-list.mjs +138 -0
  20. package/engine/core/lib/flow-registry.mjs +48 -0
  21. package/engine/core/lib/init.mjs +247 -0
  22. package/engine/core/lib/ledger.mjs +124 -0
  23. package/engine/core/lib/license-activate.mjs +813 -0
  24. package/engine/core/lib/license-device.mjs +314 -0
  25. package/engine/core/lib/license-file.mjs +349 -0
  26. package/engine/core/lib/license-guard.mjs +123 -0
  27. package/engine/core/lib/license-ops.mjs +1645 -0
  28. package/engine/core/lib/license-verdict.mjs +133 -0
  29. package/engine/core/lib/license.mjs +577 -0
  30. package/engine/core/lib/notify/dedup.mjs +101 -0
  31. package/engine/core/lib/notify/feishu.mjs +38 -0
  32. package/engine/core/lib/notify/index.mjs +95 -0
  33. package/engine/core/lib/notify/verb.mjs +327 -0
  34. package/engine/core/lib/notify/webhook.mjs +135 -0
  35. package/engine/core/lib/notify/wecom.mjs +36 -0
  36. package/engine/core/lib/python.mjs +145 -0
  37. package/engine/core/lib/sop.mjs +116 -0
  38. package/engine/core/lib/versions.mjs +205 -0
  39. package/engine/core/mcp/client.mjs +67 -0
  40. package/engine/core/mcp/http.mjs +138 -0
  41. package/engine/core/mcp/server.mjs +702 -0
  42. package/engine/scripts/_engine_rules_probe.py +15 -0
  43. package/engine/scripts/analyze-full-day.py +568 -0
  44. package/engine/scripts/archive/feishu-bridge.js +246 -0
  45. package/engine/scripts/argent-agents.mjs +527 -0
  46. package/engine/scripts/argent-capabilities.mjs +776 -0
  47. package/engine/scripts/argent-catalog.mjs +186 -0
  48. package/engine/scripts/argent-datadir.py +62 -0
  49. package/engine/scripts/argent-doctor.mjs +624 -0
  50. package/engine/scripts/argent-env-write.mjs +302 -0
  51. package/engine/scripts/argent-schedule.mjs +620 -0
  52. package/engine/scripts/argent-setup-check.mjs +369 -0
  53. package/engine/scripts/argent-upgrade.mjs +1046 -0
  54. package/engine/scripts/base-bootstrap.sh +575 -0
  55. package/engine/scripts/billing-mode-daily.sh +12 -0
  56. package/engine/scripts/build-engine-package.mjs +123 -0
  57. package/engine/scripts/capability-packs.lock.json +484 -0
  58. package/engine/scripts/capability_pack.py +1242 -0
  59. package/engine/scripts/check-1.0-criteria.mjs +355 -0
  60. package/engine/scripts/check-agent-catalog.mjs +141 -0
  61. package/engine/scripts/check-billing-mode.sh +377 -0
  62. package/engine/scripts/check-child-sessions.mjs +83 -0
  63. package/engine/scripts/check-config-catalog.mjs +677 -0
  64. package/engine/scripts/check-docs-drift.mjs +522 -0
  65. package/engine/scripts/check-dsh-web-expose.sh +98 -0
  66. package/engine/scripts/check-judgment-drift.mjs +403 -0
  67. package/engine/scripts/check-license.mjs +199 -0
  68. package/engine/scripts/check-model-auth.py +443 -0
  69. package/engine/scripts/check-pack-closure.mjs +661 -0
  70. package/engine/scripts/check-portable-symbols.mjs +116 -0
  71. package/engine/scripts/check-preset-landing.mjs +124 -0
  72. package/engine/scripts/check-production-home.mjs +602 -0
  73. package/engine/scripts/check-role-mapping.mjs +461 -0
  74. package/engine/scripts/check-sample-redaction.mjs +320 -0
  75. package/engine/scripts/check-skill-classification.mjs +188 -0
  76. package/engine/scripts/check-sop-consistency.mjs +431 -0
  77. package/engine/scripts/check-test-env-safety.mjs +627 -0
  78. package/engine/scripts/check-version-consistency.mjs +452 -0
  79. package/engine/scripts/ci-run.sh +558 -0
  80. package/engine/scripts/config-explain.mjs +278 -0
  81. package/engine/scripts/configure-provider.rb +29 -0
  82. package/engine/scripts/customer-config-migrate.mjs +179 -0
  83. package/engine/scripts/customer-preflight.py +892 -0
  84. package/engine/scripts/customer-readiness.sh +134 -0
  85. package/engine/scripts/daily-report-inbox.py +242 -0
  86. package/engine/scripts/daily-report-reconcile.mjs +312 -0
  87. package/engine/scripts/daily-report.mjs +1310 -0
  88. package/engine/scripts/daily-report.sh +46 -0
  89. package/engine/scripts/data/lip-product-dict.json +306 -0
  90. package/engine/scripts/deliver-customer.mjs +932 -0
  91. package/engine/scripts/delivery-faces.txt +31 -0
  92. package/engine/scripts/desktop-trial.sh +146 -0
  93. package/engine/scripts/dev-window.sh +260 -0
  94. package/engine/scripts/drill-clean-machine.sh +315 -0
  95. package/engine/scripts/dsh-api-client.mjs +215 -0
  96. package/engine/scripts/dsh-backup-scope.sh +83 -0
  97. package/engine/scripts/dsh-offline-pack.mjs +77 -0
  98. package/engine/scripts/dsh-overlay-pin.sh +87 -0
  99. package/engine/scripts/dsh-plane.mjs +153 -0
  100. package/engine/scripts/dsh-portforward.py +223 -0
  101. package/engine/scripts/dsh-preflight.mjs +179 -0
  102. package/engine/scripts/dsh-release-watch-daily.sh +19 -0
  103. package/engine/scripts/dsh-release-watch.mjs +458 -0
  104. package/engine/scripts/dsh-remote-check.sh +99 -0
  105. package/engine/scripts/dsh-restart.sh +179 -0
  106. package/engine/scripts/dsh-restore.sh +111 -0
  107. package/engine/scripts/dsh-scope-rewire.mjs +803 -0
  108. package/engine/scripts/dsh-snapshot.mjs +116 -0
  109. package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
  110. package/engine/scripts/dsh-upgrade.mjs +612 -0
  111. package/engine/scripts/dsh-url.sh +86 -0
  112. package/engine/scripts/dsh-web-launch.mjs +155 -0
  113. package/engine/scripts/ensure-store-windows.py +509 -0
  114. package/engine/scripts/entitlements.py +458 -0
  115. package/engine/scripts/env_probe.py +411 -0
  116. package/engine/scripts/feishu-card.mjs +252 -0
  117. package/engine/scripts/fix-skill-frontmatter.py +209 -0
  118. package/engine/scripts/flow-auto-retry.py +454 -0
  119. package/engine/scripts/flow-requirements.json +720 -0
  120. package/engine/scripts/flow-scaffold.py +275 -0
  121. package/engine/scripts/flow-timers-plist.sh +86 -0
  122. package/engine/scripts/flow-timers.sh +182 -0
  123. package/engine/scripts/flow-trigger-wake.mjs +284 -0
  124. package/engine/scripts/flow-trigger.sh +571 -0
  125. package/engine/scripts/flow_billing.py +354 -0
  126. package/engine/scripts/flow_capability_stats.py +314 -0
  127. package/engine/scripts/flow_case.py +194 -0
  128. package/engine/scripts/flow_claim.py +497 -0
  129. package/engine/scripts/flow_config.py +1024 -0
  130. package/engine/scripts/flow_failure.py +588 -0
  131. package/engine/scripts/flow_learn.py +105 -0
  132. package/engine/scripts/flow_ledger.py +235 -0
  133. package/engine/scripts/flow_policy.py +238 -0
  134. package/engine/scripts/flow_preflight.py +339 -0
  135. package/engine/scripts/flow_registry.py +341 -0
  136. package/engine/scripts/flow_retry.py +304 -0
  137. package/engine/scripts/flow_return_to_ops.py +191 -0
  138. package/engine/scripts/flow_source.py +315 -0
  139. package/engine/scripts/flow_sources/__init__.py +53 -0
  140. package/engine/scripts/flow_sources/cases_json.py +106 -0
  141. package/engine/scripts/flow_sources/common.py +428 -0
  142. package/engine/scripts/flow_sources/feishu_base.py +273 -0
  143. package/engine/scripts/flow_stale.py +185 -0
  144. package/engine/scripts/flow_status_reconcile.py +344 -0
  145. package/engine/scripts/flow_triage.py +849 -0
  146. package/engine/scripts/flow_write_verify.py +496 -0
  147. package/engine/scripts/fulfillment.js +586 -0
  148. package/engine/scripts/gen-agent-bundles.mjs +276 -0
  149. package/engine/scripts/gen-preset-rows.mjs +259 -0
  150. package/engine/scripts/init-ml.sh +2275 -0
  151. package/engine/scripts/install-internal-role.sh +190 -0
  152. package/engine/scripts/install-license.mjs +641 -0
  153. package/engine/scripts/install-one-shot.mjs +665 -0
  154. package/engine/scripts/integration_contract.py +220 -0
  155. package/engine/scripts/integrations/wenyao/adapter.py +98 -0
  156. package/engine/scripts/integrations/xzh/adapter.py +75 -0
  157. package/engine/scripts/invoice-mapping.example.json +40 -0
  158. package/engine/scripts/invoice-table.sh +194 -0
  159. package/engine/scripts/invoice_carrier_notice.py +279 -0
  160. package/engine/scripts/invoice_fail_closeout.py +297 -0
  161. package/engine/scripts/invoice_lip_runner.py +1508 -0
  162. package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
  163. package/engine/scripts/invoice_ready_audit.py +191 -0
  164. package/engine/scripts/invoice_runner.py +675 -0
  165. package/engine/scripts/invoice_stageb.py +516 -0
  166. package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
  167. package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
  168. package/engine/scripts/lease.py +377 -0
  169. package/engine/scripts/lib/agent-install.mjs +649 -0
  170. package/engine/scripts/lib/argent-datadir.mjs +16 -0
  171. package/engine/scripts/lib/argent_datadir.py +262 -0
  172. package/engine/scripts/lib/argent_filelock.py +507 -0
  173. package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
  174. package/engine/scripts/lib/cdp_ports.py +197 -0
  175. package/engine/scripts/lib/customer-env.mjs +102 -0
  176. package/engine/scripts/lib/daily-report-core.mjs +517 -0
  177. package/engine/scripts/lib/dep-declaration.mjs +81 -0
  178. package/engine/scripts/lib/deps-migrate.mjs +310 -0
  179. package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
  180. package/engine/scripts/lib/dsh-scope.mjs +500 -0
  181. package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
  182. package/engine/scripts/lib/dsh-versions.mjs +96 -0
  183. package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
  184. package/engine/scripts/lib/is-direct-run.mjs +61 -0
  185. package/engine/scripts/lib/patch-compose.mjs +241 -0
  186. package/engine/scripts/lib/patch-target.mjs +35 -0
  187. package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
  188. package/engine/scripts/lib/plugin-packages.mjs +332 -0
  189. package/engine/scripts/lib/preset-compat.mjs +127 -0
  190. package/engine/scripts/lib/preset-install.mjs +575 -0
  191. package/engine/scripts/lib/preset-mechanism.mjs +56 -0
  192. package/engine/scripts/lib/preset-sync.mjs +59 -0
  193. package/engine/scripts/lib/profile-retire.mjs +97 -0
  194. package/engine/scripts/lib/python-runtime.mjs +190 -0
  195. package/engine/scripts/lib/registry.mjs +72 -0
  196. package/engine/scripts/lib/repo-root.sh +56 -0
  197. package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
  198. package/engine/scripts/lib/schedule-probe.mjs +835 -0
  199. package/engine/scripts/lib/scope-pointer.mjs +164 -0
  200. package/engine/scripts/lib/settings-panels.mjs +816 -0
  201. package/engine/scripts/lib/stdio_utf8.py +86 -0
  202. package/engine/scripts/lib/wake-auth.mjs +392 -0
  203. package/engine/scripts/lib/webdriver_ipc.py +264 -0
  204. package/engine/scripts/lib/zstd-frames.mjs +104 -0
  205. package/engine/scripts/license-e2e-drill.mjs +426 -0
  206. package/engine/scripts/license-gate.mjs +245 -0
  207. package/engine/scripts/lip_backlog.py +171 -0
  208. package/engine/scripts/lip_table.py +185 -0
  209. package/engine/scripts/local-usage.mjs +214 -0
  210. package/engine/scripts/migrate-providers.mjs +127 -0
  211. package/engine/scripts/migrate-skills.sh +65 -0
  212. package/engine/scripts/ml-oauth-setup.mjs +550 -0
  213. package/engine/scripts/ml-store-ids.json +26 -0
  214. package/engine/scripts/ml_artifact_attribution.py +333 -0
  215. package/engine/scripts/ml_capacity_probe.py +1158 -0
  216. package/engine/scripts/ml_hub_status.py +1556 -0
  217. package/engine/scripts/ml_schema_check.py +115 -0
  218. package/engine/scripts/ml_store_ids.py +139 -0
  219. package/engine/scripts/npm-publish-argent.mjs +499 -0
  220. package/engine/scripts/npm-publish-argent.sh +20 -0
  221. package/engine/scripts/orchestrator.py +13811 -0
  222. package/engine/scripts/pack-plugins.mjs +140 -0
  223. package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
  224. package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
  225. package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
  226. package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
  227. package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
  228. package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
  229. package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
  230. package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
  231. package/engine/scripts/patches/verify-hardening.mjs +167 -0
  232. package/engine/scripts/patches/verify-session-migration.mjs +149 -0
  233. package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
  234. package/engine/scripts/plan-role-rename.mjs +125 -0
  235. package/engine/scripts/plugin-bootcheck.mjs +451 -0
  236. package/engine/scripts/plugin-composition.json +15 -0
  237. package/engine/scripts/poc-mcp-host.mjs +455 -0
  238. package/engine/scripts/poll-fulfillment.sh +293 -0
  239. package/engine/scripts/poll-invoice.sh +74 -0
  240. package/engine/scripts/profile-doctor.mjs +686 -0
  241. package/engine/scripts/provider-select.sh +70 -0
  242. package/engine/scripts/provider-switch.sh +146 -0
  243. package/engine/scripts/provider-sync-target.check.mjs +64 -0
  244. package/engine/scripts/python-runtime.sh +78 -0
  245. package/engine/scripts/query_runner.py +310 -0
  246. package/engine/scripts/release-argent.sh +574 -0
  247. package/engine/scripts/repair-unresolved-tools.mjs +178 -0
  248. package/engine/scripts/run-tests.sh +256 -0
  249. package/engine/scripts/self-sign-license.mjs +179 -0
  250. package/engine/scripts/session-health-scan.mjs +63 -0
  251. package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
  252. package/engine/scripts/setup.sh +22 -0
  253. package/engine/scripts/skill-install.mjs +629 -0
  254. package/engine/scripts/sku_translate.py +148 -0
  255. package/engine/scripts/sync-plugins.sh +90 -0
  256. package/engine/scripts/sync-presets.sh +27 -0
  257. package/engine/scripts/sync-scripts.sh +445 -0
  258. package/engine/scripts/telemetry.py +873 -0
  259. package/engine/scripts/ui-copy-count.mjs +220 -0
  260. package/engine/scripts/usage-anchor.sh +136 -0
  261. package/engine/scripts/usage_report.py +531 -0
  262. package/engine/scripts/validate-preset-schema.mjs +208 -0
  263. package/engine/scripts/vertical-init.mjs +323 -0
  264. package/engine/scripts/vertical-lint.mjs +482 -0
  265. package/engine/scripts/wenyao-browser.sh +73 -0
  266. package/engine/scripts/wenyao-fixture-export.py +116 -0
  267. package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
  268. package/engine/scripts/wenyao-login.sh +34 -0
  269. package/engine/scripts/wenyao-template-map.json +292 -0
  270. package/engine/scripts/wenyao_attach.py +131 -0
  271. package/engine/scripts/wenyao_download_template.py +144 -0
  272. package/engine/scripts/wenyao_fill_template.py +568 -0
  273. package/engine/scripts/wenyao_login.py +145 -0
  274. package/engine/scripts/wenyao_portal.py +633 -0
  275. package/engine/scripts/wenyao_recon.py +230 -0
  276. package/engine/scripts/wenyao_run.py +503 -0
  277. package/engine/scripts/wenyao_shadow_diff.py +160 -0
  278. package/engine/scripts/wenyao_skill_contract.py +156 -0
  279. package/engine/scripts/wenyao_template_fetch.py +175 -0
  280. package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
  281. package/engine/scripts/ziniao-auth-probe.sh +268 -0
  282. package/engine/scripts/ziniao-keepalive.sh +216 -0
  283. package/engine/scripts/ziniao-post-upgrade-check.sh +120 -0
  284. package/engine/scripts/ziniao-windows.sh +153 -0
  285. package/engine/version.mjs +1 -0
  286. package/engine/verticals/README.md +103 -0
  287. package/engine/verticals/_engine-words.baseline.txt +237 -0
  288. package/engine/verticals/_engine-words.json +19 -0
  289. package/engine/verticals/mercadolibre/README.md +43 -0
  290. package/engine/verticals/mercadolibre/config.json +709 -0
  291. package/engine/verticals/mercadolibre/flows.json +706 -0
  292. package/engine/verticals/mercadolibre/manifest.json +538 -0
  293. package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
  294. package/engine/verticals/mercadolibre/preflight.py +143 -0
  295. package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
  296. package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
  297. package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
  298. package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
  299. package/engine/verticals/mercadolibre/prompts/full.md +91 -0
  300. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
  301. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
  302. package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
  303. package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
  304. package/package.json +21 -4
  305. package/README.md +0 -3
@@ -0,0 +1,344 @@
1
+ #!/usr/bin/env python3
2
+ """flow_status_reconcile —— **只读**「表 ⇄ 台账」双向对账(两个方向都要能报出来)。
3
+
4
+ ## 为什么需要它(2026-10-09 一天里两个方向各出现一次)
5
+
6
+ - **台账 done,表=失败**(本现场,货件 79191341):引擎把"其实已生效的取消"报成了失败 ⇒
7
+ 表 `FULL状态=失败` / `当前步骤=步骤8 取消预约失败`,而角色自己只读核验后写台账 `done`
8
+ ⇒ **两处相反**,只有人工改表才对齐(本次就是人工纠正的)。
9
+ 根因已修(步骤3/4/8 加"失败前核验",见 `scripts/flow_write_verify.py`),但**历史行**与
10
+ "核验/写表之间再出岔子"的行仍会留下这种错位 ⇒ 需要一条**只读对账**把它们**报出来**。
11
+ - **表=已完成,台账=blocked**(更早出现过):表说做完了、账本说卡住了 ⇒ 下游(发票流程的
12
+ 开启闸门是 `FULL状态=已完成`)与失败回路读到的"事实"互相矛盾。
13
+
14
+ ⇒ 本命令把两条边**同时**报出来,并给**建议动作**(含同源文案)。
15
+ ⛔ **只读**:不写表、不写台账、不派发、不落卡。`FULL状态` / `当前步骤` 的**改写权在人/用户**
16
+ (引擎侧唯一受控写入口是 `_hub_manual_complete` 的"识别为运营手动完成"路径,本命令不碰)。
17
+
18
+ ## 判据(确定性;全部来自注册表 / 台账,⛔ 不猜)
19
+
20
+ | 报告桶 | 判据 |
21
+ |---|---|
22
+ | `ledger_done_table_failed` | 台账该 case 最后事件 = `done` ∧ 表「状态」∈ 失败值集(注册表 `pick.retryFrom`) |
23
+ | `table_done_ledger_not_done` | 表「状态」∈ 完成值集 ∧ 台账该 case 最后事件 ∈ {failed, blocked, retry, planned, dispatched, requeued} |
24
+ | `consistent_failed` | 表=失败 ∧ 台账=失败/阻塞(一致,无需动作) |
25
+ | `consistent_done` | 表=已完成 ∧ 台账=done(一致) |
26
+ | `table_done_no_ledger_case` | 表=已完成 ∧ 台账**没有该 case**(历史/人工完成,引擎没跑过;信息,**不算错位**) |
27
+ | `table_failed_no_ledger_case` | 表=失败 ∧ 台账**没有该 case**(同上;信息,**不算错位**) |
28
+ | `unknown` | 表「状态」为空/其它(**只报数**,不下结论) |
29
+
30
+ ⚠️ **"台账没有该 case" 不是错位**(2026-10-09 生产实测:读客户 lip 表 530 行,表=已完成 187 行里
31
+ **128 行**属于这种"历史/人工完成")—— 把它当错位会把真正的矛盾(实测 3 条)淹掉。
32
+ 故本命令的 `findings`(= 退出码 1 的来源)**只含两类真矛盾**,其余进桶计数。
33
+
34
+ ## 用法
35
+
36
+ python3 scripts/flow_status_reconcile.py --flow full [--examples 3] [--json]
37
+ python3 scripts/flow_status_reconcile.py --flow full --rows-file rows.json # 离线夹具(纯回归用)
38
+ python3 scripts/flow_status_reconcile.py --flow full --ledger /path/flow-ledger.jsonl
39
+
40
+ 退出码:0 = 无错位;1 = 有错位(**只报不改**);2 = 读取失败。
41
+ """
42
+ from __future__ import annotations
43
+
44
+ import argparse
45
+ import json
46
+ import pathlib
47
+ import subprocess
48
+ import sys
49
+
50
+ HERE = pathlib.Path(__file__).resolve().parent
51
+ sys.path.insert(0, str(HERE))
52
+ sys.path.insert(0, str(HERE / 'lib'))
53
+ from argent_datadir import resolve_data_dir # noqa: E402 数据目录唯一判据(勿在此复制链)
54
+
55
+ import flow_failure as F # noqa: E402 文案唯一来源(ops_message / case_prefixes)
56
+ import flow_ledger as L # noqa: E402 台账读取(唯一实现)
57
+
58
+ CASES = pathlib.Path(resolve_data_dir()['dir']) / 'flow-cases'
59
+
60
+ CAT_DONE_TABLE_FAILED = 'ledger_done_table_failed'
61
+ CAT_TABLE_DONE_LEDGER_NOT_DONE = 'table_done_ledger_not_done'
62
+ CAT_CONSISTENT_FAILED = 'consistent_failed'
63
+ CAT_CONSISTENT_DONE = 'consistent_done'
64
+ CAT_TABLE_DONE_NO_LEDGER_CASE = 'table_done_no_ledger_case'
65
+ CAT_TABLE_FAILED_NO_LEDGER_CASE = 'table_failed_no_ledger_case'
66
+ CAT_UNKNOWN = 'unknown'
67
+ CATS = (CAT_DONE_TABLE_FAILED, CAT_TABLE_DONE_LEDGER_NOT_DONE,
68
+ CAT_CONSISTENT_FAILED, CAT_CONSISTENT_DONE,
69
+ CAT_TABLE_DONE_NO_LEDGER_CASE, CAT_TABLE_FAILED_NO_LEDGER_CASE, CAT_UNKNOWN)
70
+ # **只有这两类是真矛盾**(= findings = 退出码 1 的来源);其余桶只计数(见模块 docstring)
71
+ CAT_CONTRADICTIONS = (CAT_DONE_TABLE_FAILED, CAT_TABLE_DONE_LEDGER_NOT_DONE)
72
+
73
+ LABELS = {
74
+ CAT_DONE_TABLE_FAILED: '❌ 台账 done ✗ 表=失败(本现场:79191341)',
75
+ CAT_TABLE_DONE_LEDGER_NOT_DONE: '❌ 表=已完成 ✗ 台账未 done(更早出现过)',
76
+ CAT_CONSISTENT_FAILED: '· 表=失败 ∧ 台账=失败/阻塞(一致)',
77
+ CAT_CONSISTENT_DONE: '· 表=已完成 ∧ 台账=done(一致)',
78
+ CAT_TABLE_DONE_NO_LEDGER_CASE: '· 表=已完成 ∧ 台账无该 case(历史/人工完成;不算错位)',
79
+ CAT_TABLE_FAILED_NO_LEDGER_CASE: '· 表=失败 ∧ 台账无该 case(历史遗留;不算错位)',
80
+ CAT_UNKNOWN: '? 判不了(表「状态」为空/其它)',
81
+ }
82
+ # 台账事件 → 是否等价于"表做完了";其余(failed/blocked/retry/planned/dispatched/无)都算"没做完"
83
+ LEDGER_DONE_EVENTS = ('done',)
84
+ LEDGER_OPEN_EVENTS = ('failed', 'blocked', 'retry', 'wake', 'planned', 'dispatched', 'requeued')
85
+
86
+
87
+ def _one(v):
88
+ """飞书单元格取值(list/dict 包装 ⇒ 取标量;与 `invoice_ready_audit.one()` 同口径)。"""
89
+ if isinstance(v, list):
90
+ v = v[0] if v else None
91
+ if isinstance(v, dict):
92
+ v = v.get('text') or v.get('name')
93
+ return v
94
+
95
+
96
+ def _text(v) -> str:
97
+ return str(_one(v) or '').strip()
98
+
99
+
100
+ def flow_target(flow: str) -> dict:
101
+ """该流程的表侧栏位名(复用唯一读取点 `flow_config.py`,它自己会解析 source 变体)。"""
102
+ r = subprocess.run([sys.executable, str(HERE / 'flow_config.py'), '--flow', flow, '--json'],
103
+ capture_output=True, text=True)
104
+ if r.returncode != 0 or not r.stdout.strip().startswith('{'):
105
+ raise RuntimeError((r.stderr or r.stdout).strip()[:300])
106
+ doc = json.loads(r.stdout)
107
+ src = doc.get('source') or {}
108
+ names = src.get('fields') or {}
109
+ picks = []
110
+ for name in ([str(src.get('default') or '')] + list((src.get('variants') or {}).keys()))[::-1]:
111
+ v = (src.get('variants') or {}).get(name) or {}
112
+ if v.get('pick'):
113
+ picks.append(v['pick'])
114
+ picks.append(src.get('pick') or {})
115
+ retry_from = []
116
+ for p in picks:
117
+ for x in (p.get('retryFrom') or []):
118
+ if str(x).strip():
119
+ retry_from.append(str(x).strip())
120
+ return {'flow': flow, 'names': names, 'retryFrom': retry_from, 'base': src.get('base') or '',
121
+ 'table': src.get('table') or ''}
122
+
123
+
124
+ def read_live_rows(flow: str) -> list:
125
+ """**只读**拉该流程的全部行(不应用 pick 门)。
126
+
127
+ 与既有的只读清点脚本(`read_flow_rows`)**同一条读取路径**(`flow_source.py --no-pick-filter`
128
+ + 逐 case 的 `flow-cases/<flow>__<ticket>.json`),本文件自带一份是为了让本命令**可独立分发**
129
+ (不依赖未进能力包的兄弟脚本);口径若变,两处必须同改。
130
+ """
131
+ p = subprocess.run([sys.executable, str(HERE / 'flow_source.py'), '--flow', flow,
132
+ '--source', 'feishu-base', '--no-pick-filter', '--json'],
133
+ capture_output=True, text=True, timeout=600)
134
+ if not p.stdout.strip().startswith('{'):
135
+ raise RuntimeError((p.stderr or p.stdout).strip()[:300])
136
+ doc = json.loads(p.stdout)
137
+ rows = []
138
+ for item in doc.get('cases') or []:
139
+ ticket = str(item.get('ticket') or '')
140
+ f = CASES / f'{flow}__{ticket}.json'
141
+ fields = {}
142
+ if f.exists():
143
+ try:
144
+ fields = json.loads(f.read_text(encoding='utf-8')).get('fields') or {}
145
+ except Exception: # noqa: BLE001
146
+ fields = {}
147
+ rows.append({'ticket': ticket, 'recordId': str(item.get('recordId') or ''),
148
+ 'fields': fields, 'missing': item.get('missing') or []})
149
+ return rows
150
+
151
+
152
+ def ledger_index(path: pathlib.Path, flow: str) -> dict:
153
+ """台账 → `{caseId: {'event','reason','ts','phase','by'}}`(该 case 的**最后一条**事件)。
154
+
155
+ 前缀口径复用 `flow_failure.case_prefixes()`(规范 id 与旧短名都认)⇒ `--flow full` 与
156
+ `--flow <业务线>.<流程>` 都能找到同一批 case(台账里写的是当时的名字)。
157
+ """
158
+ prefixes = F.case_prefixes(flow) or ()
159
+ out = {}
160
+ for e in L.read(path):
161
+ cid = str(e.get('caseId') or '')
162
+ if not cid or (prefixes and not cid.startswith(prefixes)):
163
+ continue
164
+ out[cid] = e # 文件按时间追加 ⇒ 后者覆盖前者 = 最后一条
165
+ return out
166
+
167
+
168
+ def table_state(fields: dict, names: dict, *, done_values, failed_values) -> tuple:
169
+ """表侧状态 → `(state, why)`;`state` ∈ {done, failed, other}。"""
170
+ status_col = str(names.get('status') or '')
171
+ step_col = str(names.get('step') or '')
172
+ status = _text((fields or {}).get(status_col)) if status_col else ''
173
+ step = _text((fields or {}).get(step_col)) if step_col else ''
174
+ if status and status in done_values:
175
+ return 'done', f'{status_col}={status}'
176
+ if status and status in failed_values:
177
+ return 'failed', f'{status_col}={status}'
178
+ if step.endswith('失败'):
179
+ # 状态列既非完成也非失败、但「当前步骤」停在 `<步骤>失败` ⇒ 也是失败态(口径见 _progress_failed_step)
180
+ return 'failed', f'{step_col}={step}'
181
+ return 'other', f'{status_col}={status or "(空)"}' + (f'|{step_col}={step}' if step else '')
182
+
183
+
184
+ def classify_pairs(rows: list, ledger: dict, *, flow: str, names: dict,
185
+ done_values=None, failed_values=None) -> dict:
186
+ """把「表行 + 台账最后事件」逐行判桶(**纯函数**:离线夹具可回归,⛔ 不碰表/台账)。
187
+
188
+ 返回 `{'flow','total','buckets','findings','examples'}`;`findings` 只含两类**错位**,
189
+ 每条带 `suggest`(建议动作)与 `opsMessage`(**同源文案**:`flow_failure.ops_message()`)。
190
+ """
191
+ done_values = set(done_values or ['已完成'])
192
+ failed_values = set(failed_values or ['失败'])
193
+ prefixes = F.case_prefixes(flow) or (f'{flow}__',)
194
+ buckets, findings, examples = {}, [], {}
195
+ for r in rows:
196
+ ticket = str(r.get('ticket') or '')
197
+ fields = r.get('fields') or {}
198
+ cid, ev = '', None
199
+ for p in prefixes:
200
+ if f'{p}{ticket}' in ledger:
201
+ cid, ev = f'{p}{ticket}', ledger[f'{p}{ticket}']
202
+ break
203
+ tstate, twhy = table_state(fields, names, done_values=done_values,
204
+ failed_values=failed_values)
205
+ lev = str((ev or {}).get('event') or '')
206
+ if tstate == 'done':
207
+ if lev in LEDGER_DONE_EVENTS:
208
+ cat = CAT_CONSISTENT_DONE
209
+ elif not lev:
210
+ # ⚠️ **不是错位**:表说完成、台账压根没这个 case(历史/人工完成的行,引擎没跑过)
211
+ cat = CAT_TABLE_DONE_NO_LEDGER_CASE
212
+ else:
213
+ cat = CAT_TABLE_DONE_LEDGER_NOT_DONE
214
+ elif tstate == 'failed':
215
+ if lev in LEDGER_DONE_EVENTS:
216
+ cat = CAT_DONE_TABLE_FAILED
217
+ elif not lev:
218
+ cat = CAT_TABLE_FAILED_NO_LEDGER_CASE
219
+ else:
220
+ cat = CAT_CONSISTENT_FAILED if lev in LEDGER_OPEN_EVENTS else CAT_UNKNOWN
221
+ else:
222
+ cat = CAT_UNKNOWN
223
+ buckets[cat] = buckets.get(cat, 0) + 1
224
+ examples.setdefault(cat, []).append(ticket)
225
+ if cat in CAT_CONTRADICTIONS:
226
+ note_col = str(names.get('note') or '')
227
+ ledger_reason = F.canonical_reason((ev or {}).get('reason') or (ev or {}).get('error') or '')
228
+ table_note = _text(fields.get(note_col)) if note_col else ''
229
+ findings.append({
230
+ 'category': cat,
231
+ 'ticket': ticket, 'caseId': cid or f'{prefixes[0]}{ticket}',
232
+ 'recordId': str(r.get('recordId') or ''),
233
+ 'table': twhy,
234
+ 'ledger': f"{lev or '(无该 case)'}"
235
+ + (f"|reason={ledger_reason[:160]}" if ledger_reason else ''),
236
+ 'tableNote': table_note[:200],
237
+ 'ledgerReason': ledger_reason[:200],
238
+ # 建议动作(**只读**:本命令不改表、不改台账)
239
+ 'suggest': _suggest(cat, status_col=str(names.get('status') or ''),
240
+ note_col=note_col),
241
+ # 同源文案(单一来源 flow_failure.ops_message())—— 供运维照抄
242
+ 'opsMessage': _ops_message(flow, cat, ledger_reason, table_note),
243
+ })
244
+ return {'flow': flow, 'total': len(rows), 'buckets': buckets, 'findings': findings,
245
+ 'examples': examples}
246
+
247
+
248
+ def _suggest(cat: str, *, status_col: str, note_col: str) -> str:
249
+ """建议动作(**审计口径**:告诉人去做什么,⛔ 本命令自己不写)。"""
250
+ if cat == CAT_DONE_TABLE_FAILED:
251
+ return (f"① 只读复核真相(hub-v2 + 列表页,或查台账该 case 的 done 依据);"
252
+ f"② 复核确认已完成后,**由人工/用户**把「{status_col}」置为「已完成」、"
253
+ f"「{note_col}」更新为核验结论(⛔ 引擎不自动改表状态);"
254
+ f"③ 若复核发现其实没完成 ⇒ 按失败处置(表状态改回「失败」并把备注改成真实原因)。")
255
+ return (f"① 若表确为完成(或 hub-v2 实测完成)⇒ 由角色补一条台账 done:"
256
+ f"`python3 scripts/flow_ledger.py --case-id <caseId> --event done --by <角色> "
257
+ f"--detail \"依据:hub-v2/列表页实测\"`;"
258
+ f"② 若实际未完成 ⇒ 按失败重跑(并**由人工**把「{status_col}」改回「失败」)。")
259
+
260
+
261
+ def _ops_message(flow: str, cat: str, ledger_reason: str, table_note: str) -> str:
262
+ """错位行的**同源文案**(`flow_failure.ops_message()` 是唯一措辞来源,⛔ 不另写一套)。
263
+
264
+ 为什么用 ops_message:运维在表里看到的"结论+动作"与台账/卡片必须是**同一串**
265
+ (生产实测过的教训:两处口径不一致会把人带偏)。这里把它作为**建议文案**附在对账结果里。
266
+ """
267
+ reason = table_note or ledger_reason or ''
268
+ if reason.startswith('失败:'):
269
+ reason = reason[len('失败:'):]
270
+ if not reason:
271
+ return ''
272
+ try:
273
+ return F.ops_message(flow, reason, kind='needs_agent' if cat == CAT_DONE_TABLE_FAILED else None)
274
+ except Exception: # noqa: BLE001
275
+ return ''
276
+
277
+
278
+ def _load_rows_file(path: str) -> list:
279
+ """离线夹具:`[{ticket, fields, recordId?}]` 或 `{'rows': [...]}`。"""
280
+ doc = json.loads(pathlib.Path(path).read_text(encoding='utf-8'))
281
+ rows = doc.get('rows') if isinstance(doc, dict) else doc
282
+ return [{'ticket': str(r.get('ticket') or ''), 'recordId': str(r.get('recordId') or ''),
283
+ 'fields': r.get('fields') or {}} for r in (rows or [])]
284
+
285
+
286
+ def main(argv=None) -> int:
287
+ ap = argparse.ArgumentParser(description='表 ⇄ 台账 双向只读对账(不自动改表)')
288
+ ap.add_argument('--flow', default='full', help='流程 id(规范 id 或旧短名,别名都认)')
289
+ ap.add_argument('--ledger', default=str(L.DEFAULT_LEDGER))
290
+ ap.add_argument('--rows-file', help='离线夹具行 JSON(给了就不读表)')
291
+ ap.add_argument('--fields-file', help='离线夹具栏位名 JSON({"status":…,"step":…,"note":…})')
292
+ ap.add_argument('--done-values', help='逗号分隔的"完成"状态值(缺省:已完成)')
293
+ ap.add_argument('--failed-values', help='逗号分隔的"失败"状态值(缺省:注册表 retryFrom ∪ 失败)')
294
+ ap.add_argument('--examples', type=int, default=3)
295
+ ap.add_argument('--json', action='store_true')
296
+ a = ap.parse_args(argv)
297
+
298
+ try:
299
+ tgt = flow_target(a.flow)
300
+ names = dict(tgt['names'])
301
+ if a.fields_file:
302
+ names.update(json.loads(pathlib.Path(a.fields_file).read_text(encoding='utf-8')) or {})
303
+ rows = _load_rows_file(a.rows_file) if a.rows_file else read_live_rows(a.flow)
304
+ ledger = ledger_index(pathlib.Path(a.ledger), a.flow)
305
+ except Exception as e: # noqa: BLE001
306
+ print(f'❌ 读取失败:{e}', file=sys.stderr)
307
+ return 2
308
+
309
+ done_values = [x.strip() for x in (a.done_values or '已完成').split(',') if x.strip()]
310
+ failed_values = [x.strip() for x in (a.failed_values or '').split(',') if x.strip()] \
311
+ or (tgt['retryFrom'] or []) + ['失败']
312
+ res = classify_pairs(rows, ledger, flow=a.flow, names=names,
313
+ done_values=done_values, failed_values=failed_values)
314
+ res['ledger'] = str(a.ledger)
315
+ res['table'] = tgt.get('table') or ''
316
+ res['readOnly'] = True
317
+ if a.json:
318
+ print(json.dumps(res, ensure_ascii=False, indent=2))
319
+ else:
320
+ n_bad = len(res['findings'])
321
+ print(f"\n── {a.flow}|表 {res['total']} 行 ⇄ 台账 {len(ledger)} case"
322
+ f"|**错位 {n_bad} 条**(只读,不改表)")
323
+ for cat in CATS:
324
+ n = res['buckets'].get(cat, 0)
325
+ if not n:
326
+ continue
327
+ print(f" {LABELS[cat]:44} {n:>4}"
328
+ + (f"|例:{', '.join(str(x) for x in res['examples'][cat][:a.examples])}"
329
+ if a.examples else ''))
330
+ for f in res['findings'][:max(0, a.examples)]:
331
+ print(f"\n 【{f['category']}】{f['ticket']}(caseId={f['caseId']})")
332
+ print(f" 表:{f['table']}|台账:{f['ledger']}")
333
+ if f['tableNote']:
334
+ print(f" 表备注:{f['tableNote'][:120]}")
335
+ print(f" 建议:{f['suggest']}")
336
+ if f['opsMessage']:
337
+ print(f" 同源文案(ops_message):{f['opsMessage'][:200]}")
338
+ if not n_bad:
339
+ print(" ✅ 无错位(表与台账两个方向都一致)")
340
+ return 1 if res['findings'] else 0
341
+
342
+
343
+ if __name__ == '__main__':
344
+ raise SystemExit(main())