openqj
c007fafcc7
Merge pull request #78 from openqj/feat-quota-hardening
...
worker: enforce quotas during execution
2026-10-27 11:13:49 +08:00
host
ad014393ee
worker: enforce quotas during execution (claim/retry/fallback/followup/poll kill)
...
- claim-time gate: queued task whose quota was spent while waiting is
marked interrupted with a quota reason, no cost row, resume kept
- retry/fallback boundaries and every followup round check DB month
usage plus this run's accumulated usage (priced via cost table)
- local popen poll loop kills the process group on quota (30s throttle);
Docker/sync paths get boundary checks only (documented)
- quota stops finalize as interrupted + quota_exceeded error, keep the
actual cost row, no push, followups restored to pending
2026-10-27 11:06:40 +08:00
openqj
c189df4a20
Merge pull request #76 from openqj/fix-handler-500
...
fix: 请求处理未捕获异常回 500 JSON,不再断连变 nginx 502
2026-10-27 11:01:31 +08:00
openqj
ad8bf280d0
Merge pull request #77 from openqj/feat-staging-cleanup
...
fix: 提前失败路也清附件暂存
2026-10-27 11:00:27 +08:00
host
99de5c77f8
fix: 提前失败路也清附件暂存(仓库解析失败/缺 key 不再残留 bundle)
...
2026-10-27 附件实弹:仓库解析失败的任务不走正常终局清理,
bundle 目录永久残留(首个失败任务的暂存目录须手动删)。
根因:run_one 里缺 key 门与仓库解析失败两路提前返回,够不
到正常终局后的暂存清理;只有走完 _run_attempt 的任务才删。
修法:把终局清理抽成 _cleanup_task_seed 唯一入口,三路(正
常终局、缺 key 门、仓库解析失败)都走它;取消仍走 server
侧删,互不冲突;删失败不影响任务结果本身。
测试:test_worker.py 新增 2 个(仓库解析失败、缺 key 时
bundle 目录都被删);反向验证:撤掉 worker 修复时正好这
2 个变红,有修复时全绿。全量 deploy 19、host 62、cloud
610 全过。
2026-10-27 10:57:22 +08:00
host
df8c493281
fix: 请求处理未捕获异常回 500 JSON,不再断连变 nginx 502
...
2026-10-27 死锁事件的次生问题:_dispatch 里未捕获异常逃出
handle_one_request 后连接无响应,前端只看到 nginx 502、拿不到
任何可读错误;死锁本身修掉后,同类内部异常仍会以 502 露出来。
兜底(_dispatch_safe,四个 do_* 方法统一走它):
- 任何异常先记 _server_log.exception(堆栈进服务端日志),
再回固定 {"error": "internal_error"},不泄漏堆栈给客户端;
- _send_json/_send_html/SSE 发头后即标记已响应,之后异常
只记日志、不二次发送(响应一旦开始不能再发状态行);
- _responded 每请求重置,同一连接 keep-alive 复用不串请求。
单测:异常请求得 500 JSON(非断连)+ 服务端记日志;
同一连接上 500 之后再请求仍 200。
2026-10-27 10:56:08 +08:00
openqj
b0772d0eac
Merge pull request #75 from openqj/feat-deploy-key
...
feat: deploy 无密码时走密钥直连
2026-10-27 05:50:52 +08:00
openqj
0a862c11f7
Merge pull request #74 from openqj/feat-repo-picker2
...
GitHub 仓库选择菜单:安装仓库列表 + PATCH 补仓库 + 控制台下拉
2026-10-27 05:48:08 +08:00
host
420a7fecfd
feat: deploy 无密码时走密钥直连(收进 main,替代丢失的工作区补丁)
...
背景:2026-10-27 另一条线给 deploy.py 打的密钥补丁只放在
env-console 工作区未提交,切分支后丢失;部署一度只能靠它 +
--allow-dirty。本批把逻辑收进 main,并加一处加固。
分发:有 SSH_PASSWORD 时走 helper(pty 喂密码,实测最稳的一
路);无密码时 helper 不可用(会卡在密码提示),改走密钥直连:
ProxyCommand 显式环境变量优先、其次 helper 目录的
proxy_tunnel.py(存在才用),密钥按 CODEX_DEPLOY_KEY →
/root/.ssh/id_codex_deploy → ~/.ssh/id_codex_deploy 顺序显式传
-i——不能只依赖 /root/.ssh/config 的 Host 条目,那个目录会在
VM 回收时被清空(2026-10-27 05:04 实测),而 ~/.ssh 的那份
持久。两处都没有时不硬编造,靠 ssh 配置或 agent。
测试:deploy/tests 新增 TestSshScpCmd 5 个(有密码走 helper、
无密码绝不用 helper、无 helper 无代理时直连、显式代理优先、
显式密钥优先);全量 deploy 19、host 62、cloud 598 全过。
2026-10-27 05:47:33 +08:00
openqj
d91e5134f5
Merge pull request #73 from openqj/fix-rate-limit-deadlock
...
fix: 限流清理移出请求路径,根治 rate_limit_hits 死锁
2026-10-27 05:45:18 +08:00
host
6ba671e051
GitHub 仓库选择菜单:安装仓库列表 + PATCH 补仓库 + 控制台下拉
2026-10-27 05:43:56 +08:00
host
8bda29eac3
fix: 限流清理移出请求路径,根治 rate_limit_hits 死锁
...
allow() 原先每请求在同一事务里 DELETE 旧行 + INSERT + COUNT,
控制台并发加载 + 双 server 下两个事务在 rate_limit_hits 表上
死锁,未捕获异常直接断连、nginx 报 502(2026-10-27 实测,
环境与链路两区同时 502 均源于此,与各自端点逻辑无关)。
结构修复:
- allow() 只做 INSERT + 窗口内 COUNT(COUNT 按 ts > now-window
过滤,过期行残留不影响计数正确性);建表/索引每实例只做一次。
- 过期行改由后台线程 sweep_rate_limit_hits() 每 60 秒清一次;
Postgres 下拿事务级 advisory lock,另一台 server 正在清时
直接跳过,清理者之间也不死锁;清理失败只记 warning。
- main() 里对 DbRateLimiter 起 sweeper 线程(内存版不需要)。
单测:allow 不再删旧行、窗口计数忽略过期行、sweep 只删过期
行、空库 sweep 不炸。
2026-10-27 05:40:30 +08:00
openqj
5ba4d35839
Merge pull request #72 from openqj/feat-goal-marker
...
fix: goal 宣告检测容忍行尾句号
2026-10-27 05:32:11 +08:00
host
49dfb7c458
fix: goal 宣告检测容忍行尾句号,修提示与检测自矛盾致空转
...
_llm_declared_done 此前要求回合文本里单独一行恰好等于
目标已达成,但 run_goal_loop 三处提示都让模型输出
目标已达成。(带句号);模型照做后检测永不触发,实弹
goal 任务空转 292 轮才被手动取消。改成比对前去行尾空白
与句末标点(。!?;,、. 等),提示不动、单独一行要求
不动,行内带其它文字仍不算。
回归测试 test_goal_marker_trailing_punctuation_counts:裸标记、
句号、感叹号、句号前空格、行尾空格算达成;前缀文字、逗号
后续、反问、空串不算。
2026-10-27 05:28:59 +08:00
openqj
43748804d3
Merge pull request #71 from openqj/feat-quotas
...
租户配额与计费:按用户月度 USD 上限 + 建任务/分支/重试准入 402
2026-10-27 05:28:25 +08:00
host
970b181bab
租户配额与计费:按用户月度 USD 上限 + 建任务/分支/重试准入 402 + 管理/自查端点 + 控制台展示
2026-10-27 05:18:37 +08:00
openqj
2a7cdd582f
Merge pull request #70 from openqj/feat-continue
...
feat: 旧任务继续会话——终端任务带新消息 + 全部选项同一会话续跑
2026-10-27 04:46:06 +08:00
host
f9be9205ab
fix: goal_resume_task 只在 LocalExecutor 上调,其余回落 goal_task
...
与附件分支同款隐患:CloudExecutor 基类与假执行器没有
goal_resume_task,直接调会 AttributeError;回落到 goal_task 让
它们按原语义失败(CloudExecutor 即“不支持 goal”)。
2026-10-27 04:40:34 +08:00
host
3a4997d346
feat: 旧任务继续会话——终端任务带新消息 + 全部选项同一会话续跑
...
server:POST /tasks/{id}/continue(终端非 merged 任务重排队,
resume_message 列存本轮消息,不传选项沿用原值,plan/goal 冲突
与新建一致,goal_mode=1 时转新 goal 语义;CAS 防并发改状态,
审计 task_continued);tasks.resume_message 三处迁移 + PG 建表。
worker:认领后本轮 prompt 换成续跑消息(只换内存副本),
goal 续跑走 goal-resume 而不是 goal,跑完清 resume_message
(进程被杀时不清,下次认领重跑同一条消息)。
host:goal-resume --goal-text(旧任务接续 goal 模式:跳过状态
与记录守卫,旧线程上从第 1 轮开新目标循环,新语义不限轮次;
无线程时拒绝)。
sandbox:Local/Docker goal_resume_task + popen goal-resume kind;
附带 popen goal 命令改成新语义(无验收不传 --verify,轮次 0
原样传,此前 goal 任务到本地 worker 会直接报错)。
console:详情页终端非 merged 任务出继续会话表单(消息 +
模型/强度/权限/计划/goal,模型下拉复用中转列表)。
测试:新增 test_task_continue.py 11 个 + test_host_continue.py 2 个。
2026-10-27 04:40:11 +08:00
openqj
cf293914d9
Merge pull request #69 from openqj/feat-attachments
...
feat: 任务附件——文件/文件夹/图片进任务工作区
2026-10-27 04:39:05 +08:00
host
9306c957f3
fix: seed 参数只传给 LocalExecutor,假执行器/CloudExecutor 旧签名不再炸
...
worker 曾无条件给同步路径的 goal_task/dispatch_task 传 seed_dir,
CloudExecutor 基类与测试假执行器只有 (task) 一个参数,直接
TypeError,炸掉 test_project_repo 2 个 + test_cloud_executor_goal_unsupported
1 个(后者本应报“不支持 goal”)。改成 isinstance(LocalExecutor)
才传 seed_dir,其余走原签名;Docker 路径已有 is_docker 守卫。
2026-10-27 04:30:43 +08:00
host
7c444a688b
feat: 任务附件——server/worker/console 链路打通(+ popen goal 新语义补漏)
...
server:POST /uploads(multipart,413 预检、路径/数量/大小校验、
24h 过期包、used 防一包多用)+ 建任务绑定(同事务写附件行 +
包置 used,cloud 执行器 400)+ 列表/详情带 attachments +
取消删暂存 + 分支带走附件清单;db.py PG 建表 + init_db 补表。
worker:_task_seed_dir(无附件/目录已删静默 None)+ dispatch/
goal 三路(同步本地、popen、Docker 只读挂载 /tmp/codex-seed)
+ cloud 执行器兜底失败 + 终局删暂存。
console:文件/文件夹/图片三路输入,先上传拿 upload_id 再建任务,
列表 [附件×n]、详情列附件路径。
附带:popen 的 goal 命令此前强制要求 verify_command,goal 新语义
任务到本地 worker(走 popen)会直接报错,改成与同步 goal_task
一致(无验收不传 --verify,轮次 0 原样传)。
测试:新增 test_task_attachments.py 15 个。
2026-10-27 04:30:43 +08:00
host
9537e6f0d2
WIP: 任务附件——host seed-dir 与上传模块(未完,server/worker/console 待续)
2026-10-27 04:29:40 +08:00
openqj
c102327040
Merge pull request #68 from openqj/env-console
...
feat: 命令台云端环境——选、建、管 + 任务绑定环境
2026-10-27 04:13:11 +08:00
host
3d45edc8b4
feat: 命令台云端环境——选、建、管 + 任务绑定环境
...
环境是项目仓库的命名基准检出(项目 + 仓库快照 + 分支 + 执行器),
不是官方 Codex 云端环境(无公开调用口,不宣称能建它)。
- DB:environments 表(creating/ready/failed/archived,部分唯一索引
保证同项目未归档名唯一、归档名可重用)+ tasks.environment_id,
server/worker/db 三处迁移同步(含 #67 的 goal_mode,不丢新语义)。
- API:POST/GET /environments、GET /environments/{id}、
POST /environments/{id}/archive;任务创建选环境须本项目已 ready、
执行器一致或继承、cloud 执行器拒绝。
- Worker:run_all 每轮开头准备 creating 环境(真实 git clone +
checkout 分支,幂等、无 preparing 卡死态,准备期归档 CAS 不覆盖);
环境任务派发时 task[repo] 指到环境检出(租户 Docker 容器内路径 +
envs 整目录挂载),任务仍一任务一 worktree。
- Console:环境卡(列表/新建/归档,未 ready 置灰)+ 新建任务环境下拉
+ 任务列表 [环境] 标记;公网 nginx 须同批代理 /environments。
- 单测:新增 test_task_environments.py 14 个;本地实弹(真实 git
clone octocat/Hello-World,不跑模型)项目→环境→任务→归档全通。
2026-10-27 04:07:54 +08:00
openqj and host
c86b450851
feat: goal 模式新语义——prompt 即目标,无验收命令,不限轮次,模型宣告完成 ( #67 )
...
console 打勾 goal 模式时不再有专用 goal 文本、验收命令、轮次
三个输入(已移除,换成说明行),只发 goal_mode=1;server 侧
goal_mode=1 时 goal 存 prompt、验收命令空、轮次存 0,带验收
命令或与计划模式同用时 400,旧的带验收命令任务照原样可用。
host.py goal 循环:verify 为空时不跑验测,轮次提示里写明
标记约定;模型在完整跑完的回合末尾单独一行输出“目标已
达成”即达标(超时回合的部分文本含标记也不算);--max-rounds
0 为不限轮次,goal-resume 续跑按 0 处理(不用 or 兜底成 3)。
刹车只有取消与预算耗尽(后者记 interrupted、可续跑)。
迁移与 worker:tasks 加 goal_mode 列(server 迁移、worker 列
补齐、PG schema 三处同步);worker 按 goal_mode 或
verify_command 判定 goal 路径,重试与后续消息排空对新语义
同样只跑一次/不排空;Local/Docker goal_task 的 --verify 可选、
--max-rounds 0 原样传。
Co-authored-by: host <host@test.local >
2026-10-27 03:54:48 +08:00
openqj and host
4179fb1a0a
fix: 命令台初始化有 key 即加载模型列表 ( #66 )
...
回访用户 key 已在 localStorage 里:此前模型列表只在点“保存”
时加载一次,重开页面下拉永远只有默认项(2026-10-27 实测)。
初始化序列补 if (getKey()) loadRelayModels();单测补初始化
序列含该调用的标记断言。
另:公网 /relay/models 曾因 nginx 缺 /relay/ 代理返回 404,
与本修无关,nginx 侧另行补 location。
Co-authored-by: host <host@test.local >
2026-10-27 03:33:35 +08:00
openqj and host
f4443dca4f
feat: 计划模式 + 后续消息 + 会话分支接云端 ( #65 )
...
- 计划模式:tasks 加 plan 列;plan 与 approval=full / goal 冲突时
400;执行器在 approval 参数后追加 --sandbox read-only(本地
dispatch/run/goal/popen 与 Docker 内层命令全覆盖),host 任务
存下 sandbox 后续轮次自动继承只读。
- 后续消息:新表 task_followups;POST/GET /tasks/{id}/followups
(仅 queued/running 的非 goal 任务;direct 立即往链路追加
phase=followup-direct 条目,本轮跑完后优先于排队执行;排队
按 direct 优先、队内先来先服务逐条再跑一轮,同一 thread;
一条失败即停、剩余 pending 留给重试入口;取消时回 pending)。
- 会话分支:POST /tasks/{id}/fork(仅终态任务;继承 executor/
模型/强度/权限/计划,goal 字段置空);worker 先 fork 再跑首轮,
首轮失败不自动重试、不重复分叉,失败后走重试入口重跑首轮。
- 修任务级模型被覆盖:worker 此前无条件写成主模型,控制台
选的模型实际不生效;现为任务级 > worker 主模型 > 配置。
- Docker 租户准备抽成 _docker_task_context,供普通尝试与 fork 共用。
- console:新建任务计划勾选(含冲突拦截)、详情计划徽标与
分支来源、分支按钮与表单、后续消息区、列表 [计划]/[分支] 标记。
- 测试:cloud/tests/test_task_links.py 17 个(server 校验与继承、
worker 模型优先、只读命令、排空顺序与停排、goal 不排空、
fork 单次性);全量 cloud 542、host 49、deploy 14。
Co-authored-by: host <host@test.local >
2026-10-27 03:17:51 +08:00
openqj
6da3745127
Merge pull request #64 from openqj/goal-align-2
...
CI verify pass 2m2s,合并后走新链路推生产。
2026-10-27 03:08:49 +08:00
host
1128fd3076
feat: 长任务补齐——流式进度 + 跨进程中断 + worker 超时约定
...
- 长等待流式进度:run_turn 改 1 秒切片轮询(_wait_turn),每满 --progress-interval(默认 30 秒,0 关闭,CLI > 配置 > 默认)往 stderr 打 turn progress 并往链路文件记 kind=progress;进度绝不走 stdout,不破坏 dispatch JSON 与 goal 结论行/用量行解析(经 cloud 真正则验过)
- 跨进程中断:interrupt 直接不生效时写 <state_dir>/interrupt-requests/<task>.json,属主等 turn 时每秒轮询到后用自己的 live 连接发 turn/interrupt 并记 interrupted;10 秒内未响应也不删文件,属主下次轮询仍处理;接线覆盖 dispatch/run/goal/goal-resume/delegate/parallel/fix-review/run-due;旧 server 不接受 interrupt_file 时退回不传再跑
- cloud worker 超时约定:--task-timeout 0 归一化为 None(不限时,与 host turn_timeout 同约定,Docker 路径仍受 docker_timeout 约束),默认 1800 不变;超时仍记 failed(现有单测合同不动)
测试:host 56 全过(新增 7)、cloud 526 全过(4 跳过,新增 1 个 0=不限时)。
2026-10-27 03:05:15 +08:00
openqj
028ef02a86
Merge pull request #63 from openqj/goal-long-task
...
CI verify pass 2m10s,合并后走新链路推生产。
2026-10-27 02:49:13 +08:00
host
ede7c6afee
feat: 长任务对齐 Codex App + /goal 检查点续跑
...
- --turn-timeout / turn_timeout 可配置,0 不限时(对齐桌面 App 等待语义);超时回合保留部分文本,任务记 interrupted 而非 failed
- Server.interrupt_turn + host.py interrupt --task(协议 turn/interrupt,无活动回合时不改任务状态)
- goal 循环抽成 run_goal_loop:每轮落盘检查点(含首轮前初始保存),goal-resume 从下一轮续跑;终局 goal-met/goal-unmet 后拒绝续跑
- 回合超时/中断时下一轮提示写明是回合中断而非验收失败;预算耗尽时记 interrupted 不冒充 unmet;--verify-timeout(默认 600,0 不限时,超时记 rc 124)
- 云端解析契约未动:goal met/unmet after rounds、round 行、usage 行格式保持
- 真模型实弹:端到端、断点续跑、正常多轮反馈修复(低档、gpt-5.6-terra)
2026-10-27 02:45:44 +08:00
openqj and host
fc016fd459
style: 命令台 GUI 美化 ( #62 )
...
只改样式与布局骨架,不动 JS 逻辑与接口:
- 卡片化分区(API Key / 健康 / 项目+Workers 两栏 / 新建任务 / 任务列表 / 任务详情),柔和底色 + 圆角 + 细阴影
- 按钮分主次:保存 key 与提交任务为主按钮(蓝),取消为红色弱化按钮;输入框获焦有蓝圈
- 任务状态改圆点 + 彩色文字(running 蓝、completed 绿、failed 红、merged 紫、interrupted 橙),行悬停高亮
- 表格去网格线、表头浅灰,详情/链路区改浅灰圆角框;窄屏单栏自适应
- 验证:HTML 标签平衡、全部 41 个 JS 用 id 齐全、接口标记齐全、零外部引用、JS 经 node --check、test_console 4/4
Co-authored-by: host <host@test.local >
2026-10-27 02:45:08 +08:00
openqj and host
799f7c3edb
feat: 任务实时链路 SSE ( #61 )
...
host.py:回合通知摘要成链路条目,增量追加到 <state>/host-traces/<task>.jsonl(上限 5000 行);dispatch/run/goal/delegate 设链路文件
cloud/server.py:GET /tasks/{id}/trace 快照 + /trace/stream SSE(snapshot → entry → done;worktree 在别机时空链路 + 说明)
console:详情链路区,运行中 fetch 流式渲染(EventSource 不能带 X-API-Key 头),终态走快照;DOM 上限 1000 条
单测:test_host_trace 7 + test_task_trace 6;host 49、cloud 525、deploy 14 全过
Co-authored-by: host <host@test.local >
2026-10-27 02:27:15 +08:00
openqj and host
6022f1dada
chore: default model gpt-6.1-sol -> gpt-5.6-terra ( #60 )
...
Co-authored-by: host <host@test.local >
2026-10-27 02:00:09 +08:00
openqj and host
8b2f3968dc
feat: per-task model/effort/approval + relay models endpoint ( #59 )
...
Co-authored-by: host <host@test.local >
2026-10-27 01:44:53 +08:00
openqj and host
cff152c5a2
fix: console reloads data right after saving key ( #58 )
...
Co-authored-by: host <host@test.local >
2026-10-27 01:26:19 +08:00
host
3c3b59faae
docs: console public entry via api.diante.me (nginx locations)
2026-10-27 01:11:30 +08:00
openqj and host
e40c2c33bb
feat: web command console v1 (GET /console) ( #57 )
...
Co-authored-by: host <host@test.local >
2026-10-27 01:02:24 +08:00
openqj and host
c6e633d162
fix: host_deploy stops/starts and health-checks server-2 too ( #56 )
...
Co-authored-by: host <host@test.local >
2026-10-27 00:50:33 +08:00
openqj and host
84ff968111
fix: deploy verify parsing must ignore ssh warning lines ( #55 )
...
Co-authored-by: host <host@test.local >
2026-10-27 00:41:40 +08:00
openqj and host
32125f7a98
docs: new-host onboarding checklist (HA stage 4) ( #54 )
...
Co-authored-by: host <host@test.local >
2026-10-27 00:30:32 +08:00
openqj and host
de81b1643f
docs: Postgres fast-rebuild runbook (HA stage 3, option 1) ( #53 )
...
Co-authored-by: host <host@test.local >
2026-10-27 00:22:31 +08:00
openqj and host
06b9fd12fd
HA stage 2: DB-shared rate limiting + second server unit + monitor both-servers check ( #52 )
...
Co-authored-by: host <host@test.local >
2026-10-27 00:15:16 +08:00
openqj and host
fc8e337d0f
HA 阶段 1:worktree_host + 跨主机 /pr 队列化 ( #50 )
...
* 部署自动化:唯一发布链路 deploy.py + host_deploy.sh,sha/commit 双重验真,失败回滚
旧链路三次栽过的坑:通用 tar 路径导致旧包误部署、传输不验 sha、
主机脚本多份漂移、health 通过不等于新代码上线。本单把四点变硬门禁:
- 产物唯一命名 codex-app-server-<commit>.tar.gz + .sha256,远端唯一路径
- git archive 产物 + DEPLOYED_COMMIT 清单,默认拒绝脏工作区
- 传输链 scp -> scp_run -> chunk 分片,每路传完远端验 sha,不符删掉重来
- 主机侧唯一脚本:先验 sha/清单,staging 解包 + 编译检查,换目录上线,
work/ 原样保留,health + 上线 commit 不符即回滚并退出非零
- deploy.py 成功后独立 ssh 再读一次 live 清单二次核对
- CI:host-verify 加 deploy/tests;release.yml 只在手动/tag + production
environment 审批时部署,主干推送绝不自动部署生产
- deploy.sh 标记为旧链路弃用
- health 的 commit 字段已在父提交 HA 阶段 0 里随 server.py 落地,
本分支整体包含它;commit 为空时只靠文件核对,门禁同样成立
测试:host 32、cloud 487(4 跳过)、deploy 9,全部通过;
host_deploy.sh 仿真覆盖成功/坏 sha/清单不符/health commit 不符回滚。
生产实机部署未做,等确认后再用新链路推。
* HA stage 1: worktree_host + queued cross-host PR jobs
---------
Co-authored-by: host <host@test.local >
2026-10-27 00:05:18 +08:00
openqj and host
455513dd33
HA stage 0: health reads workers registry, daily pg_dump timer, backup freshness in monitor ( #48 )
...
Co-authored-by: host <host@test.local >
2026-10-26 23:51:54 +08:00
openqj and host
275de39148
docs: multi-machine HA design (phased, grounded in current single-host facts) ( #47 )
...
Co-authored-by: host <host@test.local >
2026-10-26 23:41:38 +08:00
openqj
4c6b6ad039
Merge pull request #46 from openqj/pg-decimal-cost
...
修项目成本按天接口在 Postgres 上的 Decimal 序列化崩溃
2026-10-26 23:19:54 +08:00
host
e384245722
修项目成本按天接口在 Postgres 上的 Decimal 序列化崩溃
...
2026-10-26 项目成本总览实弹:总览一次通过,但 ?day= 请求让
服务端请求线程直接死掉、客户端只看到连接断开。日志根因:
Postgres 的 SUM(bigint) 返回 NUMERIC,psycopg2 映成 Decimal,
按天分支把 SUM 原值塞进 JSON 时 json.dumps 抛 TypeError。
这是按天路径的潜 bug(此前从未在生产 PG 上跑通过按天查询),
不是 PR #45 新加列引入的,但本次实弹把它暴露了出来。
修复:按天两分支与模型总览的数值全部 int() 转换;_send_json
加 Decimal 兜底(整数转 int、非整数转 float),防止同类泄漏
再把请求线程拖死。单测:按天端点 + _json_default。
2026-10-26 23:17:25 +08:00
openqj
70177989f2
Merge pull request #45 from openqj/real-cost
...
逐任务真实成本:上游用量事件 + 价目表计价
2026-10-26 18:44:09 +08:00