Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c121c42e39 | ||
|
|
04eff611e7 | ||
|
|
68f3eefeca | ||
|
|
152479b350 | ||
|
|
ceb0f35418 | ||
|
|
440d95cad8 | ||
|
|
f1b50ad91a | ||
|
|
78b6267d3a | ||
|
|
59fb06304f | ||
|
|
e291058754 | ||
|
|
bf234570c0 | ||
|
|
e978e878df | ||
|
|
1ae243f286 |
@@ -68,10 +68,20 @@ routing:
|
||||
|
||||
# ---- 预算 ----
|
||||
# 没有预算控制的架构,在第一个月账单出来时就会崩。
|
||||
# HEL-882:请求次数硬顶与 token 一样是真实硬闸(发送前原子预占,到顶拒绝,
|
||||
# 重启不归零)。以下数值全部是「未批准」的 fail-closed 默认:执行表
|
||||
# docs/budget-plan-hel874.md A/C 部分待批上限不构成批准,批准前一律 0=拒绝。
|
||||
budget:
|
||||
daily_tokens: 2000000
|
||||
warn_ratio: 0.7
|
||||
hard_stop_ratio: 0.95
|
||||
daily_requests:
|
||||
dialogue: 0 # 主对话+主动措辞+危机(C 表建议 60/日,待批)
|
||||
life: 0 # 生活事件生成(C 表建议 8/日,待批)
|
||||
background: 0 # gist/判定/成长(C 表建议 40/日,待批)
|
||||
semantic: 0 # Mem0 提取 LLM(C 表建议 40/日,待批)
|
||||
# total_requests: 55 # A 部分一次性团队检查总请求硬顶(待批,由总管填)
|
||||
# total_tokens: 500000 # A 部分一次性总 token 硬顶(待批,由总管填)
|
||||
|
||||
# =============================================================================
|
||||
# 语音链路
|
||||
|
||||
@@ -1,24 +1,61 @@
|
||||
# CyberGril 语义记忆层(Mem0 固定源)脱敏配置示例
|
||||
#
|
||||
# 复制为 config/semantic.yaml 并由总管在获批准的预算数值内填写。
|
||||
# embed_cap=0 是安全默认:真实嵌入通道保持关闭(fail-closed),任何调用都会被拒绝,
|
||||
# 不会碰 498/500 的冻结余额。只有白栖知批准的新预算经总管核定后才可调高。
|
||||
# embed_cap=0 / llm_cap=0 是安全默认:真实嵌入与 SDK 内部提取 LLM 全部关闭
|
||||
# (fail-closed,任何真实工作前拒绝),不会碰 498/500 的冻结余额。只有白栖知
|
||||
# 批准的新预算经总管核定后才可调高。硬闸与执行表见 docs/budget-plan-hel874.md。
|
||||
#
|
||||
# 后端选择(环境变量 CYBERGRIL_SEMANTIC):
|
||||
# off —— 关闭(默认;对话记忆检索回落到 R3 词面 FTS 通道)
|
||||
# stub —— 编排接通的自测后端(确定性子串匹配,非语义,不冒充语义能力)
|
||||
# mem0 —— 真实固定源 Mem0(2c81af3bbe27d9bccb09c772d583df9e9be72ee5,mem0ai 2.2.1,
|
||||
# 需已安装的固定 venv,见 docs/runbook-hel870.md)
|
||||
# 需发布 venv 已离线并入固定栈 wheel,见 deploy/hel874-release.md §4)
|
||||
# HEL-874/882 已对固定安装实测:search 必须带 user_id 过滤、payload 直读走
|
||||
# SDK 自带 client、内部 LLM 过独立 llm_ops 闸(证据 docs/rework/hel874/)。
|
||||
|
||||
# 嵌入账本:沿用 HEL-857 系列唯一权威账(embed_ops 表)续账,绝不另起新账
|
||||
# 嵌入账本:沿用 HEL-857 系列唯一权威账(embed_ops 表)续账,绝不另起新账;
|
||||
# SDK 内部提取 LLM 记同库 llm_ops 新表(独立 cap,互不挪用)
|
||||
#
|
||||
# ── 累计上限算式(HEL-885,写入前照此换算,禁止直接填增量)───────────────
|
||||
# 权威账现状:已用 498 条 / 原批上限 500(其中冻结不动 2 条余额)。
|
||||
# 白栖知批准的 B 部分新增嵌入额度(例如 180 条)并入同一累计账:
|
||||
# embed_cap = 500 + 180 = 680 # 原上限 + 新批增量(不是 180!)
|
||||
# embed_reserve = 2 # 原冻结余额 2 条继续保留不动
|
||||
# 可用余量 = 680 − 498 − 2 = 180
|
||||
# 错误示例:embed_cap: 180(对已用 498 的账等于瞬间耗尽并掩盖历史);
|
||||
# 动到旧冻结 2 条(min_used: 498 会拒绝)。
|
||||
ledger_path: /absolute/path/to/embed_ledger.sqlite3
|
||||
embed_cap: 0 # 本产品环境允许使用的嵌入条数上限;0=未批准=全部拒绝
|
||||
embed_reserve: 0 # 预留不动的余量(例如为复测保留 40)
|
||||
embed_cap: 0 # 本产品环境允许使用的嵌入条数累计上限;0=未批准=全部拒绝
|
||||
embed_daily: 0 # 嵌入条数日顶(执行表 C:建议 60/日,待批);0=未批准=拒绝
|
||||
embed_reserve: 0 # 预留不动的余量(例如为复测保留 40;冻结口径见上方算式)
|
||||
llm_cap: 0 # Mem0 内部提取 LLM 调用累计上限(执行表 B:建议 60,待批)
|
||||
llm_daily: 0 # Mem0 内部提取 LLM 日顶(执行表 C:建议 40/日,待批)
|
||||
|
||||
# 冻结前缀(HEL-885 起为必填,不可注释掉):只要 embed_cap / llm_cap 任一 >0,
|
||||
# 缺少、留空或填错的 authority 块都会让 mem0 模式在构造前直接拒绝
|
||||
# (ledger_authority_required / ledger_authority_*,fail-closed,不降级)。
|
||||
# ledger_path 必须仍是含冻结前缀的权威账——指向新空文件重开余额同样被拒。
|
||||
# 数值由总管按权威账现状填写(前 N 行逐行 SHA256 与最少已用条数):
|
||||
authority:
|
||||
prefix_rows: 181
|
||||
prefix_sha256: "<权威账前 N 行逐行摘要,总管填写>"
|
||||
min_used: 498
|
||||
|
||||
# 记录作用域:add(user_id=) 与 search(filters={"user_id":…}) 必须一致;
|
||||
# mem0 2.2.1 拒绝无过滤 search,此键一般不需要改
|
||||
user_scope: cybergril
|
||||
|
||||
# 网关(现有配置同源,config/models.yaml 的 halogen 网关)
|
||||
gateway_base_url: http://192.168.200.11:8731/v1
|
||||
gateway_model: halogen-qwen3.8-flash-next
|
||||
gateway_key: cybergril-local-test
|
||||
# 每次网关调用的硬超时(秒,HEL-889):关闭排空阶段因此有界——已被接受但
|
||||
# 长期不答的请求不会把 worker 与进程关闭无限挂住。默认 60,一般不改。
|
||||
gateway_timeout: 60
|
||||
|
||||
# 统一请求闸:产品进程会把 router 的 PersistentBudget 传给后端,SDK 内部提取
|
||||
# LLM 每次发送前在 "semantic" 类别 + 日 token 池里原子预占(models.yaml 的
|
||||
# daily_requests.semantic 与 daily_tokens 批准值生效),无需在此重复配置。
|
||||
|
||||
# 本地向量库与推导史目录(独立数据目录,随实例一起备份/回滚)
|
||||
vector_root: /absolute/path/to/sem/vec
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Review/install by 总工 only. HEL-874/882 isolated unified candidate, port 8360.
|
||||
# Fresh EMPTY data dir — never the 8300/8310/8320/8340/8350 dirs, never demo data.
|
||||
# CYBERGRIL_SEMANTIC stays stub until Part-B budget approval AND the pinned mem0
|
||||
# stack wheels are staged for 3.11 (deploy/hel874-release.md §4); not presenting a
|
||||
# stub/FTS fallback as the user's real memory experience.
|
||||
# HEL-882: PYTHONPATH makes the product importable from the requirements.lock-only
|
||||
# venv (deps only, no editable install); budget request caps ship at 0 = refuse
|
||||
# every real provider exit until the manager writes approved numbers (see
|
||||
# config/models.yaml budget block + docs/budget-plan-hel874.md).
|
||||
[Unit]
|
||||
Description=CyberGril HEL-874 unified candidate (isolated, R4-assembly)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=cybergril
|
||||
Group=cybergril
|
||||
WorkingDirectory=/opt/cybergril-hel874/current
|
||||
Environment=PYTHONPATH=/opt/cybergril-hel874/current/src
|
||||
Environment=CYBERGRIL_DB=/opt/cybergril-hel874/data/companion.db
|
||||
Environment=CYBERGRIL_MODELS=/opt/cybergril-hel874/current/config/models.yaml
|
||||
Environment=CYBERGRIL_WEB=/opt/cybergril-hel874/current/web/dist
|
||||
Environment=CYBERGRIL_R2_ENABLED=1
|
||||
Environment=CYBERGRIL_SEMANTIC=stub
|
||||
Environment=CYBERGRIL_HOST=0.0.0.0
|
||||
Environment=CYBERGRIL_PORT=8360
|
||||
ExecStart=/opt/cybergril-hel874/current/.venv311/bin/python -m cybergril.api
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
TimeoutStopSec=15
|
||||
UMask=0077
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ReadWritePaths=/opt/cybergril-hel874/data
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,147 @@
|
||||
# HEL-874/882 · 8360 隔离发布包(离线核验过,未执行部署)
|
||||
|
||||
**状态:审核通过与预算批准前不启动面向用户的真实循环、不部署、不合并 PR、
|
||||
不向用户交体验地址。**本文件是发布卡照抄即可执行的包;不触 8300/8310/8320/
|
||||
8340/8350 与共享 ASR/TTS/口型,不访问 4080,不导入演示/测试档案为用户伴侣。
|
||||
|
||||
HEL-882 修正:空目录 checkout、服务 import(PYTHONPATH)、服务账号/权限、
|
||||
mem0 同进程导入的准确缺项与不下载解决路径、空账初始化、请求上限随包默认 0。
|
||||
离线核验命令见 §6(tools/check_release_offline_hel882.py,仅回环、echo 替身、
|
||||
临时目录,零真实网关请求)。
|
||||
|
||||
## 1. 交付物清单
|
||||
|
||||
- 代码:分支 `hel-870-assembly`(PR #11,head 见交付评论),含
|
||||
`deploy/cybergril-hel874-8360.service.example`、`tools/check_release_offline_hel882.py`
|
||||
与本文件。
|
||||
- 依赖:Python 3.11 + `requirements.lock`(uv 离线可装);前端 `web/` 原作者
|
||||
构建产物(`npm ci && npm run build`);语义真实模式另需固定 mem0 栈(§4,
|
||||
需先落 wheel,当前缓存没有 3.11 版)。
|
||||
|
||||
## 2. 安装命令(发布卡执行)
|
||||
|
||||
```bash
|
||||
# 全新目录,逐条照抄;任何一步失败即停
|
||||
# 2.1 服务账号与目录(独立可写数据路径,权限边界明确)
|
||||
useradd --system --home-dir /opt/cybergril-hel874 --shell /usr/sbin/nologin cybergril || true
|
||||
mkdir -p /opt/cybergril-hel874/data # data 必须为空:新账从零初始化
|
||||
chown -R cybergril:cybergril /opt/cybergril-hel874
|
||||
|
||||
# 2.2 代码 checkout:current 必须是全新空目录后 clone(空目录里没有仓库,
|
||||
# 不能直接 git fetch —— HEL-882 修正)
|
||||
git clone ssh://git@192.168.200.36:222/leefer/CyberGril.git /opt/cybergril-hel874/current
|
||||
git -C /opt/cybergril-hel874/current checkout <交付评论中的精确head>
|
||||
|
||||
# 2.3 依赖与前端(复用 uv/npm 离线缓存;缓存缺失时此步需要内网源,发布卡留证)
|
||||
cd /opt/cybergril-hel874/current
|
||||
uv venv --python 3.11 .venv311
|
||||
uv pip install --python .venv311/bin/python -r requirements.lock
|
||||
(cd web && npm ci && npm run build)
|
||||
|
||||
# 2.4 服务单元(单元内已带 PYTHONPATH=<current>/src:requirements.lock 只装依赖,
|
||||
# 产品代码经 PYTHONPATH 进程内可 import —— HEL-882 修正)
|
||||
cp deploy/cybergril-hel874-8360.service.example /etc/systemd/system/cybergril-hel874.service
|
||||
systemctl daemon-reload && systemctl enable --now cybergril-hel874
|
||||
curl -s http://127.0.0.1:8360/api/health | python3 -m json.tool
|
||||
```
|
||||
|
||||
随包 `config/models.yaml` 的请求上限全部为 0(未批准即拒绝):在总管写入批准
|
||||
值(A 部分一次性总量或 C 部分类别日顶)之前,创建/对话/生活/gist/主动措辞/
|
||||
Mem0 提取都会在发送前被硬闸拒绝(`budget_*_exhausted`)——这是设计行为,
|
||||
不是故障;批准值的写入位置与口径见 `docs/budget-plan-hel874.md`。
|
||||
|
||||
## 3. 健康能力矩阵(/api/health 预期)
|
||||
|
||||
| 字段 | 预期 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| version | `R4-assembly` | |
|
||||
| backend.mood / reach_out / semantic | true | 情绪/关系/生活/主动联系接线 |
|
||||
| backend.semantic_backend | `stub`(发布初期) | 真实 mem0 待 B 部分预算批准(§4) |
|
||||
| capabilities.proactive | true | 生活循环开启时 |
|
||||
| capabilities.semantic_memory | `stub` | 不冒充语义 |
|
||||
| voice / vision / weather / materials / backup | false | 界面诚实文案,不伪装 |
|
||||
|
||||
## 4. 真实语义激活前置(B 部分批准后;准确缺项与不下载路径)
|
||||
|
||||
**当前事实(HEL-882 离线核实,tools/check_release_offline_hel882.py R6):**
|
||||
固定安装 hel857-venv 是 Python 3.10.12,产品发布 venv 是 3.11.16,两者
|
||||
site-packages 不能混用;本机 uv/pip 缓存里没有 mem0ai/fastembed/qdrant-client
|
||||
的 3.11 wheel,本卡不下载。因此发布卡要在 **B 部分批准后** 先落 wheel 再激活。
|
||||
|
||||
**HEL-885 命令勘误:旧版此处的 `uv pip download …` 在本机 uv(0.13.5)不存在
|
||||
(`uv pip download --help` 退出 2:unrecognized subcommand 'download',已复测,
|
||||
见 docs/rework/hel885/)。本机确实可用的落 wheel 工具是 pip 的 download 子命令
|
||||
(`python3 -m pip download --help` 退出 0)。发布 venv 由 uv 创建、不带 pip,
|
||||
这不影响:下载在任一装有 pip 的 Python 上执行即可,wheel 按目标平台解析。**
|
||||
|
||||
```bash
|
||||
# 4.1 在有内网源权限的环境把固定栈拉全(发布卡执行并留证;不在本卡执行、
|
||||
# 不下载)。先核对目标机平台:uname -m 必须与 --platform 一致(当前
|
||||
# 部署机为 x86_64 Linux)。
|
||||
uname -m # 预期 x86_64;不符则改 --platform
|
||||
mkdir -p /opt/cybergril-hel874/wheels
|
||||
python3 -m pip download -r deploy/mem0-stack.lock -d /opt/cybergril-hel874/wheels \
|
||||
--only-binary :all: --implementation cp --python-version 3.11 \
|
||||
--platform manylinux2014_x86_64 --abi cp311
|
||||
# 4.1b 固定源身份与哈希留证:下载当时逐 wheel 记录 sha256(与发布评论一并
|
||||
# 存档;mem0ai 的源码身份 2c81af3 / tarball SHA256 见 deploy/mem0-stack.lock
|
||||
# 注释——如需从源码 tarball 侧核对,比对 lock 注释中的值)。
|
||||
sha256sum /opt/cybergril-hel874/wheels/*.whl | tee /opt/cybergril-hel874/wheels/SHA256SUMS
|
||||
# 4.2 同一发布进程离线并入(一个 venv 同时 import 产品与 mem0 —— 唯一被认可形态;
|
||||
# uv pip install 是本机确实存在的子命令,--no-index 离线装)
|
||||
uv pip install --python .venv311/bin/python --no-index \
|
||||
--find-links /opt/cybergril-hel874/wheels -r deploy/mem0-stack.lock
|
||||
.venv311/bin/python -c "import cybergril.api, mem0, qdrant_client, fastembed; print('co-import ok')"
|
||||
```
|
||||
|
||||
**准确缺项与如实上报(HEL-885):** 本机当前没有这些 3.11 wheel,本卡零下载,
|
||||
`wheels/` 目录与 SHA256SUMS 均不存在——发布卡执行 4.1 时才能产生。若某个固定
|
||||
版本在 cp311/manylinux2014_x86_64 没有 wheel(`--only-binary :all:` 会直接报错
|
||||
指出包名),按 lock 注释如实上报缺项,不得换版本号冒充固定安装、不得改用 sdist
|
||||
混装。4.2 的 co-import 是最终判据:任何一个 import 失败即未完成,不得以 stub/
|
||||
FTS 降级包冒充用户的真实完整记忆体验(体验交付必须等本节全部完成 + 新增预算
|
||||
批准)。发布前可先跑 `python3 tools/check_release_offline_hel882.py` 的 R7
|
||||
核验本节两条命令的工具面(pip download 存在、uv pip download 不存在)。
|
||||
|
||||
激活步骤(wheel 落好之后):
|
||||
|
||||
1. `cp config/semantic.example.yaml config/semantic.yaml`,总管填批准值:
|
||||
`embed_cap`/`llm_cap`(B 部分累计,换算算式见示例文件与执行表 §2——
|
||||
500+增量,不是把增量直接填进累计上限)、`embed_daily`/`llm_daily`
|
||||
(C 部分日顶)、`embed_reserve`;`ledger_path` 指向唯一权威
|
||||
`embed_ledger.sqlite3` 续账(`llm_ops` 同库新表),并**填写** `authority`
|
||||
冻结前缀块(HEL-885 起必填:缺/空/错一律在构造前拒绝
|
||||
`ledger_authority_required`,指向新空文件绕过 498/500 冻结同样被拒)。
|
||||
2. `CYBERGRIL_SEMANTIC=mem0` 重启。任何未批调用 fail-closed
|
||||
(`semantic_*_budget_not_approved/exhausted/daily_exhausted`);SDK 内部
|
||||
提取 LLM 同时过统一请求闸(`semantic` 类别 + 日 token 池,发送前预占)。
|
||||
|
||||
## 5. 停用与保留数据回退
|
||||
|
||||
- 停语义层:`CYBERGRIL_SEMANTIC=off`(或 stub)重启;`sem_batch/sem_covered/
|
||||
sem_row_done` 按后端区分保留,重开续跑不重发已完成行(逐行锚点+逐行完成
|
||||
记录,HEL-885),stub 批次不阻塞真实重建。
|
||||
- 停主动联系/生活:`CYBERGRIL_R2_ENABLED=0`。
|
||||
- 整实例回退:`systemctl stop cybergril-hel874` → 数据目录
|
||||
`/opt/cybergril-hel874/data/` 原样保留(聊天/记忆/生活/情绪/关系全量 +
|
||||
请求/token 硬闸计数,SQLite WAL 单文件可整体复制)→ 切回上一 release /
|
||||
上一提交。R4 迁移全部为增量新表(r4.sql:语义/活动/主动/硬闸计数),
|
||||
旧代码可安全打开同一数据库。
|
||||
- 彻底撤销:`systemctl disable --now cybergril-hel874 && rm /etc/systemd/system/
|
||||
cybergril-hel874.service`;数据目录留档不删(用户资料不重置边界)。
|
||||
|
||||
## 6. 发布前必过检查(发布卡执行并留证)
|
||||
|
||||
1. 离线可执行检查(本卡已跑通,证据见交付评论;发布卡在目标机重跑):
|
||||
`python3 tools/check_release_offline_hel882.py --venv <发布venv>/bin/python`
|
||||
—— R1 空目录 checkout、R2 服务 import+health、R3 未批上限 fail-closed、
|
||||
R4 批准后全链(echo 替身)、R5 停止后数据保留/重开 epoch+1、
|
||||
R6 mem0 同进程导入现状(缺什么、怎么补,§4)。
|
||||
2. `python3 scripts/check_requirements.py --base-ref origin/main --handoff
|
||||
docs/requirements/handoff-HEL-882.json` 及 `--base-ref
|
||||
origin/agent/agent/de8874a3803b`(双目标各含/不含 handoff)。
|
||||
3. 3.11 venv 全量 pytest + ruff + web typecheck/test/build。
|
||||
4. 隐私负例(未分享伴侣行不出现在任何 provider 输入/DTO/页面)与重开恢复
|
||||
(epoch+1、消息一致、主动消息带标记)。
|
||||
5. A 部分预算未批前:`semantic.yaml` 缺省即 cap=0,`/api/health` 的
|
||||
semantic_backend 必须仍为 stub;`models.yaml` 请求上限未批前必须全 0。
|
||||
@@ -0,0 +1,138 @@
|
||||
# HEL-895 · 统一发布候选(离线核验过,未执行部署)
|
||||
|
||||
> **HEL-896 修订(2026-10-27,正常追加 PR#11)**:按总管独立复审补齐——①浏览器
|
||||
> 「真实服务 7/7」中 B3 原为已证实误报,测试已修为精确交互+严格断言(9 项检查,
|
||||
> 含权限 API 调用计数与非回环出站守卫,证据 `docs/rework/hel896/browser/`);
|
||||
> ②A/B/C 预算改为独立团队测试实例 + 全新用户实例的可执行分账
|
||||
> (`docs/budget-package-hel895.md` §6);③固定 SDK 缺项勘误为 41 项闭包清单
|
||||
> (§4);④归档/回退改为一致性快照口径(§5)。候选 head 与交付物不变,未部署、
|
||||
> 未合并、未写权威账。
|
||||
|
||||
**状态:候选。审核通过与预算/部署授权前不启动面向用户的真实循环、不部署、不合并
|
||||
PR、不交体验地址。** 本文件把已通过独立复审的内核(HEL-894 @ `152479b`)与
|
||||
HEL-881 已审核的主动留言前端(`bf234570`)装成一个统一候选,给出精确 head、
|
||||
交付物清单、安装命令、健康矩阵与停用回退。发布卡照抄即可执行;不触 8300–8350
|
||||
与共享 ASR/TTS/口型,不访问 4080,不导入演示/测试档案为用户伴侣。
|
||||
|
||||
## 0. 精确 head 与整合方式
|
||||
|
||||
- **候选 head**:见交付评论(分支 `hel-870-assembly`,PR #11 的正常追加提交,
|
||||
未强推、未合并)。第一父为 HEL-894 通过复审的 `152479b3503c3d1ab27681170e6bfd592c54380a`,
|
||||
合并入 HEL-881 已审核的 `bf234570c0b6595a72f8dbc79e42c63685e08e01`
|
||||
(origin/hel-880-inbox-continuation,与 origin/hel-875-proactive-wiring 同点)。
|
||||
- **整合的机械性**:母线 `31970c4..152479b` 从未触碰 `web/`(`git diff 31970c4
|
||||
152479b -- web/` 为空),合并只带入原作者六个前端文件与
|
||||
`handoff-HEL-875/880.json` 共 9 个文件;`src/tests/tools/config` 相对 `152479b`
|
||||
逐字不变。无重设计、无视觉决定;旧 `b48d28b` 测试合并未推送,由本候选取代。
|
||||
- **合成后验证(全部在本 head 实跑)**:299 pytest(3.11.16 venv +
|
||||
requirements.lock 离线)+ ruff 干净;web typecheck 0 错、lint 0 错/1 条既有警告、
|
||||
vitest 55/55、build 通过且 CSS 仍为 `index-DdDYzR5S.css`(视觉零改动);
|
||||
固定 SDK 回归:关闭所有权 16/16、关闭排队 12/12、处理中生命周期 6/6、语义恢复
|
||||
7/7、真实接线 12/12;发布离线检查 15/15;**真实 HTTP 分页合同 7/7**
|
||||
(`tools/verify_http_pagination_hel895.py`,真实服务 + 327 行播种 + fetchAfter
|
||||
缺口游标移植对真实后端复验);**真实服务浏览器 7/7**(`web/tests/hel895_real.mjs`,
|
||||
Chromium CDP 对真实 API:留言卡、舞台播放、260 行缺口不跳留言、重启 epoch 去重、
|
||||
零权限申请。**HEL-896 勘误:原 7/7 中 B3 为已证实误报**——兜底断言+点击
|
||||
「日子与回忆」导航钮,`stage=false` 仍 PASS;修订版改精确交互+严格断言共 9 项
|
||||
检查全过,含权限 API 调用计数 0 与非回环出站守卫 0,证据
|
||||
`docs/rework/hel896/browser/`,原误报证据保留于 `docs/rework/hel896/browser-before/`)。双目标及实际母线需求检查各含/不含
|
||||
handoff-HEL-895 共 6/6 通过(origin/main、原产品目标 de8874a3803b、实际母线)。
|
||||
|
||||
## 1. 交付物清单
|
||||
|
||||
| 交付物 | 位置/来源 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 代码 | 分支 `hel-870-assembly`,精确 head 见交付评论 | 含本文件、`docs/budget-package-hel895.md`、`tools/verify_http_pagination_hel895.py`、`web/tests/hel895_real.mjs` |
|
||||
| Python 依赖 | `requirements.lock`(19 项固定版本,uv 离线可装) | Python 3.11(发布机 3.11.16 实测) |
|
||||
| 前端产物 | `web/`(原作者界面 + HEL-875/880 主动留言) | `npm ci && npm run build`;CSS 哈希与已审核版一致 |
|
||||
| mem0 固定栈 | `deploy/mem0-stack.lock`(mem0ai 2.2.1 / qdrant-client 1.20.0 / fastembed 0.9.1) | **3.11 缺 41 项闭包,见 §4;缺项不阻塞其余发布** |
|
||||
| systemd 模板 | `deploy/cybergril-hel874-8360.service.example`(PYTHONPATH=src 已内置) | 独立单元/端口/数据目录模式 |
|
||||
| 配置模板 | `config/models.yaml`(预算全 0)+ `config/semantic.example.yaml` | 批准值写入位置见 `docs/budget-package-hel895.md`(A 仅团队实例,§6) |
|
||||
| 预算执行包 | `docs/budget-package-hel895.md`(含 §6 HEL-896 分账修订) | A/B/C 一次批准核定稿,默认 0 |
|
||||
| HEL-896 工具 | `tools/verify_budget_split_hel896.py`、`tools/audit_sdk_offline_hel896.py`、`tools/release_snapshot_hel896.py`、修订版 `web/tests/hel895_real.mjs` | 分账切换验证、SDK 余缺只读审计、一致性快照/回退验证、严格浏览器检查 |
|
||||
|
||||
## 2. 安装命令(发布卡执行)
|
||||
|
||||
与 `deploy/hel874-release.md` §2 完全相同(服务账号 → 全新空目录 clone 本 head →
|
||||
`uv venv --python 3.11` + `uv pip install -r requirements.lock` → `npm ci &&
|
||||
npm run build` → 安装 service → `curl /api/health`),此处不重复抄写;唯一差别是
|
||||
`git checkout <本文件 §0 的精确 head>`。发布前必过检查同样按 `deploy/hel874-release.md`
|
||||
§6 执行,另加本卡两个新工具(真实 HTTP 分页合同、真实服务浏览器)在目标机重跑。
|
||||
|
||||
## 3. 健康能力矩阵(/api/health 预期)
|
||||
|
||||
与 `deploy/hel874-release.md` §3 一致:`version=R4-assembly`;backend
|
||||
mood/reach_out/semantic 接线;`semantic_backend=stub`(发布初期,真实 mem0 待 §4
|
||||
与 B 部分预算);capabilities.proactive=true(生活循环开启时);voice/vision/
|
||||
weather/materials/backup=false。**主动留言前端已并入本候选:生活循环开启且产生
|
||||
真实 reach-out 后,打开的页面每 20 秒读一次新行、回来立即读,未读有标记;该行为
|
||||
已在本 head 用真实服务验证(§0)。**
|
||||
|
||||
## 4. 固定 SDK(3.11)准确缺项与不下载路径
|
||||
|
||||
**HEL-896 只读审计修订(2026-10-27,`tools/audit_sdk_offline_hel896.py`,证据
|
||||
`docs/rework/hel896/sdk-audit/`)**:此前「只缺 mem0ai/qdrant-client/fastembed 三个
|
||||
cp311 wheel 及纯 Python 传递依赖」的概括**不准确**,勘误如下:
|
||||
|
||||
- 三个顶层包固定版本(mem0ai 2.2.1 / qdrant-client 1.20.0 / fastembed 0.9.1)的
|
||||
WHEEL 均为 `py3-none-any` 纯 Python,本身无 cp311 专用轮一说;**mem0ai 的安装来源
|
||||
是本地固定源码**(hel857-src,`deploy/mem0-stack.lock` 源码提交 2c81af3、tarball
|
||||
SHA256 `9dcef930…dda23` 已在本机 hel857-data 实测一致)——从索引下载同名 2.2.1
|
||||
wheel 不证明与固定源码相同,发布必须用固定 tarball/源码离线构建安装。
|
||||
- 按 3.11 Linux markers + extras 从 3.10 固定栈安装元数据求闭包共 **55 项,产品
|
||||
3.11 venv 缺 41 项**(28 纯 Python + 13 原生扩展:grpcio/numpy/onnxruntime/
|
||||
tokenizers/sqlalchemy/protobuf/greenlet/pillow 等)。这是**依赖闭包清单**,不是
|
||||
已完成的 cp311 解析或下载结果。
|
||||
- 当前本机缓存对 41 项缺项**立即可复用**的只有 3 项(greenlet 3.5.6 cp311、
|
||||
pillow 12.3.0 cp311、truststore 0.10.5 py3-none-any,uv wheels-v6/v7 缓存实测);
|
||||
其余 37 项 + mem0ai 固定源码离线构建所需 build backend(hatchling 未在缓存)由
|
||||
发布卡获授权后按 `deploy/hel874-release.md` §4 拉取,逐项 sha256 留证,下载清单
|
||||
保持 `deploy/mem0-stack.lock` 与固定源码身份;缺项不阻塞其余发布工作。
|
||||
- **fastembed 模型缓存实测**:hel857-data/fastembed-cache 含真实模型
|
||||
`Qdrant/bge-small-zh-v1.5` 与 `Qdrant/bm25`(约 92MB);发布机落位=实例数据目录
|
||||
内该缓存的拷贝,真实 mem0 模式的 cache_dir 指向它;本卡零下载、不推理。
|
||||
- **seq48–51 pip 事件勘误(缓存取证)**:HEL-895 运行中去掉 `--offline` 安装的
|
||||
pip 26.2.2 在 uv 缓存的 wheel 条目创建于 2026-10-27 02:10:04(该运行窗口内,
|
||||
来源 files.pythonhosted.org,且为窗口内唯一新增缓存条目)——该步发生了网络
|
||||
获取,前卡「零下载」对此步不成立;requirements.lock 装机同期未产生新缓存条目
|
||||
(全部命中既有缓存)。字节级完整下载与条件请求无法由缓存取证区分,按保守口径
|
||||
记为下载。**后续一切安装强制 `--offline`/`--no-index`。**
|
||||
|
||||
产品 venv 目前 `import mem0/qdrant_client/fastembed` 均缺、产品代码导入不受影响;
|
||||
在 wheel 落位且 co-import 通过前,不得以 stub/FTS 冒充真实 Mem0 体验就绪。
|
||||
|
||||
## 5. 停用与保留数据回退
|
||||
|
||||
与 `deploy/hel874-release.md` §5 相同:`CYBERGRIL_SEMANTIC=off|stub` 停语义层
|
||||
(表保留、按后端区分续跑);`CYBERGRIL_R2_ENABLED=0` 停生活/主动联系;整实例
|
||||
`systemctl stop` → 数据目录原样保留 → 切回上一 release/上一提交(R4 迁移全部
|
||||
增量新表,旧代码可安全打开同一数据库);彻底撤销留数据档不删。
|
||||
|
||||
**HEL-896 归档/回退修订(2026-10-27)**:此前「SQLite WAL 单文件可整体复制/归档」
|
||||
的口径**不安全**——在线库只复制主 `.db` 会丢 WAL 里已提交的资料(总管复现件连
|
||||
`companion` 表都不在;本卡 `tools/release_snapshot_hel896.py` 自测同样复现
|
||||
`no such table: companion`,证据 `docs/rework/hel896/release-snapshot/selftest.json`)。
|
||||
发布归档一律二选一:**停服务 + `PRAGMA wal_checkpoint(TRUNCATE)` 后整目录归档**
|
||||
(推荐),或**在线用 SQLite backup API 逐库一致性快照**。照抄命令:
|
||||
|
||||
```bash
|
||||
# 覆盖范围:产品库(CYBERGRIL_DB)、语义向量目录 vector_root、推导史 history_root、
|
||||
# 权威嵌入账 ledger_path(按部署实例 config/semantic.yaml 实际路径逐项代入)
|
||||
.venv311/bin/python tools/release_snapshot_hel896.py snapshot \
|
||||
--name <release-或阶段名> --db <产品库> [--db <权威账>] \
|
||||
--file <vector_root> --file <history_root> --out /opt/<实例>/archives/
|
||||
.venv311/bin/python tools/release_snapshot_hel896.py verify --archive <产物>.tar.gz
|
||||
```
|
||||
|
||||
产物含逐文件 SHA256 清单、**代码精确提交**(快照时刻 `git rev-parse HEAD`)、
|
||||
逐表行数核对与回退演练结果;不删档,旧归档永久保留。回退=停服务 → 解包快照到
|
||||
新数据目录 → 指回上一 release/上一提交 → 起 service → `verify` 通过后交付。
|
||||
本节是**发布归档/回退要求**,不冒充 R6(CG-009 每日自动备份/手动完整导出)完成。
|
||||
|
||||
## 6. 本候选不做的事
|
||||
|
||||
不合并任何 PR(PR #11 保持开放待总管/白栖知决定);不部署、不交体验地址;
|
||||
零真实 LLM/嵌入/下载;不触 8300–8350、共享语音口型、GPU/4080;不导入演示测试
|
||||
档案为用户伴侣;不替视觉作者改稿(前端内容以原作者六文件与 CSS 可追溯为准)。
|
||||
R5(HEL-878/879 语音)、R6(天气/素材/完整备份)、R7(有条件视觉观察)未接入,
|
||||
界面保持诚实文案,不冒充已运行。
|
||||
@@ -0,0 +1,11 @@
|
||||
# CyberGril 语义记忆层固定栈清单(HEL-882;B 部分批准后由发布卡拉取)
|
||||
# 固定源:mem0ai 2.2.1(源码提交 2c81af3bbe27d9bccb09c772d583df9e9be72ee5,
|
||||
# tarball SHA256 9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23)。
|
||||
# 用于 deploy/hel874-release.md §4:在具备内网源权限的环境按 3.11 平台拉全
|
||||
# wheel 后离线并入发布 venv;本卡不执行下载。版本来自 hel857-venv 实测安装
|
||||
#(Python 3.10.12 侧 mem0ai 2.2.1 / qdrant-client 1.20.0 / fastembed 0.9.1),
|
||||
# 3.11 侧需同版本 wheel;若某包无 3.11 wheel,发布卡须如实上报缺项,不得
|
||||
# 换版本号冒充固定安装。
|
||||
mem0ai==2.2.1
|
||||
qdrant-client==1.20.0
|
||||
fastembed==0.9.1
|
||||
@@ -0,0 +1,163 @@
|
||||
# HEL-895 · 真实全链一次批准执行包(A/B/C 合并核定稿;仍全部待授权,默认 0 不变)
|
||||
|
||||
> **HEL-896 分账修订(2026-10-27)**:总管独立复审证实,若按本文件 §1 原表把 A 的
|
||||
> `total_requests=55` 写进与 B/C 共用的产品库,B 的 `mem0_extract` 与 C 的
|
||||
> `dialogue_main` 在 A 用满 55 次后都被 `budget_total_requests_exhausted:55/55` 挡死
|
||||
> (复现 `tools/verify_budget_split_hel896.py` S0;账表也无阶段/授权来源字段)。
|
||||
> **执行口径改为 §6:A 落在独立团队测试实例自己的库,B/C 落在全新用户实例的库,
|
||||
> 用户实例永不配置一次性总量。** §1–§5 与本修订冲突处以 §6 为准;四道闸叠加、
|
||||
> 保守入账、碰顶停机等语义不变。
|
||||
|
||||
本文件把 `docs/budget-plan-hel874.md` 的 A/B/C 三表收拢成**一次可批准的具体执行包**:
|
||||
每一步的请求/嵌入/失败-取消-重试上限、累计与日顶的叠加口径、一次性团队检查与用户
|
||||
日常使用的分账、碰顶停机语义与留证方式。技术核对基于本卡实际代码与配置
|
||||
(`src/cybergril/life/model.py` 的 `PersistentBudget`、`src/cybergril/think/budget.py`、
|
||||
`config/models.yaml`、`config/semantic.example.yaml`、299 pytest 含
|
||||
`tests/test_hard_gates_hel882.py`)。
|
||||
|
||||
**批准状态:三部分均未批准。随包 `models.yaml` 全部上限为 0(未批准即发送前拒绝),
|
||||
`semantic.example.yaml` 缺省全 0。本卡零真实调用、零真实嵌入、零权威账写入。**
|
||||
旧账 39/60(真实请求)与 498/500(嵌入,冻结 2 条)及 HEL-835 闸原样保全,
|
||||
不挪用、不重置、不并入。真实数值只有白栖知批准后由总管写入。
|
||||
|
||||
## 1. 批准项(一个包,三个独立账)
|
||||
|
||||
| 部分 | 用途 | 硬顶(批准后由总管写入) | 账本(HEL-896 修订:见 §6 实例分账) |
|
||||
| --- | --- | --- | --- |
|
||||
| A · 一次性团队整体检查 | 8360 隔离实例按体验链 1–7 全链走一遍(stub 语义,零嵌入) | 总量 55 次请求 / 500,000 tokens / 0 嵌入(`budget.total_requests`/`total_tokens`,跨日累计)——**只写团队实例自己的库** | 团队实例库的 `budget_total`/`request_usage`/`token_usage` |
|
||||
| B · 首周语义激活 | `CYBERGRIL_SEMANTIC=mem0` 真实激活观察 | 提取 LLM ≤60 次(`llm_cap`);嵌入 ≤180 条新批 → 按算式写 `embed_cap=680`、`embed_reserve=2`(权威账已用 498/原批 500) | `llm_ops`(新表)/`embed_ops`(权威账续账,`authority` 冻结前缀必填) |
|
||||
| C · 用户日常使用 | 体验地址交付后每日 | dialogue ≤60/日、life ≤8/日、background ≤40/日、semantic ≤40/日(`daily_requests`);嵌入 ≤60 条/日(`embed_daily`);tokens ≤2,000,000/日(`daily_tokens`,全类别共池) | 用户实例库的持久表 + 日顶列(**不配置一次性总量**) |
|
||||
|
||||
【HEL-896 勘误】原文「A 当天同时受 C 类日顶约束」按共用产品账本表述,会把 A 的消费
|
||||
算进用户的日账并让 A 的 55 总量挡住 B/C;修订后 A 在自己的实例与账本内受**同数值**的
|
||||
类别日顶约束(§6 S1 验证:life 日顶 8/8 先于总量拒绝、semantic 余量内由总量拒绝——
|
||||
叠加取最紧),与用户实例日账互不相干。B 期间的对话/生活/判定请求并入 C 日账,
|
||||
不占 B 的 60 次。三部分独立授权来源保持:A 需总管新批(产品环境网关用量不属于
|
||||
HEL-857 的 60 次实验授权);B/C 需白栖知明确批准。
|
||||
|
||||
## 2. 叠加口径(逐笔发送前原子预占;总管核定值写入后自动生效)
|
||||
|
||||
一次真实 provider 发送(创建/对话/生活/gist/判定/成长/主动措辞/Mem0 内部提取)
|
||||
在 `PersistentBudget.acquire` 同时过四道闸,任一到顶即 `BudgetStop` 拒绝,**不降档续调**:
|
||||
|
||||
1. 类别日顶(该调用所属 `daily_requests` 类别:dialogue/life/background/semantic);
|
||||
2. 日 token 池(`daily_tokens`,全类别共池;70% 警告、95% 非关键场景 structured 预检先停);
|
||||
3. A 部分一次性总量(`total_requests`/`total_tokens`,跨日累计,重启不归零);
|
||||
4. B 部分类别账:Mem0 内部提取 LLM 另过 `llm_cap` 累计 + `llm_daily`;嵌入过
|
||||
`embed_cap` 累计(含冻结 `embed_reserve`)+ `embed_daily`。
|
||||
|
||||
**交叉核对结论(本卡逐项核过代码与测试):**
|
||||
- **交叉(有意叠加,非重复计费)**:A 的每次请求同时计入 A 总量与当天类别日顶;
|
||||
B 的每次提取 LLM 同时计入 `llm_ops` 累计与 semantic 日顶 + 日 token 池。两处计数
|
||||
用途不同(一个限一次性总量/首周观察,一个限每天),不会互相退款或放宽。
|
||||
- **不重复**:嵌入只记 `embed_ops`(条数),不进请求计数;提取 LLM 只在
|
||||
`llm_ops`+统一请求闸各记一次;主对话与语义层各自独立闸(`tests/` 双实例同库复现)。
|
||||
- **不漏记**:四个请求类别(dialogue/life/background/semantic)+ 嵌入 + 全池 token
|
||||
已覆盖全部产品出站点;SDK 隐藏重试已禁用(`max_retries=0`),每次实际发送都过闸。
|
||||
|
||||
## 3. 失败、取消与重试(保守入账,不退款)
|
||||
|
||||
- 发送前预占(请求次数 + 保守 token 估算:输入字符 1:1 + 声明 max_tokens 全额);
|
||||
provider 返回真实 usage 按实际修正(多补少退)。
|
||||
- 未知 usage/断流/失败/取消保守保留全部预占;已发送的成本不当未发生。唯一释放
|
||||
路径是可证明未连接(`httpx.ConnectError` 级)的显式 `release()`。
|
||||
- 真实错误重试 ≤3 次/批;预算拒绝不耗重试次数——作业回 `queued`
|
||||
(reason=`semantic_*_budget_*`),总管调 cap 后同批续跑,已完成行不重发。
|
||||
- 嵌入:单条失败释放;批次中途失败整批保守落账。
|
||||
|
||||
## 4. 碰顶停机语义与留证
|
||||
|
||||
- **全部硬闸在发送前**拒绝(`budget_*_exhausted` / `semantic_*_budget_*`),拒绝如实
|
||||
记录,不补发、不换渠道。
|
||||
- 语义层到顶只停语义(检索回落词面 FTS,主对话不中断);日 token 到顶停当日全部
|
||||
真实调用;A 总量到顶即团队检查收尾。
|
||||
- 留证:`budget_total`/`request_usage`/`token_usage`/`embed_ops`/`llm_ops` 表 +
|
||||
服务日志 + 隔离实例数据目录(【HEL-896 勘误】在线库直接复制主 `.db` 会丢 WAL 中
|
||||
已提交行——归档一律停服务+checkpoint 或 SQLite backup API 一致性快照,见
|
||||
`tools/release_snapshot_hel896.py` 与 `deploy/hel895-release-candidate.md` §5);
|
||||
碰顶当时的拒绝 reason 与计数快照截图/导出一并归档到交付评论。
|
||||
|
||||
## 5. 本包默认状态(本卡随包可执行)
|
||||
|
||||
`config/models.yaml`:`daily_tokens: 2000000`(结构性默认值,非授权)、
|
||||
`daily_requests.*` 全 0、A 部分总量两行保持注释(待批);`config/semantic.example.yaml`
|
||||
全部 cap/daily 为 0、`authority` 必填结构就位。**未写入批准值前,任何创建/对话/
|
||||
生活/gist/判定/成长/主动措辞/提取调用都在发送前被硬闸拒绝(`budget_*_exhausted`),
|
||||
这是设计行为;已由 `tools/check_release_offline_hel882.py` R3 与 299 pytest 复现。**
|
||||
|
||||
## 6. HEL-896 可执行分账与阶段切换(独立团队测试实例 + 全新用户实例)
|
||||
|
||||
本节把 A/B/C 落成可直接执行的实例/账本拆分;全部语义由
|
||||
`tools/verify_budget_split_hel896.py` 在临时库+替身上验证(零 provider、零真实 cap
|
||||
变更,证据 `docs/rework/hel896/budget-split/budget-split.json`)。不新造预算引擎,
|
||||
不改表结构:`budget_total/request_usage/token_usage` 无阶段字段这一事实,
|
||||
正是用实例隔离代替字段拆分的理由。
|
||||
|
||||
### 6.1 实例与账本拆分
|
||||
|
||||
| | 团队测试实例(A) | 用户实例(B/C) |
|
||||
| --- | --- | --- |
|
||||
| 形态 | 8360 端口隔离 systemd 单元(`deploy/cybergril-hel874-8360.service.example` 模式),独立数据目录 | 发布候选部署的全新实例,**全新空库**(用户自己创建唯一真实档案) |
|
||||
| `models.yaml` | `total_requests: 55`、`total_tokens: 500000` + 当日类别日顶(同 C 表数值)+ `daily_tokens` | **永不配置 `total_requests`/`total_tokens`**(保持注释/缺省);只写 C 表日顶;`budget_total` 行仅作流量记录,无总量闸(S3 验证) |
|
||||
| 语义层 | `CYBERGRIL_SEMANTIC=stub`(零嵌入、零真实 Mem0) | B 批准后 `=mem0`;`embed_cap/llm_cap/embed_daily/llm_daily` 只在白栖知批准后由总管写入 `config/semantic.yaml`(含 `authority` 冻结前缀) |
|
||||
| 账本 | 自己的 SQLite(`budget_total` 等);封账后归档留存 | 自己的 SQLite 日账 + 权威 `embed_ops` 续账(498/500 + 冻结 2 条原样) |
|
||||
| 授权来源 | 总管新批(产品环境网关用量,不属于 HEL-857 60 次实验授权) | B/C 均需白栖知明确批准(一次批准包 = 本文件) |
|
||||
|
||||
误配守门:用户实例若误写 `total_requests`,A 型堵死会立刻复现(S4)——发布卡
|
||||
部署检查单必须逐项核对用户实例 `models.yaml` 无一次性总量两行、团队实例配置只存在
|
||||
于 8360 数据目录。
|
||||
|
||||
### 6.2 A 的执行与封账
|
||||
|
||||
1. 总管批准后写入团队实例 `models.yaml`(仅该实例),按
|
||||
`docs/budget-plan-hel874.md` §1 体验链 1–7 逐步执行:创建/诠释/习惯 4、对话 16、
|
||||
生活 8、判定/成长/gist 20、主动措辞 3,其余额度备用至 55;每步在四道闸下原子
|
||||
预占(类别日顶→日 token 池→一次性总量;S1 验证叠加取最紧)。
|
||||
2. **A 结束即封账**,两种到达方式同义:额度用满(`budget_total_requests_exhausted:55/55`
|
||||
如实拒绝、不补发)或体验链走完。封账动作:`systemctl stop` 8360 实例 →
|
||||
`tools/release_snapshot_hel896.py` 做一致性快照(停服+checkpoint/backup API)→
|
||||
归档(数据目录 + 哈希清单)交交付评论。封账跨重启、跨归档不失效(S2 验证),
|
||||
`budget_total` 永不手工清零。
|
||||
3. **旧测试数据不会转成用户伴侣**:测试档案、聊天、生活数据只存在于团队实例库与
|
||||
归档;用户实例是全新空库,由用户自己创建档案;产品无任何「测试库导入用户库」
|
||||
路径,发布卡明令禁止把 8360 数据目录复制/合并进用户实例数据目录。
|
||||
4. 后续任何团队复检 = 新批准 + 新实例/新库(或在 8360 实例上以新批准抬
|
||||
`total_requests`),绝不续在用户账上。
|
||||
|
||||
### 6.3 B/C 的开始与日常
|
||||
|
||||
1. **B 开始的配置与授权**:白栖知批准后,总管把 `llm_cap: 60`、按算式
|
||||
`embed_cap: 680`、`embed_reserve: 2`、`llm_daily/embed_daily`(C 表值)与
|
||||
`authority`(权威账前缀逐行摘要 + `min_used: 498`)写入用户实例
|
||||
`config/semantic.yaml`,`CYBERGRIL_SEMANTIC=mem0` 生效;缺/错 authority 在构造前
|
||||
fail-closed(HEL-885 既有验证,不在本卡重复)。
|
||||
2. **真实 Mem0 冒烟与 B 首周的关系**:A 的 stub 检查证明产品链路零语义也能走通;
|
||||
B 激活当天先做**真实 Mem0 冒烟**(数条 add/search/原话回读),其 LLM/嵌入消费
|
||||
全部计入 B 的 `llm_ops`/`embed_ops`(即 60/180 的一部分,不另设隐藏额度),冒烟
|
||||
通过即进入首周观察;B 期间用户对话/生活/判定照走 C 日账,与 B 的累计账并行不占额。
|
||||
3. **B 到顶的停/续语义**:任一 cap 到顶 → 作业批回 `queued`
|
||||
(reason=`semantic_*_budget_*`)不丢行不耗重试,语义层停(检索回落词面 FTS,
|
||||
主对话不中断);**续跑唯一路径 = 新批准按算式抬 cap**(`embed_cap = 原上限 +
|
||||
新增量`),总管写入后同批续跑,已完成行不重发。无新批准则语义层停在原地,
|
||||
不自动翻页、不挪用 C 额度、不动冻结余额。
|
||||
4. **首周结束的停/续语义**:7 天观察期满,总管导出 `llm_ops`/`embed_ops`/日账实际
|
||||
用量与质量观察报告交白栖知;未获新批准,B 保持当时 cap(用尽即停语义),
|
||||
C 日常照常;获批续期则按新算式写新 cap。B 与 C 无任何自动转换或继承。
|
||||
5. **全局累计与日顶的叠加**(仅用户实例内部):每笔真实发送四道闸原子预占、取最紧
|
||||
(类别日顶 → 日 token 池 → B 的累计 cap×2 → embed/llm 日顶);A 的总量闸不在
|
||||
用户实例存在(S3/S4 验证两侧)。39/60(HEL-857 真实请求旧账)与 498/500、冻结
|
||||
2 条、HEL-835 闸是历史授权事实记录,不属于任何实例配置,本轮零改动(S5)。
|
||||
|
||||
### 6.4 一次负责的有限推荐(供总管一次性向白栖知申请;默认值不变)
|
||||
|
||||
- **A(总管权限内新批)**:55 次请求 / 500,000 tokens / 0 嵌入,仅 8360 团队实例,
|
||||
体验链走完即封账归档。
|
||||
- **B(白栖知批准)**:提取 LLM 累计 60 次;嵌入新增 180 条(`embed_cap=680`、
|
||||
`embed_reserve=2` 续权威账);`llm_daily=40`、`embed_daily=60`;首周观察,期满
|
||||
凭实际用量报告决定停/续。
|
||||
- **C(白栖知批准)**:dialogue 60/日、life 8/日、background 40/日、semantic 40/日;
|
||||
嵌入 60 条/日;tokens 2,000,000/日。
|
||||
- 推荐理由:A 按体验链实测上界+备用;B 按 HEL-874 实测单次开销(add 1 LLM + 2–3
|
||||
嵌入)对 60 次提取 ≈ 最多 180 条嵌入;C 按生活槽 4/天与 60 轮对话日常上界。
|
||||
真实失败/取消保守入账不退款、重试 ≤3 次/批(§3)不额外放大。
|
||||
- 三笔互相独立、逐笔硬闸、默认 0;未批准部分保持 fail-closed,不因其余获批自动放开。
|
||||
@@ -0,0 +1,140 @@
|
||||
# HEL-874/882 · 真实整体检查执行表(有确定上限,交总管核定)
|
||||
|
||||
本表取代 runbook-hel870 §6 的估算口径:**原「约 20–30 次请求 / 100–200 条嵌入」
|
||||
只是估算,不构成任何批准额度。**HEL-874 本卡零真实模型调用、零出站;
|
||||
**HEL-882 勘正**:HEL-874 的真实 SDK 验证脚本当时未替换 fastembed 边界,
|
||||
运行过真实本地嵌入推理(可证下限 12 条,详见 docs/rework/hel874/erratum-hel882.json),
|
||||
自 HEL-882 起验证边界为确定性 512 维替身 + 禁非回环,零真实嵌入。
|
||||
|
||||
三部分各自独立账本与授权来源;超出既有授权的部分由总管向白栖知申请,
|
||||
批准前对应硬闸保持 `cap=0`(fail-closed,任何真实工作前拒绝)。
|
||||
HEL-857 系列冻结余额(真实请求 39/60、嵌入 498/500、HEL-835 闸)不挪用、
|
||||
不清零、不并入本表任何一部分;**换新账本文件绕过冻结会被
|
||||
`verify_ledger_authority` 拒绝(HEL-882)**。
|
||||
|
||||
## 0. 实测单次开销与入账口径(真实固定 SDK 验证得出,docs/rework/hel874/)
|
||||
|
||||
| 操作 | LLM 请求 | 嵌入条数 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| Mem0 单行 add(infer) | 1 | 2–3 | phase1 查询嵌入 1 + phase3 提取批嵌入 N + phase7 实体 upsert 0..N |
|
||||
| Mem0 search | 0 | 1 | 问答每轮 1 条查询嵌入 |
|
||||
| 失败/取消(HEL-882 口径) | 见下 | 见下 | **保守入账,不退款** |
|
||||
| 重试上限 | 每批 ≤3 次 | 同上×3 | 预算拒绝不耗次数(重排队等预算);真实错误 3 次后 failed |
|
||||
|
||||
**入账口径(HEL-882 修正,总管独立复现驱动):**
|
||||
|
||||
- **发送前预占**:所有真实 provider 出口(创建/对话/生活/gist/判定/成长/主动
|
||||
措辞及 Mem0 内部提取 LLM)在发送前经 `PersistentBudget.acquire` 原子预占
|
||||
「请求次数(类别日顶 + 一次性总量)+ 日 token(保守估算)」,任一到顶即
|
||||
`BudgetStop` 拒绝——**不再降档继续调用**;持久落库(`token_usage`/
|
||||
`request_usage`/`budget_total`),重启不归零、并发不越顶。
|
||||
- **token 保守性**:估算 = 输入字符数 1:1 + 声明的 max_tokens 全额(中英文
|
||||
两个方向都高估);provider 返回真实 usage 按实际修正(多补少退),未知
|
||||
usage/断流/失败/取消保守保留全部预占——已发送的成本不当未发生。
|
||||
- **SDK 隐藏重试已禁用**:固定安装内 OpenAI client `max_retries=0`,每次实际
|
||||
发送过闸计数;被网关应答过的失败(含 5xx)照实入账,只有可证明未连接
|
||||
(httpx.ConnectError)才释放预占。
|
||||
- **嵌入批次保守性**:单条嵌入失败(未产出向量)释放;批次中途失败无法证明
|
||||
哪些已推理,整批保守落账。
|
||||
- **Mem0 内部 LLM**:独立 `llm_ops` 累计账(B)+ `llm_daily` 日顶(C)+ 统一
|
||||
请求闸(`semantic` 类别 + 日 token 池,发送前预占)。
|
||||
- **未知预占不退款(HEL-885)**:`reserve:` 预占行不再按一小时超时清除——
|
||||
已发送后崩溃未 settle 的消费事实必须保留;唯一删除路径是可证明未发送的
|
||||
显式 `release()`(如 httpx.ConnectError 级未连接)。作业租约过期不是
|
||||
「未发生」的证明。跨日结算/释放一律按预占凭据的 `hold.day` 记回原日,
|
||||
不冲新一天、不产生负用量(`token_usage`/`request_usage` 同口径)。
|
||||
|
||||
## 1. A 部分 · 一次性团队整体检查(隔离空库,非用户档案)
|
||||
|
||||
用途:统一候选在 8360 隔离实例上按体验链路清单 1–7 全链走一遍(stub 语义,
|
||||
不激活 Mem0),验证创建→对话→生活→情绪/关系→主动联系→重开同步与隐私负例。
|
||||
|
||||
| 项 | 次数上限(硬顶) |
|
||||
| --- | --- |
|
||||
| 创建 + 自主诠释 + 初始习惯 | 4 次请求 |
|
||||
| 对话(含多轮、中断重试) | 12 轮 ≤ 16 次请求 |
|
||||
| 生活槽(4 槽)+ 打断/恢复 | ≤ 8 次请求 |
|
||||
| 分享判定 / 成长 / gist | ≤ 20 次请求(每完成轮 2 条 gist × 最多 8 轮 + 判定/成长若干) |
|
||||
| 主动联系措辞 | ≤ 3 次请求 |
|
||||
| **合计硬顶** | **55 次请求 / 500,000 tokens / 0 嵌入** |
|
||||
|
||||
- 硬闸(HEL-882 起为真实硬闸):`config/models.yaml` `budget.total_requests=55`
|
||||
`budget.total_tokens=500000`(跨日累计,落 `budget_total` 表)+ 各类别
|
||||
`daily_requests`(A 检查当天也受日顶约束,按 C 表或总管核定值)+ `daily_tokens`。
|
||||
任一到顶在发送前拒绝(`budget_*_exhausted`),拒绝如实留证不补发。
|
||||
- 授权来源:**需总管新批**(.11:8731 网关在产品环境的用量不属于 HEL-857 的
|
||||
60 次实验授权)。
|
||||
|
||||
## 2. B 部分 · 首周后台/语义激活(需白栖知批准后执行)
|
||||
|
||||
用途:`CYBERGRIL_SEMANTIC=mem0` 真实激活,独立新账(`embed_ops` 续账 +
|
||||
`llm_ops` 新表),观察提取/检索/原话回读在真实使用中的质量。
|
||||
|
||||
| 项 | 上限(硬顶) |
|
||||
| --- | --- |
|
||||
| Mem0 提取 LLM(随公开轮 add) | ≤ 60 次(`llm_cap`) |
|
||||
| 嵌入(提取批 + 实体 + 查询) | ≤ 180 条(`embed_cap`) |
|
||||
| 对话/生活/判定等其余 | 并入 C 部分日账 |
|
||||
|
||||
**累计 cap / 保留量算式(HEL-885,写入 config 前照此换算,禁止把增量直接填进累计上限):**
|
||||
|
||||
```
|
||||
权威账现状 :已用 498 条,原批上限 500,冻结不动余额 2 条
|
||||
新批增量 :白栖知批准的 B 部分嵌入条数(示例 180)
|
||||
embed_cap = 500 + 180 = 680 # 原批上限 + 新批增量(≠ 180)
|
||||
embed_reserve= 2 # 原冻结余额继续保留,不得消耗
|
||||
可用余量 = 680 − 498 − 2 = 180
|
||||
authority = {prefix_rows: <权威账前N行>, prefix_sha256: <逐行摘要>,
|
||||
min_used: 498} # HEL-885 起任一 cap>0 时必填,缺/空/错即拒绝构造
|
||||
```
|
||||
|
||||
错误示例:对已用 498 的账把 `embed_cap` 填成 180(等于瞬间耗尽并掩盖历史用量);
|
||||
以任何方式消耗旧冻结 2 条(`min_used: 498` 保证拒绝)。`authority` 块缺省或
|
||||
整段注释时,mem0 模式在构造前 fail-closed(`ledger_authority_required`),
|
||||
不再是可以跳过的可选项。
|
||||
|
||||
- 硬闸:`embed_cap`/`llm_cap`(累计)+ `embed_daily`/`llm_daily`(日顶)写入
|
||||
config/semantic.yaml(默认全 0=拒绝);预占-落账事务制保证并发不越 cap;
|
||||
`embed_reserve` 预留复测余量;`authority` 冻结前缀块拒绝新文件绕过 498/500。
|
||||
- 碰上限即停:作业批回到 `queued`(reason=`semantic_*_budget_*`),**不耗重试
|
||||
次数、不丢行**——总管调 cap 后同一批续跑,已完成的行不再重发(向量库逐行
|
||||
锚点 + `sem_row_done` 逐行完成记录,HEL-885:每个向量只证明自己那一行,
|
||||
空提取行同样有完成证明);检索返回空块、对话回落词面 FTS,主对话不中断;
|
||||
不自动翻页。
|
||||
- SDK 吞错已堵住:中途嵌入被预算挡住时 mem0 返回空列表,产品现在识别为预算
|
||||
拒绝并重排队(不再误写 done/[] 造成永久漏整理)。
|
||||
- 授权来源:**需白栖知明确批准**(提高嵌入上限或另批独立账;现有 498/500
|
||||
余额 2 条不够任何真实激活,属 HEL-835 未解闸范围)。
|
||||
|
||||
## 3. C 部分 · 用户日常使用(体验地址交付后)
|
||||
|
||||
| 项 | 日上限(硬顶,建议值交核定) |
|
||||
| --- | --- |
|
||||
| 主对话 + 主动措辞 + 危机 | ≤ 60 次/日(类别 `dialogue`) |
|
||||
| 生活事件生成(4 槽/天) | ≤ 8 次/日(类别 `life`) |
|
||||
| gist/判定/成长(含创建诠释) | ≤ 40 次/日(类别 `background`) |
|
||||
| Mem0 提取 LLM | ≤ 40 次/日(类别 `semantic`,`llm_daily`) |
|
||||
| 嵌入(全部) | ≤ 60 条/日(`embed_daily`) |
|
||||
| tokens | 2,000,000/日(`daily_tokens`) |
|
||||
|
||||
- 硬闸(HEL-882):上表全部落 `daily_requests`/`embed_daily`/`llm_daily`/
|
||||
`daily_tokens`,发送前原子预占、到顶拒绝、重启不归零;`daily_tokens` 到
|
||||
95% 非关键场景仍先降档/停(structured 预检),**到顶一律硬停不降档续调**;
|
||||
嵌入/提取到顶只停语义层,主对话可继续(其类别独立计账)。
|
||||
- 授权来源:**需白栖知批准日预算后**才能交付体验地址;首周日用量按本表
|
||||
B 部分上限受控观察,超限如实记录不补发。
|
||||
|
||||
## 4. 本卡已核实的闸门事实(代码级,测试可复现)
|
||||
|
||||
1. `embed_cap=0` / `llm_cap=0` / `daily_requests.*=0` 在任何真实工作前拒绝,
|
||||
出站为 0(真实 SDK 实测 C5 + tests/test_hard_gates_hel882.py)。
|
||||
2. 每次 search 恰好 1 条嵌入记账;并发预占不越 cap(C4/C6)。
|
||||
3. 对话同一轮绝不双发(chat_lock + 轮次幂等重放);预算到顶 0 次 provider
|
||||
调用、如实 chat.error(总管 api 复现已拦住,tests 复现)。
|
||||
4. 请求计数与 token 预占持久化,重启不归零(tests 双实例同库复现)。
|
||||
5. 语义作业:预算拒绝重排队不耗次数;真实错误 ≤3 次重试;租约回收与向量库
|
||||
级幂等保证不重发已入库行;stub→mem0 按后端区分覆盖,真实完成批不重发。
|
||||
6. SDK 隐藏重试已禁用(max_retries=0),被应答的失败照实入账;只有可证明
|
||||
未连接的预占才释放(总管 sdk 复现已拦住,tests 复现)。
|
||||
7. 主对话 token/请求与语义层独立闸,均发送前预占(含 Mem0 内部 LLM 并入
|
||||
`semantic` 类别 + 日 token 池)。
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"baseline_version": "1.11",
|
||||
"read_commit": "31970c4dd3851aae67a922b03264743d504b8e97",
|
||||
"task": "HEL-874 返工:补齐原话回读与真实记忆库接线——原话有界回读进入真实 provider 输入、实际固定 Mem0 SDK 合同核验并修复接线缺陷、预算闸可核验(嵌入预占制+内部 LLM 独立闸)、有确定上限的真实整体检查执行表、8360 隔离发布包、补记 2026-10-26 用户原话来源;不做部署、不合并、不启动用户真实循环、零真实调用",
|
||||
"requirement_ids": [
|
||||
"CG-006",
|
||||
"CG-008",
|
||||
"CG-011",
|
||||
"CG-012",
|
||||
"CG-013",
|
||||
"CG-014",
|
||||
"CG-015",
|
||||
"CG-016",
|
||||
"CG-021",
|
||||
"AM-001",
|
||||
"AM-002",
|
||||
"AM-003",
|
||||
"UX-004",
|
||||
"UX-006",
|
||||
"GOV-001",
|
||||
"GOV-002",
|
||||
"GOV-003",
|
||||
"GOV-005",
|
||||
"GOV-006",
|
||||
"DEL-001"
|
||||
],
|
||||
"deliverable": "在统一候选母线 hel-870-assembly(基线 31970c4 保留作前后对照)上完成四类修正,全部零真实模型/零嵌入/零出站(127.0.0.1 确定性假网关与本地模型缓存除外):1) 原话有界回读——memory/semantic.py chain_resolve 现返回锚定行的原话文本与时间戳,context() 把来源原话(每行≤200字窗口+可见截断标记)、角色、时间随命中注入模型上下文,摘要仅作一行指针,预算仍为 min(600,budget);memory/episodic.py context() 由『gist 优先截120字』改为原话优先(≤240字窗口)+角色+M月D日 HH:MM,gist 不再充当回读载荷;tests/test_readback_hel874.py 三例全部捕获真实发给 provider 的 messages(经 TestClient 全链):已退出近20轮窗口的旧轮细节(工牌编号A8823,gist 中不存在)原话进入模型输入且带角色与时间、语义链命中同样回读原话、未分享伴侣行原话在 FTS/语义两通道与任何锚点中都不出现(AM-001)。2) 实际固定 SDK 合同核验并修复——tools/verify_mem0_real_hel874.py 在 mem0ai 2.2.1(2c81af3,tarball SHA256 9dcef930 复核)固定安装上实跑:before(31970c4) 构造即死(同路径第二个 QdrantClient 被固定 SDK 拒绝,RuntimeError 已留证)、search 无 user_id 过滤被 2.2.1 直接 ValueError、limit 形参被静默忽略(正确为 top_k)、search 外层+包装双记账、内部提取 LLM 不过任何闸、并发 check-then-debit 可越 cap;after 全部修复(单client=SDK 自带 vector_store.client、add(user_id=)+search(filters,top_k)、包装为唯一记账点、LlmLedgerGuard 独立 llm_ops 表 cap 默认0、EmbedLedgerGuard 改预占-落账-释放事务制含过期预占清理)并 12/12 检查通过(C1构造/C2锚点与作用域与实测开销 1 LLM+2-3嵌入/C3默认提取输入集=旧记忆与滚动history均来自此前过门批次/C3b未分享canary零出站/C4过滤与单记账/C5双cap=0时出站LLM为0/C6并发不越cap/C7关闭恢复),证据 docs/rework/hel874/{real-sdk-before.json,real-sdk-after.json,README.md}。3) 预算闸可核验——临时隔离账实测(权威三账零写入);tests/test_mem0_backend.py 以镜像真实 2.2.1 表面的假SDK 9 例覆盖:双cap=0在任何真实工作前拒绝且 LLM 零调用、失败释放预占不记账、8路并发不越cap、llm_ops 计数、e2e 原话入块。4) 八字敏感度映射如实定案——mood_runtime 的阶梯 1.25/0.8(HEL-870 自造常数)改回原仓库奠基提交 1012d8b 既有连续规则 1.35-0.70*strength(bazi.sensitivity_from_strength 唯一处),并明确标注为工程启发式、非用户批准的命理科学事实;外部智能体材料中的固定公式未被采用(discussion-takeaways-20261025 不直接照搬清单)。另交 docs/budget-plan-hel874.md(A/B/C 三部分有确定上限执行表+代码级硬闸事实+授权来源,原20-30次/100-200条仅为估算的声明)、deploy/hel874-release.md + deploy/cybergril-hel874-8360.service.example(全新空数据目录、systemd、健康能力矩阵、停用与保留数据回退,未执行)、docs/requirements/sources/comment-evidence-full-experience-20261026.json(白栖知 2026-10-26 两条原话,01a17806 与 01a1780e『完整项目体验然后慢慢迭代』,作者/时间/父评论已核,manifest 只增新条目)、runbook §4/§6/§8 与 config/semantic.example.yaml 更新(llm_cap/user_scope/实测事实)。",
|
||||
"acceptance": [
|
||||
"原话回读进真实 provider 输入:tests/test_readback_hel874.py 3 例——旧轮细节原话(gist 无编号)+角色+时间出现在捕获的模型输入、语义链命中回读锚定行原话、未分享伴侣行原话与锚点双通道零泄漏(tests/test_readback_hel874.py)",
|
||||
"实际固定 SDK 合同:verify_mem0_real_hel874.py before(31970c4) 构造失败留证、after 12/12 通过(构造/锚点/作用域/提取输入集/canary/过滤/单记账/并发/双cap=0零出站LLM/关闭恢复),证据 docs/rework/hel874/",
|
||||
"预算闸:假SDK 9 例——embed/llm cap=0 在任何真实工作前拒绝(LLM 服务器零请求)、失败释放预占零记账、并发不越 cap、llm_ops 独立计数;权威三账与冻结余额零写入(tests/test_mem0_backend.py)",
|
||||
"八字敏感度:sensitivity_from_strength=1.35-0.70*strength 为原仓库既有程序规则(1012d8b),mood_runtime 回归该规则并如实标注工程启发式;外部智能体公式未采用(src/cybergril/core/bazi.py、mood_runtime.py 注释)",
|
||||
"执行表与发布包:docs/budget-plan-hel874.md 三部分有确定上限+授权来源;deploy/hel874-release.md+systemd example 全新空目录/健康矩阵/回退,未部署未合并未交体验地址",
|
||||
"用户原话来源:sources/comment-evidence-full-experience-20261026.json 只增两条原话及来源校验,manifest 增一条,baseline/current/coverage/changes 零改动(GOV-002 不擅改完整要求)",
|
||||
"工程检查:python 3.11 venv 全套 256 项测试通过、ruff 0 错、web typecheck+vitest+build 通过;双目标需求结构检查 4/4 通过(含本 handoff)"
|
||||
],
|
||||
"deferred": [
|
||||
"统一候选独立复审与发布:由总管对最终 head 独立复审(本卡作者不自行宣布通过);通过并按 A/B 预算批准后由发布卡执行 deploy/hel874-release.md(8360 隔离实例),本卡未部署未合并 PR#11 未交体验地址",
|
||||
"真实语义激活(CYBERGRIL_SEMANTIC=mem0):待 B 部分预算批准(embed_cap/llm_cap>0)后执行;固定 venv 并入产品进程的方案由发布卡决定",
|
||||
"真实整体检查 A/B/C 三部分执行:待总管核定并向白栖知申请授权(docs/budget-plan-hel874.md);本卡零真实调用",
|
||||
"R5 语音/R6 天气、素材、备份/R7 视觉观察承接不变(runbook §7);视觉仍交原作者;HEL-835 未解闸、39/60 与 498/500 冻结不动",
|
||||
"原话回读的用户体验效果(回读量是否恰当、是否影响语气)属体验验收,待统一体验地址交付后由白栖知判断"
|
||||
],
|
||||
"requirement_changes": [],
|
||||
"verification": [
|
||||
"分支 hel-870-assembly 追加提交(最终 head 见交付评论),PR#11 更新未合并未强推;视觉总监分支未覆盖",
|
||||
"python 3.11.16 venv + requirements.lock 离线安装:256 项测试全过(新增 test_readback_hel874.py 3 例、test_mem0_backend.py 重写为真实 2.2.1 表面镜像 9 例);ruff 0 错;web typecheck/vitest/build 通过",
|
||||
"真实固定 SDK 验证:hel857-venv(3.10.12+mem0ai 2.2.1+已有 fastembed 缓存只读)实跑 before/after 双证据 JSON;零真实出站(LLM=127.0.0.1 假网关)、零权威账写入",
|
||||
"python3 scripts/check_requirements.py --base-ref origin/main [--handoff docs/requirements/handoff-HEL-874.json] 与 --base-ref origin/agent/agent/de8874a3803b [--handoff …] 四项通过"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"baseline_version": "1.11",
|
||||
"read_commit": "31970c4dd3851aae67a922b03264743d504b8e97",
|
||||
"task": "HEL-875 接线:沿已认可界面把真实主动留言(R4 reach-out,chat_message proactive=1)接进普通入口——打开页面期间接收、去重、未读提示与清除、不抢断阅读与回复;只改视觉总监负责的前端,不重定视觉方向,不改后端",
|
||||
"requirement_ids": [
|
||||
"CG-008",
|
||||
"CG-011",
|
||||
"CG-018",
|
||||
"CG-031",
|
||||
"VIS-001",
|
||||
"VIS-002",
|
||||
"VIS-003",
|
||||
"VIS-004",
|
||||
"VIS-005",
|
||||
"VIS-006",
|
||||
"VIS-007",
|
||||
"VIS-008",
|
||||
"VIS-009",
|
||||
"VIS-010",
|
||||
"VIS-011",
|
||||
"VIS-012",
|
||||
"VIS-013",
|
||||
"UX-001",
|
||||
"UX-002",
|
||||
"UX-005",
|
||||
"UX-006",
|
||||
"AM-001",
|
||||
"GOV-001",
|
||||
"GOV-002",
|
||||
"GOV-003",
|
||||
"GOV-004",
|
||||
"GOV-005",
|
||||
"GOV-006",
|
||||
"DEL-001"
|
||||
],
|
||||
"deliverable": "自统一母线 origin/hel-870-assembly @ 31970c4 起的前端补丁(仅 web/):1) 接口核实:后端没有 /api/sync;现有只读 GET /api/messages(按行 id 倒序分页、limit+1 探测、next_before,主动行带 proactive:true,见 src/cybergril/api/app.py:399-414 与 life/public.py:26-34)已足够,未改后端、未造假服务。2) web/src/adapters/inbox.ts:打开页面期间每 20 秒(标签页隐藏时停读,回到页面或网络恢复时立即读一次;失败安静退避至最长 2 分钟)读取比已持有行更新的记录,长时间离开会有界回翻;只接收她的主动行(proactive、companion、sent),你自己的轮次及她的回复按 turn_id 已在页面上,绝不重复插入;重复读取、重连、epoch 变化返回同一 id 不再插入。未读规则:进入交谈且页面可见时,已进入页面记录的主动留言都算已读(单句在舞台呈现;多句从此刻留言卡或「回她」进入时打开完整记录并定位第一句;直接点导航「交谈」时舞台只呈现最新一句,较早一句在记录里);交谈中排队、尚未呈现的不算已读;标记为本浏览器 localStorage cg.real.seen(最新已读 id),不写服务器;本浏览器首次加载时既有历史一律视为已读,不把旧历史标成刚来的新消息。3) web/src/app/RealApp.tsx:此刻页沿用已认可的留言卡(letter)——有未读时显示「她留给你的 · 还没看」与她的原话(多句时显示「N 句还没看」),无未读时「最近说过的话」用既有 .letter.read 状态;导航「交谈」沿用既有未读微光点(.nav-item.unread .badge);日子/设置页沿用演示已认可的主动留言卡(.reach),换成真实原话、去掉「模拟」,只有「回她 / 稍后再看」,绝不自行打开交流;一句未读回她时在舞台呈现,多句时打开完整记录并定位第一句未读。4) web/src/chat/ChatPanel.tsx:交谈中到达的新留言先排队,在她正在回答、你暂停或回看、完整记录/全文打开、鼠标停在台词上、当前最后一句尚未停留满时都不插入;条件满足后作为新一轮按既有新轮呈现动效进入;被你的阅读卡住时状态行只用既有文字样式说明「她刚又说了一句 · 继续后呈现 / 读完这段呈现」;草稿不受影响;离开交谈时排队留言并入记录并保持未读。5) 测试与证据:web/tests/inbox.test.ts(隔离 HTTP 合同 fixture,7 项);web/tests/hel875_mock.mjs + hel875_shots.mjs(隔离持久消息 mock + 无头 Edge,25 项浏览器检查、两主题电脑/手机截图与三段帧序列)。普通入口与 ?demo=1 完全分开:演示的「模拟主动消息」未改、未搬入真实入口,演示入口不请求 /api/messages。未新增 CSS、颜色、动效或组件(构建 CSS 产物哈希与改动前一致 index-DdDYzR5S.css)。",
|
||||
"acceptance": [
|
||||
"页面打开时收到服务器新主动留言:此刻页留言卡换成原话并显示未读,导航「交谈」亮未读点;日子/设置页出现主动留言卡;从不自行打开交流(浏览器检查 2–3、9)",
|
||||
"关闭重开:完整记录、行 id 唯一不重复;未读跨重开保留;本浏览器首次加载既有历史不算新消息(浏览器检查 1、7;inbox.test unread 3 项)",
|
||||
"重复读取、epoch 递增、503 失败后重连:只保留一份、画面无报错提示(浏览器检查 2;inbox.test 第 1 项)",
|
||||
"不抢断:她回答中到达的留言等回答结束且最后一句停留满后才作为新一轮进入;暂停时停在你的那一句并以文字说明,继续后进入;草稿保持不变(浏览器检查 4–5,帧序列 m2-admit)",
|
||||
"读过按明确规则清未读:进入交谈且页面可见时清除(排队未呈现的除外);稍后再看只收起卡片、未读点保留;多句未读时打开记录并定位第一句(浏览器检查 3、8、9)",
|
||||
"只做网页内文字:未申请麦克风、摄像头或通知权限(浏览器检查计数为 0),无外部推送",
|
||||
"工程:web tsc 0 错;eslint src 0 错(1 条既有警告在 ChatPanel 全文按钮焦点归还处,非本卡引入);vitest 6 文件 49 项全过(原 42 + 新 7);vite build 通过"
|
||||
],
|
||||
"deferred": [
|
||||
"真机手机与 Safari 未测;手机为 390×844 等效视口,不冒充真机(UX-001)",
|
||||
"未读标记只存本浏览器;同一账号换浏览器或清除站点数据后,既有留言按首次加载规则视为已读;多标签页之间不实时同步已读(下一次读取时各自按自己的标记)。若需要跨设备已读,须后端增加已读字段,此为最小后端需求,交总管决定,本卡未改后端",
|
||||
"轮询为 20 秒;若要即时到达须后端推送通道(SSE/WebSocket),属后端合同变更,本卡未做",
|
||||
"她在你等待回复期间生成的主动留言,服务器行 id 可能落在你那一轮的用户行与回复行之间;本页面先呈现回复再呈现留言,重开后按服务器 id 顺序显示,记录内容一致、顺序可能与本次所见不同",
|
||||
"日子页电脑 1440×900 下,既有 .reach 卡与既有「她此刻」人物窗说明相距很近(未重叠);位置沿用既有样式,如需调整属新视觉,先出样图",
|
||||
"R5 语音连续对话、语音开启时主动开口;R6 天气/素材包/备份;R7 有条件视觉观察——均未接,界面诚实文案不变",
|
||||
"用户整体沉浸体验判断与验收由白栖知决定;本卡为功能接线,工程通过不代表项目完成"
|
||||
],
|
||||
"requirement_changes": [],
|
||||
"verification": [
|
||||
"实际读取提交 31970c4dd3851aae67a922b03264743d504b8e97(gitea 归档下载,origin/hel-870-assembly 分支头 API 核对一致);已读 AGENTS.md、docs/requirements/README.md、current.md v1.11、runbook-hel870.md §5/§7、handoff-HEL-870.json 及后端 /api/messages 与 reach_out 实现",
|
||||
"web:tsc --noEmit 0;eslint src 0 错;vitest run 49/49;vite build 成功(依赖按 package-lock 由本机既有 npm 缓存离线铺设,176 个包,与此前 npm ci 一致,无新增依赖、无下载)",
|
||||
"浏览器:node tests/hel875_mock.mjs 8875 + node tests/hel875_shots.mjs <msedge.exe> <out> 8875,25/25 通过;截图覆盖月光/雾光 × 电脑 1440×900 / 手机 390×844",
|
||||
"需求结构检查:本运行环境无 Python 与 git,check_requirements.py 由逐行移植的 Node 版本执行,比较目标为 gitea 只读取得的 origin/main 与 origin/agent/agent/de8874a3803b,各含/不含本交接;结果见交付评论。Python 原脚本须由总管/总工在有 Python 的环境复跑"
|
||||
]
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
{
|
||||
"baseline_version": "1.11",
|
||||
"read_commit": "31970c4dd3851aae67a922b03264743d504b8e97",
|
||||
"task": "HEL-876:核清 .12 已有语音底座真实状态并把 R5 语音接入工程材料备齐;同步只读核对 R6 接缝;零真实模型/嵌入/合成/识别请求,不改现网",
|
||||
"requirement_ids": [
|
||||
"CG-006",
|
||||
"CG-009",
|
||||
"CG-010",
|
||||
"CG-018",
|
||||
"CG-019",
|
||||
"CG-020",
|
||||
"CG-021",
|
||||
"CG-022",
|
||||
"CG-023",
|
||||
"CG-028",
|
||||
"CG-029",
|
||||
"CG-030",
|
||||
"CG-031",
|
||||
"AM-001",
|
||||
"GOV-001",
|
||||
"GOV-002",
|
||||
"GOV-003",
|
||||
"GOV-004",
|
||||
"GOV-005",
|
||||
"GOV-006",
|
||||
"DEL-001"
|
||||
],
|
||||
"deliverable": "docs/voice-r5-readiness-HEL876.md(带时间戳的 .12 只读核查:7880/10095/8090 存活证据、8081=voice worker 的身份修正、9880 TTS 于 2026-10-24 22:17 优雅停机未运行、8010 MuseTalk 单元已不存在、8188 ComfyUI 占 26.6GB 显存致空闲仅 3.9GB<所需 5.6GB、音色表 11 支与当前 rain、两个 venv 版本、.11 网关与 open-meteo 探活;R5 worker 复用 app.py:488 同一 chat 链的接缝与协议;R6 城市天气/素材/备份四处接缝定位;三栏结论与零成本工程清单)+ scripts/verify_hel876_voice_seam.py 离线零模型协议检查(六项全过:epoch 同步、完整轮含 receipt、replay 零重复调用、received 确认、barge-in 新轮取消旧行=cancelled、409 重同步)。src/ 与 web/ 零改动,无新产品 PR。",
|
||||
"acceptance": [
|
||||
"端口/进程/健康证据全部为本轮 2026-10-26 13:22-13:38 UTC 实测,未照抄 8090/8081 旧解释:8081 实为 livekit-agents worker(pid 451883),MuseTalk 8010 单元已不存在,TTS 9880 停机(tts.log 优雅停机于 10-24 22:17)",
|
||||
"凭据与 token 未读取未输出;.env 仅列白名单字段名;证书/防火墙/系统环境未改;未启停任何服务;未访问 4080;零真实 LLM 对话/嵌入/合成/识别请求",
|
||||
"语音 worker 复用同一条业务链的结论落到文件/行为级:chat 链六项协议性质(epoch/串行/取消/replay/receipt/断开清理)以 fake provider 离线证明,全部通过;明确该证明不等于端到端真实通话通过",
|
||||
"R5 材料备齐:目录/端口/worker 方案、复用依赖与版本、启动顺序与 TTS 重启命令、音色候选清单(无用户决定则不替定,D1 附试听/克隆真实入口及 TTS 停机期间不可试听的如实说明)、D3 麦克风用户动作、GPU 时段协调项",
|
||||
"R6 只读核对只定位不实现:城市(ManagePanel.tsx:182 占位、后端无代码)、天气(无代码、open-meteo 出网已证)、素材 manifest(仅演示态 :178/:183、后端无代码)、备份边界(companion.db+vec_public+mem0_public_history.db+嵌入账本,复制单 .db 不算完整;Persona.fingerprint 可作同一位她校验)",
|
||||
"三栏结论如实:已证实/未验证/确实缺资源(GPU 3.9GB<5.6GB 为唯一硬缺口);零成本工程清单六项;文档独立分支提交、候选 PR 不合并"
|
||||
],
|
||||
"deferred": [
|
||||
"R5 真实验收:连续 3 轮、开口打断 ~1s 停声、结束即停麦、无残音、移动端麦克风——TTS 恢复并接好 worker 后在 R5 施工卡执行",
|
||||
"TTS 重启:需 GPU 空闲≥6GB(ComfyUI 释放协调),由总管与白栖知安排时段,本卡未执行任何启停",
|
||||
"D1 音色最终决定与 D3 用户设备信任操作:交总管集中沟通;D2 天气源采纳、D4 视觉观察仍按原依赖待用户决策",
|
||||
"R6 城市/天气/素材/备份的实现(本卡仅定位接缝):建议总管按 §7 零成本清单连续派工",
|
||||
"统一候选独立审核部署(8360)与语音产品代码增量(/api/voice/session、HTTPS 口、前端语音按钮):后续卡"
|
||||
],
|
||||
"requirement_changes": [],
|
||||
"verification": [
|
||||
"SSH 只读核查 2026-10-26 13:22-13:38 UTC(ss/ps/systemctl/docker/nvidia-smi/curl/日志尾部/源码阅读),未做任何写操作",
|
||||
"scripts/verify_hel876_voice_seam.py 于 3.11 venv(uv 离线缓存安装,零下载)+ requirements.lock 全过六项;ruff 0 错",
|
||||
"check_requirements.py 双目标(origin/main 与 origin/hel-870-assembly)含本 handoff 退出码 0",
|
||||
"未验证并如实声明:本轮端到端语音可用性(TTS 停机)、真机移动端麦克风、TTS 恢复后延迟"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"baseline_version": "1.11",
|
||||
"read_commit": "1ae243f286b14789289e0bdca77eec818074c879",
|
||||
"task": "HEL-880 返工(父卡 HEL-875):补齐长时间离开页面后的主动留言续读——有界分页读取未覆盖旧游标时不再把游标推进到最新行,保留缺口并在后续读取中续读,所有主动留言按 id 顺序且仅一次进入;只改前端逻辑与测试/交接,不改界面、CSS、后端、已读政策,不增加推送",
|
||||
"requirement_ids": [
|
||||
"CG-008",
|
||||
"CG-011",
|
||||
"CG-018",
|
||||
"CG-031",
|
||||
"VIS-001",
|
||||
"VIS-002",
|
||||
"VIS-003",
|
||||
"VIS-004",
|
||||
"VIS-005",
|
||||
"VIS-006",
|
||||
"VIS-007",
|
||||
"VIS-008",
|
||||
"VIS-009",
|
||||
"VIS-010",
|
||||
"VIS-011",
|
||||
"VIS-012",
|
||||
"VIS-013",
|
||||
"UX-001",
|
||||
"UX-002",
|
||||
"UX-005",
|
||||
"UX-006",
|
||||
"AM-001",
|
||||
"GOV-001",
|
||||
"GOV-002",
|
||||
"GOV-003",
|
||||
"GOV-004",
|
||||
"GOV-005",
|
||||
"GOV-006",
|
||||
"DEL-001"
|
||||
],
|
||||
"deliverable": "自候选 hel-875-proactive-wiring @ 1ae243f 起的前端补丁(仅 web/ 与本交接):1) 缺陷:web/src/adapters/inbox.ts 的 fetchAfter 每次最多读 5 页×50 条,未读到旧游标就把本次见到的最新 id 作为 newest 返回,RealApp 随即推进游标,缺口内较早的主动留言永远不再读取(总管 fixture:页面持有 id=1,id=2 为主动留言,普通记录至 id=301,arrivals 为空)。另:after=0 时只读首页即停,超过 50 条同样漏读。2) 修复:游标改为 InboxCursor { through, gap }——through 以下全部已读并已交出;有界读取停在中途时 gap 记住下一次从哪一行(before)往下续读、已读到哪一行(top),以及途中读到的她的主动留言(held,只留主动行,内存有界)。下一次调用先沿 gap 往下续读,碰到 through 或记录开头即一次性按 id 顺序交出 held,through 推进到 top;若这个缺口是之前留下的,同一次调用再读一次最新页取新到的记录。读取失败或中止时整体抛出,不改调用方游标,下次从同一位置重来;不以扩大上限掩盖。较新的留言只等待尚欠的读取(每次 250 条,缺口未补齐时 RealApp 1 秒后续读,而非 20 秒),不会因最旧记录永久卡住。3) RealApp.tsx:游标存 InboxCursor,成功才提交;缺口存在时 1 秒后续读,失败退避与隐藏时停读不变。去重、排序(arrivals 不变,交出顺序随 through 单调递增)、交谈中排队不抢断、草稿、未读规则 cg.real.seen、演示入口隔离均未改。ChatPanel、CSS、后端、服务、档案均未改;构建 CSS 产物仍为 index-DdDYzR5S.css。4) 测试:web/tests/inbox.test.ts 改用新游标并新增 6 项(总管原 fixture 精确复现、1200 条散布 13 句主动留言、after=0 超过 50 条、分页第 3 页失败后恢复、追读期间又有新消息、重复读取/epoch 变化/页面已持有不重复);web/tests/hel875_mock.mjs 增 /__bulk/<n>,hel875_shots.mjs 增 8b 长时间离开步骤(2 项)。",
|
||||
"acceptance": [
|
||||
"总管 fixture(持有 id=1;id=2 主动留言;普通记录至 301):首次读取不交出、游标保留缺口 before=52/top=301;第二次读取交出 id=2,游标推进至 301(inbox.test「HEL-875 review fixture」)",
|
||||
"超过 250 条:1200 条中散布的 13 句主动留言全部按 id 顺序各进入一次,追平后再读不新增(inbox.test)",
|
||||
"after=0 且超过 50 条:读过首页继续续读,两句主动留言全部进入(inbox.test)",
|
||||
"分页中途失败:调用抛出、页面游标与失败前完全一致;恢复后补齐全部留言(inbox.test)",
|
||||
"分页途中又有新消息:先交出缺口内较早的,再交出新到的,各一次(inbox.test)",
|
||||
"重复读取同一游标结果一致、epoch 变化不影响、页面已持有的不再加入(inbox.test)",
|
||||
"无头 Edge:长时间离开(中间 320 条普通记录)回到页面后此刻留言卡显示「2 句还没看」,记录里各一句、较早者在前并定位到较早一句(浏览器检查 8b);原 25 项检查全部仍通过",
|
||||
"工程:tsc 0 错;eslint src 0 错(1 条既有警告,非本卡);vitest 6 文件 55/55(原 49 + 新 6);vite build 通过"
|
||||
],
|
||||
"deferred": [
|
||||
"原版 Python 需求检查未在本运行时执行(无 Python、无 git);须总管/总工在有 Python 的环境对 origin/hel-870-assembly、origin/main 与原产品目标 agent/agent/de8874a3803b 含/不含本交接复跑",
|
||||
"真机手机与 Safari 未测;手机为 390×844 等效视口(UX-001)",
|
||||
"缺口追读期间较新的主动留言要等缺口补齐才出现(每秒续读 250 条;例如离开期间 1 万条约需 40 秒);若需即时到达须后端推送或按 id 正向分页(after 参数),属后端合同变更,本卡未做",
|
||||
"页面若在缺口补齐前关闭或刷新,重开时按完整记录加载,不丢失;但 held 不跨页面保存",
|
||||
"HEL-875 既有承接项不变:未读标记只存本浏览器、20 秒轮询、等待回复期间的留言顺序说明、日子页 .reach 位置等,见 handoff-HEL-875.json",
|
||||
"R5 语音、R6 天气/素材/备份、R7 有条件视觉观察仍由主卡 HEL-797 承接;用户整体沉浸体验验收由白栖知决定"
|
||||
],
|
||||
"requirement_changes": [],
|
||||
"verification": [
|
||||
"实际读取提交 1ae243f286b14789289e0bdca77eec818074c879(gitea 归档下载 zip SHA256 E55C5E95B7DF575D56E305961E29A85B46CCBE963818271754208641C1FCE87C);已读 AGENTS.md、docs/requirements/README.md、current.md v1.11、coverage.md、changes.md、sources/integration-plan-v1.md、handoff-HEL-875.json 及后端 GET /api/messages(api/app.py:id < before 倒序、limit+1 探测、next_before 为页内最旧 id)",
|
||||
"web:tsc --noEmit 0;eslint src 0 错;vitest run 55/55;vite build 成功(node_modules 自 HEL-875 同一 package-lock 树离线复制,无新增依赖、无下载)",
|
||||
"浏览器:node tests/hel875_mock.mjs 8880 + node tests/hel875_shots.mjs <msedge.exe> <out> 8880,27/27 通过(含新增 8b 两项)",
|
||||
"需求结构检查:仅用 HEL-875 留下的 Node 移植版作辅助参考(基准文件经 gitea 只读取得)——origin/main、agent/agent/de8874a3803b、hel-870-assembly@31970c4(本候选实际基点)各含/不含本交接均通过;对 hel-870-assembly 现头 e978e878 失败,原因是该分支在候选之后合入 HEL-874、追加了来源 comment-evidence-full-experience-20261026.json,候选尚未包含,与本补丁无关,合并前需先同步该分支。不代表原 Python 脚本通过"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"baseline_version": "1.11",
|
||||
"read_commit": "e978e878dfc34f38c781d48f87b93dc21bbf8a61",
|
||||
"task": "HEL-882 返工(总管对 HEL-874 e978e87 独立复审后的一次归拢修正):执行表上限落成真实硬闸(所有真实 provider 出口发送前持久预占请求次数与保守 token、按最紧约束拒绝、重启不归零,含 Mem0 内部提取 LLM 并入统一闸;拒绝新账本文件绕过冻结)、SDK 隐藏重试与中途失败/恢复修齐(max_retries=0 逐次受闸、被应答失败照实入账、吞错识别为预算拒绝并重排队续跑、向量库级幂等不重发不丢行、stub→mem0 按后端区分覆盖)、发布包修到离线可执行(空目录 checkout、PYTHONPATH 服务导入、服务账号/权限、mem0 同进程导入准确缺项与不下载路径、空账初始化、回环离线核验脚本)、HEL-874 嵌入证据口径勘正与验证工具边界改确定性替身;零真实调用、零真实嵌入、零非回环出站、不改视觉、不部署、不合并、不碰权威三账与冻结余额",
|
||||
"requirement_ids": [
|
||||
"CG-006",
|
||||
"CG-008",
|
||||
"CG-011",
|
||||
"CG-013",
|
||||
"CG-014",
|
||||
"CG-015",
|
||||
"CG-016",
|
||||
"CG-021",
|
||||
"AM-001",
|
||||
"AM-002",
|
||||
"AM-003",
|
||||
"UX-004",
|
||||
"UX-006",
|
||||
"GOV-001",
|
||||
"GOV-002",
|
||||
"GOV-003",
|
||||
"GOV-005",
|
||||
"GOV-006",
|
||||
"DEL-001"
|
||||
],
|
||||
"deliverable": "在统一候选母线 hel-870-assembly(基于 e978e878 追加,PR#11 只追加不强推)上完成四类修正:1) 硬闸——think/budget.py 增类别请求上限(dialogue/life/background/semantic,默认 0=拒绝)与一次性总量(A 部分)、BudgetStop、acquire/settle/release 预占制与保守 token 估算口径;life/model.py PersistentBudget 落库 request_usage/budget_total(r4.sql 新表,BEGIN IMMEDIATE 原子预占);api/app.py 主对话发送前 acquire、真实 usage 按实落账未知保守保留;structured() 全部后台出口同一闸;Mem0Backend 内部提取 LLM 接统一 request_guard(semantic 类别+日 token 池);EmbedLedgerGuard/LlmLedgerGuard 增日顶(embed_daily/llm_daily)与 verify_ledger_authority 冻结前缀校验(新空文件绕过 498/500 被拒);config/models.yaml 与 semantic.example.yaml 随包显式 0。2) SDK 边界与恢复——SDK client max_retries=0 禁隐藏重试;被网关应答过的失败照实入账、仅 httpx.ConnectError 级未发送释放;mem0 phase3/7 吞掉的嵌入预算拒绝经线程局地失败记录重抛,作业批遇预算拒绝回 queued 不耗 attempts,调 cap 后续跑;add_group 以向量库 src_group 锚做行级幂等(续传只发缺行、崩溃/重启/并发不重复发送);sem_covered 按后端区分(stub 批次不阻塞真实重建、mem0 完成批不重发),batch_key 含后端。3) 发布包——deploy/hel874-release.md 重写(空目录 git clone、cybergril 服务账号、PYTHONPATH 单元、models.yaml 硬闸默认 0 的设计行为说明);deploy/cybergril-hel874-8360.service.example 加 PYTHONPATH/Group;deploy/mem0-stack.lock 固定栈清单 + §4 准确缺项(hel857-venv 为 3.10、产品 3.11、缓存无 3.11 wheel,发布卡落 wheel 后同 venv 离线并入,本卡零下载)与不许 stub/FTS 冒充真实记忆体验;tools/check_release_offline_hel882.py 离线核验(R1 空目录 checkout/R2 服务 import+health/R3 未批上限 fail-closed/R4 批准后全链 echo 替身/R5 停止保留数据重开 epoch+1/R6 mem0 同进程导入现状)。4) 证据勘正——docs/rework/hel874/erratum-hel882.json 与 README 追加:e978e87 版验证读取缓存做过真实本地嵌入(可证下限 12 条、精确总数不可恢复)、提交信息与执行表『零真实嵌入』口径不成立、LLM=假网关/零下载/零出站口径仍成立;tools/verify_mem0_real_hel874.py 改为 TextEmbedding 构造前装确定性 512 维替身+socket 禁非回环(总管复现同口径),证据 JSON 落盘 final_embed_rows/final_llm_used 后再清临时目录;旧前后 JSON 与权威三账一律不改。验证:3.11.16 venv+requirements.lock 离线 272 项 pytest 全过(新增 tests/test_hard_gates_hel882.py 7 例:总管 api 复现拦住/类别日顶跨重启持久/后台类别闸/默认全拒绝/最紧约束与落账/持久计数器/一次性总量,tests/test_semantic_recovery_hel882.py 9 例:总管 sdk 复现拦住(1 次 HTTP、llm_ops=1)/未连接才释放/中途吞错重排队续跑/断点续传不重发/stub→mem0 重整与真实批不重发/嵌入日顶/内部 LLM 统一闸/authority 拒新文件/build_semantic fail-closed),ruff 0 错(src/tests/tools),web tsc+42 vitest+build 通过(npm ci --offline 零下载),固定 3.10 mem0 安装上确定性替身实跑 12/12(verify 工具)+总管两复现脚本复跑证据(见交付评论附件);零真实调用、零真实嵌入、零非回环出站,权威三账与冻结余额未写,未部署未合并未交体验地址,不改视觉。",
|
||||
"acceptance": [
|
||||
"总管 api 复现被拦住:日 token 已超限的轮次 0 次 provider 调用、如实 chat.error、计数不增(tests/test_hard_gates_hel882.py::test_daily_token_wall_refuses_before_any_provider_call)",
|
||||
"总管 sdk 复现一被拦住:llm_cap=1 面对本地 500 恰 1 次 HTTP、llm_ops 已用=1、第二次 add 发送前拒绝;SDK client max_retries=0(test_hidden_retries_disabled_and_wire_hits_counted)",
|
||||
"总管 sdk 复现二可恢复:embed 中途预算拒绝不再误写 done/[],批回 queued 不耗次数,调 cap 后同一批续跑 done 且 entry_ids 非空(test_mid_add_budget_swallow_requeues_then_resumes)",
|
||||
"总管 sdk 复现三修复:stub 完成批不再阻塞 mem0 重建(enqueue=1、检索命中),mem0 完成批不重发(test_stub_batches_never_block_real_rebuild_and_real_never_resends)",
|
||||
"断点续传:已入库行不重发、只补缺行;崩溃/重启后向量库级幂等(test_resume_never_loses_or_duplicates_rows)",
|
||||
"请求计数与 token 预占持久化跨重启;类别日顶/一次性总量/日 token 按最紧约束拒绝;未批类别默认 0 全拒绝(test_dialogue_request_cap_persists_across_restart 等)",
|
||||
"冻结防绕过:authority 前缀校验拒绝新空文件+cap>0 组合;build_semantic fail-closed(test_authority_block_refuses_a_fresh_file_bypassing_the_freeze)",
|
||||
"发布命令离线可用:check_release_offline_hel882.py 全过(R1–R6),mem0 同进程导入缺项如实记录且解决路径不下载",
|
||||
"证据勘正落地:erratum-hel882.json+README 追加,verify 工具确定性边界(零模型加载)实跑 12/12;旧前后 JSON 未改、权威三账未写",
|
||||
"工程检查:272 pytest、ruff 0(src/tests/tools)、web typecheck/42 vitest/build、双目标需求检查 4/4(含本 handoff)"
|
||||
],
|
||||
"deferred": [
|
||||
"发布与体验地址:由总管独立复审本 head 后交发布卡按 deploy/hel874-release.md 执行(8360 隔离实例);本卡未部署未合并 PR#11 未交体验地址",
|
||||
"真实语义激活(CYBERGRIL_SEMANTIC=mem0):需 B 部分预算批准 + 3.11 mem0 栈 wheel 落位(deploy/mem0-stack.lock,本卡零下载);未激活前 stub/FTS 不冒充真实记忆体验",
|
||||
"A/B/C 执行表数值仍为待批建议(docs/budget-plan-hel874.md):批准前所有 cap 保持 0=拒绝;新增预算由总管向白栖知一次性申请",
|
||||
"R5 语音/R6 天气、素材、备份/R7 有条件观察承接不变;视觉仍交原作者,本卡未改视觉",
|
||||
"真实体验效果(含硬闸拒绝时用户侧措辞是否需人设化)待体验地址交付后由白栖知判断"
|
||||
],
|
||||
"requirement_changes": [],
|
||||
"verification": [
|
||||
"分支 hel-870-assembly 追加提交(最终 head 见交付评论),PR#11 只追加未强推未合并;视觉总监分支未覆盖",
|
||||
"python 3.11.16 venv + requirements.lock 离线:272 项测试全过(新增 16 例硬闸/恢复测试);ruff 0 错(src/tests/tools);web typecheck/vitest 42/build 通过(npm ci --offline)",
|
||||
"固定 mem0 2.2.1 安装(hel857-venv 3.10.12)确定性替身实跑:tools/verify_mem0_real_hel874.py 12/12(零真实嵌入、零非回环连接、临时目录清理前逐笔账落盘);总管两复现脚本对本 checkout 复跑证据见交付评论附件",
|
||||
"tools/check_release_offline_hel882.py(echo 替身、随机回环端口、临时目录)全过;mem0 同进程导入缺项(3.11 无 wheel)如实记录,解决路径 deploy/hel874-release.md §4,本卡零下载",
|
||||
"python3 scripts/check_requirements.py --base-ref origin/main [--handoff docs/requirements/handoff-HEL-882.json] 与 --base-ref origin/agent/agent/de8874a3803b [--handoff …] 四项通过"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"baseline_version": "1.11",
|
||||
"read_commit": "59fb06304fb9f230837b43e431e2a1cc303e16c9",
|
||||
"task": "HEL-885 返工(总管对 HEL-882 59fb063 独立复审五项缺口的一次归拢修正):逐行恢复与真实来源一致(每次 add 只带自己那一行的锚点,向量只证明自身行;空提取行落 sem_row_done 完成证明;预算失败/崩溃重启/取消租约/并发不漏行、不伪完成、不重发完成行)、跨日结算按预占日记回原日并维护累计账(不再冲出新日负用量)、未知已发送预占永不超时退款(reserve 行唯一删除路径是可证明未发送的 release,llm/embed 同路径)、正 cap 必须验证权威来源(authority 缺/空/错在构造前 fail-closed,示例配置必填并给出 498已用+冻结2+新批增量的累计 cap/保留量算式)、发布依赖补齐命令改为本机确实支持的 python3 -m pip download 并入 R7 离线探针;零真实调用、零真实嵌入、零非回环出站、不部署、不合并、不改视觉、不碰权威三账",
|
||||
"requirement_ids": [
|
||||
"CG-006",
|
||||
"CG-008",
|
||||
"CG-011",
|
||||
"CG-013",
|
||||
"CG-014",
|
||||
"CG-015",
|
||||
"CG-016",
|
||||
"CG-021",
|
||||
"AM-001",
|
||||
"AM-002",
|
||||
"AM-003",
|
||||
"UX-004",
|
||||
"UX-006",
|
||||
"GOV-001",
|
||||
"GOV-002",
|
||||
"GOV-003",
|
||||
"GOV-005",
|
||||
"GOV-006",
|
||||
"DEL-001"
|
||||
],
|
||||
"deliverable": "在统一候选母线 hel-870-assembly(基于 59fb063 追加,PR#11 只追加不强推)上完成五项修正:1) 逐行进度与来源证明——Mem0Backend/StubBackend add_group 改为逐行发送、每次 add 的 metadata 只带该行锚点(turn_ids/turn_roles/input_sha256 均单行),向量存在只证明自身行;新增 sem_row_done 表(r4.sql 增量迁移)在每行 add 完成瞬间于 worker 线程内落持久完成记录(合法空提取=零向量也有完成证明),tick 恢复时以 向量库锚点∪sem_row_done 为跳过集,批次 entry_ids 跨部分尝试合并;吞错检测改逐行口径。2) 跨日账——PersistentBudget.settle/release 一律写 hold.day(token_usage/request_usage 按预占日,budget_total 全局),不再向 state.day 冲账制造负用量;另修 budget_total 空表时 total_requests_used/total_tokens_used 的 TypeError 崩溃(coalesce 子查询),旧用例靠该崩溃“通过”的假绿改为如实入账。3) 未知预占——_atomic_reserve 删除 1 小时过期 DELETE(reserve 行不再按龄清除),未知消费保守计费、跨重启保留,唯一删除路径是可证明未发送的显式 release();embed/llm 共用路径同修。4) 权威来源必填——Mem0Backend 在 import mem0 之前:任一 cap>0 而 authority 缺/空/非 dict 即抛 ledger_authority_required(构造前拒绝,不发任何请求),错的 authority 仍被 verify_ledger_authority 前缀校验拒绝;config/semantic.example.yaml authority 段改为必填示例并附累计算式(embed_cap=500+增量、embed_reserve=2、min_used=498,禁把 180 直接填成累计 cap、禁动旧冻结 2 条);docs/budget-plan-hel874.md §2 补同套算式与 HEL-885 记账口径。5) 发布命令——deploy/hel874-release.md §4 改为 python3 -m pip download(--only-binary :all: --platform manylinux2014_x86_64 --python-version 3.11 --implementation cp --abi cp311)+ sha256 留证 + uv pip install --no-index --find-links 离线并入 + 同进程 co-import 判据,如实列明本机无 3.11 wheel、本卡零下载、无包则如实上报不换版本;tools/check_release_offline_hel882.py 新增 R7 双探针(pip download 存在 rc=0、已删除的 uv pip download 保持不存在)。验证:3.11.16 venv+requirements.lock 离线 281 项 pytest 全过(新增 tests/test_rework_hel885.py 9 例:部分批次只认已发行/空提取不重发/进程重启只补缺行/并发 tick 不双发/跨日结算与释放按预占日/未知预占 llm+embed 不退款/缺空错 authority fail-closed;test_resume_never_loses_or_duplicates_rows 重写为真实产品路径,不再手工植入单行锚点),ruff 0 错(src/tests/tools),web tsc+42 vitest+build(npm ci --offline),固定 mem0 2.2.1 安装确定性替身:verify_mem0_real_hel874.py 12/12、新增 verify_semantic_recovery_hel885.py 7/7(S0 缺 authority 拒绝/S1 逐行部分/S2 重启只补缺行以 HTTP 计数证明/S3 租约回收+并发不双发/S4 未知预占不退款),总管 review_hel882_{partial,extra}.py 两脚本原样复跑全过(证据 docs/rework/hel885/),发布离线检查 R1–R7 全过;零真实调用、零真实嵌入、零非回环出站,权威三账与冻结余额未写,未部署未合并未交体验地址,不改视觉。",
|
||||
"acceptance": [
|
||||
"总管 partial 复现修复:首 tick 后 completed_message_ids=[2](仅伴侣行,向量逐行锚点),补 cap 后 done、entry_ids 2 条、回环 HTTP=2、用户原话确实发出且每行恰一次(manager-review-partial-rerun.json 原样脚本复跑)",
|
||||
"总管 extra 复现修复:跨日结算次日 used=0、估算 150 被拒、原日记实际 10;未知预占 second_allowed=false、used 保持 1(manager-review-extra-rerun.json 原样脚本复跑)",
|
||||
"逐行恢复真实产品路径:空提取行有 sem_row_done 完成证明且不再重发;进程重启后只补缺行;并发 tick 不双发(tests/test_rework_hel885.py + recovery-real-sdk.json S1–S3)",
|
||||
"未知预占保守计费:llm_ops 与 embed_ops 同路径,一小时前的未结算预占跨重启仍占额度,唯一删除路径是显式可证明未发送的 release(S4a/S4b + 对应用例)",
|
||||
"authority 必填:缺/空/错 authority 在构造前拒绝(ledger_authority_required / 前缀不匹配),示例配置必填并附 500+增量、reserve=2、min_used=498 算式,明确禁把 180 填成累计 cap、禁动旧冻结 2 条(tests + recovery-real-sdk.json S0 + semantic.example.yaml + budget-plan-hel874.md)",
|
||||
"发布命令真实可用:§4 改为 python3 -m pip download(本机实测 rc=0),R7 双探针入离线检查;本机无 3.11 wheel、零下载、缺包如实上报的口径不变(release-offline-hel885.json)",
|
||||
"工程检查:281 pytest、ruff 0(src/tests/tools)、web typecheck/42 vitest/build、双目标需求检查 4/4(含本 handoff);固定 SDK 合同 12/12 复跑通过"
|
||||
],
|
||||
"deferred": [
|
||||
"发布与体验地址:由总管独立复审本 head 后交发布卡按 deploy/hel874-release.md 执行(8360 隔离实例);本卡未部署未合并 PR#11 未交体验地址",
|
||||
"真实语义激活(CYBERGRIL_SEMANTIC=mem0):需 B 部分预算批准 + 3.11 mem0 栈 wheel 落位(按 §4 新命令执行并留 sha256 证);未激活前 stub/FTS 不冒充真实记忆体验",
|
||||
"A/B/C 执行表数值仍为待批建议:批准前所有 cap 保持 0=拒绝;authority 前缀实际值由总管按权威账现状填写;新增预算由总管向白栖知一次性申请",
|
||||
"R5 语音/R6 天气、素材、备份/R7 有条件观察承接不变;视觉仍交原作者,本卡未改视觉",
|
||||
"真实体验效果待体验地址交付后由白栖知判断"
|
||||
],
|
||||
"requirement_changes": [],
|
||||
"verification": [
|
||||
"分支 hel-870-assembly 追加提交(最终 head 见交付评论),PR#11 只追加未强推未合并;视觉总监分支未覆盖",
|
||||
"python 3.11.16 venv + requirements.lock 离线:281 项测试全过(新增 tests/test_rework_hel885.py 9 例,重写 test_resume_never_loses_or_duplicates_rows);ruff 0 错(src/tests/tools);web typecheck/vitest 42/build 通过(npm ci --offline)",
|
||||
"固定 mem0 2.2.1 安装(hel857-venv 3.10.12)确定性替身实跑:tools/verify_mem0_real_hel874.py 12/12;新增 tools/verify_semantic_recovery_hel885.py 7/7(零真实嵌入、零非回环);总管 review_hel882_{partial,extra}.py 原样复跑证据 docs/rework/hel885/manager-review-{partial,extra}-rerun.json",
|
||||
"tools/check_release_offline_hel882.py(echo 替身、随机回环端口、临时目录)R1–R7 全过;mem0 同进程导入缺项(3.11 无 wheel)如实记录,解决路径 deploy/hel874-release.md §4 新命令,本卡零下载",
|
||||
"python3 scripts/check_requirements.py --base-ref origin/main [--handoff docs/requirements/handoff-HEL-885.json] 与 --base-ref origin/agent/agent/de8874a3803b [--handoff …] 四项通过"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"baseline_version": "1.11",
|
||||
"read_commit": "78b6267d3a9cc9adc1136fc4bf22c4178a38cab2",
|
||||
"task": "HEL-886 返工(总管对 HEL-885 候选 78b6267 独立复审确认的两个缺口一次修完):处理中取消与租约回收不得把仍在运行的真实 worker 当死亡而双发同一行(总管实际固定 SDK 复现:取消只取消等待协程、to_thread 中 HTTP 仍在跑时 finally 立即 failed/清 owner,第二 tick 重领并发第二 HTTP;不取消、注入时钟前进 41 秒 recover 同样回收活 worker 重发;两例同一句 2 HTTP/2 向量而批 done 只记 1 entry)、已全完成批次无新余额也须可收尾(总管第三例:llm_cap=1 单行已真实完成,落『行完成已持久、批状态未写』过期 pending 后重启,add_group 在计算 skip 前先 check(1) 导致全完成仍 queued/budget_exhausted);零真实调用、零真实嵌入、零非回环出站、不部署、不合并、不改视觉、不碰权威三账、不新增任何 cap",
|
||||
"requirement_ids": [
|
||||
"CG-006",
|
||||
"CG-008",
|
||||
"CG-011",
|
||||
"CG-013",
|
||||
"CG-014",
|
||||
"CG-015",
|
||||
"CG-016",
|
||||
"CG-021",
|
||||
"AM-001",
|
||||
"AM-002",
|
||||
"AM-003",
|
||||
"UX-004",
|
||||
"UX-006",
|
||||
"GOV-001",
|
||||
"GOV-002",
|
||||
"GOV-003",
|
||||
"GOV-005",
|
||||
"GOV-006",
|
||||
"DEL-001"
|
||||
],
|
||||
"deliverable": "在统一候选母线 hel-870-assembly(基于 78b6267 追加,PR#11 只追加不强推)完成两项修正:1) 活跃 worker 生命周期——SemanticMemory 增进程内活跃注册表(batch_id→_LiveWorker,登记先于线程启动);批次的行构建/续跑集/真实 add/终态写入全部移入 worker 线程函数 _run(Mem0Backend/StubBackend 抽出同步核心 _add_group_sync,add_group 保持异步合同仅包装);取消等待协程不再触碰批次状态(旧缺陷:finally 把活跃批次写成 failed 供重领);recover() 只回收无活跃 worker 的过期 pending(真死 owner 语义保持保守:结果未知、预占不退、重试只发无证明行、不宣称严格一次);并发 tick 对活跃批次不可领不重发;新增 SemanticMemory.aclose() 排空仍运行 worker 后才关 backend,app lifespan 改走该路径先排空再关 store。2) 完成证明先于预算闸——_add_group_sync 先算 完成集(向量锚点∪sem_row_done)∩剩余工作,全完成零新 HTTP/嵌入即返回完成(重启收尾 done、entry_ids 跨尝试完整、cap 不变不退款);仍有缺行时维持 ledger/llm_guard 原硬闸 fail-closed。验证:3.11.16 venv+requirements.lock 离线 287 pytest 全过(新增 tests/test_rework_hel886.py 6 例:处理中取消不双发/跨租约仍活跃不回收+并发 tick 不重发/真死 owner 租约照常回收/关闭排空先于关库/全完成零新预算收尾/部分未完成仍硬闸拒绝,全部用屏障阻塞的真实产品路径驱动,不再用预置死租约或快任务并发冒充),ruff 0 错(src/tests/tools),固定 mem0 2.2.1 确定性替身:总管 review_hel885_inflight.py 原样复跑在 2-add 屏障处如预期断言失败(修复前该屏障通过且 2 HTTP/2 向量——双发已消失,唯一调整是把屏障改为 1 次完成的同形脚本 review_hel886_inflight_rerun.py:两场景 1 HTTP/1 向量/1 sem_row_done、飞行中批次保持 pending、终态 done 且 entry_ids 完整;第三例重启后 done、零新 HTTP、llm_used 仍 1、entry_ids 原样);新增 tools/verify_inflight_lifecycle_hel886.py 6/6(L1 取消单发全记录/L2 跨租约活跃不重发/L3 关闭排空先于关库/L4a 全完成零新预算收尾/L4b 缺行仍硬闸/L5 真死租约照常回收);verify_semantic_recovery_hel885.py 7/7、verify_mem0_real_hel874.py 12/12、check_release_offline_hel882.py R1–R7 15/15、总管 review_hel882_{partial,extra}.py 原样复跑全过;零真实调用、零真实嵌入、零非回环出站,权威三账与冻结余额未写,未部署未合并未交体验地址,不改视觉。",
|
||||
"acceptance": [
|
||||
"总管 inflight 复现修复(取消例):第一 HTTP 已接受未返回时取消 tick,批次保持 pending/owner 不被写 failed;并发第二/三 tick 不重发;worker 完成后 1 HTTP、1 向量、1 条 sem_row_done,批 done 且 entry_ids 与行记录一致(docs/rework/hel886/HEL-886-inflight-rerun.json + inflight-real-sdk.json L1)",
|
||||
"总管 inflight 复现修复(租约例):注入时钟前进 41 秒、worker 仍在第一请求中,recover 不回收活跃批次,并发 tick 不重发;最终同样 1 HTTP/1 向量/1 行记录(L2)",
|
||||
"晚到写入不漏记:飞行中批次不被重领,最终 entry_ids 与 sem_row_done 完整一致,无重复向量、无漏记完成条目(两场景 JSON)",
|
||||
"关闭先排空:aclose 等待仍运行 worker 的持久写落盘后才关向量库 client(L3:排空期间完成的行 done_at/finished_at 证明写在 aclose 开始之后)",
|
||||
"总管第三例修复:llm_cap=1 已用完且单行已真实完成,重启后零新 HTTP、cap 不变即 done、entry_ids 原样;部分未完成仍按原硬闸拒绝(L4a/L4b + tests/test_rework_hel886.py)",
|
||||
"真死进程恢复语义未停用:无活跃 worker 的过期 pending 照常 failed/lease_expired 并重试缺行;未知预占不退款口径不变(L5 + S4a/S4b 复跑)",
|
||||
"工程检查:287 pytest(含 6 新例)、ruff 0(src/tests/tools)、web typecheck/42 vitest/build、双目标需求检查 4/4(含本 handoff);固定 SDK 合同 12/12、HEL-885 恢复 7/7、发布离线 R1–R7 15/15、总管两脚本原样复跑通过"
|
||||
],
|
||||
"deferred": [
|
||||
"发布与体验地址:由总管独立复审本 head 后交发布卡按 deploy/hel874-release.md 执行(8360 隔离实例);本卡未部署未合并 PR#11 未交体验地址",
|
||||
"真实语义激活(CYBERGRIL_SEMANTIC=mem0):需 B 部分预算批准 + 3.11 mem0 栈 wheel 落位;未激活前 stub/FTS 不冒充真实记忆体验",
|
||||
"A/B/C 执行表数值仍为待批建议:批准前所有 cap 保持 0=拒绝;新增预算由总管统一处理,不作为本卡离线修复阻塞",
|
||||
"真死进程『严格一次』不在本卡宣称范围:远端无幂等支持时崩溃结果未知,恢复保守留账、只发无证明行(运行手册 §4 已如实写明)",
|
||||
"R5 语音/R6 天气、素材、备份/R7 有条件观察承接不变;视觉仍交原作者,本卡未改视觉;真实体验效果待体验地址交付后由白栖知判断"
|
||||
],
|
||||
"requirement_changes": [],
|
||||
"verification": [
|
||||
"分支 hel-870-assembly 追加提交(最终 head 见交付评论),PR#11 只追加未强推未合并;视觉总监分支未覆盖",
|
||||
"python 3.11.16 venv + requirements.lock 离线:287 项测试全过(新增 tests/test_rework_hel886.py 6 例,全部屏障阻塞真实产品路径);ruff 0 错(src/tests/tools);web typecheck/vitest 42/build 通过(npm ci --offline)",
|
||||
"固定 mem0 2.2.1 安装(hel857-venv 3.10.12)确定性替身实跑(docs/rework/hel886/):总管 review_hel885_inflight.py 原样复跑在 worker_done 2-add 屏障断言失败(original-verbatim-after-fix.log,修复前基线 HEL-885-inflight-baseline-78b6267.json 为 2 HTTP/2 向量);调整屏障为 1 的同形脚本 review_hel886_inflight_rerun.py 全过(HEL-886-inflight-rerun.json);新增 tools/verify_inflight_lifecycle_hel886.py 6/6(inflight-real-sdk.json)",
|
||||
"回归:tools/verify_semantic_recovery_hel885.py 7/7、tools/verify_mem0_real_hel874.py 12/12、tools/check_release_offline_hel882.py R1–R7 15/15、总管 review_hel882_{partial,extra}.py 原样复跑通过(docs/rework/hel886/)",
|
||||
"python3 scripts/check_requirements.py --base-ref origin/main [--handoff docs/requirements/handoff-HEL-886.json] 与 --base-ref origin/agent/agent/de8874a3803b [--handoff …] 四项通过"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"baseline_version": "1.11",
|
||||
"read_commit": "f1b50ad91ad8c856f28383ee59eddb6547d9c586",
|
||||
"task": "HEL-889 返工(总管对 HEL-886 候选 f1b50ad 独立复审确认的两个生命周期边界缺口一次修完):关闭超时不得把仍可能写入的活 worker 当死而关掉其依赖(总管实际固定 SDK 复现:回环网关先接受第一 HTTP 未答,aclose(drain_timeout=0.05) 返回时 worker 仍活,按 app 顺序关 Store 后放行,Qdrant 报 QdrantLocal instance is closed、worker 报 ProgrammingError: Cannot operate on a closed database,重开批次仍 pending/row_done=0/llm_used=1——仍活进程主动关资源造成的丢落盘窗口,不能归入真崩溃结果未知)、执行器排队阶段取消不得永久假活跃(先登记 _live 再 await to_thread,执行器忙时取消 tick 会取消排队 future,_run/finally 从未运行,注册表留下不存在的 worker,批次 pending 卡死且之后恢复一直跳过,只能重启;单 worker 执行器+41 秒时钟前进+recover+tick 复现 _live=1/entries=0/row_done=0/队列空);零真实调用、零真实嵌入、零下载、不部署、不合并、不改视觉、不碰权威三账、不新增任何 cap、不改运行时/模型/认证",
|
||||
"requirement_ids": [
|
||||
"CG-006",
|
||||
"CG-008",
|
||||
"CG-011",
|
||||
"CG-013",
|
||||
"CG-014",
|
||||
"CG-015",
|
||||
"CG-016",
|
||||
"CG-018",
|
||||
"CG-021",
|
||||
"AM-001",
|
||||
"AM-002",
|
||||
"AM-003",
|
||||
"UX-004",
|
||||
"UX-006",
|
||||
"GOV-001",
|
||||
"GOV-002",
|
||||
"GOV-003",
|
||||
"GOV-004",
|
||||
"GOV-005",
|
||||
"GOV-006",
|
||||
"DEL-001"
|
||||
],
|
||||
"deliverable": "在统一候选母线 hel-870-assembly(基于 f1b50ad 追加,PR#11 只追加不强推不合并)完成两项修正:1) 关闭所有权协议——批 worker 移入 SemanticMemory 专用执行器(本层持有 concurrent.futures.Future,即权威的排队/已启动判定,且长批次不再挤占 loop 共享默认执行器);aclose 先置 closing 信号:tick 拒新领、后端 _add_group_sync 在每行发送前查 should_stop 只在行边界中止(已完行保留 sem_row_done 证明,未发行回 queued,绝无在飞中断)、未启动 worker 直接按 CANCELLED 权威取消(从未运行=零出站,安全回队并复原 attempts);随后等待所有活跃 worker 真实退出才关 backend 与执行器——drain_timeout 只界定礼貌阶段(超时如实告警并继续等,绝不把活 worker 当死),Mem0Backend 构造时给网关 OpenAI 客户端设 gateway_timeout(默认 60s,可配)保证排空有界;aclose 被取消时什么都不关且 closing 复位可续用,并发 aclose 用 asyncio.Lock 串行化且幂等,完成端关闭 asyncio.shield 保护;真崩溃语义不变(保守留账、只发无证明行、不宣称严格一次)。2) 排队取消恢复——_LiveWorker 增加 started 事件与 future 槽位区分排队/已启动/已完成;取消等待协程时以 future.cancelled() 权威判定:从未运行则立即释放假活跃注册项并回队(attempts 复原、零发送),释放执行器、时钟前进 41 秒后 recover+tick 进程内完成,无需重启;已开始/已完成则维持 HEL-886 语义(worker 线程写终态、并发 tick 不重发,不回到双发)。保留全部已通过行为(回读、隐私、单行恢复、预算硬闸、未知预占不退款、全完成零预算收尾)。app lifespan 注释同步为 HEL-889 所有权口径。验证:python 3.11.16 venv + requirements.lock 离线 294 pytest 全过(新增 tests/test_rework_hel889.py 7 例,全部屏障驱动真实产品路径:排队取消恢复/关闭超时不关活 worker 且落盘先于关库/行间中止下一进程只发缺行/未启动即关闭零发送并可续跑/关闭被取消什么都不关/并发关闭串行化/关闭后拒新领),ruff 0 错(src/tests/tools);固定 mem0 2.2.1 确定性替身实跑 tools/verify_shutdown_queued_hel889.py 12/12(Q1 排队取消、S1 关闭超时分支固定 SDK 实跑、S2 行间中止、S3 关闭被取消、S4 并发关闭);总管 review_hel886_edges.py 原负例与基线保留(edges-verbatim-before-fix-f1b50ad.json 与总管独立结果一致),同形复跑 review_hel886_edges_rerun_hel889.py 全过(queued:done/attempts=1/registered 0/entries 1/row_done 1;timeout:aclose_returned_while_worker_alive=false/late_worker_error=null/persisted done+entry_ids+row_done=1/llm_used=1),原样复跑如实停在其自身前提断言(批 worker 不再走 loop 默认执行器,docs/rework/hel889/original-verbatim-after-fix.log);回归 verify_inflight_lifecycle_hel886.py 6/6、verify_semantic_recovery_hel885.py 7/7、verify_mem0_real_hel874.py 12/12、check_release_offline_hel882.py R1–R7 15/15、总管 review_hel882_{partial,extra}.py 原样复跑通过、review_hel885_inflight.py 仍在 2-add 屏障如预期失败(单发保持);零真实调用、零真实嵌入、零非回环出站,权威三账与冻结余额未写,未部署未合并未交体验地址,不改视觉",
|
||||
"acceptance": [
|
||||
"P1 修复(固定 SDK 实跑关闭超时分支):第一 HTTP 已接受未答时 aclose(drain_timeout=0.05) 在 worker 仍活时绝不返回;应答放行后 worker 完成落盘,aclose 才返回、才关 client;按 app 顺序关库重开:批 done、row_done=1、entry_ids 完整、llm_used=1、无任何晚到错误(docs/rework/hel889/edges-real-sdk-hel889.json S1 + HEL-889-edges-rerun-fixed.json shutdown_timeout;修复前基线 aclose_returned_while_worker_alive=true/ProgrammingError/pending/row_done=0 保留对照)",
|
||||
"P1 关闭取消/并发:排空中的 aclose 被取消什么都不关(client 可用、批次仍 pending、closing 复位可续用),worker 正常完成后再次 aclose 干净收尾;两个 aclose 并发串行化、排空一次、资源只关一次、第三次调用幂等即返(S3/S4 + tests/test_rework_hel889.py)",
|
||||
"P2 修复(排队取消):单 worker 执行器占住、批次仅入队未启动即取消 tick——假活跃注册项立即释放、批次回 queued 且 attempts 复原、零出站;释放执行器、推进 41 秒后 recover+tick 进程内完成:done、1 条目、1 sem_row_done、注册表清零,无需重启(Q1 + 同形复跑 queued_cancel;修复前基线 pending/_live=1/entries=0/row_done=0 保留对照)",
|
||||
"已开始取消仍单发:处理中取消/跨租约仍活跃不重发不回收(HEL-886 L1/L2 回归 6/6),review_hel885_inflight.py 仍在 2-add 屏障如预期失败,未回退双发",
|
||||
"行间中止与恢复:两行批次关闭中止保留第 1 行完成证明、批次回 queued/shutdown_between_rows、关闭期间网关 POST 不新增;下一进程同账同库只发缺行(POST 仅 +1)后 done 且两行 entry_ids 完整(S2 + pytest)",
|
||||
"原有能力全部保留:回读/隐私/单行恢复/预算硬闸/未知预占不退款/全完成零预算收尾/真死租约回收(recovery-hel885-rerun 7/7、inflight-hel886-rerun 6/6、contract-hel874-rerun 12/12、release-offline R1–R7 15/15、总管 882 两脚本原样复跑通过)",
|
||||
"工程检查:294 pytest(含 7 新例)全过、ruff 0 错(src/tests/tools);本卡不改前端,未重跑前端 42 项/构建,不以历史数字冒充本轮实测;双目标需求检查 4/4(含本 handoff)"
|
||||
],
|
||||
"deferred": [
|
||||
"发布与体验地址:由总管独立复审本 head 后交发布卡按 deploy/hel874-release.md 执行(8360 隔离实例);本卡未部署未合并 PR#11 未交体验地址",
|
||||
"真实语义激活(CYBERGRIL_SEMANTIC=mem0):需 B 部分预算批准 + 3.11 mem0 栈 wheel 落位;未激活前 stub/FTS 不冒充真实记忆体验",
|
||||
"A/B/C 执行表数值仍待批建议:批准前所有 cap 保持 0=拒绝;新增预算由总管统一处理,不作为本卡阻塞",
|
||||
"真死进程『严格一次』不在本卡宣称范围:远端无幂等支持时崩溃结果未知,恢复保守留账、只发无证明行(runbook §4 HEL-889 段已如实写明)",
|
||||
"R5 语音 HEL-878/879、R6 天气/素材/完整备份、R7 有条件观察承接不变;视觉仍交原作者,本卡未改视觉;真实体验效果待体验地址交付后由白栖知判断"
|
||||
],
|
||||
"requirement_changes": [],
|
||||
"verification": [
|
||||
"分支 hel-870-assembly 追加提交(最终 head 见交付评论),PR#11 只追加未强推未合并;视觉总监分支未覆盖",
|
||||
"python 3.11.16 venv + requirements.lock 离线:294 项测试全过(新增 tests/test_rework_hel889.py 7 例,全部屏障驱动真实产品路径,无预置死租约冒充);ruff 0 错(src/tests/tools);本卡未改前端故未重跑前端检查,如实注明",
|
||||
"固定 mem0 2.2.1 安装(hel857-venv 3.10.12)确定性替身实跑(docs/rework/hel889/):tools/verify_shutdown_queued_hel889.py 12/12(edges-real-sdk-hel889.json);总管脚本基线原样复跑 edges-verbatim-before-fix-f1b50ad.json 与总管独立结果一致;同形复跑 review_hel886_edges_rerun_hel889.py 全过(HEL-889-edges-rerun-fixed.json);原样复跑停在其自身前提断言(original-verbatim-after-fix.log,原负例与基线不删)",
|
||||
"回归:verify_inflight_lifecycle_hel886.py 6/6、verify_semantic_recovery_hel885.py 7/7、verify_mem0_real_hel874.py 12/12、check_release_offline_hel882.py R1–R7 15/15、总管 review_hel882_{partial,extra}.py 原样复跑通过;review_hel885_inflight.py 原样复跑仍在 2-add 屏障如预期失败(单发保持,与 HEL-886 存档口径一致)",
|
||||
"python3 scripts/check_requirements.py --base-ref origin/main [--handoff docs/requirements/handoff-HEL-889.json] 与 --base-ref origin/agent/agent/de8874a3803b [--handoff …] 四项通过"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"baseline_version": "1.11",
|
||||
"read_commit": "ceb0f35418f0d0b28b4e2a359615194db8eb4567",
|
||||
"task": "HEL-894 返工(总管对 HEL-889 head ceb0f35 独立复审确认的关闭取消两处组合漏洞一次修完):关闭提交阶段被取消仍须保持关闭所有权——semantic.py 原实现 shield(_finish()) 让内部关闭在后台继续,却向外抛取消、释放锁并清 closing,层次被误报可续用,新 tick 把批次领进即将消失的资源(总管实际固定 SDK 屏障复现:取消返回 closing=false/closed=false/client 可用 → 新批 HTTP 已接受未答 → 旧 _finish 关 Qdrant → 不关 Store 也 failed/row_done=0/llm_used=1(QdrantLocal instance is closed);调用方随后关 Store 则晚到 ProgrammingError,重开 pending/row_done=0);取消等锁者不得修改持锁关闭者状态——第二 aclose 等锁即取消,外层 except 清掉第一关闭者的 closing(固定 SDK 复现第一 close 仍 pending 但 signal=false,原 worker 未放行时新批已 done)。零真实调用、零真实嵌入、零下载、不部署、不合并、不改视觉、不碰权威三账、不变真实 cap 与冻结余额、不改运行时/模型/认证",
|
||||
"requirement_ids": [
|
||||
"CG-006",
|
||||
"CG-008",
|
||||
"CG-011",
|
||||
"CG-013",
|
||||
"CG-014",
|
||||
"CG-015",
|
||||
"CG-016",
|
||||
"CG-018",
|
||||
"CG-021",
|
||||
"AM-001",
|
||||
"AM-002",
|
||||
"AM-003",
|
||||
"UX-004",
|
||||
"UX-006",
|
||||
"GOV-001",
|
||||
"GOV-002",
|
||||
"GOV-003",
|
||||
"GOV-004",
|
||||
"GOV-005",
|
||||
"GOV-006",
|
||||
"DEL-001"
|
||||
],
|
||||
"deliverable": "在统一候选母线 hel-870-assembly(基于 ceb0f35 正常追加,PR#11 只追加不强推不合并)完成两项修正:1) 两阶段取消语义——aclose 明确排空可撤销阶段(尚未关任何资源:取消什么都不关、closing 复位、层次可续用,保持 HEL-889 行为)与提交不可逆阶段(backend.aclose 已开始:被取消的调用方不清 closing、不放行 tick(半关闭绝不宣称可用)、不释放锁,等待自己启动的层持有关闭任务(_close_task,asyncio.create_task 创建恰一次、存于本层从不丢到后台、重复取消只重新 shield 同一任务)落成后才传播 CancelledError,资源恰关一次;_finish_close 里本层自有执行器 shutdown 改走 asyncio.to_thread,取消方不可能以同步 join 阻塞事件循环;调用方注入执行器仍由调用方持有,与总管脚本口径一致)。2) 所有权最小化——aclose 增加 owns_close 判定:只有真正取得 _aclose_lock 并置位 closing 的调用才可在仍可撤销(无 _close_task)时复位信号;等锁被取消的调用不改变持锁关闭者的任何状态。模块 docstring、aclose docstring、app lifespan 注释同步新协议口径。真崩溃语义不变(保守留账、只发无证明行、不宣称严格一次)。验证:python 3.11.16 venv + requirements.lock 离线 299 pytest 全过(新增 tests/test_rework_hel894.py 5 例,全部屏障驱动真实产品路径:提交阶段取消保持所有权并拒新工作零发送零计费/重复取消仍一次落成/同阶段第二调用等待后幂等返回且第三个等锁者取消不动状态/等锁者取消+持锁排空+新批 tick 拒绝+原 worker 完成组合/排空阶段取消仍可续用续跑),ruff 0 错(src/tests/tools);固定 mem0 2.2.1 确定性替身实跑 tools/verify_close_ownership_hel894.py 16/16(总管两负例同形:F1 提交取消 SDK-only——取消后 closing=true/closed=false/client 可用、取消方等关闭落成才返回、新批 tick 被拒(网关零到达、queued/owner NULL/attempts 0、llm_used=0)、放行后关闭恰一次、重开批次 queued 完好/row_done=0/llm_used=0;F2 +Store 关闭分支同形且无晚到错误;W 等锁者取消——closing 保持、新批 queued、原 worker 放行后 done/row_done=1、一次收尾);总管脚本原件在未改动 ceb0f35 上原样复跑基线与总管独立结果一致(before-ceb0f35.json),修复后原样复跑如实停在其自身前提断言 server.arrived(新协议下 tick 拒新领,前提结构性移除,verbatim-after-fix.log,原负例与基线保留不删);回归 verify_shutdown_queued_hel889.py 12/12、verify_inflight_lifecycle_hel886.py 6/6、verify_semantic_recovery_hel885.py 7/7、verify_mem0_real_hel874.py 12/12、check_release_offline_hel882.py R1–R7 15/15、总管 review_hel882_{partial,extra}.py 原样复跑通过、HEL-889 同形复跑通过;零真实 LLM/真实嵌入/非回环出站,权威三账与冻结余额未写,未部署未合并未交体验地址,不改视觉",
|
||||
"acceptance": [
|
||||
"负例一修复(固定 SDK 实跑,SDK-only 分支):真实 client.close 进入后取消 aclose——取消返回时 closing=true/closed=false/client 可用(不再宣称可续用);新批 tick 被拒:网关零到达、批次 queued/owner NULL/attempts 0、llm_used=0(修复前:closing=false + 新批被领 + QdrantLocal instance is closed + failed/row_done=0/llm_used=1);被取消调用方等层持有关闭任务落成后才传播(closed=true/client 不可用/live_workers=0/close 恰一次);重开批次 queued 完好、row_done=0、llm_used=0",
|
||||
"负例一加强分支(调用方随后关 Store,单独记录不与 SDK-only 混淆):同上且无任何晚到错误(修复前 ProgrammingError: Cannot operate on a closed database,重开 pending/row_done=0/llm_used=1)",
|
||||
"负例二修复(固定 SDK 实跑):第一 aclose 排空中、第二等锁即取消——closing 保持置位(修复前被清);新批 tick 被拒仍 queued;原 worker 放行后 done/row_done=1;第一关闭随后一次收尾关库;新批保持 queued",
|
||||
"所有权完整回归(pytest 屏障驱动真实路径):排空取消可续用(closing 复位、后续批次正常跑完)、提交阶段取消不得半关可用、同阶段第二调用等待后幂等且一次关闭、重复取消仍一次落成、等锁者取消不动状态、并发关闭一次(HEL-889 既有用例保持全绿)、成功关闭拒新任务;原排队取消、活 worker 超时排空、行间续跑、处理中取消单发、全完成零预算收尾全部保持(299 pytest、shutdown-queued 12/12、inflight 6/6、recovery 7/7、contract 12/12、release R1–R7 15/15)",
|
||||
"总管脚本原件保留:未改动 ceb0f35 上原样复跑基线与总管独立 JSON 一致(before-ceb0f35.json);修复后原样复跑停在其自身前提断言并如实存档(verbatim-after-fix.log);同形重跑断言按新安全协议改写并逐条注明(tools/verify_close_ownership_hel894.py,close-ownership-real-sdk.json 16/16)",
|
||||
"工程检查:299 pytest(含 5 新例)全过、ruff 0 错(src/tests/tools);本卡未改前端、未重跑前端检查、不冒充历史数字;双目标需求检查 4/4(含本 handoff)"
|
||||
],
|
||||
"deferred": [
|
||||
"发布与体验地址:由总管独立复审本 head 后交发布卡按 deploy/hel874-release.md 执行(8360 隔离实例);本卡未部署未合并 PR#11 未交体验地址",
|
||||
"真实语义激活(CYBERGRIL_SEMANTIC=mem0):需 B 部分预算批准 + 3.11 mem0 栈 wheel 落位;未激活前 stub/FTS 不冒充真实记忆体验",
|
||||
"A/B/C 执行表数值仍待批准建议:批准前所有 cap 保持 0=拒绝;新增预算由总管统一处理,不作为离线修复阻塞",
|
||||
"真死进程『严格一次』不在宣称范围:远端无幂等支持时崩溃结果未知,恢复保守留账、只发无证明行(runbook §4 HEL-889/894 段已如实写明)",
|
||||
"R5 语音 HEL-878/879、R6 天气/素材/完整备份、R7 有条件观察承接不变;视觉仍交原作者,本卡未改视觉;真实体验效果待体验地址交付后由白栖知判断"
|
||||
],
|
||||
"requirement_changes": [],
|
||||
"verification": [
|
||||
"分支 hel-870-assembly 追加提交(最终 head 见交付评论),PR#11 只追加未强推未合并;视觉总监分支未覆盖",
|
||||
"python 3.11.16 venv + requirements.lock 离线:299 项测试全过(新增 tests/test_rework_hel894.py 5 例,全部屏障驱动真实产品路径,无预置死租约冒充活线程);ruff 0 错(src/tests/tools);本卡未改前端故未重跑前端检查,如实注明",
|
||||
"固定 mem0 2.2.1 安装(hel857-venv 3.10.12)确定性替身实跑(docs/rework/hel894/):tools/verify_close_ownership_hel894.py 16/16(close-ownership-real-sdk.json,F1/F2/W 为总管两负例同形重跑,断言按新协议改写并注明);总管脚本基线原样复跑与独立结果一致(before-ceb0f35.json);修复后原样复跑停在自身前提断言(verbatim-after-fix.log)",
|
||||
"回归:verify_shutdown_queued_hel889.py 12/12、verify_inflight_lifecycle_hel886.py 6/6、verify_semantic_recovery_hel885.py 7/7、verify_mem0_real_hel874.py 12/12、check_release_offline_hel882.py R1–R7 15/15、总管 review_hel882_{partial,extra}.py 原样复跑通过、HEL-889 同形复跑(review_hel886_edges_rerun_hel889.py)通过;review_hel885_inflight.py 本轮未重跑,其存档口径不变",
|
||||
"python3 scripts/check_requirements.py --base-ref origin/main [--handoff docs/requirements/handoff-HEL-894.json] 与 --base-ref origin/agent/agent/de8874a3803b [--handoff …] 四项通过"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"baseline_version": "1.11",
|
||||
"read_commit": "152479b3503c3d1ab27681170e6bfd592c54380a",
|
||||
"task": "HEL-895 发布准备:把 HEL-894 已过独立复审的统一内核(152479b)与 HEL-881 已审核的主动留言前端原稿(bf234570,原作者六文件逐字、CSS 零改动)机械装成同一可审核可执行的统一发布候选。先核对包含关系(bf234570 不是 152479b 的祖先;母线 31970c4..152479b 从未触碰 web/),以正常合并带入恰两个提交共 9 个文件,src/tests/tools/config 相对 152479b 逐字不变,全部 HEL-874..894 后端修复保留;不重设计、不做视觉决定、不覆盖视觉分支,旧 b48d28b 测试合并未推送由本候选取代。零真实 LLM、零真实嵌入、零新增下载、不部署、不合并 PR、不交体验地址、不触 8300–8350 与共享语音口型、不访问 4080、不导入演示测试档案为用户伴侣、不写权威账、不变真实 cap、不改运行时/模型/认证",
|
||||
"requirement_ids": [
|
||||
"CG-006",
|
||||
"CG-008",
|
||||
"CG-009",
|
||||
"CG-010",
|
||||
"CG-011",
|
||||
"CG-012",
|
||||
"CG-013",
|
||||
"CG-014",
|
||||
"CG-015",
|
||||
"CG-016",
|
||||
"CG-018",
|
||||
"CG-021",
|
||||
"CG-023",
|
||||
"CG-029",
|
||||
"CG-030",
|
||||
"CG-031",
|
||||
"VIS-013",
|
||||
"AM-001",
|
||||
"AM-002",
|
||||
"AM-003",
|
||||
"UX-001",
|
||||
"UX-005",
|
||||
"UX-006",
|
||||
"GOV-001",
|
||||
"GOV-002",
|
||||
"GOV-003",
|
||||
"GOV-004",
|
||||
"GOV-005",
|
||||
"GOV-006",
|
||||
"DEL-001"
|
||||
],
|
||||
"deliverable": "统一发布候选(分支 hel-870-assembly 正常追加,精确 head 见交付评论,PR#11 只追加不强推不合并):1) 机械整合合并提交——第一父 152479b、合入 bf234570,仅新增原作者前端六文件(web/src/adapters/inbox.ts、real.ts、app/RealApp.tsx、chat/ChatPanel.tsx 与三个测试)+ handoff-HEL-875/880.json,web/ 逐字等于 bf234570、src/tests/tools/config 逐字等于 152479b;2) 合成后全量验证在本 head 实跑:python 3.11.16 venv + requirements.lock 离线 299 pytest 全过、ruff 0 错;web typecheck 0 错、lint 0 错(1 条既有警告非本卡)、vitest 55/55、build 通过且 CSS 仍 index-DdDYzR5S.css(视觉零改动);固定 SDK(hel857-venv 3.10.12,未混 site-packages)确定性 512 维替身 + 仅回环实跑:关闭所有权 16/16、关闭排队 12/12、处理中生命周期 6/6、语义恢复 7/7、真实接线 12/12、发布离线检查 15/15;3) 新增并实跑两项本卡验证:真实 HTTP 分页合同 7/7(tools/verify_http_pagination_hel895.py——真实产品服务、327 行播种、倒序+limit+1 探测+next_before+epoch 合同、公开字段允许清单与 proactive 标记恰在播种行、inbox.ts fetchAfter 缺口游标的机械移植对真实后端复验:一次调用最多 5 页缺口保留、下次补齐按 id 序恰好交出两条新留言不跳不重);真实服务浏览器 7/7(web/tests/hel895_real.mjs——Chromium CDP 对真实 API + web/dist:真实启动、留言卡过真实 HTTP 到达、舞台播放、260 行在其上的长缺口语义(HEL-880 回归)真实服务复验不跳留言且无重复、服务重启 epoch+1 后同批行不加第二次、零权限申请),证据 docs/rework/hel895/;4) 发布包与清单 deploy/hel895-release-candidate.md(精确 head、交付物清单、安装引用、健康矩阵、停用回退、固定 SDK 3.11 准确缺项);5) 一次可批准预算执行包 docs/budget-package-hel895.md(A 55 请求/500000 tokens/0 嵌入一次性、B 60 提取 LLM/180 嵌入按 500+180 算式、C 逐类日顶;逐笔发送前四道闸叠加口径、交叉=有意叠加、无重复、无漏记、失败取消保守入账不退款、碰顶停机与留证;默认全 0 不变,冻结 39/60 与 498/500 及 HEL-835 闸原样保全);6) 本地离线核实 3.11 固定 SDK 现状:产品 venv 无 mem0/qdrant_client/fastembed,本机 uv/pip 缓存无其任何 wheel,hel857-venv(3.10.12 固定栈)存在且全部 SDK 回归在其上实跑;缺项=mem0ai 2.2.1/qdrant-client 1.20.0/fastembed 0.9.1 的 cp311 manylinux2014_x86_64 wheel,落位命令照 deploy/hel874-release.md §4 不变,本卡零下载",
|
||||
"acceptance": [
|
||||
"包含关系与机械性:合并前核实 bf234570 不是 152479b 祖先、git diff 31970c4 152479b -- web/ 为空;合并后 git diff bf234570 HEAD -- web/ 为空、git diff 152479b HEAD -- src tests tools config 为空(仅 9 个前端/治理文件变化),无重设计无视觉决定,原作者六文件与 CSS 可追溯",
|
||||
"合成后工程检查在本 head 全过:299 pytest(3.11.16 venv + requirements.lock 离线)、ruff 0 错(src/tests/tools)、web typecheck 0 错/lint 0 错 1 既有警告/vitest 55/55/build 通过且 CSS 哈希 index-DdDYzR5S.css 不变",
|
||||
"HEL-894/889/886/885/874/882 回归全绿(固定 SDK 确定性替身 + 仅回环):verify_close_ownership_hel894.py 16/16、verify_shutdown_queued_hel889.py 12/12、verify_inflight_lifecycle_hel886.py 6/6、verify_semantic_recovery_hel885.py 7/7、verify_mem0_real_hel874.py 12/12、check_release_offline_hel882.py R1–R7 15/15",
|
||||
"真实 HTTP 分页合同 7/7(tools/verify_http_pagination_hel895.py 对真实服务):无档案 404 负例;首页恰 50 行升序 next_before=本页最小 id;沿 next_before 整走 327 行无重无缺页边界正确;公开允许清单字段恰为 id/ts/role/text/turn_id/status/lifeIds(+proactive) 且 proactive 恰在播种留言行;fetchAfter 缺口游标(HEL-880)真实服务复验:5 页封顶缺口保留→下次补齐→两条新留言按 id 序恰好交出一次;epoch 分页稳定、未知路径 404",
|
||||
"真实服务浏览器 7/7(web/tests/hel895_real.mjs,Chromium CDP 对真实 API):真实启动无服务未接通卡;新留言行写入后 20 秒内/可见性触发读到达并出现留言卡;进交谈舞台播放;260 行长缺口在其下的新留言经补读到达且无重复;SIGTERM 重启(epoch+1)后同批行不加第二次;麦克风/相机权限保持 prompt 零申请",
|
||||
"发布候选文档 deploy/hel895-release-candidate.md 给出精确 head、交付物清单(requirements.lock 19 项固定依赖、前端产物 CSS 哈希一致、systemd/配置模板、独立数据空间、健康矩阵 R4-assembly/stub/诚实未接项、停用与保留数据回退)与 3.11 固定 SDK 准确缺项(零下载,落位路径不变)",
|
||||
"预算执行包 docs/budget-package-hel895.md:A/B/C 数值、四道闸叠加口径与交叉/重复/漏记核对结论(交叉=总量+日顶有意叠加;嵌入只记条数不进请求计数;四类别+嵌入+全池 token 覆盖全部出站点)、失败取消保守入账、碰顶停机与留证;随包默认全 0、权威账与冻结余额零写入(check_release_offline R3 复现未批即拒)",
|
||||
"双目标及实际母线需求检查各含/不含本 handoff 共 6/6 通过(origin/main、origin/agent/agent/de8874a3803b、实际母线 152479b)"
|
||||
],
|
||||
"deferred": [
|
||||
"真实预算授权:A/B/C 全部待白栖知经总管批准;批准前 cap 全 0=发送前拒绝;旧 39/60、498/500(冻结 2 条)与 HEL-835 闸不挪用不重置;真实调用、真实嵌入、权威账写入本卡零执行",
|
||||
"3.11 固定 SDK wheel 落位:本机缓存无 cp311 wheel,发布卡按 deploy/hel874-release.md §4 用 pip download(mem0-stack.lock)落 wheel 后离线并入并 co-import 验证;落位前 stub/FTS 不冒充真实 Mem0 体验就绪",
|
||||
"部署与体验地址:待总管复核通过 + 预算/部署授权后由发布卡按 deploy/hel895-release-candidate.md §2 执行(8360 隔离实例);本卡未部署、未合并 PR#11、未交体验地址;8300–8350 与共享语音口型未触碰",
|
||||
"R5 语音:HEL-878(原作者会话额度受限,已有定时唤醒)/HEL-879(待前端完成)继续接续;R6 天气/素材包/完整备份、R7 有条件视觉观察(默认关,需白栖知授权 + 4080 决策)均未接入,界面保持诚实文案,不冒充已运行",
|
||||
"真机与 Safari 未验证(等效视口与桌面 Chromium 已过);跨设备已读仍为本浏览器口径;即时到达需后端推送/正向分页属合同变更未做——沿用 HEL-881 承接口径",
|
||||
"整体沉浸体验方向:用户 2026-10-23 认可动效质感但整体沉浸感未满意、新方向未定;本卡为发布准备不接管视觉作者、不触其运行时/模型/认证;界面小样认可前不替换正式界面"
|
||||
],
|
||||
"requirement_changes": [],
|
||||
"verification": [
|
||||
"分支 hel-870-assembly 正常追加(精确 head 见交付评论,第一父 152479b、合入 bf234570),PR#11 只追加不强推不合并;视觉总监分支未覆盖",
|
||||
"python 3.11.16 venv + requirements.lock 离线:299 pytest 全过;ruff 0 错(src/tests/tools);web typecheck/lint(1 既有警告)/vitest 55/55/build 全过,CSS 仍 index-DdDYzR5S.css",
|
||||
"固定 SDK(hel857-venv 3.10.12 + mem0ai 2.2.1/qdrant-client 1.20.0/fastembed 0.9.1,确定性 512 维替身、仅回环、临时账库):verify_close_ownership_hel894.py 16/16(docs/rework/hel895/close-ownership-hel895.json)、verify_shutdown_queued_hel889.py 12/12、verify_inflight_lifecycle_hel886.py 6/6、verify_semantic_recovery_hel885.py 7/7、verify_mem0_real_hel874.py 12/12(同目录证据 JSON);check_release_offline_hel882.py R1–R7 15/15(release-offline-hel895.json,R6 如实记录 3.11 缺 mem0)",
|
||||
"新增 tools/verify_http_pagination_hel895.py:真实服务 7/7(docs/rework/hel895/http-pagination.json);新增 web/tests/hel895_real.mjs:真实服务 + Chromium CDP 7/7(docs/rework/hel895/real-browser.json 与 real-*.png 截图)",
|
||||
"python3 scripts/check_requirements.py --base-ref {origin/main | origin/agent/agent/de8874a3803b | 152479b} 各含/不含 --handoff docs/requirements/handoff-HEL-895.json 共 6/6 通过",
|
||||
"零真实 LLM、零真实嵌入、零非回环出站、零下载;权威三账与冻结余额未写;8300–8350、共享语音口型、GPU/4080 未触碰;未部署、未合并 PR、未交体验地址"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"baseline_version": "1.11",
|
||||
"read_commit": "04eff611e7d85e539d43717711172441420ac853",
|
||||
"task": "HEL-896 发布补齐:按总管独立复审(附件 HEL-895-总管独立复审.md 及证据 zip)一次修正四项发布工程与如实记载——①A/B/C 可执行分账与阶段切换(临时库复现共库误配、独立团队测试实例+全新用户实例方案、封账/授权来源/叠加口径/停续语义、一次有限推荐);②修真实浏览器验证误报(精确点击可见留言卡回她、严格断言舞台文本/未读变化/消息ID去重/重启epoch+1、权限API调用计数0、临时回环配置+拒绝非回环连接,保留原误报证据+勘误+修后证据,不改视觉产品);③SDK 离线可复用部分与准确余缺(41 项闭包逐项分类、mem0 固定源码身份哈希核验、fastembed 模型缓存实测、seq48–51 pip 26.2.2 下载事件缓存取证与勘误);④发布归档/回退改一致性快照(WAL 丢失复现、停服+checkpoint 与 backup API 双口径、可审核产物/哈希/命令/代码提交对应,不冒充 R6 完成)。零真实 LLM、零真实嵌入、除既有缓存外零新增下载、不部署、不合并 PR、不交体验地址、不触 8300–8350 与共享语音口型、不访问 4080、不写权威账、真实 cap 保持 0、不改任何队员运行时/模型/认证、不导入测试档案为用户伴侣",
|
||||
"requirement_ids": [
|
||||
"CG-006",
|
||||
"CG-008",
|
||||
"CG-009",
|
||||
"CG-011",
|
||||
"CG-012",
|
||||
"CG-013",
|
||||
"CG-014",
|
||||
"CG-015",
|
||||
"CG-018",
|
||||
"CG-021",
|
||||
"CG-023",
|
||||
"CG-029",
|
||||
"CG-030",
|
||||
"CG-031",
|
||||
"VIS-013",
|
||||
"AM-001",
|
||||
"AM-002",
|
||||
"AM-003",
|
||||
"UX-001",
|
||||
"UX-005",
|
||||
"UX-006",
|
||||
"GOV-001",
|
||||
"GOV-002",
|
||||
"GOV-003",
|
||||
"GOV-004",
|
||||
"GOV-005",
|
||||
"GOV-006",
|
||||
"DEL-001"
|
||||
],
|
||||
"deliverable": "分支 hel-870-assembly 正常追加 PR#11(不强推不合并):1) docs/budget-package-hel895.md 增 §6 可执行分账(独立团队测试实例 8360 独库独配置承载 A 的 55/500000 一次性总量、全新用户实例永不配置一次性总量、A 封账=停服+一致性快照归档+budget_total 永不清零、旧测试数据无导入用户库路径、B/C 开始的配置与授权来源、B 真实 Mem0 冒烟计入 60/180、B 到顶 queued 不丢行不耗重试、首周结束凭实际用量报告定停续、一次负责的有限推荐 A55/500k+B60/180+C 逐日顶),并勘误原文『A 当天同时受 C 类日顶约束』与『WAL 单文件整体归档』两处不安全表述;新增 tools/verify_budget_split_hel896.py 8/8(S0 共库误配复现与总管同形、S1 团队实例 55 硬顶+日顶取最紧、S2 封账跨重启跨归档、S3 用户实例 B/C 放行+日顶叠加+拒绝不落账、S4 误配守门、S5 仓库配置与权威事实零改动),证据 docs/rework/hel896/budget-split/budget-split.json;2) web/tests/hel895_real.mjs 修订(B2 信件卡精确断言+未读标记、B3a/B3b 导航日子→可见留言卡 aside.reach.show→只点该卡『回她』→严格断言 .conversation .sr 舞台原文+未读清零、B4/B5 服务器消息 ID data-mid 去重 0 重复、B5 epoch 恰 +1 实测 1→2、B6 页面自身 Permissions API/getUserMedia 调用计数 0、B7 临时回环 models.yaml+sitecustomize socket 守卫拒绝并计数非回环连接=0 且非回环 DNS=0);勘误前证据 browser-before/(原脚本逐字重跑 7/7 含 B3 stage=false 误报 PASS)、修后 browser/ 9/9;docs/rework/hel896/README.md 勘误说明;未改任何 web/src 产品代码,精确交互下舞台/未读/去重/epoch 语义真实成立,未发现产品行为缺陷;3) tools/audit_sdk_offline_hel896.py 只读审计:闭包 55 项/缺 41(纯 28+原生 13)逐项分类(立即可复用仅 greenlet cp311/pillow cp311/truststore py3-none-any 三项)、mem0ai 固定源码 direct_url+tarball SHA256 与 mem0-stack.lock 一致、索引同名 wheel 不作数、hel857 构建轮在临时缓存未持久、hatchling 离线构建待发布卡、fastembed 真实模型缓存 92MB 实测(bge-small-zh-v1.5+bm25)、pip 26.2.2 事件缓存取证(uv 条目创建于 2026-10-27 02:10:04、files.pythonhosted.org、窗口内唯一新增→该步发生了网络获取,前卡零下载不成立,按保守口径记为下载;requirements.lock 装机全部命中既有缓存);deploy/hel895-release-candidate.md §4 勘误为准确清单、后续安装强制 offline/no-index;4) tools/release_snapshot_hel896.py 一致性快照/回退工具(停服+checkpoint 与 backup API 双口径、逐文件 SHA256 MANIFEST、代码精确提交、逐表行数核对、verify 回退演练)自测 4/4:在线裸复制主 .db 复现 no such table: companion、backup API 与 checkpoint 两口径均完整、归档哈希与行数全过;§5 改一致性快照口径含照抄命令与覆盖范围(产品库/向量/推导史/权威账),不冒充 R6 完成;deploy/hel895-release-candidate.md 增 HEL-896 修订头与交付物表更新",
|
||||
"acceptance": [
|
||||
"分账方案可执行且经临时库+替身验证:tools/verify_budget_split_hel896.py 8/8 全过(共库误配复现与总管独立复现同形;团队实例 55 硬顶跨重启跨 backup API 归档不失效;用户实例无总量闸时 B/C 放行、日顶逐项到顶拒绝且拒绝不落账;误配总量守门;仓库配置与权威账零改动)",
|
||||
"docs/budget-package-hel895.md §6 明确 A 封账步骤、B/C 开始配置与授权来源、旧测试数据不会转成用户伴侣(无导入路径+发布检查单禁复制)、全局累计与日顶叠加口径、B 到顶与首周结束的停/续授权语义、A stub 检查/真实 Mem0 冒烟/B 首周/C 日常关系、一次负责的有限推荐;原文两处不安全表述已勘误标注",
|
||||
"浏览器验证误报修正:原脚本逐字重跑复现误报(B3 stage=false 仍 PASS,7/7 作废口径);修订版 9/9 全过——精确点击可见留言卡的回她、舞台 .conversation .sr 含留言原文、未读标记真实清零、记录 data-mid 全无重复且缺口/现场留言各恰一次、/api/health epoch 恰 +1(1→2)、页面自身权限 API 调用计数 0、非回环连接尝试 0 且非回环 DNS 0(回环配置+socket 守卫强制拒绝);产品代码零改动,原证据保留+勘误+修后证据三件齐",
|
||||
"SDK 余缺准确清单:闭包 55/缺 41 与总管独立计算一致;三个顶层包 WHEEL 均 py3-none-any;mem0ai 固定源码身份(direct_url+tarball SHA256 与 lock 注释一致)核实;缓存可复用仅 3 项、其余 37 项+离线构建依赖准确留给发布卡不阻塞;fastembed 模型缓存实测 92MB 两模型;pip 26.2.2 事件取证明确(该步发生网络获取,前卡零下载勘误不成立,保守记为下载;lock 装机无新增缓存条目);本卡零下载零安装零推理",
|
||||
"发布归档/回退一致性:在线裸复制丢 WAL 已提交行复现(no such table: companion);停服+checkpoint 与 backup API 双口径快照完整;归档含逐文件 SHA256+代码精确提交+逐表行数核对;verify 回退演练全过;§5 命令与覆盖范围(产品库/语义向量/推导史/权威账)可照抄,不删档,不冒充 R6 完整备份完成",
|
||||
"全量回归在本 head 实跑:3.11.16 venv + requirements.lock 离线 pytest 299/299、ruff 干净(src/tests/tools);web typecheck 0 错、lint 0 错 1 条既有警告、vitest 55/55、build 通过 CSS 仍 index-DdDYzR5S.css;固定 SDK 回归(hel857-venv 3.10.12 替身+回环)verify_close_ownership_hel894 16/16、verify_shutdown_queued_hel889 12/12、verify_inflight_lifecycle_hel886 6/6、verify_semantic_recovery_hel885 7/7、verify_mem0_real_hel874 12/12、check_release_offline_hel882 R1–R7 15/15;真实 HTTP 分页合同 7/7;三目标需求检查各含/不含 handoff-HEL-896 共 6/6"
|
||||
],
|
||||
"deferred": [
|
||||
"R5 语音仍由 HEL-878/879 接续;R6 天气/素材/完整备份(CG-009 每日自动备份与手动导出)未接入,本卡一致性快照只是发布归档/回退要求,不冒充完成",
|
||||
"R7 有条件视觉观察继续保留;手机真机与 Safari 未验证",
|
||||
"真实部署、体验地址、PR 合并、真实预算写入、SDK 41 项缺口的授权下载与离线并入、真实 Mem0 激活(B)与团队整体检查(A)执行:全部留给发布卡经总管向白栖知一次申请批准后执行;A/B/C 批准前所有 cap 保持 0 fail-closed",
|
||||
"视觉方向讨论与整套界面验收仍由视觉总监线承接;本卡未改任何界面文件"
|
||||
],
|
||||
"requirement_changes": [],
|
||||
"verification": [
|
||||
"docs/rework/hel896/budget-split/budget-split.json(8/8)、docs/rework/hel896/browser-before/(误报复现)与 docs/rework/hel896/browser/(修后 9/9 + netguard 报告 + 截图 + run-commands.txt)、docs/rework/hel896/sdk-audit/sdk-audit.json+summary.md(含 pip 事件缓存取证)、docs/rework/hel896/release-snapshot/selftest.json(4/4)",
|
||||
"HEL-895 原始证据 docs/rework/hel895/ 原样保留未改;总管复审附件与其独立证据 zip 引用见 docs/rework/hel896/README.md",
|
||||
"回归命令与需求检查输出见交付评论;人工核对原话:GOV-006(01a1734c)、完整体验委托(01a1780e 原话『我觉得这是工程问题…直奔核心』)已由本卡阅读并在方案中落实为『不再问用户选择算法、直接接续已授权工程』",
|
||||
"未验证:真机/Safari/手机、真实 LLM 与真实嵌入行为(本卡零真实调用)、目标发布机的实际归档执行(工具已自测,发布卡照抄)"
|
||||
]
|
||||
}
|
||||
@@ -14,5 +14,6 @@
|
||||
"dynamic-sample-effect-acceptance-20261023.md": "f5e9fde65a58ebc365de47569053ad7c30ce77ae82bb78773cc070e3cbffd90e",
|
||||
"comment-evidence-mature-reuse-20261025.json": "38ca076e26477e89cd161eaadb0fb00b0ee7bb5a26de2bd6ab41de52faaae79e",
|
||||
"mature-reuse-principle-20261025.md": "a2a75e071d3af5db722727bfaf8083bb60d07dcdd74e90d1e1374c1aafd2064d",
|
||||
"other-agent-discussion-20261025.txt": "42cd97029d73eace110e49e0566343dfeb25fe4f0633a10b78b126289938aaeb"
|
||||
"other-agent-discussion-20261025.txt": "42cd97029d73eace110e49e0566343dfeb25fe4f0633a10b78b126289938aaeb",
|
||||
"comment-evidence-full-experience-20261026.json": "75dd55b8755bdad217b44c3f516c9372b046aae06dff511527014c7b209eccff"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"id": "01a17806-cb7e-76f3-8f3f-3f6360ddc668",
|
||||
"author_type": "member",
|
||||
"author_id": "e5c6c938-db48-4ff9-954f-4fb8b063a132",
|
||||
"content": "我听不懂你的意思,用大白话问我",
|
||||
"created_at": "2026-10-26T11:44:33Z",
|
||||
"parent_id": "01a174b3-867e-7a90-9628-e1abd82a71b7"
|
||||
},
|
||||
{
|
||||
"id": "01a1780e-6954-73ad-91da-3a5d81831a07",
|
||||
"author_type": "member",
|
||||
"author_id": "e5c6c938-db48-4ff9-954f-4fb8b063a132",
|
||||
"content": "我觉得这是工程问题,我不懂所以我无法抉择,但我知道截止目前,整个项目仅前端通过了我的审核,我没有现有的聊天和资料,也不存在真实或假聊天,因为一切都还没开始,我期望的是进行完整的项目体验,然后慢慢迭代,否则我根本不知道哪里是对的哪里是错的,你交由我决定的东西我认为是在原地打转或者说是在外围转圈而不是直奔核心",
|
||||
"created_at": "2026-10-26T11:52:52Z",
|
||||
"parent_id": "01a174b3-867e-7a90-9628-e1abd82a71b7"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,60 @@
|
||||
# HEL-874 · 实际固定 Mem0 SDK 合同验证证据
|
||||
|
||||
运行脚本:`tools/verify_mem0_real_hel874.py`(仓库根,用 HEL-857 的固定 venv 执行;
|
||||
零真实出站——LLM 走 127.0.0.1 确定性假网关,嵌入用已有本地 fastembed 缓存,
|
||||
账本全部写在临时目录,不触权威三账)。
|
||||
|
||||
```bash
|
||||
/home/.../hel857-venv/bin/python tools/verify_mem0_real_hel874.py docs/rework/hel874/real-sdk-after.json
|
||||
```
|
||||
|
||||
固定安装事实(两份 JSON 的 `install` 字段):mem0ai 2.2.1、qdrant-client 1.20.0、
|
||||
fastembed 0.9.1;源码 tarball SHA256 `9dcef930…dda23` 与 HEL-857 记录一致;
|
||||
关键模块逐文件 SHA256 见 JSON。
|
||||
|
||||
## 结论(before = 31970c4 原适配层,after = 本卡修复后)
|
||||
|
||||
| 检查 | before | after | 事实 |
|
||||
| --- | --- | --- | --- |
|
||||
| C1 构造 | **失败** | 通过 | before 在同一路径开第二个 QdrantClient,固定 SDK 直接拒绝:`Storage folder … is already accessed by another instance of Qdrant client`——31970c4 的 mem0 模式根本起不来。after 复用 SDK 自带 `vector_store.client`,不再另开 client。 |
|
||||
| C2 add 锚点/作用域 | 无法到达 | 通过 | metadata(src_group/turn_roles/input_sha256)逐字落进 payload;`user_id` 作用域随 add 写入。 |
|
||||
| C2 每次真实开销 | 无法到达 | 通过 | 单行 add 实测 = 1 次提取 LLM + 2–3 条嵌入(phase1 查询嵌入 + phase3 提取批嵌入 + phase7 实体 upsert 嵌入),全部经包装记账。 |
|
||||
| C3 默认提取输入集 | 无法到达 | 通过 | 第二批 add 的提取提示实测包含第一批的提取记忆(旧记忆通道)与滚动 history——二者全部只来自此前过门批次,同一可证明输入集成立。 |
|
||||
| C3b 未分享 canary | 无法到达 | 通过 | 植入主档案的未分享伴侣行经 enqueue/tick 后不出现在任何出站 LLM 请求体,也不进任何锚点。 |
|
||||
| C4 search 过滤参数 | 无法到达 | 通过 | mem0 2.2.1 对无 `user_id` 过滤的 search 直接 `ValueError`(源码 main.py:1466 起);after 恒带 `filters={"user_id": …}`。`limit=` 形参在 2.2.1 被静默忽略,正确形参是 `top_k`。 |
|
||||
| C4 单次记账 | 无法到达 | 通过 | 每次 search 恰好 +1 条嵌入(before 设计上外层+包装双记,已在 fake-SDK 测试中证明并移除)。 |
|
||||
| C5 embed_cap=0 / llm_cap=0 | 无法到达 | 通过 | 任何真实工作前拒绝:假网关在此段收到 **0** 个请求;内部 LLM 现在过独立 llm_ops 闸(默认 0)。 |
|
||||
| C6 并发预占 | 无法到达 | 通过 | 8 路并发 search 对 cap=3 临时账,落账永不越 cap(BEGIN IMMEDIATE 预占)。 |
|
||||
| C7 关闭恢复 | 无法到达 | 通过 | aclose 后同目录重建实例,数据与锚点完好。 |
|
||||
|
||||
## 判定为真实缺陷并已修复的项(来源:固定安装源码核对 + 本脚本实跑复现)
|
||||
|
||||
1. 同路径双 QdrantClient → 构造即死(before JSON 的 trace)。
|
||||
2. 无过滤 search → 2.2.1 ValueError;`limit` 形参被忽略。
|
||||
3. search 外层+包装双记账。
|
||||
4. 内部提取 LLM 不过任何请求闸/账(embed 闸为 0 时也拦不住 LLM——本卡前
|
||||
LLM 先于嵌入扣账的说法不成立:实测 phase1 嵌入在前,但 LLM 从未被计入
|
||||
任何账)。
|
||||
5. 并发 check-then-debit 竞态可越 cap;失败/取消不回滚预占。
|
||||
|
||||
## 未发现问题的项(如实记录)
|
||||
|
||||
- 真实 fastembed `embed(text, memory_action)` / `embed_batch(texts, memory_action)`
|
||||
表面与适配层包装完全吻合(计数=落账)。
|
||||
- metadata 锚点在真实 qdrant payload 中逐字往返。
|
||||
- 滚动 history(save_messages 10 条窗)只含本产品此前 add 的原始消息。
|
||||
|
||||
## 勘正(HEL-882,2026-10-26,原文与前后 JSON 不改写)
|
||||
|
||||
本目录两份 JSON 的运行(e978e87 版脚本)**发生过真实本地嵌入推理**:脚本当时
|
||||
只把 LLM 换成 127.0.0.1 假网关,没有替换 fastembed 边界——`FastEmbedEmbedding`
|
||||
构造时加载了缓存模型,首条 add 即逐笔记了 3 笔真实向量推理
|
||||
(`embed_rows_after_first_add`)。因此提交信息与执行表中「零真实嵌入」的说法
|
||||
不成立;「零真实 LLM 请求/零下载/零非回环出站」仍成立。可证下限
|
||||
**≥12 条真实本地嵌入**(各分段合计,详见 `erratum-hel882.json`);精确总数因
|
||||
临时目录随任务沙箱清除不可恢复。权威三账未写、未补改。
|
||||
|
||||
自 HEL-882 起,`tools/verify_mem0_real_hel874.py` 在 `TextEmbedding` 构造前安装
|
||||
确定性 512 维向量替身(零模型加载、零真实嵌入),socket 层拒绝一切非回环连接
|
||||
(与总管独立复现口径一致),并在清理临时目录前把逐笔账写进证据 JSON
|
||||
(`final_embed_rows` / `final_llm_used`)。
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"card": "HEL-882",
|
||||
"kind": "evidence_erratum",
|
||||
"date": "2026-10-26",
|
||||
"subject": "HEL-874 真实 SDK 验证的嵌入证据口径勘正(原 JSON 一律不改写)",
|
||||
"incorrect_claims": [
|
||||
{
|
||||
"location": "git commit e978e878dfc34f38c781d48f87b93dc21bbf8a61 提交信息",
|
||||
"claim": "zero real model calls, zero real embeddings, zero outbound (fake gateway on loopback + existing local model cache only)",
|
||||
"fact": "LLM 确为零真实请求(全部打在 127.0.0.1 假网关);但嵌入并非零——脚本只替换了 LLM 网关,未替换 fastembed 边界,FastEmbedEmbedding.__init__ 加载了缓存模型并做了真实本地向量推理",
|
||||
"correction": "应为:零真实 LLM 请求、零出站连接、存在真实本地嵌入推理(见 evidence_recovery 的可证下限)"
|
||||
},
|
||||
{
|
||||
"location": "docs/budget-plan-hel874.md 首段(e978e87 版)",
|
||||
"claim": "本卡(HEL-874)零真实模型调用、零真实嵌入、零出站(真实 SDK 验证全部走本地确定性替身与本地模型缓存)",
|
||||
"fact": "『本地模型缓存』那半句与『零真实嵌入』互相矛盾:读取缓存做推理就是真实嵌入,只是零下载、零出站",
|
||||
"correction": "本卡(HEL-882 版)已改写为:零真实模型调用、零真实嵌入(确定性 512 维替身)、零非回环连接"
|
||||
},
|
||||
{
|
||||
"location": "docs/requirements/handoff-HEL-874.json verification/deliverable 字段",
|
||||
"claim": "全部零真实模型/零嵌入/零出站(127.0.0.1 确定性假网关与本地模型缓存除外)",
|
||||
"fact": "『除外』条款实际承认了本地模型缓存的使用,但『零嵌入』表述优先读作零嵌入推理,与事实不符",
|
||||
"correction": "handoff 为历史交接记录不改写;以本勘正为准"
|
||||
}
|
||||
],
|
||||
"evidence_recovery": {
|
||||
"recoverable": [
|
||||
"docs/rework/hel874/real-sdk-after.json: embed_rows_after_first_add 共 3 笔(product:search / product:batch:add / product:add),即首条 add 发生 3 次真实本地嵌入推理",
|
||||
"同 JSON C2_add_ledger_shape: embed_items=3 llm_calls=1(第二条为逐笔明细佐证)",
|
||||
"同 JSON C3_extraction_input_set: 第二条 add 成功 → 按同一流程至少再 2 笔(查询嵌入+提取批嵌入,实体 upsert 0..N 不确定)",
|
||||
"同 JSON C3b_unshared_canary_never_outbound: queued_public_batches=2,tick 处理 1 个批次 → 至少再 2 笔",
|
||||
"同 JSON C4_search_single_debit: embed_delta=1",
|
||||
"同 JSON C6_concurrency_cap: used=3(独立 conc 临时账,同为真实推理)",
|
||||
"同 JSON C7_reopen_after_close: 重开后的 search → +1 笔"
|
||||
],
|
||||
"lower_bound_real_local_embeddings": 12,
|
||||
"upper_bound_estimate": 14,
|
||||
"unrecoverable": [
|
||||
"精确总数不可恢复:脚本临时目录 /tmp/multica-task-4220825970/hel874-real-sdk-*(含逐笔临时账)已随任务沙箱清除,仓库内只留有上述分段 JSON"
|
||||
],
|
||||
"llm_requests": "全部为 127.0.0.1 确定性假网关(gateway_requests_total=3,零真实 LLM 请求,此口径原判断正确,无需勘正)",
|
||||
"downloads_and_outbound": "零下载、零非回环出站(模型缓存为既有只读复用),此口径原判断正确"
|
||||
},
|
||||
"authoritative_ledgers": "权威三账(真实请求 39/60、嵌入 498/500、HEL-835 闸)全程未写、未补改;临时账全部在已清除的临时目录,与权威账无关",
|
||||
"fix_in_this_card": "tools/verify_mem0_real_hel874.py 自本版本起在 TextEmbedding 构造前安装确定性 512 维向量替身(零模型加载)且 socket 拒绝一切非回环连接(总管独立复现同口径);证据 JSON 增记 final_embed_rows/final_llm_used 并在清理临时目录前落盘,避免同类不可恢复"
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"started": "2026-10-26T14:02:45Z",
|
||||
"install": {
|
||||
"python": "3.10.12",
|
||||
"mem0ai": "2.2.1",
|
||||
"qdrant-client": "1.20.0",
|
||||
"fastembed": "0.9.1",
|
||||
"mem0_path": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/mem0",
|
||||
"module_sha256": {
|
||||
"memory/main.py": "f329ca4a17af1fbefb32ac10d4cc7ae7ddbdb7435a87eb74c30915cff98b8b7c",
|
||||
"memory/storage.py": "5f5ecdadd2b260fd14f36a0f338336eb760275465dd1056a99a47e1ef07702bd",
|
||||
"embeddings/fastembed.py": "3ec2ae87df5d0bebedb8b5471366c3412975847458499f66097f7b8c9ac8c9b8",
|
||||
"llms/openai.py": "37e6d3c22b55df0731740f4e0868b95bd480ce44363227fe53461db328c5f67e",
|
||||
"vector_stores/qdrant.py": "da570579d5cad69e6f123fdfe32b725235a25604e70047666bec763ba6e2bf43"
|
||||
},
|
||||
"source_tarball_sha256": "9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23"
|
||||
},
|
||||
"adapter_has_llm_guard": true,
|
||||
"checks": [
|
||||
{
|
||||
"name": "C1_construct",
|
||||
"ok": true,
|
||||
"detail": "client_path=sdk-vector-store-client"
|
||||
},
|
||||
{
|
||||
"name": "C2_add_anchor_payload",
|
||||
"ok": true,
|
||||
"detail": "created=True payload_keys=['created_at', 'data', 'hash', 'input_sha256', 'src_group', 'text_lemmatized', 'turn_ids', 'turn_roles']"
|
||||
},
|
||||
{
|
||||
"name": "C2_add_user_scope",
|
||||
"ok": true,
|
||||
"detail": "user_id present in stored payload"
|
||||
},
|
||||
{
|
||||
"name": "C2_add_ledger_shape",
|
||||
"ok": true,
|
||||
"detail": "embed_items=3 llm_calls=1"
|
||||
},
|
||||
{
|
||||
"name": "C3_extraction_input_set",
|
||||
"ok": true,
|
||||
"detail": "old_memory=True new_row=True history_carry=True"
|
||||
},
|
||||
{
|
||||
"name": "C3b_unshared_canary_never_outbound",
|
||||
"ok": true,
|
||||
"detail": "queued_public_batches=2 leaked=False anchor_taint=False"
|
||||
},
|
||||
{
|
||||
"name": "C4_search_filters",
|
||||
"ok": true,
|
||||
"detail": "hits=3"
|
||||
},
|
||||
{
|
||||
"name": "C4_search_single_debit",
|
||||
"ok": true,
|
||||
"detail": "embed_delta=1 (pre-fix=2)"
|
||||
},
|
||||
{
|
||||
"name": "C6_concurrency_cap",
|
||||
"ok": true,
|
||||
"detail": "outcomes=['ok', 'refused'] used=3 cap=3"
|
||||
},
|
||||
{
|
||||
"name": "C7_reopen_after_close",
|
||||
"ok": true,
|
||||
"detail": "hits_after_reopen=3"
|
||||
},
|
||||
{
|
||||
"name": "C5_embed_zero_refuses",
|
||||
"ok": true,
|
||||
"detail": "err=semantic_embed_budget_not_approved llm_sent=0"
|
||||
},
|
||||
{
|
||||
"name": "C5_llm_zero_refuses",
|
||||
"ok": true,
|
||||
"detail": "err=semantic_llm_budget_not_approved llm_sent=0"
|
||||
}
|
||||
],
|
||||
"embed_rows_after_first_add": [
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
}
|
||||
],
|
||||
"finished": "2026-10-26T14:02:47Z",
|
||||
"gateway_requests_total": 3,
|
||||
"failures": []
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"started": "2026-10-26T14:02:48Z",
|
||||
"install": {
|
||||
"python": "3.10.12",
|
||||
"mem0ai": "2.2.1",
|
||||
"qdrant-client": "1.20.0",
|
||||
"fastembed": "0.9.1",
|
||||
"mem0_path": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/mem0",
|
||||
"module_sha256": {
|
||||
"memory/main.py": "f329ca4a17af1fbefb32ac10d4cc7ae7ddbdb7435a87eb74c30915cff98b8b7c",
|
||||
"memory/storage.py": "5f5ecdadd2b260fd14f36a0f338336eb760275465dd1056a99a47e1ef07702bd",
|
||||
"embeddings/fastembed.py": "3ec2ae87df5d0bebedb8b5471366c3412975847458499f66097f7b8c9ac8c9b8",
|
||||
"llms/openai.py": "37e6d3c22b55df0731740f4e0868b95bd480ce44363227fe53461db328c5f67e",
|
||||
"vector_stores/qdrant.py": "da570579d5cad69e6f123fdfe32b725235a25604e70047666bec763ba6e2bf43"
|
||||
},
|
||||
"source_tarball_sha256": "9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23"
|
||||
},
|
||||
"adapter_has_llm_guard": false,
|
||||
"checks": [
|
||||
{
|
||||
"name": "C1_construct",
|
||||
"ok": false,
|
||||
"detail": "RuntimeError: Storage folder /tmp/multica-task-4220825970/hel874-real-sdk-3tml6wtr/vec/vec_public is already accessed by another instance of Qdrant client. If you require concurrent access, use Qdrant server instead."
|
||||
}
|
||||
],
|
||||
"trace": "Traceback (most recent call last):\n File \"/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/portalocker/portalocker.py\", line 1008, in lock\n self.locker(fd, flags)\nBlockingIOError: [Errno 11] Resource temporarily unavailable\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n File \"/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/qdrant_client/local/qdrant_local.py\", line 178, in _load\n portalocker.lock(\n File \"/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/portalocker/portalocker.py\", line 1151, in lock\n _posix_locker_instance.lock(file, flags)\n File \"/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/portalocker/portalocker.py\", line 1011, in lock\n raise exceptions.AlreadyLocked(\nportalocker.exceptions.AlreadyLocked: (BlockingIOError(11, 'Resource temporarily unavailable'), '[Errno 11] Resource temporarily unavailable')\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n File \"/tmp/multica-task-4220825970/opencode/before-31970c4/tools/verify_mem0_real_hel874.py\", line 222, in main_async\n backend = Mem0Backend(config_for(tmp, ledger, port=port, cap=64, llm_cap=32))\n File \"/tmp/multica-task-4220825970/opencode/before-31970c4/src/cybergril/memory/semantic.py\", line 163, in __init__\n self._memory = Memory.from_config(\n File \"/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/mem0/memory/main.py\", line 737, in from_config\n return cls(config)\n File \"/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/mem0/memory/main.py\", line 496, in __init__\n self.vector_store = VectorStoreFactory.create(\n File \"/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/mem0/utils/factory.py\", line 216, in create\n return vector_store_instance(**config)\n File \"/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/mem0/vector_stores/qdrant.py\", line 81, in __init__\n self.client = QdrantClient(**params)\n File \"/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/qdrant_client/qdrant_client.py\", line 130, in __init__\n self._client = QdrantLocal(\n File \"/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/qdrant_client/local/qdrant_local.py\", line 91, in __init__\n self._load()\n File \"/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/qdrant_client/local/qdrant_local.py\", line 183, in _load\n raise RuntimeError(\nRuntimeError: Storage folder /tmp/multica-task-4220825970/hel874-real-sdk-3tml6wtr/vec/vec_public is already accessed by another instance of Qdrant client. If you require concurrent access, use Qdrant server instead.\n",
|
||||
"finished": "2026-10-26T14:02:49Z",
|
||||
"gateway_requests_total": 0,
|
||||
"failures": [
|
||||
"C1_construct: RuntimeError: Storage folder /tmp/multica-task-4220825970/hel874-real-sdk-3tml6wtr/vec/vec_public is already accessed by another instance of Qdrant client. If you require concurrent access, use Qdrant server instead."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"head": "e29105875440b88c7951c399ee29c4931bf55a60",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"embedding_boundary": "deterministic 512-dim; no model loaded",
|
||||
"external_network": "socket.connect rejects non-loopback",
|
||||
"stub_batch": {
|
||||
"backend": "stub",
|
||||
"state": "done"
|
||||
},
|
||||
"mem0_enqueue_count": 1,
|
||||
"mem0_batch_after_tick": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"entry_ids": "[\"38bfb1e0-9f9b-4b1f-85c3-8b437d140e53\"]"
|
||||
},
|
||||
"mem0_hits": [
|
||||
{
|
||||
"id": "38bfb1e0-9f9b-4b1f-85c3-8b437d140e53",
|
||||
"memory": "合成提取记忆HEL874-1"
|
||||
}
|
||||
],
|
||||
"mem0_reenqueue_after_done": 0,
|
||||
"embed_ledger_used": 4,
|
||||
"llm_ledger_used": 1,
|
||||
"deterministic_vector_items": 3
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# HEL-882 · 硬闸/恢复/发布包修正证据
|
||||
|
||||
读取提交:本目录 JSON 的 `head` 字段(`e29105875440b88c7951c399ee29c4931bf55a60`,
|
||||
最终交付 head 见交付评论——本目录随证据一次追加提交)。全部运行零真实 LLM、
|
||||
零真实嵌入(确定性 512 维替身)、零非回环出站、权威三账零写入。
|
||||
|
||||
## 1. 总管两复现脚本原样复跑(仅改仓库路径与 head)
|
||||
|
||||
脚本:`review_hel882_sdk.py` / `review_hel882_api.py`(与总管附件逐行同构,
|
||||
HEL-874 原版在总管附件)。结果对比:
|
||||
|
||||
| 复现 | e978e87(总管复现) | 本 head(复跑) |
|
||||
| --- | --- | --- |
|
||||
| api:日 token 超限仍发请求 | 1 次 provider 请求、chat.final、已用 2962→5005 | **0 次请求、chat.error、已用 2962 不变**(manager-review-api-rerun.json) |
|
||||
| sdk①:llm_cap=1 撞 500 隐藏重试 | 6 次 HTTP、llm_ops=0、max_retries=2 | **1 次 HTTP、llm_ops=1、max_retries=0**,第二次 add 发送前拒绝(manager-review-sdk-rerun.json) |
|
||||
| sdk②:embed 中途耗尽被吞 | done/[],调 cap 后仍 done、enqueue=0 | **首 tick queued(reason=semantic_add_swallowed_failure:…budget_exhausted,attempts 不耗);调 cap 后同批续跑 done 且 entry_ids 非空** |
|
||||
| sdk③:stub→mem0 同档案 | enqueue=0、真实库无命中 | **enqueue=1**(脚本本身不 tick;补证见 §2) |
|
||||
|
||||
## 2. 补证:stub→mem0 重整全链(真实固定 SDK + 确定性替身)
|
||||
|
||||
`addon_stub_switch_hel882.py` → `HEL-882-sdk-addon-stub-switch.json`:stub 批
|
||||
done 后切 mem0,enqueue=1 → tick → **done + 真实 entry** → search **命中** →
|
||||
再次 enqueue=0(真实完成批不重发)。同边界:零模型加载、禁非回环。
|
||||
|
||||
## 3. 固定安装合同验证(工具本体,确定性边界)
|
||||
|
||||
`real-sdk-hel882.json`:`tools/verify_mem0_real_hel874.py` 12/12 通过;
|
||||
`embedding_boundary` = TextEmbedding 构造前确定性 512 维替身、零模型加载;
|
||||
`final_embed_rows`/`final_llm_used` 在清理临时目录前落盘(逐笔可审计)。
|
||||
e978e87 版真实嵌入勘正见 `../hel874/erratum-hel882.json`。
|
||||
|
||||
## 4. 发布包离线核验
|
||||
|
||||
`release-offline-hel882.json`:`tools/check_release_offline_hel882.py` 全过——
|
||||
R1 空目录 checkout、R2 lock-only venv + PYTHONPATH 服务 import/health
|
||||
(R4-assembly / stub)、R3 未批上限 fail-closed(创建可过、对话在 provider 前
|
||||
拒绝并如实 chat.error)、R4 填批准值后全链(echo 替身、409=数据已在)、
|
||||
R5 停止后数据保留、重开 epoch+1、R6 mem0 同进程导入缺项(3.11 无 wheel,
|
||||
解决路径 deploy/hel874-release.md §4,本卡零下载)。
|
||||
@@ -0,0 +1,106 @@
|
||||
"""HEL-882 add-on to the manager's SDK review: the re-enqueued mem0 batch is
|
||||
actually processed (tick) and becomes searchable — completing the stub→mem0
|
||||
recovery proof (the review script stops after enqueue_count). Same boundary:
|
||||
deterministic 512-dim embedding installed before TextEmbedding construction,
|
||||
socket rejects non-loopback, temp ledger/vector dirs only, zero real LLM
|
||||
(fake 127.0.0.1 gateway), zero real embeddings, zero downloads."""
|
||||
import asyncio
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import socket
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
ROOT = Path('/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-882-f72ebe62dddc/workdir')
|
||||
REPO = ROOT / 'CyberGril'
|
||||
sys.path.insert(0, str(REPO / 'src'))
|
||||
for name in ('OPENROUTER_API_KEY', 'OPENAI_API_KEY', 'OPENAI_BASE_URL'):
|
||||
os.environ.pop(name, None)
|
||||
spec = importlib.util.spec_from_file_location('contract', REPO / 'tools/verify_mem0_real_hel874.py')
|
||||
contract = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(contract)
|
||||
|
||||
real_connect = socket.socket.connect
|
||||
|
||||
|
||||
def connect(sock, addr):
|
||||
if isinstance(addr, tuple) and addr[0] not in ('127.0.0.1', '::1', 'localhost'):
|
||||
raise AssertionError('addon forbids non-loopback connection')
|
||||
return real_connect(sock, addr)
|
||||
|
||||
|
||||
socket.socket.connect = connect
|
||||
|
||||
import numpy as np
|
||||
import mem0.embeddings.fastembed as embedding_module
|
||||
|
||||
|
||||
class DeterministicEmbedding:
|
||||
embedding_size = 512
|
||||
calls = 0
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def embed(self, texts, *args, **kwargs):
|
||||
for text in ([texts] if isinstance(texts, str) else texts):
|
||||
type(self).calls += 1
|
||||
digest = hashlib.sha256(text.encode()).digest()
|
||||
yield np.array([digest[i % 32] / 255.0 for i in range(512)], dtype=np.float32)
|
||||
|
||||
|
||||
embedding_module.TextEmbedding = DeterministicEmbedding
|
||||
|
||||
from cybergril.memory.semantic import Mem0Backend, SemanticMemory, StubBackend
|
||||
from cybergril.memory.store import Store
|
||||
|
||||
|
||||
async def main():
|
||||
full = os.popen(f'git -C {REPO} rev-parse HEAD').read().strip()
|
||||
result = {'head': full, 'real_llm_requests': 0, 'real_embeddings': 0,
|
||||
'embedding_boundary': 'deterministic 512-dim; no model loaded',
|
||||
'external_network': 'socket.connect rejects non-loopback'}
|
||||
with tempfile.TemporaryDirectory(prefix='hel882-addon-', dir=ROOT) as td:
|
||||
tmp = Path(td)
|
||||
server = contract.start_gateway()
|
||||
try:
|
||||
store = Store(tmp / 'switch.db')
|
||||
with store.conn:
|
||||
store.execute("INSERT INTO chat_message(ts,role,text,turn_id,status) "
|
||||
"VALUES(1,'user','另一条合成原话','t2','sent')")
|
||||
stub = SemanticMemory(store, StubBackend())
|
||||
stub.enqueue_uncovered()
|
||||
await stub.tick()
|
||||
ledger = tmp / 'switch.sqlite3'
|
||||
contract.make_ledger(ledger, 50, 5)
|
||||
cfg = contract.config_for(tmp / 'switch', ledger, port=server.server_address[1],
|
||||
cap=50, llm_cap=5)
|
||||
backend = Mem0Backend(cfg)
|
||||
live = SemanticMemory(store, backend)
|
||||
result['stub_batch'] = dict(store.query_one("SELECT backend,state FROM sem_batch"
|
||||
" WHERE backend='stub'"))
|
||||
result['mem0_enqueue_count'] = live.enqueue_uncovered()
|
||||
await live.tick()
|
||||
batch = dict(store.query_one("SELECT state,reason,entry_ids FROM sem_batch"
|
||||
" WHERE backend='mem0'"))
|
||||
result['mem0_batch_after_tick'] = batch
|
||||
result['mem0_hits'] = await backend.search('另一条合成原话')
|
||||
result['mem0_reenqueue_after_done'] = live.enqueue_uncovered()
|
||||
result['embed_ledger_used'] = backend.ledger.used()
|
||||
result['llm_ledger_used'] = backend.llm_guard.used()
|
||||
backend._memory.llm.client.close()
|
||||
await backend.aclose()
|
||||
store.close()
|
||||
finally:
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
result['deterministic_vector_items'] = DeterministicEmbedding.calls
|
||||
out = ROOT / 'HEL-882-sdk-addon-stub-switch.json'
|
||||
out.write_text(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"daily_tokens_limit": 1,
|
||||
"used_before": 2962,
|
||||
"used_after": 2962,
|
||||
"provider_requests_after_exhaustion": 0,
|
||||
"events": [
|
||||
"chat.error"
|
||||
],
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"head": "e29105875440b88c7951c399ee29c4931bf55a60",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"embedding_boundary": "deterministic 512-dimensional vectors; no model loaded",
|
||||
"external_network": "socket.connect rejects all non-loopback hosts",
|
||||
"llm_cap_1_failure_retries": {
|
||||
"add_attempts": 2,
|
||||
"loopback_http_requests": 1,
|
||||
"llm_ledger_used": 1,
|
||||
"errors": [
|
||||
"LLMError",
|
||||
"SemanticBudgetError"
|
||||
],
|
||||
"openai_client_max_retries": 0
|
||||
},
|
||||
"embed_exhaustion_mid_add": {
|
||||
"embed_cap_at_start": 1,
|
||||
"after_first_tick": {
|
||||
"state": "queued",
|
||||
"reason": "semantic_add_swallowed_failure:refused:semantic_embed_budget_exhausted",
|
||||
"entry_ids": null,
|
||||
"attempts": 0
|
||||
},
|
||||
"after_raise_cap_and_tick": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"entry_ids": "[\"2ccf2ee9-0dc2-45c5-8804-a866c69b6549\"]",
|
||||
"attempts": 1
|
||||
},
|
||||
"uncovered_requeued": 0,
|
||||
"embed_ledger_used": 4
|
||||
},
|
||||
"stub_to_mem0_same_archive": {
|
||||
"prior_batch": {
|
||||
"backend": "stub",
|
||||
"state": "done",
|
||||
"entry_ids": "[\"stub-1\"]"
|
||||
},
|
||||
"mem0_enqueue_count": 1,
|
||||
"mem0_hits": []
|
||||
},
|
||||
"deterministic_vector_items": 5
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
{
|
||||
"started": "2026-10-26T15:22:27Z",
|
||||
"embedding_boundary": "deterministic 512-dimensional vectors installed before TextEmbedding construction; no model loaded; zero real embeddings",
|
||||
"external_network": "socket.connect rejects all non-loopback hosts",
|
||||
"install": {
|
||||
"python": "3.10.12",
|
||||
"mem0ai": "2.2.1",
|
||||
"qdrant-client": "1.20.0",
|
||||
"fastembed": "0.9.1",
|
||||
"mem0_path": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/mem0",
|
||||
"module_sha256": {
|
||||
"memory/main.py": "f329ca4a17af1fbefb32ac10d4cc7ae7ddbdb7435a87eb74c30915cff98b8b7c",
|
||||
"memory/storage.py": "5f5ecdadd2b260fd14f36a0f338336eb760275465dd1056a99a47e1ef07702bd",
|
||||
"embeddings/fastembed.py": "3ec2ae87df5d0bebedb8b5471366c3412975847458499f66097f7b8c9ac8c9b8",
|
||||
"llms/openai.py": "37e6d3c22b55df0731740f4e0868b95bd480ce44363227fe53461db328c5f67e",
|
||||
"vector_stores/qdrant.py": "da570579d5cad69e6f123fdfe32b725235a25604e70047666bec763ba6e2bf43"
|
||||
},
|
||||
"source_tarball_sha256": "9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23"
|
||||
},
|
||||
"adapter_has_llm_guard": true,
|
||||
"checks": [
|
||||
{
|
||||
"name": "C1_construct",
|
||||
"ok": true,
|
||||
"detail": "client_path=sdk-vector-store-client"
|
||||
},
|
||||
{
|
||||
"name": "C2_add_anchor_payload",
|
||||
"ok": true,
|
||||
"detail": "created=True payload_keys=['created_at', 'data', 'hash', 'input_sha256', 'src_group', 'text_lemmatized', 'turn_ids', 'turn_roles']"
|
||||
},
|
||||
{
|
||||
"name": "C2_add_user_scope",
|
||||
"ok": true,
|
||||
"detail": "user_id present in stored payload"
|
||||
},
|
||||
{
|
||||
"name": "C2_add_ledger_shape",
|
||||
"ok": true,
|
||||
"detail": "embed_items=3 llm_calls=1"
|
||||
},
|
||||
{
|
||||
"name": "C3_extraction_input_set",
|
||||
"ok": true,
|
||||
"detail": "old_memory=True new_row=True history_carry=True"
|
||||
},
|
||||
{
|
||||
"name": "C3b_unshared_canary_never_outbound",
|
||||
"ok": true,
|
||||
"detail": "queued_public_batches=2 leaked=False anchor_taint=False"
|
||||
},
|
||||
{
|
||||
"name": "C4_search_filters",
|
||||
"ok": true,
|
||||
"detail": "hits=3"
|
||||
},
|
||||
{
|
||||
"name": "C4_search_single_debit",
|
||||
"ok": true,
|
||||
"detail": "embed_delta=1 (pre-fix=2)"
|
||||
},
|
||||
{
|
||||
"name": "C6_concurrency_cap",
|
||||
"ok": true,
|
||||
"detail": "outcomes=['ok', 'refused'] used=3 cap=3"
|
||||
},
|
||||
{
|
||||
"name": "C7_reopen_after_close",
|
||||
"ok": true,
|
||||
"detail": "hits_after_reopen=3"
|
||||
},
|
||||
{
|
||||
"name": "C5_embed_zero_refuses",
|
||||
"ok": true,
|
||||
"detail": "err=semantic_embed_budget_not_approved llm_sent=0"
|
||||
},
|
||||
{
|
||||
"name": "C5_llm_zero_refuses",
|
||||
"ok": true,
|
||||
"detail": "err=semantic_llm_budget_not_approved llm_sent=0"
|
||||
}
|
||||
],
|
||||
"embed_rows_after_first_add": [
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
}
|
||||
],
|
||||
"final_embed_rows": [
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
}
|
||||
],
|
||||
"final_llm_used": 3,
|
||||
"finished": "2026-10-26T15:22:29Z",
|
||||
"gateway_requests_total": 3,
|
||||
"failures": [],
|
||||
"temp_dir": "/tmp/multica-task-2170941/hel874-real-sdk-oawg5lsd"
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"started": "2026-10-26T15:29:53Z",
|
||||
"venv_python": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-874-db7635e6b65e/workdir/CyberGril/.venv311/bin/python",
|
||||
"venv_python_version": "3.11.16",
|
||||
"checks": [
|
||||
{
|
||||
"name": "R1_checkout_fresh_dir",
|
||||
"ok": true,
|
||||
"detail": "cloned tracked source into /tmp/multica-task-2170941/hel882-release-mxps8kap/cybergril-hel874/current"
|
||||
},
|
||||
{
|
||||
"name": "R2_service_imports_product",
|
||||
"ok": true,
|
||||
"detail": ""
|
||||
},
|
||||
{
|
||||
"name": "R6_mem0_coimport_status_recorded",
|
||||
"ok": true,
|
||||
"detail": "import mem0 → ModuleNotFoundError: No module named 'mem0' (resolution path in deploy/hel874-release.md §4; no download in this check)"
|
||||
},
|
||||
{
|
||||
"name": "R2_service_serves_health",
|
||||
"ok": true,
|
||||
"detail": "version=R4-assembly semantic=stub"
|
||||
},
|
||||
{
|
||||
"name": "R3_create_status_recorded",
|
||||
"ok": true,
|
||||
"detail": "create status=201(409=前次运行的档案仍在,也是保留数据的表现)"
|
||||
},
|
||||
{
|
||||
"name": "R3_dialogue_refused_honestly",
|
||||
"ok": true,
|
||||
"detail": "http=200 events=b'data: {\"t\": \"chat.error\", \"epoch\": 1, \"turn_id\": \"00000000-0000-4000-8000-000000000001\", \"phi\": \"\\xe5\\xa5\\xb9\\xe8\\xbf\\x99\\xe4\\xbc\\x9a\\xe5\\x84\\xbf\\xe6\\xb2\\xa1\\xe6\\x8e\\xa5\\xe4\\xb8\\x8a\\xe8\\xaf'"
|
||||
},
|
||||
{
|
||||
"name": "R4_service_restarts_on_same_data",
|
||||
"ok": true,
|
||||
"detail": "epoch=2"
|
||||
},
|
||||
{
|
||||
"name": "R5_epoch_bumped_on_reopen",
|
||||
"ok": true,
|
||||
"detail": "1 → 2"
|
||||
},
|
||||
{
|
||||
"name": "R4_companion_created_offline",
|
||||
"ok": true,
|
||||
"detail": "status=409"
|
||||
},
|
||||
{
|
||||
"name": "R4_dialogue_stream_http",
|
||||
"ok": true,
|
||||
"detail": "status=200 events=b'data: {\"t\": \"chat.delta\", \"epoch\": 2, \"turn_id\": \"00000000-0000-4000-8000-000000000002\", \"delta\": \"\\xe6\\x89\\xb9\"}\\n\\ndata: {\"t\": \"c'"
|
||||
},
|
||||
{
|
||||
"name": "R4_messages_roundtrip",
|
||||
"ok": true,
|
||||
"detail": "roles=['user', 'user', 'companion']"
|
||||
},
|
||||
{
|
||||
"name": "R5_data_dir_preserved",
|
||||
"ok": true,
|
||||
"detail": "['companion.db']"
|
||||
},
|
||||
{
|
||||
"name": "R5_companion_row_survives_restart",
|
||||
"ok": true,
|
||||
"detail": "companion rows=1"
|
||||
}
|
||||
],
|
||||
"mem0_import_in_release_venv": "ModuleNotFoundError: No module named 'mem0'",
|
||||
"failures": [],
|
||||
"finished": "2026-10-26T15:29:55Z"
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
"""Independent real application test with deterministic fake provider."""
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
ROOT = Path('/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-882-f72ebe62dddc/workdir')
|
||||
sys.path[:0] = [str(ROOT / 'CyberGril/src'), str(ROOT / 'CyberGril/tests')]
|
||||
from fastapi.testclient import TestClient
|
||||
from test_r4_api import r4_app
|
||||
from test_api import CREATE, turn, events
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix='hel874-api-review-', dir=ROOT) as td:
|
||||
app, provider = r4_app(Path(td), semantic='off')
|
||||
with TestClient(app) as client:
|
||||
assert client.post('/api/companion', json=CREATE).status_code == 201
|
||||
guard = app.state.life.router.guard
|
||||
guard.config.daily_tokens = 1
|
||||
guard.record(1, 'review_budget_already_used')
|
||||
used_before = guard.used
|
||||
calls_before = len(provider.calls)
|
||||
response = turn(client, '合成测试:额度用完后仍能发出请求吗')
|
||||
result = {'daily_tokens_limit': 1, 'used_before': used_before,
|
||||
'used_after': guard.used, 'provider_requests_after_exhaustion': len(provider.calls)-calls_before,
|
||||
'events': [e['t'] for e in events(response)],
|
||||
'real_llm_requests': 0, 'real_embeddings': 0}
|
||||
(ROOT / 'HEL-874-api-independent.json').write_text(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
@@ -0,0 +1,134 @@
|
||||
"""Independent review: actual pinned SDK, deterministic embedding boundary only."""
|
||||
import asyncio
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import socket
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
ROOT = Path('/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-882-f72ebe62dddc/workdir')
|
||||
REPO = ROOT / 'CyberGril'
|
||||
sys.path.insert(0, str(REPO / 'src'))
|
||||
for name in ('OPENROUTER_API_KEY', 'OPENAI_API_KEY', 'OPENAI_BASE_URL'):
|
||||
os.environ.pop(name, None)
|
||||
spec = importlib.util.spec_from_file_location('contract', REPO / 'tools/verify_mem0_real_hel874.py')
|
||||
contract = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(contract)
|
||||
|
||||
# Block every network connection except our loopback fake HTTP server.
|
||||
real_connect = socket.socket.connect
|
||||
def connect(sock, addr):
|
||||
if isinstance(addr, tuple) and addr[0] not in ('127.0.0.1', '::1', 'localhost'):
|
||||
raise AssertionError('review forbids non-loopback connection')
|
||||
return real_connect(sock, addr)
|
||||
socket.socket.connect = connect
|
||||
|
||||
import numpy as np
|
||||
import mem0.embeddings.fastembed as embedding_module
|
||||
class DeterministicEmbedding:
|
||||
embedding_size = 512
|
||||
calls = 0
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
def embed(self, texts, *args, **kwargs):
|
||||
for text in ([texts] if isinstance(texts, str) else texts):
|
||||
type(self).calls += 1
|
||||
digest = hashlib.sha256(text.encode()).digest()
|
||||
yield np.array([digest[i % 32] / 255.0 for i in range(512)], dtype=np.float32)
|
||||
embedding_module.TextEmbedding = DeterministicEmbedding
|
||||
|
||||
from cybergril.memory.semantic import Mem0Backend, SemanticMemory, StubBackend
|
||||
from cybergril.memory.store import Store
|
||||
|
||||
async def main():
|
||||
result = {'head': 'e29105875440b88c7951c399ee29c4931bf55a60',
|
||||
'real_llm_requests': 0, 'real_embeddings': 0,
|
||||
'embedding_boundary': 'deterministic 512-dimensional vectors; no model loaded',
|
||||
'external_network': 'socket.connect rejects all non-loopback hosts'}
|
||||
with tempfile.TemporaryDirectory(prefix='hel874-independent-', dir=ROOT) as td:
|
||||
tmp = Path(td)
|
||||
server = contract.start_gateway()
|
||||
try:
|
||||
ledger = tmp / 'retry.sqlite3'
|
||||
contract.make_ledger(ledger, 50, 1)
|
||||
cfg = contract.config_for(tmp / 'retry', ledger, port=server.server_address[1], cap=50, llm_cap=1)
|
||||
backend = Mem0Backend(cfg)
|
||||
normal_post = contract.FakeGateway.do_POST
|
||||
requests = []
|
||||
def fail_post(handler):
|
||||
handler.rfile.read(int(handler.headers.get('content-length') or 0))
|
||||
requests.append(handler.path)
|
||||
body = b'{"error":{"message":"review controlled 500"}}'
|
||||
handler.send_response(500)
|
||||
handler.send_header('content-type', 'application/json')
|
||||
handler.send_header('content-length', str(len(body)))
|
||||
handler.end_headers()
|
||||
handler.wfile.write(body)
|
||||
contract.FakeGateway.do_POST = fail_post
|
||||
rows = [{'text': '合成测试钥匙在第三个花盆下面', 'turn_id': 't1', 'role': 'user', 'message_id': 1}]
|
||||
errors = []
|
||||
for _ in range(2):
|
||||
try:
|
||||
await backend.add_group(rows, {}, 'retry')
|
||||
except Exception as exc:
|
||||
errors.append(type(exc).__name__)
|
||||
result['llm_cap_1_failure_retries'] = {
|
||||
'add_attempts': 2, 'loopback_http_requests': len(requests),
|
||||
'llm_ledger_used': backend.llm_guard.used(), 'errors': errors,
|
||||
'openai_client_max_retries': backend._memory.llm.client.max_retries}
|
||||
backend._memory.llm.client.close()
|
||||
await backend.aclose()
|
||||
contract.FakeGateway.do_POST = normal_post
|
||||
|
||||
ledger2 = tmp / 'exhaust.sqlite3'
|
||||
contract.make_ledger(ledger2, 1, 5)
|
||||
cfg2 = contract.config_for(tmp / 'exhaust', ledger2, port=server.server_address[1], cap=1, llm_cap=5)
|
||||
backend2 = Mem0Backend(cfg2)
|
||||
store = Store(tmp / 'exhaust.db')
|
||||
with store.conn:
|
||||
store.execute("INSERT INTO chat_message(ts,role,text,turn_id,status) VALUES(1,'user',?,'t1','sent')", (rows[0]['text'],))
|
||||
semantic = SemanticMemory(store, backend2)
|
||||
semantic.enqueue_uncovered()
|
||||
await semantic.tick()
|
||||
before = dict(store.query_one('SELECT state,reason,entry_ids,attempts FROM sem_batch'))
|
||||
backend2.ledger.cap = 50
|
||||
await semantic.tick()
|
||||
after = dict(store.query_one('SELECT state,reason,entry_ids,attempts FROM sem_batch'))
|
||||
result['embed_exhaustion_mid_add'] = {
|
||||
'embed_cap_at_start': 1, 'after_first_tick': before,
|
||||
'after_raise_cap_and_tick': after,
|
||||
'uncovered_requeued': semantic.enqueue_uncovered(),
|
||||
'embed_ledger_used': backend2.ledger.used()}
|
||||
backend2._memory.llm.client.close()
|
||||
await backend2.aclose()
|
||||
store.close()
|
||||
|
||||
store2 = Store(tmp / 'switch.db')
|
||||
with store2.conn:
|
||||
store2.execute("INSERT INTO chat_message(ts,role,text,turn_id,status) VALUES(1,'user','另一条合成原话','t2','sent')")
|
||||
stub = SemanticMemory(store2, StubBackend())
|
||||
stub.enqueue_uncovered()
|
||||
await stub.tick()
|
||||
ledger3 = tmp / 'switch.sqlite3'
|
||||
contract.make_ledger(ledger3, 50, 5)
|
||||
cfg3 = contract.config_for(tmp / 'switch', ledger3, port=server.server_address[1], cap=50, llm_cap=5)
|
||||
backend3 = Mem0Backend(cfg3)
|
||||
live = SemanticMemory(store2, backend3)
|
||||
result['stub_to_mem0_same_archive'] = {
|
||||
'prior_batch': dict(store2.query_one('SELECT backend,state,entry_ids FROM sem_batch')),
|
||||
'mem0_enqueue_count': live.enqueue_uncovered(),
|
||||
'mem0_hits': await backend3.search('另一条合成原话')}
|
||||
backend3._memory.llm.client.close()
|
||||
await backend3.aclose()
|
||||
store2.close()
|
||||
finally:
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
result['deterministic_vector_items'] = DeterministicEmbedding.calls
|
||||
(ROOT / 'HEL-874-sdk-independent.json').write_text(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,43 @@
|
||||
# HEL-885 · 总管独立复审五项修正证据
|
||||
|
||||
读取基线:`59fb06304fb9f230837b43e431e2a1cc303e16c9`(HEL-882 最终 head,
|
||||
总管独立复审「暂不通过发布审核」)。本目录证据均在修正后的代码上实际执行,
|
||||
边界与 HEL-882 一致:确定性 512 维向量替身(TextEmbedding 构造前安装)、
|
||||
socket 拒绝非回环、127.0.0.1 假网关、临时账/库,零真实 LLM、零真实嵌入、
|
||||
零下载、零权威三账写入。
|
||||
|
||||
## 修正对照(复现 → 修复后实测)
|
||||
|
||||
| # | 总管复现(59fb063) | 修复后实测 | 证据 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | 整批 send_anchor 复制给每次逐行 add;首向量即宣称两行完成;补 cap 后 done/[]、HTTP 仍 1、用户原话从未发出 | 每向量逐行锚点;首 tick 后 completed=[2];补 cap 后 done 且 entry_ids 2 条、HTTP=2、用户原话确实发出(每行恰一次) | manager-review-partial-rerun.json(脚本原样复跑) |
|
||||
| 2 | settle 用 state.day:次日 used=-80,估算 150 放行 | 结算记回预占日:次日 used=0,150 被拒,原日记实际 10 | manager-review-extra-rerun.json(脚本原样复跑) |
|
||||
| 3 | reserve:* 一小时 DELETE:未知预占被删,第二次 reserve 又放行 | 未知预占保留:second_allowed=false,used 保持 1(llm/embed 同路径、跨重启) | manager-review-extra-rerun.json + recovery-real-sdk.json S4a/S4b |
|
||||
| 4 | 缺 authority 时跳过校验:新空账+正 cap 成功构造并发送 | 缺/空/错 authority 一律构造前拒绝 ledger_authority_required;示例配置 authority 必填+算式 | recovery-real-sdk.json S0 + tests/test_rework_hel885.py |
|
||||
| 5 | `uv pip download` 本机不存在(rc=2) | 文档改用 `python3 -m pip download`(rc=0 实测);R7 双探针入离线检查 | release-offline-hel885.json R7 |
|
||||
|
||||
## 文件
|
||||
|
||||
- `review_hel882_partial.py` / `review_hel882_extra.py`:总管原样脚本(附件同版,
|
||||
自 hel-882-bcbb63d27399 工作目录复制),对本 checkout 复跑。
|
||||
- `manager-review-partial-rerun.json` / `manager-review-extra-rerun.json`:
|
||||
原样脚本复跑结果(head 字段仍为脚本内写定的 59fb063 字符串,实际执行于本
|
||||
修正后的 checkout;JSON 内容即修复后行为)。
|
||||
- `recovery-real-sdk.json`:tools/verify_semantic_recovery_hel885.py(新增)——
|
||||
固定 mem0ai 2.2.1 安装上 S0 缺 authority 拒绝 / S1 逐行部分完成 / S2 进程重启
|
||||
只补缺行(HTTP 计数证明,不依赖文本出现次数:mem0 提取提示会合法携带旧行
|
||||
滚动历史)/ S3 租约回收与并发 tick 不双发 / S4a/S4b 未知预占不退款。
|
||||
- `real-sdk-contract.json`:tools/verify_mem0_real_hel874.py 复跑 12/12
|
||||
(make_ledger/config_for 现带合成 authority 前缀,产品默认未被放宽)。
|
||||
- `release-offline-hel885.json`:tools/check_release_offline_hel882.py 全过,
|
||||
新增 R7(pip download 存在 rc=0;已删除的 uv pip download 保持不存在)。
|
||||
|
||||
## 回归
|
||||
|
||||
- 3.11.16 venv + requirements.lock 离线:281 项 pytest 全过(新增
|
||||
tests/test_rework_hel885.py 9 例;test_resume_never_loses_or_duplicates_rows
|
||||
重写为真实产品路径,不再手工植入单行锚点);ruff 0 错(src/tests/tools);
|
||||
web tsc + 42 vitest + build(npm ci --offline)。
|
||||
- 发现并修复一处被旧测试掩盖的崩溃:budget_total 空表时
|
||||
total_tokens_used() 抛 TypeError(旧用例靠该崩溃“通过”)——现
|
||||
coalesce+空行处理,用例改为如实入账一次性总量。
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"head": "59fb06304fb9f230837b43e431e2a1cc303e16c9",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"midnight_settlement": {
|
||||
"daily_cap": 100,
|
||||
"old_hold": 90,
|
||||
"actual": 10,
|
||||
"new_day_used_before_next_request": 0,
|
||||
"request_estimate": 150,
|
||||
"over_cap_request_allowed": false,
|
||||
"rows": [
|
||||
{
|
||||
"day": "2026-10-26",
|
||||
"scene": "dialogue_main",
|
||||
"used": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
"aged_unknown_reservation": {
|
||||
"cap": 1,
|
||||
"used_before": 1,
|
||||
"second_allowed": false,
|
||||
"first_hold_id": 1,
|
||||
"second_hold_id": null,
|
||||
"used_after": 1
|
||||
},
|
||||
"release_documented_download_command": {
|
||||
"command": "uv pip download --help",
|
||||
"returncode": 2,
|
||||
"stderr": "error: unrecognized subcommand 'download'\n\nUsage: uv pip [OPTIONS] <COMMAND>\n\nFor more information, try '--help'."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"head": "59fb06304fb9f230837b43e431e2a1cc303e16c9",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"boundary": "deterministic 512 vectors, non-loopback forbidden",
|
||||
"missing_authority_constructed_with_positive_caps": false,
|
||||
"public_batch_enqueued": 1,
|
||||
"batch_after_first_tick": {
|
||||
"state": "queued",
|
||||
"attempts": 0,
|
||||
"reason": "LLM extraction failed: semantic_llm_budget_exhausted",
|
||||
"entry_ids": null
|
||||
},
|
||||
"loopback_requests_before_resume": 1,
|
||||
"stored_points_after_first": [
|
||||
{
|
||||
"src_group": "chat_public:mem0:synthetic:1bc8667410",
|
||||
"turn_ids": [
|
||||
"synthetic"
|
||||
],
|
||||
"turn_roles": [
|
||||
[
|
||||
"synthetic",
|
||||
"companion",
|
||||
2
|
||||
]
|
||||
],
|
||||
"input_sha256": "840dde45eec07416f21c56cefb0f93f4e71af3edc3100c332f84bb1266637862",
|
||||
"user_id": "cybergril",
|
||||
"data": "合成提取记忆HEL874-1",
|
||||
"text_lemmatized": "合成提取记忆HEL874-1",
|
||||
"hash": "69195446402294f6e77f8ee993ce3456",
|
||||
"created_at": "2026-10-26T15:56:46.108106+00:00",
|
||||
"updated_at": "2026-10-26T15:56:46.108106+00:00"
|
||||
}
|
||||
],
|
||||
"claimed_completed_message_ids": [
|
||||
2
|
||||
],
|
||||
"batch_after_resume": {
|
||||
"state": "done",
|
||||
"attempts": 1,
|
||||
"reason": null,
|
||||
"entry_ids": "[\"2bc5a040-cd80-47a6-b865-c3b3edb7c711\", \"fe98487a-e3f4-4633-abda-2680544a9aaf\"]"
|
||||
},
|
||||
"loopback_requests_after_resume": 2,
|
||||
"unsent_user_row_actually_sent": true
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
{
|
||||
"started": "2026-10-26T15:58:41Z",
|
||||
"embedding_boundary": "deterministic 512-dimensional vectors installed before TextEmbedding construction; no model loaded; zero real embeddings",
|
||||
"external_network": "socket.connect rejects all non-loopback hosts",
|
||||
"install": {
|
||||
"python": "3.10.12",
|
||||
"mem0ai": "2.2.1",
|
||||
"qdrant-client": "1.20.0",
|
||||
"fastembed": "0.9.1",
|
||||
"mem0_path": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/mem0",
|
||||
"module_sha256": {
|
||||
"memory/main.py": "f329ca4a17af1fbefb32ac10d4cc7ae7ddbdb7435a87eb74c30915cff98b8b7c",
|
||||
"memory/storage.py": "5f5ecdadd2b260fd14f36a0f338336eb760275465dd1056a99a47e1ef07702bd",
|
||||
"embeddings/fastembed.py": "3ec2ae87df5d0bebedb8b5471366c3412975847458499f66097f7b8c9ac8c9b8",
|
||||
"llms/openai.py": "37e6d3c22b55df0731740f4e0868b95bd480ce44363227fe53461db328c5f67e",
|
||||
"vector_stores/qdrant.py": "da570579d5cad69e6f123fdfe32b725235a25604e70047666bec763ba6e2bf43"
|
||||
},
|
||||
"source_tarball_sha256": "9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23"
|
||||
},
|
||||
"adapter_has_llm_guard": true,
|
||||
"checks": [
|
||||
{
|
||||
"name": "C1_construct",
|
||||
"ok": true,
|
||||
"detail": "client_path=sdk-vector-store-client"
|
||||
},
|
||||
{
|
||||
"name": "C2_add_anchor_payload",
|
||||
"ok": true,
|
||||
"detail": "created=True payload_keys=['created_at', 'data', 'hash', 'input_sha256', 'src_group', 'text_lemmatized', 'turn_ids', 'turn_roles']"
|
||||
},
|
||||
{
|
||||
"name": "C2_add_user_scope",
|
||||
"ok": true,
|
||||
"detail": "user_id present in stored payload"
|
||||
},
|
||||
{
|
||||
"name": "C2_add_ledger_shape",
|
||||
"ok": true,
|
||||
"detail": "embed_items=3 llm_calls=1"
|
||||
},
|
||||
{
|
||||
"name": "C3_extraction_input_set",
|
||||
"ok": true,
|
||||
"detail": "old_memory=True new_row=True history_carry=True"
|
||||
},
|
||||
{
|
||||
"name": "C3b_unshared_canary_never_outbound",
|
||||
"ok": true,
|
||||
"detail": "queued_public_batches=2 leaked=False anchor_taint=False"
|
||||
},
|
||||
{
|
||||
"name": "C4_search_filters",
|
||||
"ok": true,
|
||||
"detail": "hits=3"
|
||||
},
|
||||
{
|
||||
"name": "C4_search_single_debit",
|
||||
"ok": true,
|
||||
"detail": "embed_delta=1 (pre-fix=2)"
|
||||
},
|
||||
{
|
||||
"name": "C6_concurrency_cap",
|
||||
"ok": true,
|
||||
"detail": "outcomes=['ok', 'refused'] used=3 cap=3"
|
||||
},
|
||||
{
|
||||
"name": "C7_reopen_after_close",
|
||||
"ok": true,
|
||||
"detail": "hits_after_reopen=3"
|
||||
},
|
||||
{
|
||||
"name": "C5_embed_zero_refuses",
|
||||
"ok": true,
|
||||
"detail": "err=semantic_embed_budget_not_approved llm_sent=0"
|
||||
},
|
||||
{
|
||||
"name": "C5_llm_zero_refuses",
|
||||
"ok": true,
|
||||
"detail": "err=semantic_llm_budget_not_approved llm_sent=0"
|
||||
}
|
||||
],
|
||||
"embed_rows_after_first_add": [
|
||||
{
|
||||
"kind": "authority_prefix",
|
||||
"items": 0,
|
||||
"note": "authority:synthetic-verify-prefix"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
}
|
||||
],
|
||||
"final_embed_rows": [
|
||||
{
|
||||
"kind": "authority_prefix",
|
||||
"items": 0,
|
||||
"note": "authority:synthetic-verify-prefix"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
}
|
||||
],
|
||||
"final_llm_used": 3,
|
||||
"finished": "2026-10-26T15:58:42Z",
|
||||
"gateway_requests_total": 3,
|
||||
"failures": [],
|
||||
"temp_dir": "/tmp/multica-task-4169170952/hel874-real-sdk-zbdu5fkf"
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"head": "HEL-885 rework",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"boundary": "deterministic 512 vectors, non-loopback forbidden",
|
||||
"checks": [
|
||||
{
|
||||
"name": "S0_positive_caps_without_authority_fail_closed",
|
||||
"ok": true,
|
||||
"detail": "missing=ledger_authority_required empty=ledger_authority_required"
|
||||
},
|
||||
{
|
||||
"name": "S1_p1_first_tick_partial",
|
||||
"ok": true,
|
||||
"detail": "state=queued reason=LLM extraction failed: semantic_llm_budget_exhausted http=1 completed=[2] points=1 per_row_anchor=True"
|
||||
},
|
||||
{
|
||||
"name": "S2_p1_restart_sends_only_missing_row",
|
||||
"ok": true,
|
||||
"detail": "state=done http=1 user_sent=True completed=[1, 2] entry_ids=[\"0419c0c8-a6b1-4a24-864b-295658e23b19\", \"3d1de06a-bc8c-4f4b-9914-049ca13edb5e\"]"
|
||||
},
|
||||
{
|
||||
"name": "S3a_lease_expired_batch_recovers",
|
||||
"ok": true,
|
||||
"detail": "recovered={'state': 'failed', 'reason': 'lease_expired'} state_after=done http=2"
|
||||
},
|
||||
{
|
||||
"name": "S3b_concurrent_ticks_single_send",
|
||||
"ok": true,
|
||||
"detail": "http=2 states=['done']"
|
||||
},
|
||||
{
|
||||
"name": "S4a_aged_unknown_llm_reservation_kept",
|
||||
"ok": true,
|
||||
"detail": "second_allowed=False used_after_restart=1"
|
||||
},
|
||||
{
|
||||
"name": "S4b_aged_unknown_embed_reservation_kept",
|
||||
"ok": true,
|
||||
"detail": "second_allowed=False used=1 after_release=0"
|
||||
}
|
||||
],
|
||||
"gateway_requests_total": 6,
|
||||
"failures": []
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"started": "2026-10-26T15:58:29Z",
|
||||
"venv_python": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-874-db7635e6b65e/workdir/CyberGril/.venv311/bin/python",
|
||||
"venv_python_version": "3.11.16",
|
||||
"checks": [
|
||||
{
|
||||
"name": "R1_checkout_fresh_dir",
|
||||
"ok": true,
|
||||
"detail": "cloned tracked source into /tmp/multica-task-4169170952/hel882-release-qd9sq7nv/cybergril-hel874/current"
|
||||
},
|
||||
{
|
||||
"name": "R2_service_imports_product",
|
||||
"ok": true,
|
||||
"detail": ""
|
||||
},
|
||||
{
|
||||
"name": "R6_mem0_coimport_status_recorded",
|
||||
"ok": true,
|
||||
"detail": "import mem0 → ModuleNotFoundError: No module named 'mem0' (resolution path in deploy/hel874-release.md §4; no download in this check)"
|
||||
},
|
||||
{
|
||||
"name": "R7_pip_download_command_exists",
|
||||
"ok": true,
|
||||
"detail": "/usr/bin/python3 -m pip download --help → rc=0"
|
||||
},
|
||||
{
|
||||
"name": "R7_removed_uv_pip_download_still_absent",
|
||||
"ok": true,
|
||||
"detail": "uv pip download --help → rc=2 (kept absent on purpose; do not re-document it)"
|
||||
},
|
||||
{
|
||||
"name": "R2_service_serves_health",
|
||||
"ok": true,
|
||||
"detail": "version=R4-assembly semantic=stub"
|
||||
},
|
||||
{
|
||||
"name": "R3_create_status_recorded",
|
||||
"ok": true,
|
||||
"detail": "create status=201(409=前次运行的档案仍在,也是保留数据的表现)"
|
||||
},
|
||||
{
|
||||
"name": "R3_dialogue_refused_honestly",
|
||||
"ok": true,
|
||||
"detail": "http=200 events=b'data: {\"t\": \"chat.error\", \"epoch\": 1, \"turn_id\": \"00000000-0000-4000-8000-000000000001\", \"phi\": \"\\xe5\\xa5\\xb9\\xe8\\xbf\\x99\\xe4\\xbc\\x9a\\xe5\\x84\\xbf\\xe6\\xb2\\xa1\\xe6\\x8e\\xa5\\xe4\\xb8\\x8a\\xe8\\xaf'"
|
||||
},
|
||||
{
|
||||
"name": "R4_service_restarts_on_same_data",
|
||||
"ok": true,
|
||||
"detail": "epoch=2"
|
||||
},
|
||||
{
|
||||
"name": "R5_epoch_bumped_on_reopen",
|
||||
"ok": true,
|
||||
"detail": "1 → 2"
|
||||
},
|
||||
{
|
||||
"name": "R4_companion_created_offline",
|
||||
"ok": true,
|
||||
"detail": "status=409"
|
||||
},
|
||||
{
|
||||
"name": "R4_dialogue_stream_http",
|
||||
"ok": true,
|
||||
"detail": "status=200 events=b'data: {\"t\": \"chat.delta\", \"epoch\": 2, \"turn_id\": \"00000000-0000-4000-8000-000000000002\", \"delta\": \"\\xe6\\x89\\xb9\"}\\n\\ndata: {\"t\": \"c'"
|
||||
},
|
||||
{
|
||||
"name": "R4_messages_roundtrip",
|
||||
"ok": true,
|
||||
"detail": "roles=['user', 'user', 'companion']"
|
||||
},
|
||||
{
|
||||
"name": "R5_data_dir_preserved",
|
||||
"ok": true,
|
||||
"detail": "['companion.db']"
|
||||
},
|
||||
{
|
||||
"name": "R5_companion_row_survives_restart",
|
||||
"ok": true,
|
||||
"detail": "companion rows=1"
|
||||
}
|
||||
],
|
||||
"mem0_import_in_release_venv": "ModuleNotFoundError: No module named 'mem0'",
|
||||
"removed_uv_pip_download_rc": 2,
|
||||
"failures": [],
|
||||
"finished": "2026-10-26T15:58:34Z"
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
"""Independent regressions, temporary ledgers, no provider or remote access."""
|
||||
import json
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(ROOT / 'CyberGril/src'))
|
||||
from cybergril.life.model import PersistentBudget
|
||||
from cybergril.memory.store import Store
|
||||
from cybergril.memory.semantic import LlmLedgerGuard
|
||||
from cybergril.think.budget import BudgetConfig, BudgetGuard
|
||||
|
||||
result = {'head': '59fb06304fb9f230837b43e431e2a1cc303e16c9',
|
||||
'real_llm_requests': 0, 'real_embeddings': 0}
|
||||
with tempfile.TemporaryDirectory(dir=ROOT) as td:
|
||||
tmp = Path(td)
|
||||
store = Store(tmp / 'midnight.db')
|
||||
guard = PersistentBudget(store, BudgetGuard(BudgetConfig(
|
||||
daily_tokens=100, daily_requests={'dialogue': 10})))
|
||||
guard._roll = lambda: None
|
||||
guard.state.day = '2026-10-26'
|
||||
hold = guard.acquire('dialogue_main', tokens=90)
|
||||
guard.state.day = '2026-10-27'
|
||||
guard.settle(hold, actual_tokens=10)
|
||||
used = guard.used
|
||||
try:
|
||||
guard.acquire('dialogue_main', tokens=150)
|
||||
allowed = True
|
||||
except RuntimeError:
|
||||
allowed = False
|
||||
result['midnight_settlement'] = {
|
||||
'daily_cap': 100, 'old_hold': 90, 'actual': 10,
|
||||
'new_day_used_before_next_request': used,
|
||||
'request_estimate': 150, 'over_cap_request_allowed': allowed,
|
||||
'rows': [dict(r) for r in store.query('SELECT * FROM token_usage')]}
|
||||
store.close()
|
||||
ledger = tmp / 'crash.db'
|
||||
llm = LlmLedgerGuard(ledger, cap=1)
|
||||
pending = llm.reserve(1, 'sent_but_crashed_before_settlement')
|
||||
with sqlite3.connect(ledger) as conn:
|
||||
conn.execute("UPDATE llm_ops SET ts='2000-01-01T00:00:00Z' WHERE id=?", (pending,))
|
||||
before = llm.used()
|
||||
try:
|
||||
second = llm.reserve(1, 'second_send_after_restart')
|
||||
allowed = True
|
||||
except RuntimeError:
|
||||
second, allowed = None, False
|
||||
result['aged_unknown_reservation'] = {
|
||||
'cap': 1, 'used_before': before, 'second_allowed': allowed,
|
||||
'first_hold_id': pending, 'second_hold_id': second,
|
||||
'used_after': llm.used()}
|
||||
probe = subprocess.run(['uv','pip','download','--help'], capture_output=True, text=True)
|
||||
result['release_documented_download_command'] = {
|
||||
'command': 'uv pip download --help', 'returncode': probe.returncode,
|
||||
'stderr': probe.stderr.strip()}
|
||||
(ROOT / 'HEL-882-extra-independent.json').write_text(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
@@ -0,0 +1,68 @@
|
||||
"""Actual pinned SDK; deterministic vectors and loopback HTTP only."""
|
||||
import asyncio
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
REPO = ROOT / 'CyberGril'
|
||||
sys.path.insert(0, str(REPO / 'src'))
|
||||
spec = importlib.util.spec_from_file_location('contract', REPO / 'tools/verify_mem0_real_hel874.py')
|
||||
contract = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(contract)
|
||||
contract.install_deterministic_boundary()
|
||||
from cybergril.memory.semantic import Mem0Backend, SemanticMemory
|
||||
from cybergril.memory.store import Store
|
||||
|
||||
async def main():
|
||||
result = {'head': '59fb06304fb9f230837b43e431e2a1cc303e16c9',
|
||||
'real_llm_requests': 0, 'real_embeddings': 0,
|
||||
'boundary': 'deterministic 512 vectors, non-loopback forbidden'}
|
||||
with tempfile.TemporaryDirectory(dir=ROOT) as td:
|
||||
tmp = Path(td)
|
||||
server = contract.start_gateway()
|
||||
backend = None
|
||||
try:
|
||||
ledger = tmp / 'ledger.db'
|
||||
contract.make_ledger(ledger, cap=100, llm_cap=1)
|
||||
cfg = contract.config_for(tmp, ledger, port=server.server_address[1], cap=100, llm_cap=1)
|
||||
backend = Mem0Backend(cfg)
|
||||
result['missing_authority_constructed_with_positive_caps'] = 'authority' not in cfg
|
||||
store = Store(tmp / 'product.db')
|
||||
with store.conn:
|
||||
store.execute("INSERT INTO chat_message(ts,role,text,turn_id,status) VALUES"
|
||||
"(1,'user','合成用户原话甲','synthetic','sent')")
|
||||
inserted = store.execute("INSERT INTO chat_message(ts,role,text,turn_id,status) VALUES"
|
||||
"(2,'companion','合成已公开伴侣原话乙','synthetic','sent')")
|
||||
store.execute("INSERT INTO sharing_attempt(message_id,turn_id,epoch,token,text_hash,expires_at,"
|
||||
"ack_at,candidates,state) VALUES(?,'synthetic',1,'synthetic','synthetic',999999,3,'[]','judged')",
|
||||
(inserted.lastrowid,))
|
||||
live = SemanticMemory(store, backend)
|
||||
result['public_batch_enqueued'] = live.enqueue_uncovered()
|
||||
await live.tick()
|
||||
job = dict(store.query_one('SELECT * FROM sem_batch'))
|
||||
result['batch_after_first_tick'] = {k: job[k] for k in ('state','attempts','reason','entry_ids')}
|
||||
result['loopback_requests_before_resume'] = len(contract.FakeGateway.requests)
|
||||
points, _ = backend._client.scroll(collection_name=backend.collection, with_payload=True, limit=100)
|
||||
result['stored_points_after_first'] = [dict(p.payload) for p in points]
|
||||
result['claimed_completed_message_ids'] = sorted(backend.completed_message_ids(job['batch_key']))
|
||||
backend.llm_guard.cap = 5
|
||||
await live.tick()
|
||||
result['batch_after_resume'] = dict(store.query_one('SELECT state,attempts,reason,entry_ids FROM sem_batch'))
|
||||
result['loopback_requests_after_resume'] = len(contract.FakeGateway.requests)
|
||||
result['unsent_user_row_actually_sent'] = any(
|
||||
'合成用户原话甲' in json.dumps(json.loads(r['body']), ensure_ascii=False)
|
||||
for r in contract.FakeGateway.requests)
|
||||
store.close()
|
||||
finally:
|
||||
if backend:
|
||||
backend._memory.llm.client.close()
|
||||
await backend.aclose()
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
(ROOT / 'HEL-882-partial-independent.json').write_text(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"head": "78b6267d3a9cc9adc1136fc4bf22c4178a38cab2",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"scenarios": [
|
||||
{
|
||||
"kind": "cancel",
|
||||
"before": {
|
||||
"state": "pending",
|
||||
"owner": "21ce5e01a2534b601718ffa14f66a303",
|
||||
"lease_until": 1040000
|
||||
},
|
||||
"accepted_http_before_first_finished": 2,
|
||||
"batch_before_first_finished": {
|
||||
"state": "done",
|
||||
"entry_ids": "[\"2edd0645-1f95-453d-b139-a9d80c984363\"]"
|
||||
},
|
||||
"accepted_http_total": 2,
|
||||
"stored_points": 2,
|
||||
"batch_final": {
|
||||
"state": "done",
|
||||
"entry_ids": "[\"2edd0645-1f95-453d-b139-a9d80c984363\"]"
|
||||
},
|
||||
"row_done": [
|
||||
{
|
||||
"batch_id": 1,
|
||||
"message_id": 1,
|
||||
"entry_ids": "[\"2edd0645-1f95-453d-b139-a9d80c984363\"]",
|
||||
"done_at": 1000000
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "lease",
|
||||
"before": {
|
||||
"state": "pending",
|
||||
"owner": "a39018d7e1f4deaa850b01b2df3a2de0",
|
||||
"lease_until": 1040000
|
||||
},
|
||||
"accepted_http_before_first_finished": 2,
|
||||
"batch_before_first_finished": {
|
||||
"state": "done",
|
||||
"entry_ids": "[\"2d4a7ff2-3636-4486-9305-f3ae03f6c5fa\"]"
|
||||
},
|
||||
"accepted_http_total": 2,
|
||||
"stored_points": 2,
|
||||
"batch_final": {
|
||||
"state": "done",
|
||||
"entry_ids": "[\"2d4a7ff2-3636-4486-9305-f3ae03f6c5fa\"]"
|
||||
},
|
||||
"row_done": [
|
||||
{
|
||||
"batch_id": 1,
|
||||
"message_id": 1,
|
||||
"entry_ids": "[\"2d4a7ff2-3636-4486-9305-f3ae03f6c5fa\"]",
|
||||
"done_at": 1041000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"all_rows_already_completed": {
|
||||
"before_crash": {
|
||||
"state": "done",
|
||||
"entry_ids": "[\"f663dace-fdb1-463d-b76f-fa0fc3c56b31\"]"
|
||||
},
|
||||
"completed_rows": 1,
|
||||
"after_restart": {
|
||||
"state": "queued",
|
||||
"reason": "semantic_llm_budget_exhausted",
|
||||
"entry_ids": "[\"f663dace-fdb1-463d-b76f-fa0fc3c56b31\"]"
|
||||
},
|
||||
"total_http": 1,
|
||||
"llm_cap": 1,
|
||||
"llm_used": 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"head": "HEL-886 fixed checkout (see git rev in delivery)",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"scenarios": [
|
||||
{
|
||||
"kind": "cancel",
|
||||
"before": {
|
||||
"state": "pending",
|
||||
"owner": "f2e64f5989b6c88bed2563882663a99d",
|
||||
"lease_until": 1040000
|
||||
},
|
||||
"http_before_first_finished": 1,
|
||||
"batch_before_first_finished": {
|
||||
"state": "pending",
|
||||
"entry_ids": null
|
||||
},
|
||||
"http_total": 1,
|
||||
"stored_points": 1,
|
||||
"batch_final": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"entry_ids": "[\"f8b9effb-b536-4aec-82da-ff3f742ccca1\"]"
|
||||
},
|
||||
"row_done": [
|
||||
{
|
||||
"batch_id": 1,
|
||||
"message_id": 1,
|
||||
"entry_ids": "[\"f8b9effb-b536-4aec-82da-ff3f742ccca1\"]",
|
||||
"done_at": 1000000
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "lease",
|
||||
"before": {
|
||||
"state": "pending",
|
||||
"owner": "be4ab54a53b5bcafdd3ef27caa5a0656",
|
||||
"lease_until": 1040000
|
||||
},
|
||||
"http_before_first_finished": 1,
|
||||
"batch_before_first_finished": {
|
||||
"state": "pending",
|
||||
"entry_ids": null
|
||||
},
|
||||
"http_total": 1,
|
||||
"stored_points": 1,
|
||||
"batch_final": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"entry_ids": "[\"b14ec3a2-316a-452d-b913-d03980eb35ac\"]"
|
||||
},
|
||||
"row_done": [
|
||||
{
|
||||
"batch_id": 1,
|
||||
"message_id": 1,
|
||||
"entry_ids": "[\"b14ec3a2-316a-452d-b913-d03980eb35ac\"]",
|
||||
"done_at": 1041000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"all_rows_already_completed": {
|
||||
"before_crash": {
|
||||
"state": "done",
|
||||
"entry_ids": "[\"4ffc3d18-c745-42f4-be03-647c24aa05ca\"]"
|
||||
},
|
||||
"completed_rows": 1,
|
||||
"after_restart": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"entry_ids": "[\"4ffc3d18-c745-42f4-be03-647c24aa05ca\"]"
|
||||
},
|
||||
"total_http": 1,
|
||||
"llm_cap": 1,
|
||||
"llm_used": 1
|
||||
},
|
||||
"failures": []
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
{
|
||||
"started": "2026-10-26T16:27:52Z",
|
||||
"embedding_boundary": "deterministic 512-dimensional vectors installed before TextEmbedding construction; no model loaded; zero real embeddings",
|
||||
"external_network": "socket.connect rejects all non-loopback hosts",
|
||||
"install": {
|
||||
"python": "3.10.12",
|
||||
"mem0ai": "2.2.1",
|
||||
"qdrant-client": "1.20.0",
|
||||
"fastembed": "0.9.1",
|
||||
"mem0_path": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/mem0",
|
||||
"module_sha256": {
|
||||
"memory/main.py": "f329ca4a17af1fbefb32ac10d4cc7ae7ddbdb7435a87eb74c30915cff98b8b7c",
|
||||
"memory/storage.py": "5f5ecdadd2b260fd14f36a0f338336eb760275465dd1056a99a47e1ef07702bd",
|
||||
"embeddings/fastembed.py": "3ec2ae87df5d0bebedb8b5471366c3412975847458499f66097f7b8c9ac8c9b8",
|
||||
"llms/openai.py": "37e6d3c22b55df0731740f4e0868b95bd480ce44363227fe53461db328c5f67e",
|
||||
"vector_stores/qdrant.py": "da570579d5cad69e6f123fdfe32b725235a25604e70047666bec763ba6e2bf43"
|
||||
},
|
||||
"source_tarball_sha256": "9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23"
|
||||
},
|
||||
"adapter_has_llm_guard": true,
|
||||
"checks": [
|
||||
{
|
||||
"name": "C1_construct",
|
||||
"ok": true,
|
||||
"detail": "client_path=sdk-vector-store-client"
|
||||
},
|
||||
{
|
||||
"name": "C2_add_anchor_payload",
|
||||
"ok": true,
|
||||
"detail": "created=True payload_keys=['created_at', 'data', 'hash', 'input_sha256', 'src_group', 'text_lemmatized', 'turn_ids', 'turn_roles']"
|
||||
},
|
||||
{
|
||||
"name": "C2_add_user_scope",
|
||||
"ok": true,
|
||||
"detail": "user_id present in stored payload"
|
||||
},
|
||||
{
|
||||
"name": "C2_add_ledger_shape",
|
||||
"ok": true,
|
||||
"detail": "embed_items=3 llm_calls=1"
|
||||
},
|
||||
{
|
||||
"name": "C3_extraction_input_set",
|
||||
"ok": true,
|
||||
"detail": "old_memory=True new_row=True history_carry=True"
|
||||
},
|
||||
{
|
||||
"name": "C3b_unshared_canary_never_outbound",
|
||||
"ok": true,
|
||||
"detail": "queued_public_batches=2 leaked=False anchor_taint=False"
|
||||
},
|
||||
{
|
||||
"name": "C4_search_filters",
|
||||
"ok": true,
|
||||
"detail": "hits=3"
|
||||
},
|
||||
{
|
||||
"name": "C4_search_single_debit",
|
||||
"ok": true,
|
||||
"detail": "embed_delta=1 (pre-fix=2)"
|
||||
},
|
||||
{
|
||||
"name": "C6_concurrency_cap",
|
||||
"ok": true,
|
||||
"detail": "outcomes=['ok', 'refused'] used=3 cap=3"
|
||||
},
|
||||
{
|
||||
"name": "C7_reopen_after_close",
|
||||
"ok": true,
|
||||
"detail": "hits_after_reopen=3"
|
||||
},
|
||||
{
|
||||
"name": "C5_embed_zero_refuses",
|
||||
"ok": true,
|
||||
"detail": "err=semantic_embed_budget_not_approved llm_sent=0"
|
||||
},
|
||||
{
|
||||
"name": "C5_llm_zero_refuses",
|
||||
"ok": true,
|
||||
"detail": "err=semantic_llm_budget_not_approved llm_sent=0"
|
||||
}
|
||||
],
|
||||
"embed_rows_after_first_add": [
|
||||
{
|
||||
"kind": "authority_prefix",
|
||||
"items": 0,
|
||||
"note": "authority:synthetic-verify-prefix"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
}
|
||||
],
|
||||
"final_embed_rows": [
|
||||
{
|
||||
"kind": "authority_prefix",
|
||||
"items": 0,
|
||||
"note": "authority:synthetic-verify-prefix"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
}
|
||||
],
|
||||
"final_llm_used": 3,
|
||||
"finished": "2026-10-26T16:27:53Z",
|
||||
"gateway_requests_total": 3,
|
||||
"failures": [],
|
||||
"temp_dir": "/tmp/multica-task-693257945/hel874-real-sdk-1l8rzjt7"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"head": "HEL-886 inflight lifecycle",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"boundary": "deterministic 512 vectors, non-loopback forbidden",
|
||||
"checks": [
|
||||
{
|
||||
"name": "L1_cancel_single_send_and_full_record",
|
||||
"ok": true,
|
||||
"detail": "arrived=1 concurrent=1 http_total=1 points=1 batch_live={'state': 'pending', 'owner': '55b76b8ed25bdca43442bd451c02e080'} batch_concurrent=pending final={'state': 'done', 'reason': None, 'entry_ids': '[\"d1dfe2cb-7215-47de-972c-33df30263dea\"]'} row_done=1"
|
||||
},
|
||||
{
|
||||
"name": "L2_lease_single_send_and_full_record",
|
||||
"ok": true,
|
||||
"detail": "arrived=1 concurrent=1 http_total=1 points=1 batch_live={'state': 'pending', 'owner': '724fbb7d85503b0b1b1d3854d0b0dcdf'} batch_concurrent=pending final={'state': 'done', 'reason': None, 'entry_ids': '[\"63cd1007-b5dd-499f-8976-a69a52f3065f\"]'} row_done=1"
|
||||
},
|
||||
{
|
||||
"name": "L3_shutdown_drains_live_worker_before_close",
|
||||
"ok": true,
|
||||
"detail": "arrived=1 waited=0.21s row_done_at=2000000 batch={'state': 'done', 'finished_at': 2000000}"
|
||||
},
|
||||
{
|
||||
"name": "L4a_all_done_finalizes_without_new_budget",
|
||||
"ok": true,
|
||||
"detail": "before={'state': 'done', 'entry_ids': '[\"70b6e734-eb26-447e-988e-9fe32c82e930\"]'} after={'state': 'done', 'reason': None, 'entry_ids': '[\"70b6e734-eb26-447e-988e-9fe32c82e930\"]'} http=1 llm_used=1"
|
||||
},
|
||||
{
|
||||
"name": "L4b_partial_keeps_the_hard_gate",
|
||||
"ok": true,
|
||||
"detail": "after={'state': 'queued', 'reason': 'semantic_llm_budget_exhausted'} http=0"
|
||||
},
|
||||
{
|
||||
"name": "L5_dead_owner_lease_still_recovers",
|
||||
"ok": true,
|
||||
"detail": "recovered={'state': 'failed', 'reason': 'lease_expired'} after={'state': 'done'} http=2"
|
||||
}
|
||||
],
|
||||
"gateway_requests_total": 7,
|
||||
"failures": []
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"head": "59fb06304fb9f230837b43e431e2a1cc303e16c9",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"midnight_settlement": {
|
||||
"daily_cap": 100,
|
||||
"old_hold": 90,
|
||||
"actual": 10,
|
||||
"new_day_used_before_next_request": 0,
|
||||
"request_estimate": 150,
|
||||
"over_cap_request_allowed": false,
|
||||
"rows": [
|
||||
{
|
||||
"day": "2026-10-26",
|
||||
"scene": "dialogue_main",
|
||||
"used": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
"aged_unknown_reservation": {
|
||||
"cap": 1,
|
||||
"used_before": 1,
|
||||
"second_allowed": false,
|
||||
"first_hold_id": 1,
|
||||
"second_hold_id": null,
|
||||
"used_after": 1
|
||||
},
|
||||
"release_documented_download_command": {
|
||||
"command": "uv pip download --help",
|
||||
"returncode": 2,
|
||||
"stderr": "error: unrecognized subcommand 'download'\n\nUsage: uv pip [OPTIONS] <COMMAND>\n\nFor more information, try '--help'."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
Failed to load spaCy lemma model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load spaCy full model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
LLM extraction failed: semantic_llm_budget_exhausted
|
||||
semantic batch 1 requeued (budget): LLM extraction failed: semantic_llm_budget_exhausted
|
||||
{
|
||||
"head": "59fb06304fb9f230837b43e431e2a1cc303e16c9",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"boundary": "deterministic 512 vectors, non-loopback forbidden",
|
||||
"missing_authority_constructed_with_positive_caps": false,
|
||||
"public_batch_enqueued": 1,
|
||||
"batch_after_first_tick": {
|
||||
"state": "queued",
|
||||
"attempts": 0,
|
||||
"reason": "LLM extraction failed: semantic_llm_budget_exhausted",
|
||||
"entry_ids": null
|
||||
},
|
||||
"loopback_requests_before_resume": 1,
|
||||
"stored_points_after_first": [
|
||||
{
|
||||
"src_group": "chat_public:mem0:synthetic:1bc8667410",
|
||||
"turn_ids": [
|
||||
"synthetic"
|
||||
],
|
||||
"turn_roles": [
|
||||
[
|
||||
"synthetic",
|
||||
"companion",
|
||||
2
|
||||
]
|
||||
],
|
||||
"input_sha256": "840dde45eec07416f21c56cefb0f93f4e71af3edc3100c332f84bb1266637862",
|
||||
"user_id": "cybergril",
|
||||
"data": "合成提取记忆HEL874-1",
|
||||
"text_lemmatized": "合成提取记忆HEL874-1",
|
||||
"hash": "69195446402294f6e77f8ee993ce3456",
|
||||
"created_at": "2026-10-26T16:28:34.461368+00:00",
|
||||
"updated_at": "2026-10-26T16:28:34.461368+00:00"
|
||||
}
|
||||
],
|
||||
"claimed_completed_message_ids": [
|
||||
2
|
||||
],
|
||||
"batch_after_resume": {
|
||||
"state": "done",
|
||||
"attempts": 1,
|
||||
"reason": null,
|
||||
"entry_ids": "[\"8c05da20-12aa-439b-8f49-e8860b5fc6ed\", \"41ae5dca-d46f-49cc-a839-9da12632eecf\"]"
|
||||
},
|
||||
"loopback_requests_after_resume": 2,
|
||||
"unsent_user_row_actually_sent": true
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
Failed to load spaCy lemma model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load spaCy full model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
Traceback (most recent call last):
|
||||
File "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-886-7c1997665cac/workdir/review_hel885_inflight.py", line 143, in <module>
|
||||
asyncio.run(main())
|
||||
File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
|
||||
return loop.run_until_complete(main)
|
||||
File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
|
||||
return future.result()
|
||||
File "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-886-7c1997665cac/workdir/review_hel885_inflight.py", line 102, in main
|
||||
'scenarios': [await scenario('cancel'), await scenario('lease')],
|
||||
File "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-886-7c1997665cac/workdir/review_hel885_inflight.py", line 84, in scenario
|
||||
assert await asyncio.to_thread(worker_done.wait, 8)
|
||||
AssertionError
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"head": "HEL-885 rework",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"boundary": "deterministic 512 vectors, non-loopback forbidden",
|
||||
"checks": [
|
||||
{
|
||||
"name": "S0_positive_caps_without_authority_fail_closed",
|
||||
"ok": true,
|
||||
"detail": "missing=ledger_authority_required empty=ledger_authority_required"
|
||||
},
|
||||
{
|
||||
"name": "S1_p1_first_tick_partial",
|
||||
"ok": true,
|
||||
"detail": "state=queued reason=LLM extraction failed: semantic_llm_budget_exhausted http=1 completed=[2] points=1 per_row_anchor=True"
|
||||
},
|
||||
{
|
||||
"name": "S2_p1_restart_sends_only_missing_row",
|
||||
"ok": true,
|
||||
"detail": "state=done http=1 user_sent=True completed=[1, 2] entry_ids=[\"2bee5aad-59b3-4af2-be3a-fd02c0abac74\", \"7767fe14-4a8f-4275-a012-ecfce4d794a8\"]"
|
||||
},
|
||||
{
|
||||
"name": "S3a_lease_expired_batch_recovers",
|
||||
"ok": true,
|
||||
"detail": "recovered={'state': 'failed', 'reason': 'lease_expired'} state_after=done http=2"
|
||||
},
|
||||
{
|
||||
"name": "S3b_concurrent_ticks_single_send",
|
||||
"ok": true,
|
||||
"detail": "http=2 states=['done']"
|
||||
},
|
||||
{
|
||||
"name": "S4a_aged_unknown_llm_reservation_kept",
|
||||
"ok": true,
|
||||
"detail": "second_allowed=False used_after_restart=1"
|
||||
},
|
||||
{
|
||||
"name": "S4b_aged_unknown_embed_reservation_kept",
|
||||
"ok": true,
|
||||
"detail": "second_allowed=False used=1 after_release=0"
|
||||
}
|
||||
],
|
||||
"gateway_requests_total": 6,
|
||||
"failures": []
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"started": "2026-10-26T16:28:24Z",
|
||||
"venv_python": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-874-db7635e6b65e/workdir/CyberGril/.venv311/bin/python",
|
||||
"venv_python_version": "3.11.16",
|
||||
"checks": [
|
||||
{
|
||||
"name": "R1_checkout_fresh_dir",
|
||||
"ok": true,
|
||||
"detail": "reused ."
|
||||
},
|
||||
{
|
||||
"name": "R2_service_imports_product",
|
||||
"ok": true,
|
||||
"detail": ""
|
||||
},
|
||||
{
|
||||
"name": "R6_mem0_coimport_status_recorded",
|
||||
"ok": true,
|
||||
"detail": "import mem0 → ModuleNotFoundError: No module named 'mem0' (resolution path in deploy/hel874-release.md §4; no download in this check)"
|
||||
},
|
||||
{
|
||||
"name": "R7_pip_download_command_exists",
|
||||
"ok": true,
|
||||
"detail": "/usr/bin/python3 -m pip download --help → rc=0"
|
||||
},
|
||||
{
|
||||
"name": "R7_removed_uv_pip_download_still_absent",
|
||||
"ok": true,
|
||||
"detail": "uv pip download --help → rc=2 (kept absent on purpose; do not re-document it)"
|
||||
},
|
||||
{
|
||||
"name": "R2_service_serves_health",
|
||||
"ok": true,
|
||||
"detail": "version=R4-assembly semantic=stub"
|
||||
},
|
||||
{
|
||||
"name": "R3_create_status_recorded",
|
||||
"ok": true,
|
||||
"detail": "create status=201(409=前次运行的档案仍在,也是保留数据的表现)"
|
||||
},
|
||||
{
|
||||
"name": "R3_dialogue_refused_honestly",
|
||||
"ok": true,
|
||||
"detail": "http=200 events=b'data: {\"t\": \"chat.error\", \"epoch\": 1, \"turn_id\": \"00000000-0000-4000-8000-000000000001\", \"phi\": \"\\xe5\\xa5\\xb9\\xe8\\xbf\\x99\\xe4\\xbc\\x9a\\xe5\\x84\\xbf\\xe6\\xb2\\xa1\\xe6\\x8e\\xa5\\xe4\\xb8\\x8a\\xe8\\xaf'"
|
||||
},
|
||||
{
|
||||
"name": "R4_service_restarts_on_same_data",
|
||||
"ok": true,
|
||||
"detail": "epoch=2"
|
||||
},
|
||||
{
|
||||
"name": "R5_epoch_bumped_on_reopen",
|
||||
"ok": true,
|
||||
"detail": "1 → 2"
|
||||
},
|
||||
{
|
||||
"name": "R4_companion_created_offline",
|
||||
"ok": true,
|
||||
"detail": "status=409"
|
||||
},
|
||||
{
|
||||
"name": "R4_dialogue_stream_http",
|
||||
"ok": true,
|
||||
"detail": "status=200 events=b'data: {\"t\": \"chat.delta\", \"epoch\": 2, \"turn_id\": \"00000000-0000-4000-8000-000000000002\", \"delta\": \"\\xe6\\x89\\xb9\"}\\n\\ndata: {\"t\": \"c'"
|
||||
},
|
||||
{
|
||||
"name": "R4_messages_roundtrip",
|
||||
"ok": true,
|
||||
"detail": "roles=['user', 'user', 'companion']"
|
||||
},
|
||||
{
|
||||
"name": "R5_data_dir_preserved",
|
||||
"ok": true,
|
||||
"detail": "['companion.db']"
|
||||
},
|
||||
{
|
||||
"name": "R5_companion_row_survives_restart",
|
||||
"ok": true,
|
||||
"detail": "companion rows=1"
|
||||
}
|
||||
],
|
||||
"mem0_import_in_release_venv": "ModuleNotFoundError: No module named 'mem0'",
|
||||
"removed_uv_pip_download_rc": 2,
|
||||
"failures": [],
|
||||
"finished": "2026-10-26T16:28:26Z"
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
Failed to load spaCy lemma model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load spaCy full model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
{
|
||||
"head": "HEL-886 fixed checkout (see git rev in delivery)",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"scenarios": [
|
||||
{
|
||||
"kind": "cancel",
|
||||
"before": {
|
||||
"state": "pending",
|
||||
"owner": "f2e64f5989b6c88bed2563882663a99d",
|
||||
"lease_until": 1040000
|
||||
},
|
||||
"http_before_first_finished": 1,
|
||||
"batch_before_first_finished": {
|
||||
"state": "pending",
|
||||
"entry_ids": null
|
||||
},
|
||||
"http_total": 1,
|
||||
"stored_points": 1,
|
||||
"batch_final": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"entry_ids": "[\"f8b9effb-b536-4aec-82da-ff3f742ccca1\"]"
|
||||
},
|
||||
"row_done": [
|
||||
{
|
||||
"batch_id": 1,
|
||||
"message_id": 1,
|
||||
"entry_ids": "[\"f8b9effb-b536-4aec-82da-ff3f742ccca1\"]",
|
||||
"done_at": 1000000
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "lease",
|
||||
"before": {
|
||||
"state": "pending",
|
||||
"owner": "be4ab54a53b5bcafdd3ef27caa5a0656",
|
||||
"lease_until": 1040000
|
||||
},
|
||||
"http_before_first_finished": 1,
|
||||
"batch_before_first_finished": {
|
||||
"state": "pending",
|
||||
"entry_ids": null
|
||||
},
|
||||
"http_total": 1,
|
||||
"stored_points": 1,
|
||||
"batch_final": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"entry_ids": "[\"b14ec3a2-316a-452d-b913-d03980eb35ac\"]"
|
||||
},
|
||||
"row_done": [
|
||||
{
|
||||
"batch_id": 1,
|
||||
"message_id": 1,
|
||||
"entry_ids": "[\"b14ec3a2-316a-452d-b913-d03980eb35ac\"]",
|
||||
"done_at": 1041000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"all_rows_already_completed": {
|
||||
"before_crash": {
|
||||
"state": "done",
|
||||
"entry_ids": "[\"4ffc3d18-c745-42f4-be03-647c24aa05ca\"]"
|
||||
},
|
||||
"completed_rows": 1,
|
||||
"after_restart": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"entry_ids": "[\"4ffc3d18-c745-42f4-be03-647c24aa05ca\"]"
|
||||
},
|
||||
"total_http": 1,
|
||||
"llm_cap": 1,
|
||||
"llm_used": 1
|
||||
},
|
||||
"failures": []
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
"""HEL-886 rerun of the manager's review_hel885_inflight.py — same scenarios,
|
||||
barrier adjusted to the FIXED contract.
|
||||
|
||||
Adjustment vs the original (fix-before/after documented):
|
||||
- the original sets ``worker_done`` when TWO Memory.add calls complete — that
|
||||
barrier encodes the defect (a second send must happen). Before the fix the
|
||||
script passed with HTTP=2/vectors=2/row_done=1 (see
|
||||
HEL-885-inflight-baseline-78b6267.json); after the fix only ONE add ever
|
||||
happens, so the original's barrier times out (original-verbatim-after-fix.log,
|
||||
AssertionError at worker_done.wait — the exact double-send assertion).
|
||||
This rerun sets the barrier at ONE completed add, keeps all three negative
|
||||
scenarios and adds explicit no-duplicate/no-loss assertions.
|
||||
- cleanup (server shutdown) is unconditional so the process exits.
|
||||
|
||||
Everything else — real pinned SDK, real Qdrant, real OpenAI client,
|
||||
deterministic 512-dim stand-in installed before TextEmbedding construction,
|
||||
loopback-only gateway, temp ledgers — is unchanged.
|
||||
"""
|
||||
import asyncio
|
||||
import importlib.util
|
||||
import json
|
||||
import tempfile
|
||||
import threading
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
REPO = ROOT / 'CyberGril'
|
||||
import sys
|
||||
sys.path.insert(0, str(REPO / 'src'))
|
||||
spec = importlib.util.spec_from_file_location('contract', REPO / 'tools/verify_mem0_real_hel874.py')
|
||||
c = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(c)
|
||||
c.install_deterministic_boundary()
|
||||
from cybergril.memory.semantic import Mem0Backend, SemanticMemory
|
||||
from cybergril.memory.store import Store
|
||||
|
||||
FAILURES = []
|
||||
|
||||
|
||||
async def scenario_fixed(kind):
|
||||
first_arrived, release_first, worker_done = (threading.Event() for _ in range(3))
|
||||
accepted = []
|
||||
lock = threading.Lock()
|
||||
|
||||
class Gateway(c.FakeGateway):
|
||||
def do_POST(self):
|
||||
with lock:
|
||||
accepted.append(self.path)
|
||||
n = len(accepted)
|
||||
if n == 1:
|
||||
first_arrived.set()
|
||||
if not release_first.wait(15):
|
||||
raise RuntimeError('test barrier timed out')
|
||||
super().do_POST()
|
||||
|
||||
server = c._Server(('127.0.0.1', 0), Gateway)
|
||||
server_thread = threading.Thread(target=server.serve_forever)
|
||||
server_thread.start()
|
||||
with tempfile.TemporaryDirectory(dir=ROOT) as td:
|
||||
tmp = Path(td)
|
||||
c.make_ledger(tmp / 'ledger.db', cap=100, llm_cap=10)
|
||||
cfg = c.config_for(tmp, tmp / 'ledger.db', port=server.server_address[1], cap=100, llm_cap=10)
|
||||
backend = Mem0Backend(cfg)
|
||||
store = Store(tmp / 'product.db')
|
||||
clock = [1000.0]
|
||||
live = SemanticMemory(store, backend, clock=lambda: clock[0])
|
||||
add = backend._memory.add
|
||||
completed_calls = []
|
||||
|
||||
def counted_add(*args, **kwargs):
|
||||
try:
|
||||
return add(*args, **kwargs)
|
||||
finally:
|
||||
with lock:
|
||||
completed_calls.append(1)
|
||||
if len(completed_calls) == 1:
|
||||
worker_done.set()
|
||||
|
||||
backend._memory.add = counted_add
|
||||
with store.conn:
|
||||
store.execute("INSERT INTO chat_message(ts,role,text,turn_id,status) VALUES(1,'user','正在整理的合成原话','test','sent')")
|
||||
live.enqueue_uncovered()
|
||||
task = asyncio.create_task(live.tick())
|
||||
try:
|
||||
assert await asyncio.to_thread(first_arrived.wait, 5)
|
||||
before = dict(store.query_one('SELECT state,owner,lease_until FROM sem_batch'))
|
||||
if kind == 'cancel':
|
||||
task.cancel()
|
||||
try:
|
||||
await task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
else:
|
||||
clock[0] += 41 # deterministic elapsed time, no real sleep
|
||||
await asyncio.wait_for(live.tick(), 8)
|
||||
await asyncio.wait_for(live.tick(), 8)
|
||||
result = {'kind': kind, 'before': before,
|
||||
'http_before_first_finished': len(accepted),
|
||||
'batch_before_first_finished': dict(store.query_one('SELECT state,entry_ids FROM sem_batch'))}
|
||||
finally:
|
||||
release_first.set()
|
||||
if not task.cancelled():
|
||||
await task
|
||||
assert await asyncio.to_thread(worker_done.wait, 8)
|
||||
await asyncio.sleep(0.05)
|
||||
points, _ = backend._client.scroll(collection_name=backend.collection, with_payload=True, limit=100)
|
||||
row_done = [dict(r) for r in store.query('SELECT * FROM sem_row_done')]
|
||||
batch_final = dict(store.query_one('SELECT state,reason,entry_ids FROM sem_batch'))
|
||||
result.update(http_total=len(accepted), stored_points=len(points),
|
||||
batch_final=batch_final, row_done=row_done)
|
||||
await backend.aclose()
|
||||
store.close()
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
server_thread.join()
|
||||
# fixed-contract assertions: one send, one vector, one durable record
|
||||
ok = (len(accepted) == 1 and len(points) == 1 and len(row_done) == 1
|
||||
and batch_final['state'] == 'done'
|
||||
and json.loads(batch_final['entry_ids'] or '[]')
|
||||
== json.loads(row_done[0]['entry_ids']))
|
||||
if not ok:
|
||||
FAILURES.append(f"{kind}: http={len(accepted)} points={len(points)} "
|
||||
f"row_done={len(row_done)} batch={batch_final}")
|
||||
return result
|
||||
|
||||
|
||||
async def all_done_recovery():
|
||||
server = c.start_gateway()
|
||||
with tempfile.TemporaryDirectory(dir=ROOT) as td:
|
||||
tmp = Path(td)
|
||||
c.make_ledger(tmp / 'ledger.db', cap=100, llm_cap=1)
|
||||
cfg = c.config_for(tmp, tmp / 'ledger.db', port=server.server_address[1], cap=100, llm_cap=1)
|
||||
backend = Mem0Backend(cfg)
|
||||
store = Store(tmp / 'product.db')
|
||||
with store.conn:
|
||||
store.execute("INSERT INTO chat_message(ts,role,text,turn_id,status) VALUES(1,'user','完整处理的合成原话','done','sent')")
|
||||
live = SemanticMemory(store, backend)
|
||||
live.enqueue_uncovered()
|
||||
mark = len(c.FakeGateway.requests)
|
||||
await live.tick()
|
||||
original = dict(store.query_one('SELECT state,entry_ids FROM sem_batch'))
|
||||
with store.conn:
|
||||
# Crash window: row completion persisted; final batch status did not.
|
||||
store.execute("UPDATE sem_batch SET state='pending',owner='crashed',lease_until=1")
|
||||
await backend.aclose()
|
||||
store.close()
|
||||
backend = Mem0Backend(cfg)
|
||||
store = Store(tmp / 'product.db')
|
||||
live = SemanticMemory(store, backend)
|
||||
await live.tick()
|
||||
result = {'before_crash': original,
|
||||
'completed_rows': len(store.query('SELECT * FROM sem_row_done')),
|
||||
'after_restart': dict(store.query_one('SELECT state,reason,entry_ids FROM sem_batch')),
|
||||
'total_http': len(c.FakeGateway.requests) - mark,
|
||||
'llm_cap': 1, 'llm_used': backend.llm_guard.used()}
|
||||
await backend.aclose()
|
||||
store.close()
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
ok = (result['after_restart']['state'] == 'done'
|
||||
and result['after_restart']['reason'] is None
|
||||
and result['total_http'] == 1
|
||||
and result['llm_used'] == 1
|
||||
and json.loads(result['after_restart']['entry_ids'] or '[]')
|
||||
== json.loads(original['entry_ids']))
|
||||
if not ok:
|
||||
FAILURES.append(f"all_rows_already_completed: {result}")
|
||||
return result
|
||||
|
||||
|
||||
async def main():
|
||||
result = {'head': 'HEL-886 fixed checkout (see git rev in delivery)',
|
||||
'real_llm_requests': 0, 'real_embeddings': 0,
|
||||
'scenarios': [await scenario_fixed('cancel'), await scenario_fixed('lease')],
|
||||
'all_rows_already_completed': await all_done_recovery(),
|
||||
'failures': FAILURES}
|
||||
(ROOT / 'HEL-886-inflight-rerun.json').write_text(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
if FAILURES:
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"head": "HEL-889 same-shape rerun of review_hel886_edges.py (fixed tree)",
|
||||
"real_llm": 0,
|
||||
"real_embeddings": 0,
|
||||
"queued_cancel": {
|
||||
"batch": {
|
||||
"state": "done",
|
||||
"owner": null,
|
||||
"attempts": 1
|
||||
},
|
||||
"registered_workers": 0,
|
||||
"stored_entries": 1,
|
||||
"row_done": 1,
|
||||
"executor_queue_size": 0
|
||||
},
|
||||
"shutdown_timeout": {
|
||||
"aclose_returned_while_worker_alive": false,
|
||||
"batch_before_store_close": {
|
||||
"state": "done",
|
||||
"entry_ids": "[\"d3c48e4b-205b-4066-a570-e5909737138c\"]"
|
||||
},
|
||||
"late_worker_error": null,
|
||||
"persisted_batch": {
|
||||
"state": "done",
|
||||
"entry_ids": "[\"d3c48e4b-205b-4066-a570-e5909737138c\"]"
|
||||
},
|
||||
"persisted_row_done": 1,
|
||||
"llm_used": 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
# HEL-889 返工证据 · 关闭所有权与排队取消
|
||||
|
||||
总管对 HEL-886 head `f1b50ad91ad8c856f28383ee59eddb6547d9c586` 独立复审
|
||||
(`review_hel886_edges.py`,附件与本目录 `edges-verbatim-before-fix-*`)确认的两个
|
||||
缺口,在本 head 修复并复验。零真实出站、零真实嵌入、零下载;全部回环 +
|
||||
确定性 512 维替身(TextEmbedding 构造前安装);未写权威三账、未改冻结余额、
|
||||
未部署、未合并、未改视觉。
|
||||
|
||||
## 修复前基线(本卡在未改动的 f1b50ad 上原样复跑总管脚本)
|
||||
|
||||
`edges-verbatim-before-fix-f1b50ad.json`:与总管独立结果一致——
|
||||
|
||||
- queued_cancel:batch pending/owner 在/attempts=1、`registered_workers=1`、
|
||||
entries 0、row_done 0(假活跃永久卡死,只能重启)。
|
||||
- shutdown_timeout:`aclose_returned_while_worker_alive=true`,晚到 worker
|
||||
`ProgrammingError: Cannot operate on a closed database.`,重开仍
|
||||
pending/row_done=0/llm_used=1(丢落盘窗口)。
|
||||
|
||||
## 修复(src/cybergril/memory/semantic.py)
|
||||
|
||||
1. **P1 关闭所有权**:批 worker 移入本层专用执行器;`aclose` 先置 closing
|
||||
(tick 拒新领、worker 只在行边界前停、未启动项直接从队列取消——
|
||||
concurrent.futures CANCELLED 是权威「从未运行」证明);随后等待所有活跃
|
||||
worker 真实退出才关 backend/执行器。`drain_timeout` 只界定礼貌阶段,
|
||||
绝不授权在活 worker 下关资源;网关客户端超时(`gateway_timeout`,默认
|
||||
60s)保证等待有界。aclose 被取消时什么都不关、信号复位;并发 aclose
|
||||
串行化且幂等。真正的进程崩溃仍按 HEL-885/886 保守留账,不宣称严格一次。
|
||||
2. **P2 排队取消**:`_LiveWorker` 区分 queued/started;取消等待协程时以
|
||||
`future.cancelled()` 权威判定——从未运行则立即释放注册项并把批次回
|
||||
queued(attempts 复原、零发送),可进程内 recover+tick 完成;已开始则
|
||||
维持 HEL-886 单发语义(worker 线程自己写终态)。
|
||||
|
||||
## 修复后验证
|
||||
|
||||
- `edges-real-sdk-hel889.json`(tools/verify_shutdown_queued_hel889.py,固定
|
||||
mem0 2.2.1 实跑)12/12:Q1 排队取消→释放→进程内完成;S1 关闭超时分支
|
||||
aclose 在 worker 活着时绝不返回、完成后落盘才关、app 顺序关库重开
|
||||
done/row_done=1/llm_used=1、无晚到错误;S2 行间中止保留证明、下一进程
|
||||
只发缺行(POST 仅 +1)done 且两行 entry 完整;S3 被取消的关闭什么都不关;
|
||||
S4 并发关闭串行化只关一次。
|
||||
- `HEL-889-edges-rerun-fixed.json` + `review_hel886_edges_rerun_hel889.py`:
|
||||
总管原脚本同形复跑全过(queued:done/attempts=1/registered 0/entries 1/
|
||||
row_done 1;timeout:`aclose_returned_while_worker_alive=false`、
|
||||
late_worker_error null、persisted done+entry_ids+row_done=1、llm_used=1)。
|
||||
原样复跑在 `original-verbatim-after-fix.log` 如实停在它自己的前提断言
|
||||
`assert live._live and executor._work_queue.qsize() == 1`——批 worker 已
|
||||
不再走 loop 默认执行器,该负例的前提结构性消失(原负例与基线保留
|
||||
不删)。
|
||||
- 回归:`inflight-hel886-rerun.json` 6/6、`recovery-hel885-rerun.json` 7/7、
|
||||
`contract-hel874-rerun.json` 12/12、`release-offline-hel889.json` R1–R7
|
||||
15/15、总管 `review_hel882_{partial,extra}.py` 原样复跑通过
|
||||
(`manager-review-*-rerun.{json,log}`;extra 内 uv rc=2 为其自身预期负例)。
|
||||
- `inflight885-verbatim-after-fix.log`:总管 review_hel885_inflight.py 原样
|
||||
复跑仍在其 2-add 屏障断言失败(单发语义保持,与 HEL-886 存档一致;脚本
|
||||
自身 finally 中止后不关服务器线程,进程需外部结束,日志截取自其预期
|
||||
失败点)。
|
||||
- 产品测试:python 3.11.16 venv + requirements.lock 离线 294 pytest 全过
|
||||
(新增 tests/test_rework_hel889.py 7 例,全部屏障驱动真实路径)、
|
||||
ruff 0 错(src/tests/tools)。
|
||||
@@ -0,0 +1,172 @@
|
||||
{
|
||||
"started": "2026-10-26T17:05:47Z",
|
||||
"embedding_boundary": "deterministic 512-dimensional vectors installed before TextEmbedding construction; no model loaded; zero real embeddings",
|
||||
"external_network": "socket.connect rejects all non-loopback hosts",
|
||||
"install": {
|
||||
"python": "3.10.12",
|
||||
"mem0ai": "2.2.1",
|
||||
"qdrant-client": "1.20.0",
|
||||
"fastembed": "0.9.1",
|
||||
"mem0_path": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/mem0",
|
||||
"module_sha256": {
|
||||
"memory/main.py": "f329ca4a17af1fbefb32ac10d4cc7ae7ddbdb7435a87eb74c30915cff98b8b7c",
|
||||
"memory/storage.py": "5f5ecdadd2b260fd14f36a0f338336eb760275465dd1056a99a47e1ef07702bd",
|
||||
"embeddings/fastembed.py": "3ec2ae87df5d0bebedb8b5471366c3412975847458499f66097f7b8c9ac8c9b8",
|
||||
"llms/openai.py": "37e6d3c22b55df0731740f4e0868b95bd480ce44363227fe53461db328c5f67e",
|
||||
"vector_stores/qdrant.py": "da570579d5cad69e6f123fdfe32b725235a25604e70047666bec763ba6e2bf43"
|
||||
},
|
||||
"source_tarball_sha256": "9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23"
|
||||
},
|
||||
"adapter_has_llm_guard": true,
|
||||
"checks": [
|
||||
{
|
||||
"name": "C1_construct",
|
||||
"ok": true,
|
||||
"detail": "client_path=sdk-vector-store-client"
|
||||
},
|
||||
{
|
||||
"name": "C2_add_anchor_payload",
|
||||
"ok": true,
|
||||
"detail": "created=True payload_keys=['created_at', 'data', 'hash', 'input_sha256', 'src_group', 'text_lemmatized', 'turn_ids', 'turn_roles']"
|
||||
},
|
||||
{
|
||||
"name": "C2_add_user_scope",
|
||||
"ok": true,
|
||||
"detail": "user_id present in stored payload"
|
||||
},
|
||||
{
|
||||
"name": "C2_add_ledger_shape",
|
||||
"ok": true,
|
||||
"detail": "embed_items=3 llm_calls=1"
|
||||
},
|
||||
{
|
||||
"name": "C3_extraction_input_set",
|
||||
"ok": true,
|
||||
"detail": "old_memory=True new_row=True history_carry=True"
|
||||
},
|
||||
{
|
||||
"name": "C3b_unshared_canary_never_outbound",
|
||||
"ok": true,
|
||||
"detail": "queued_public_batches=2 leaked=False anchor_taint=False"
|
||||
},
|
||||
{
|
||||
"name": "C4_search_filters",
|
||||
"ok": true,
|
||||
"detail": "hits=3"
|
||||
},
|
||||
{
|
||||
"name": "C4_search_single_debit",
|
||||
"ok": true,
|
||||
"detail": "embed_delta=1 (pre-fix=2)"
|
||||
},
|
||||
{
|
||||
"name": "C6_concurrency_cap",
|
||||
"ok": true,
|
||||
"detail": "outcomes=['ok', 'refused'] used=3 cap=3"
|
||||
},
|
||||
{
|
||||
"name": "C7_reopen_after_close",
|
||||
"ok": true,
|
||||
"detail": "hits_after_reopen=3"
|
||||
},
|
||||
{
|
||||
"name": "C5_embed_zero_refuses",
|
||||
"ok": true,
|
||||
"detail": "err=semantic_embed_budget_not_approved llm_sent=0"
|
||||
},
|
||||
{
|
||||
"name": "C5_llm_zero_refuses",
|
||||
"ok": true,
|
||||
"detail": "err=semantic_llm_budget_not_approved llm_sent=0"
|
||||
}
|
||||
],
|
||||
"embed_rows_after_first_add": [
|
||||
{
|
||||
"kind": "authority_prefix",
|
||||
"items": 0,
|
||||
"note": "authority:synthetic-verify-prefix"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
}
|
||||
],
|
||||
"final_embed_rows": [
|
||||
{
|
||||
"kind": "authority_prefix",
|
||||
"items": 0,
|
||||
"note": "authority:synthetic-verify-prefix"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
}
|
||||
],
|
||||
"final_llm_used": 3,
|
||||
"finished": "2026-10-26T17:05:48Z",
|
||||
"gateway_requests_total": 3,
|
||||
"failures": [],
|
||||
"temp_dir": "/tmp/multica-task-2867647507/hel874-real-sdk-e1xct75p"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
Failed to load spaCy lemma model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load spaCy full model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
{
|
||||
"out": "docs/rework/hel889/contract-hel874-rerun.json",
|
||||
"failures": [],
|
||||
"checks": {
|
||||
"C1_construct": true,
|
||||
"C2_add_anchor_payload": true,
|
||||
"C2_add_user_scope": true,
|
||||
"C2_add_ledger_shape": true,
|
||||
"C3_extraction_input_set": true,
|
||||
"C3b_unshared_canary_never_outbound": true,
|
||||
"C4_search_filters": true,
|
||||
"C4_search_single_debit": true,
|
||||
"C6_concurrency_cap": true,
|
||||
"C7_reopen_after_close": true,
|
||||
"C5_embed_zero_refuses": true,
|
||||
"C5_llm_zero_refuses": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
{
|
||||
"head": "HEL-889 shutdown ownership & queued-cancel",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"boundary": "deterministic 512 vectors, non-loopback forbidden",
|
||||
"checks": [
|
||||
{
|
||||
"name": "Q1 queued snapshot shows phantom-liveness",
|
||||
"ok": true,
|
||||
"detail": ""
|
||||
},
|
||||
{
|
||||
"name": "Q1 cancel releases registry and requeues",
|
||||
"ok": true,
|
||||
"detail": "{\"registered_workers\": 0, \"batch\": {\"state\": \"queued\", \"reason\": \"queued_worker_cancelled\", \"owner\": null, \"attempts\": 0, \"entry_ids\": null}, \"row_done\": 0, \"stored_entries\": 0}"
|
||||
},
|
||||
{
|
||||
"name": "Q1 in-process recovery completes without restart",
|
||||
"ok": true,
|
||||
"detail": "{\"batch\": {\"state\": \"done\", \"reason\": null, \"owner\": null, \"attempts\": 1, \"entry_ids\": \"[\\\"stub-1\\\"]\"}, \"registered_workers\": 0, \"stored_entries\": 1, \"row_done\": 1}"
|
||||
},
|
||||
{
|
||||
"name": "S1 aclose does NOT return while the worker is alive",
|
||||
"ok": true,
|
||||
"detail": ""
|
||||
},
|
||||
{
|
||||
"name": "S1 worker completes and flushes before store close",
|
||||
"ok": true,
|
||||
"detail": "{\"state\": \"done\", \"reason\": null, \"owner\": null, \"attempts\": 1, \"entry_ids\": \"[\\\"afa7bce8-07f7-49ea-a65e-069ebb2baa02\\\"]\"}"
|
||||
},
|
||||
{
|
||||
"name": "S1 no late-worker error after honest drain",
|
||||
"ok": true,
|
||||
"detail": ""
|
||||
},
|
||||
{
|
||||
"name": "S1 durable reopen: done + row_done=1 + llm_used=1",
|
||||
"ok": true,
|
||||
"detail": "{\"state\": \"done\", \"reason\": null, \"owner\": null, \"attempts\": 1, \"entry_ids\": \"[\\\"afa7bce8-07f7-49ea-a65e-069ebb2baa02\\\"]\", \"row_done\": 1, \"llm_used\": 1, \"gateway_posts\": 1}"
|
||||
},
|
||||
{
|
||||
"name": "S2 shutdown between rows requeues with first row proven",
|
||||
"ok": true,
|
||||
"detail": "{\"state\": \"queued\", \"reason\": \"shutdown_between_rows\", \"owner\": null, \"attempts\": 0, \"entry_ids\": null, \"row_done\": 1, \"gateway_posts_during_shutdown\": 1}"
|
||||
},
|
||||
{
|
||||
"name": "S2 resume sends ONLY the missing row and completes",
|
||||
"ok": true,
|
||||
"detail": "{\"state\": \"done\", \"reason\": null, \"owner\": null, \"attempts\": 1, \"entry_ids\": \"[\\\"11c68b0b-78d4-477f-a553-0ea64da583ab\\\", \\\"c70134d2-99cf-4314-8dae-01d86e82e99e\\\"]\", \"row_done\": 2, \"gateway_posts_resume\": 2}"
|
||||
},
|
||||
{
|
||||
"name": "S3 cancelled aclose closes nothing and re-arms",
|
||||
"ok": true,
|
||||
"detail": ""
|
||||
},
|
||||
{
|
||||
"name": "S3 worker finishes done and a later aclose closes for real",
|
||||
"ok": true,
|
||||
"detail": ""
|
||||
},
|
||||
{
|
||||
"name": "S4 concurrent aclose drains once and closes once",
|
||||
"ok": true,
|
||||
"detail": "{\"batch\": {\"state\": \"done\", \"reason\": null, \"owner\": null, \"attempts\": 1, \"entry_ids\": \"[\\\"8a11970a-1f3b-4c9e-b9fa-ef5ef9c089e4\\\"]\", \"row_done\": 1}, \"client_usable_after_close\": false}"
|
||||
}
|
||||
],
|
||||
"queued_cancel": {
|
||||
"queued": {
|
||||
"registered_workers": 1,
|
||||
"product_executor_queue": 1
|
||||
},
|
||||
"after_cancel": {
|
||||
"registered_workers": 0,
|
||||
"batch": {
|
||||
"state": "queued",
|
||||
"reason": "queued_worker_cancelled",
|
||||
"owner": null,
|
||||
"attempts": 0,
|
||||
"entry_ids": null
|
||||
},
|
||||
"row_done": 0,
|
||||
"stored_entries": 0
|
||||
},
|
||||
"after_recovery": {
|
||||
"batch": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 1,
|
||||
"entry_ids": "[\"stub-1\"]"
|
||||
},
|
||||
"registered_workers": 0,
|
||||
"stored_entries": 1,
|
||||
"row_done": 1
|
||||
}
|
||||
},
|
||||
"shutdown_timeout": {
|
||||
"deadline": {
|
||||
"aclose_returned_while_worker_alive": false,
|
||||
"batch_before_any_close": {
|
||||
"state": "pending",
|
||||
"reason": null,
|
||||
"owner": "aa47821695b292f280306d12a8690ad1",
|
||||
"attempts": 1,
|
||||
"entry_ids": null
|
||||
},
|
||||
"client_usable": true
|
||||
},
|
||||
"before_store_close": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 1,
|
||||
"entry_ids": "[\"afa7bce8-07f7-49ea-a65e-069ebb2baa02\"]"
|
||||
},
|
||||
"late_worker_error": null,
|
||||
"persisted": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 1,
|
||||
"entry_ids": "[\"afa7bce8-07f7-49ea-a65e-069ebb2baa02\"]",
|
||||
"row_done": 1,
|
||||
"llm_used": 1,
|
||||
"gateway_posts": 1
|
||||
}
|
||||
},
|
||||
"between_rows": {
|
||||
"after_shutdown": {
|
||||
"state": "queued",
|
||||
"reason": "shutdown_between_rows",
|
||||
"owner": null,
|
||||
"attempts": 0,
|
||||
"entry_ids": null,
|
||||
"row_done": 1,
|
||||
"gateway_posts_during_shutdown": 1
|
||||
},
|
||||
"after_resume": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 1,
|
||||
"entry_ids": "[\"11c68b0b-78d4-477f-a553-0ea64da583ab\", \"c70134d2-99cf-4314-8dae-01d86e82e99e\"]",
|
||||
"row_done": 2,
|
||||
"gateway_posts_resume": 2
|
||||
}
|
||||
},
|
||||
"cancelled_aclose": {
|
||||
"after_cancel": {
|
||||
"client_usable": true,
|
||||
"batch": {
|
||||
"state": "pending",
|
||||
"reason": null,
|
||||
"owner": "1ad7597b3b7bd8df701cb56bfed9ea87",
|
||||
"attempts": 1,
|
||||
"entry_ids": null
|
||||
},
|
||||
"closing_cleared": true
|
||||
},
|
||||
"after_worker": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 1,
|
||||
"entry_ids": "[\"c3bbac6e-c569-4880-aa51-774768922365\"]"
|
||||
},
|
||||
"client_usable_after_real_close": false
|
||||
},
|
||||
"concurrent_aclose": {
|
||||
"batch": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 1,
|
||||
"entry_ids": "[\"8a11970a-1f3b-4c9e-b9fa-ef5ef9c089e4\"]",
|
||||
"row_done": 1
|
||||
},
|
||||
"client_usable_after_close": false
|
||||
},
|
||||
"failures": []
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
semantic shutdown drain: 1 live batch worker(s) still running after 0s — close signal set, waiting for their bounded exit before any resource closes (a live worker is never treated as dead)
|
||||
Failed to load spaCy lemma model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load spaCy full model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
semantic shutdown drain: 1 live batch worker(s) still running after 0s — close signal set, waiting for their bounded exit before any resource closes (a live worker is never treated as dead)
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
semantic shutdown drain: 1 live batch worker(s) still running after 0s — close signal set, waiting for their bounded exit before any resource closes (a live worker is never treated as dead)
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
semantic shutdown drain: 1 live batch worker(s) still running after 0s — close signal set, waiting for their bounded exit before any resource closes (a live worker is never treated as dead)
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
{"ok": true, "checks": 12, "failures": [], "out": "docs/rework/hel889/edges-real-sdk-hel889.json"}
|
||||
@@ -0,0 +1,34 @@
|
||||
semantic shutdown drain: 1 live batch worker(s) still running after 0s — close signal set, waiting for their bounded exit before any resource closes (a live worker is never treated as dead)
|
||||
Failed to load spaCy lemma model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load spaCy full model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
{
|
||||
"head": "HEL-889 same-shape rerun of review_hel886_edges.py (fixed tree)",
|
||||
"real_llm": 0,
|
||||
"real_embeddings": 0,
|
||||
"queued_cancel": {
|
||||
"batch": {
|
||||
"state": "done",
|
||||
"owner": null,
|
||||
"attempts": 1
|
||||
},
|
||||
"registered_workers": 0,
|
||||
"stored_entries": 1,
|
||||
"row_done": 1,
|
||||
"executor_queue_size": 0
|
||||
},
|
||||
"shutdown_timeout": {
|
||||
"aclose_returned_while_worker_alive": false,
|
||||
"batch_before_store_close": {
|
||||
"state": "done",
|
||||
"entry_ids": "[\"d3c48e4b-205b-4066-a570-e5909737138c\"]"
|
||||
},
|
||||
"late_worker_error": null,
|
||||
"persisted_batch": {
|
||||
"state": "done",
|
||||
"entry_ids": "[\"d3c48e4b-205b-4066-a570-e5909737138c\"]"
|
||||
},
|
||||
"persisted_row_done": 1,
|
||||
"llm_used": 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"head": "f1b50ad91ad8c856f28383ee59eddb6547d9c586",
|
||||
"real_llm": 0,
|
||||
"real_embeddings": 0,
|
||||
"queued_cancel": {
|
||||
"batch": {
|
||||
"state": "pending",
|
||||
"owner": "0698a359dda0416bb7b7b44e82ce2601",
|
||||
"attempts": 1
|
||||
},
|
||||
"registered_workers": 1,
|
||||
"stored_entries": 0,
|
||||
"row_done": 0,
|
||||
"executor_queue_size": 0
|
||||
},
|
||||
"shutdown_timeout": {
|
||||
"aclose_returned_while_worker_alive": true,
|
||||
"batch_before_store_close": {
|
||||
"state": "pending",
|
||||
"entry_ids": null
|
||||
},
|
||||
"late_worker_error": "ProgrammingError: Cannot operate on a closed database.",
|
||||
"persisted_batch": {
|
||||
"state": "pending",
|
||||
"entry_ids": null
|
||||
},
|
||||
"persisted_row_done": 0,
|
||||
"llm_used": 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
semantic shutdown drain: 1 live batch worker(s) still running after 0s — closing anyway, their outcome stays unknown
|
||||
semantic shutdown drain: 1 live batch worker(s) still running after 0s — closing anyway, their outcome stays unknown
|
||||
Failed to load spaCy lemma model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to insert memory 88b59cb3-b8e7-4fda-be99-9384739784c1: QdrantLocal instance is closed. Please create a new instance.
|
||||
semantic batch 1 failed: Failed to insert any of the 1 extracted memories into the vector store
|
||||
{
|
||||
"head": "f1b50ad91ad8c856f28383ee59eddb6547d9c586",
|
||||
"real_llm": 0,
|
||||
"real_embeddings": 0,
|
||||
"queued_cancel": {
|
||||
"batch": {
|
||||
"state": "pending",
|
||||
"owner": "0698a359dda0416bb7b7b44e82ce2601",
|
||||
"attempts": 1
|
||||
},
|
||||
"registered_workers": 1,
|
||||
"stored_entries": 0,
|
||||
"row_done": 0,
|
||||
"executor_queue_size": 0
|
||||
},
|
||||
"shutdown_timeout": {
|
||||
"aclose_returned_while_worker_alive": true,
|
||||
"batch_before_store_close": {
|
||||
"state": "pending",
|
||||
"entry_ids": null
|
||||
},
|
||||
"late_worker_error": "ProgrammingError: Cannot operate on a closed database.",
|
||||
"persisted_batch": {
|
||||
"state": "pending",
|
||||
"entry_ids": null
|
||||
},
|
||||
"persisted_row_done": 0,
|
||||
"llm_used": 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"head": "HEL-886 inflight lifecycle",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"boundary": "deterministic 512 vectors, non-loopback forbidden",
|
||||
"checks": [
|
||||
{
|
||||
"name": "L1_cancel_single_send_and_full_record",
|
||||
"ok": true,
|
||||
"detail": "arrived=1 concurrent=1 http_total=1 points=1 batch_live={'state': 'pending', 'owner': '2ef009e061a90c98547e30ee2d034344'} batch_concurrent=pending final={'state': 'done', 'reason': None, 'entry_ids': '[\"43a823a4-f5ca-4e5b-a7c4-087ab5b906a6\"]'} row_done=1"
|
||||
},
|
||||
{
|
||||
"name": "L2_lease_single_send_and_full_record",
|
||||
"ok": true,
|
||||
"detail": "arrived=1 concurrent=1 http_total=1 points=1 batch_live={'state': 'pending', 'owner': '317012c028229e233165848dee10ccb3'} batch_concurrent=pending final={'state': 'done', 'reason': None, 'entry_ids': '[\"c1885079-9900-4694-bfe8-24892aa8fd8a\"]'} row_done=1"
|
||||
},
|
||||
{
|
||||
"name": "L3_shutdown_drains_live_worker_before_close",
|
||||
"ok": true,
|
||||
"detail": "arrived=1 waited=0.25s row_done_at=2000000 batch={'state': 'done', 'finished_at': 2000000}"
|
||||
},
|
||||
{
|
||||
"name": "L4a_all_done_finalizes_without_new_budget",
|
||||
"ok": true,
|
||||
"detail": "before={'state': 'done', 'entry_ids': '[\"94a9bb71-a1fa-4f73-92f1-92899035a819\"]'} after={'state': 'done', 'reason': None, 'entry_ids': '[\"94a9bb71-a1fa-4f73-92f1-92899035a819\"]'} http=1 llm_used=1"
|
||||
},
|
||||
{
|
||||
"name": "L4b_partial_keeps_the_hard_gate",
|
||||
"ok": true,
|
||||
"detail": "after={'state': 'queued', 'reason': 'semantic_llm_budget_exhausted'} http=0"
|
||||
},
|
||||
{
|
||||
"name": "L5_dead_owner_lease_still_recovers",
|
||||
"ok": true,
|
||||
"detail": "recovered={'state': 'failed', 'reason': 'lease_expired'} after={'state': 'done'} http=2"
|
||||
}
|
||||
],
|
||||
"gateway_requests_total": 7,
|
||||
"failures": []
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
Failed to load spaCy lemma model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load spaCy full model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
LLM extraction failed: semantic_llm_budget_exhausted
|
||||
semantic batch 1 requeued (budget): LLM extraction failed: semantic_llm_budget_exhausted
|
||||
semantic batch 1 requeued (budget): semantic_llm_budget_exhausted
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
{
|
||||
"out": "docs/rework/hel889/inflight-hel886-rerun.json",
|
||||
"failures": [],
|
||||
"checks": {
|
||||
"L1_cancel_single_send_and_full_record": true,
|
||||
"L2_lease_single_send_and_full_record": true,
|
||||
"L3_shutdown_drains_live_worker_before_close": true,
|
||||
"L4a_all_done_finalizes_without_new_budget": true,
|
||||
"L4b_partial_keeps_the_hard_gate": true,
|
||||
"L5_dead_owner_lease_still_recovers": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
Failed to load spaCy lemma model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load spaCy full model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
Traceback (most recent call last):
|
||||
File "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-889-01bb190e1c83/workdir/review_hel885_inflight.py", line 143, in <module>
|
||||
asyncio.run(main())
|
||||
File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
|
||||
return loop.run_until_complete(main)
|
||||
File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
|
||||
return future.result()
|
||||
File "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-889-01bb190e1c83/workdir/review_hel885_inflight.py", line 102, in main
|
||||
'scenarios': [await scenario('cancel'), await scenario('lease')],
|
||||
File "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-889-01bb190e1c83/workdir/review_hel885_inflight.py", line 84, in scenario
|
||||
assert await asyncio.to_thread(worker_done.wait, 8)
|
||||
AssertionError
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"head": "59fb06304fb9f230837b43e431e2a1cc303e16c9",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"midnight_settlement": {
|
||||
"daily_cap": 100,
|
||||
"old_hold": 90,
|
||||
"actual": 10,
|
||||
"new_day_used_before_next_request": 0,
|
||||
"request_estimate": 150,
|
||||
"over_cap_request_allowed": false,
|
||||
"rows": [
|
||||
{
|
||||
"day": "2026-10-26",
|
||||
"scene": "dialogue_main",
|
||||
"used": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
"aged_unknown_reservation": {
|
||||
"cap": 1,
|
||||
"used_before": 1,
|
||||
"second_allowed": false,
|
||||
"first_hold_id": 1,
|
||||
"second_hold_id": null,
|
||||
"used_after": 1
|
||||
},
|
||||
"release_documented_download_command": {
|
||||
"command": "uv pip download --help",
|
||||
"returncode": 2,
|
||||
"stderr": "error: unrecognized subcommand 'download'\n\nUsage: uv pip [OPTIONS] <COMMAND>\n\nFor more information, try '--help'."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"head": "59fb06304fb9f230837b43e431e2a1cc303e16c9",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"midnight_settlement": {
|
||||
"daily_cap": 100,
|
||||
"old_hold": 90,
|
||||
"actual": 10,
|
||||
"new_day_used_before_next_request": 0,
|
||||
"request_estimate": 150,
|
||||
"over_cap_request_allowed": false,
|
||||
"rows": [
|
||||
{
|
||||
"day": "2026-10-26",
|
||||
"scene": "dialogue_main",
|
||||
"used": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
"aged_unknown_reservation": {
|
||||
"cap": 1,
|
||||
"used_before": 1,
|
||||
"second_allowed": false,
|
||||
"first_hold_id": 1,
|
||||
"second_hold_id": null,
|
||||
"used_after": 1
|
||||
},
|
||||
"release_documented_download_command": {
|
||||
"command": "uv pip download --help",
|
||||
"returncode": 2,
|
||||
"stderr": "error: unrecognized subcommand 'download'\n\nUsage: uv pip [OPTIONS] <COMMAND>\n\nFor more information, try '--help'."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"head": "59fb06304fb9f230837b43e431e2a1cc303e16c9",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"boundary": "deterministic 512 vectors, non-loopback forbidden",
|
||||
"missing_authority_constructed_with_positive_caps": false,
|
||||
"public_batch_enqueued": 1,
|
||||
"batch_after_first_tick": {
|
||||
"state": "queued",
|
||||
"attempts": 0,
|
||||
"reason": "LLM extraction failed: semantic_llm_budget_exhausted",
|
||||
"entry_ids": null
|
||||
},
|
||||
"loopback_requests_before_resume": 1,
|
||||
"stored_points_after_first": [
|
||||
{
|
||||
"src_group": "chat_public:mem0:synthetic:1bc8667410",
|
||||
"turn_ids": [
|
||||
"synthetic"
|
||||
],
|
||||
"turn_roles": [
|
||||
[
|
||||
"synthetic",
|
||||
"companion",
|
||||
2
|
||||
]
|
||||
],
|
||||
"input_sha256": "840dde45eec07416f21c56cefb0f93f4e71af3edc3100c332f84bb1266637862",
|
||||
"user_id": "cybergril",
|
||||
"data": "合成提取记忆HEL874-1",
|
||||
"text_lemmatized": "合成提取记忆HEL874-1",
|
||||
"hash": "69195446402294f6e77f8ee993ce3456",
|
||||
"created_at": "2026-10-26T17:06:31.737649+00:00",
|
||||
"updated_at": "2026-10-26T17:06:31.737649+00:00"
|
||||
}
|
||||
],
|
||||
"claimed_completed_message_ids": [
|
||||
2
|
||||
],
|
||||
"batch_after_resume": {
|
||||
"state": "done",
|
||||
"attempts": 1,
|
||||
"reason": null,
|
||||
"entry_ids": "[\"4e45788c-abe9-4585-943c-ae0ea48b1044\", \"37d81794-2bb5-4143-b347-ff5de238a064\"]"
|
||||
},
|
||||
"loopback_requests_after_resume": 2,
|
||||
"unsent_user_row_actually_sent": true
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
Failed to load spaCy lemma model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load spaCy full model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
LLM extraction failed: semantic_llm_budget_exhausted
|
||||
semantic batch 1 requeued (budget): LLM extraction failed: semantic_llm_budget_exhausted
|
||||
{
|
||||
"head": "59fb06304fb9f230837b43e431e2a1cc303e16c9",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"boundary": "deterministic 512 vectors, non-loopback forbidden",
|
||||
"missing_authority_constructed_with_positive_caps": false,
|
||||
"public_batch_enqueued": 1,
|
||||
"batch_after_first_tick": {
|
||||
"state": "queued",
|
||||
"attempts": 0,
|
||||
"reason": "LLM extraction failed: semantic_llm_budget_exhausted",
|
||||
"entry_ids": null
|
||||
},
|
||||
"loopback_requests_before_resume": 1,
|
||||
"stored_points_after_first": [
|
||||
{
|
||||
"src_group": "chat_public:mem0:synthetic:1bc8667410",
|
||||
"turn_ids": [
|
||||
"synthetic"
|
||||
],
|
||||
"turn_roles": [
|
||||
[
|
||||
"synthetic",
|
||||
"companion",
|
||||
2
|
||||
]
|
||||
],
|
||||
"input_sha256": "840dde45eec07416f21c56cefb0f93f4e71af3edc3100c332f84bb1266637862",
|
||||
"user_id": "cybergril",
|
||||
"data": "合成提取记忆HEL874-1",
|
||||
"text_lemmatized": "合成提取记忆HEL874-1",
|
||||
"hash": "69195446402294f6e77f8ee993ce3456",
|
||||
"created_at": "2026-10-26T17:06:31.737649+00:00",
|
||||
"updated_at": "2026-10-26T17:06:31.737649+00:00"
|
||||
}
|
||||
],
|
||||
"claimed_completed_message_ids": [
|
||||
2
|
||||
],
|
||||
"batch_after_resume": {
|
||||
"state": "done",
|
||||
"attempts": 1,
|
||||
"reason": null,
|
||||
"entry_ids": "[\"4e45788c-abe9-4585-943c-ae0ea48b1044\", \"37d81794-2bb5-4143-b347-ff5de238a064\"]"
|
||||
},
|
||||
"loopback_requests_after_resume": 2,
|
||||
"unsent_user_row_actually_sent": true
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
Traceback (most recent call last):
|
||||
File "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-889-01bb190e1c83/workdir/review_hel886_edges.py", line 117, in <module>
|
||||
'queued_cancel': asyncio.run(queued_cancel(tmp)),
|
||||
File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
|
||||
return loop.run_until_complete(main)
|
||||
File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
|
||||
return future.result()
|
||||
File "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-889-01bb190e1c83/workdir/review_hel886_edges.py", line 43, in queued_cancel
|
||||
assert live._live and executor._work_queue.qsize() == 1
|
||||
AssertionError
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"head": "HEL-885 rework",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"boundary": "deterministic 512 vectors, non-loopback forbidden",
|
||||
"checks": [
|
||||
{
|
||||
"name": "S0_positive_caps_without_authority_fail_closed",
|
||||
"ok": true,
|
||||
"detail": "missing=ledger_authority_required empty=ledger_authority_required"
|
||||
},
|
||||
{
|
||||
"name": "S1_p1_first_tick_partial",
|
||||
"ok": true,
|
||||
"detail": "state=queued reason=LLM extraction failed: semantic_llm_budget_exhausted http=1 completed=[2] points=1 per_row_anchor=True"
|
||||
},
|
||||
{
|
||||
"name": "S2_p1_restart_sends_only_missing_row",
|
||||
"ok": true,
|
||||
"detail": "state=done http=1 user_sent=True completed=[1, 2] entry_ids=[\"789be5fc-001c-4fc7-a109-69fb62f5b4e1\", \"77e7ce70-7309-4ced-ba57-81bff84edd3a\"]"
|
||||
},
|
||||
{
|
||||
"name": "S3a_lease_expired_batch_recovers",
|
||||
"ok": true,
|
||||
"detail": "recovered={'state': 'failed', 'reason': 'lease_expired'} state_after=done http=2"
|
||||
},
|
||||
{
|
||||
"name": "S3b_concurrent_ticks_single_send",
|
||||
"ok": true,
|
||||
"detail": "http=2 states=['done']"
|
||||
},
|
||||
{
|
||||
"name": "S4a_aged_unknown_llm_reservation_kept",
|
||||
"ok": true,
|
||||
"detail": "second_allowed=False used_after_restart=1"
|
||||
},
|
||||
{
|
||||
"name": "S4b_aged_unknown_embed_reservation_kept",
|
||||
"ok": true,
|
||||
"detail": "second_allowed=False used=1 after_release=0"
|
||||
}
|
||||
],
|
||||
"gateway_requests_total": 6,
|
||||
"failures": []
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
Failed to load spaCy lemma model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load spaCy full model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
LLM extraction failed: semantic_llm_budget_exhausted
|
||||
semantic batch 1 requeued (budget): LLM extraction failed: semantic_llm_budget_exhausted
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
{
|
||||
"out": "docs/rework/hel889/recovery-hel885-rerun.json",
|
||||
"failures": [],
|
||||
"checks": {
|
||||
"S0_positive_caps_without_authority_fail_closed": true,
|
||||
"S1_p1_first_tick_partial": true,
|
||||
"S2_p1_restart_sends_only_missing_row": true,
|
||||
"S3a_lease_expired_batch_recovers": true,
|
||||
"S3b_concurrent_ticks_single_send": true,
|
||||
"S4a_aged_unknown_llm_reservation_kept": true,
|
||||
"S4b_aged_unknown_embed_reservation_kept": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"started": "2026-10-26T17:06:16Z",
|
||||
"venv_python": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-874-db7635e6b65e/workdir/CyberGril/.venv311/bin/python",
|
||||
"venv_python_version": "3.11.16",
|
||||
"checks": [
|
||||
{
|
||||
"name": "R1_checkout_fresh_dir",
|
||||
"ok": true,
|
||||
"detail": "cloned tracked source into /tmp/multica-task-2867647507/hel882-release-jpq_z0co/cybergril-hel874/current"
|
||||
},
|
||||
{
|
||||
"name": "R2_service_imports_product",
|
||||
"ok": true,
|
||||
"detail": ""
|
||||
},
|
||||
{
|
||||
"name": "R6_mem0_coimport_status_recorded",
|
||||
"ok": true,
|
||||
"detail": "import mem0 → ModuleNotFoundError: No module named 'mem0' (resolution path in deploy/hel874-release.md §4; no download in this check)"
|
||||
},
|
||||
{
|
||||
"name": "R7_pip_download_command_exists",
|
||||
"ok": true,
|
||||
"detail": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/bin/python -m pip download --help → rc=0"
|
||||
},
|
||||
{
|
||||
"name": "R7_removed_uv_pip_download_still_absent",
|
||||
"ok": true,
|
||||
"detail": "uv pip download --help → rc=2 (kept absent on purpose; do not re-document it)"
|
||||
},
|
||||
{
|
||||
"name": "R2_service_serves_health",
|
||||
"ok": true,
|
||||
"detail": "version=R4-assembly semantic=stub"
|
||||
},
|
||||
{
|
||||
"name": "R3_create_status_recorded",
|
||||
"ok": true,
|
||||
"detail": "create status=201(409=前次运行的档案仍在,也是保留数据的表现)"
|
||||
},
|
||||
{
|
||||
"name": "R3_dialogue_refused_honestly",
|
||||
"ok": true,
|
||||
"detail": "http=200 events=b'data: {\"t\": \"chat.error\", \"epoch\": 1, \"turn_id\": \"00000000-0000-4000-8000-000000000001\", \"phi\": \"\\xe5\\xa5\\xb9\\xe8\\xbf\\x99\\xe4\\xbc\\x9a\\xe5\\x84\\xbf\\xe6\\xb2\\xa1\\xe6\\x8e\\xa5\\xe4\\xb8\\x8a\\xe8\\xaf'"
|
||||
},
|
||||
{
|
||||
"name": "R4_service_restarts_on_same_data",
|
||||
"ok": true,
|
||||
"detail": "epoch=2"
|
||||
},
|
||||
{
|
||||
"name": "R5_epoch_bumped_on_reopen",
|
||||
"ok": true,
|
||||
"detail": "1 → 2"
|
||||
},
|
||||
{
|
||||
"name": "R4_companion_created_offline",
|
||||
"ok": true,
|
||||
"detail": "status=409"
|
||||
},
|
||||
{
|
||||
"name": "R4_dialogue_stream_http",
|
||||
"ok": true,
|
||||
"detail": "status=200 events=b'data: {\"t\": \"chat.delta\", \"epoch\": 2, \"turn_id\": \"00000000-0000-4000-8000-000000000002\", \"delta\": \"\\xe6\\x89\\xb9\"}\\n\\ndata: {\"t\": \"c'"
|
||||
},
|
||||
{
|
||||
"name": "R4_messages_roundtrip",
|
||||
"ok": true,
|
||||
"detail": "roles=['user', 'user', 'companion']"
|
||||
},
|
||||
{
|
||||
"name": "R5_data_dir_preserved",
|
||||
"ok": true,
|
||||
"detail": "['companion.db']"
|
||||
},
|
||||
{
|
||||
"name": "R5_companion_row_survives_restart",
|
||||
"ok": true,
|
||||
"detail": "companion rows=1"
|
||||
}
|
||||
],
|
||||
"mem0_import_in_release_venv": "ModuleNotFoundError: No module named 'mem0'",
|
||||
"removed_uv_pip_download_rc": 2,
|
||||
"failures": [],
|
||||
"finished": "2026-10-26T17:06:19Z"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
PASS R1_checkout_fresh_dir — cloned tracked source into /tmp/multica-task-2867647507/hel882-release-jpq_z0co/cybergril-hel874/current
|
||||
PASS R2_service_imports_product
|
||||
PASS R6_mem0_coimport_status_recorded — import mem0 → ModuleNotFoundError: No module named 'mem0' (resolution path in deploy/hel874-release.md §4; no download in this check)
|
||||
PASS R7_pip_download_command_exists — /home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/bin/python -m pip download --help → rc=0
|
||||
PASS R7_removed_uv_pip_download_still_absent — uv pip download --help → rc=2 (kept absent on purpose; do not re-document it)
|
||||
PASS R2_service_serves_health — version=R4-assembly semantic=stub
|
||||
PASS R3_create_status_recorded — create status=201(409=前次运行的档案仍在,也是保留数据的表现)
|
||||
PASS R3_dialogue_refused_honestly — http=200 events=b'data: {"t": "chat.error", "epoch": 1, "turn_id": "00000000-0000-4000-8000-000000000001", "phi": "\xe5\xa5\xb9\xe8\xbf\x99\xe4\xbc\x9a\xe5\x84\xbf\xe6\xb2\xa1\xe6\x8e\xa5\xe4\xb8\x8a\xe8\xaf'
|
||||
PASS R4_service_restarts_on_same_data — epoch=2
|
||||
PASS R5_epoch_bumped_on_reopen — 1 → 2
|
||||
PASS R4_companion_created_offline — status=409
|
||||
PASS R4_dialogue_stream_http — status=200 events=b'data: {"t": "chat.delta", "epoch": 2, "turn_id": "00000000-0000-4000-8000-000000000002", "delta": "\xe6\x89\xb9"}\n\ndata: {"t": "c'
|
||||
PASS R4_messages_roundtrip — roles=['user', 'user', 'companion']
|
||||
PASS R5_data_dir_preserved — ['companion.db']
|
||||
PASS R5_companion_row_survives_restart — companion rows=1
|
||||
{"out": "docs/rework/hel889/release-offline-hel889.json", "failures": []}
|
||||
@@ -0,0 +1,158 @@
|
||||
"""HEL-889 rerun of the manager's review_hel886_edges.py — same scenarios,
|
||||
adjusted to the FIXED contracts.
|
||||
|
||||
Adjustments vs the original (before/after documented, negative preserved):
|
||||
|
||||
1. queued_cancel: the congestion blocker now occupies the PRODUCT's dedicated
|
||||
batch executor (``SemanticMemory(executor=...)`` — HEL-889 moved batch
|
||||
workers off the loop's shared default executor onto a dedicated one this
|
||||
layer owns, precisely so the queue/started truth is observable). The
|
||||
original blocked the loop default executor because ``to_thread`` put the
|
||||
worker there; that premise no longer holds, and re-run verbatim the
|
||||
original stops at its own ``assert live._live and
|
||||
executor._work_queue.qsize() == 1`` (original-verbatim-after-fix.log — the
|
||||
queued defect it encoded is gone). This rerun keeps every step of the
|
||||
manager's sequence: cancel the tick while queued, release, clock +41s,
|
||||
recover + tick, and the same result fields.
|
||||
2. shutdown_timeout: identical gateway, identical ``aclose(drain_timeout=0.05)``
|
||||
(the default-120s branch). The original released the wire barrier only
|
||||
AFTER ``store.close()`` — an ordering the fixed ownership protocol forbids
|
||||
(aclose returns only once the live worker exited, so store.close() can
|
||||
never precede the worker's flush). The barrier is therefore released by a
|
||||
timer thread 0.3s into the drain; every result key of the original is kept.
|
||||
|
||||
Everything else — real pinned SDK, real Qdrant, real OpenAI client,
|
||||
deterministic 512-dim stand-in installed before TextEmbedding construction,
|
||||
loopback-only gateway, temp ledgers — is unchanged.
|
||||
"""
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
REPO = ROOT / 'CyberGril'
|
||||
sys.path.insert(0, str(REPO / 'src'))
|
||||
spec = importlib.util.spec_from_file_location('contract', REPO / 'tools/verify_mem0_real_hel874.py')
|
||||
c = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(c)
|
||||
c.install_deterministic_boundary()
|
||||
from cybergril.memory.semantic import SemanticMemory, StubBackend, Mem0Backend
|
||||
from cybergril.memory.store import Store
|
||||
|
||||
|
||||
def seed(store):
|
||||
with store.conn:
|
||||
store.execute("INSERT INTO chat_message(ts,role,text,turn_id,status) VALUES"
|
||||
"(1,'user','独立关闭边界合成原话','edge','sent')")
|
||||
|
||||
|
||||
async def queued_cancel(tmp):
|
||||
store = Store(tmp / 'queued.db')
|
||||
seed(store)
|
||||
clock = [1000.0]
|
||||
backend = StubBackend()
|
||||
# HEL-889: batch workers run on the layer's OWN executor — the manager's
|
||||
# congestion now blocks exactly that one.
|
||||
executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
|
||||
entered, release = threading.Event(), threading.Event()
|
||||
blocker = executor.submit(lambda: (entered.set(), release.wait(8)))
|
||||
assert entered.wait(2)
|
||||
live = SemanticMemory(store, backend, clock=lambda: clock[0], executor=executor)
|
||||
live.enqueue_uncovered()
|
||||
task = asyncio.create_task(live.tick())
|
||||
for _ in range(500):
|
||||
if live._live and all(not h.started.is_set() for h in live._live.values()):
|
||||
break
|
||||
await asyncio.sleep(0.01)
|
||||
assert live._live and executor._work_queue.qsize() == 1
|
||||
task.cancel()
|
||||
try:
|
||||
await task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
release.set()
|
||||
blocker.result(timeout=2)
|
||||
await asyncio.sleep(0.05)
|
||||
clock[0] += 41
|
||||
live.recover()
|
||||
await live.tick()
|
||||
result = {'batch': dict(store.query_one('SELECT state,owner,attempts FROM sem_batch')),
|
||||
'registered_workers': len(live._live), 'stored_entries': len(backend._entries),
|
||||
'row_done': len(store.query('SELECT * FROM sem_row_done')),
|
||||
'executor_queue_size': executor._work_queue.qsize()}
|
||||
# There is no worker remaining: only a false registry handle prevents recovery.
|
||||
await live.aclose(drain_timeout=0)
|
||||
store.close()
|
||||
executor.shutdown(wait=True)
|
||||
return result
|
||||
|
||||
|
||||
async def shutdown_timeout(tmp):
|
||||
arrived, release = threading.Event(), threading.Event()
|
||||
|
||||
class Gateway(c.FakeGateway):
|
||||
def do_POST(self):
|
||||
arrived.set()
|
||||
if not release.wait(8):
|
||||
raise RuntimeError('barrier timed out')
|
||||
super().do_POST()
|
||||
|
||||
server = c._Server(('127.0.0.1', 0), Gateway)
|
||||
server_thread = threading.Thread(target=server.serve_forever)
|
||||
server_thread.start()
|
||||
c.make_ledger(tmp / 'ledger.db', cap=100, llm_cap=10)
|
||||
cfg = c.config_for(tmp, tmp / 'ledger.db', port=server.server_address[1], cap=100, llm_cap=10)
|
||||
backend = Mem0Backend(cfg)
|
||||
store = Store(tmp / 'shutdown.db')
|
||||
seed(store)
|
||||
live = SemanticMemory(store, backend)
|
||||
live.enqueue_uncovered()
|
||||
task = asyncio.create_task(live.tick())
|
||||
try:
|
||||
assert await asyncio.to_thread(arrived.wait, 3)
|
||||
# Same code branch as the default 120s deadline, shortened for a bounded test.
|
||||
closing = asyncio.create_task(live.aclose(drain_timeout=0.05))
|
||||
await asyncio.sleep(0.3) # deadline long past, worker still alive
|
||||
result = {'aclose_returned_while_worker_alive': closing.done(),
|
||||
'batch_before_store_close': dict(store.query_one(
|
||||
'SELECT state,entry_ids FROM sem_batch'))}
|
||||
# The fixed protocol never closes under a live worker: the wire answer
|
||||
# must arrive (here via timer) BEFORE aclose can return and the app
|
||||
# order store.close() runs.
|
||||
threading.Timer(0.2, release.set).start()
|
||||
await closing
|
||||
result['batch_before_store_close'] = dict(store.query_one(
|
||||
'SELECT state,entry_ids FROM sem_batch'))
|
||||
store.close()
|
||||
try:
|
||||
await task
|
||||
result['late_worker_error'] = None
|
||||
except Exception as exc:
|
||||
result['late_worker_error'] = f'{type(exc).__name__}: {exc}'
|
||||
check = Store(tmp / 'shutdown.db')
|
||||
result['persisted_batch'] = dict(check.query_one('SELECT state,entry_ids FROM sem_batch'))
|
||||
result['persisted_row_done'] = len(check.query('SELECT * FROM sem_row_done'))
|
||||
result['llm_used'] = backend.llm_guard.used()
|
||||
check.close()
|
||||
return result
|
||||
finally:
|
||||
release.set()
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
server_thread.join()
|
||||
|
||||
|
||||
with tempfile.TemporaryDirectory(dir=ROOT) as td:
|
||||
tmp = Path(td)
|
||||
report = {'head': 'HEL-889 same-shape rerun of review_hel886_edges.py (fixed tree)',
|
||||
'real_llm': 0, 'real_embeddings': 0,
|
||||
'queued_cancel': asyncio.run(queued_cancel(tmp)),
|
||||
'shutdown_timeout': asyncio.run(shutdown_timeout(tmp))}
|
||||
(ROOT / 'HEL-889-edges-rerun-fixed.json').write_text(
|
||||
json.dumps(report, ensure_ascii=False, indent=2))
|
||||
print(json.dumps(report, ensure_ascii=False, indent=2))
|
||||
@@ -0,0 +1,70 @@
|
||||
# HEL-894 返工证据 · 关闭被取消时的资源所有权
|
||||
|
||||
总管对 HEL-889 head `ceb0f35418f0d0b28b4e2a359615194db8eb4567` 独立复审
|
||||
(`review_hel889_cancel.py`,附件与本目录 `before-ceb0f35.*`)确认的两个组合
|
||||
漏洞,在本 head 修复并复验。零真实出站、零真实嵌入、零下载;全部回环 +
|
||||
确定性 512 维替身(TextEmbedding 构造前安装);未写权威三账、未改冻结余额
|
||||
与真实 cap、未部署、未合并、未改视觉。
|
||||
|
||||
## 修复前基线(本卡在未改动的 ceb0f35 上原样复跑总管脚本)
|
||||
|
||||
`before-ceb0f35.json` 与总管独立结果一致——
|
||||
|
||||
- finish_cancel(SDK-only 与 +Store 关闭两分支):取消返回时
|
||||
closing=false/closed=false/client 可用(宣称可续用)→ 新批被领、HTTP 已
|
||||
接受未答 → 后台旧 `_finish` 关 Qdrant(closed=true、live_workers=1)→
|
||||
不关 Store 也 failed/`Failed to insert any of the 1 extracted memories
|
||||
into the vector store`/row_done=0/llm_used=1(`QdrantLocal instance is
|
||||
closed`);+Store 分支晚到 worker 另报 `ProgrammingError: Cannot operate
|
||||
on a closed database.`,重开 pending/row_done=0/llm_used=1。
|
||||
- waiter_cancel:第一关闭排空中、第二等锁即取消后 closing 被清
|
||||
(signal=false),原 worker 未放行时新批已 done。
|
||||
|
||||
## 修复(src/cybergril/memory/semantic.py)
|
||||
|
||||
**两阶段取消语义(HEL-894)**:
|
||||
|
||||
1. **排空阶段(可撤销)**:尚未关任何资源。此阶段取消维持 HEL-889 语义——
|
||||
什么都不关、closing 复位、层次可续用,之后 aclose 再正常收尾。
|
||||
2. **提交阶段(不可撤销)**:`backend.aclose()` 一旦开始,资源只能关完。
|
||||
被取消的调用方不清 closing/不放行 tick(半关闭状态绝不宣称可用)、不释放
|
||||
锁,并等待(重复取消也只重新 shield)自己启动的**层持有**关闭任务
|
||||
(`_close_task`,创建恰一次、从不丢到后台)落成后才传播 CancelledError;
|
||||
资源恰关一次。本层自有执行器的 shutdown 也走 `to_thread`,取消方不可能
|
||||
用同步 join 阻塞事件循环(调用方注入执行器仍由调用方持有,与总管脚本
|
||||
口径一致)。
|
||||
3. **所有权最小化**:只有真正取得锁并置位信号的调用(`owns_close`)才可在
|
||||
仍可撤销时复位信号;等锁者被取消不改变持锁关闭者的任何状态(修复负例
|
||||
二)。app lifespan 注释同步为该口径。
|
||||
|
||||
## 修复后验证
|
||||
|
||||
- `close-ownership-real-sdk.json`(tools/verify_close_ownership_hel894.py,
|
||||
固定 mem0 2.2.1 + 真实 Qdrant/OpenAI client 实跑,总管两负例同形、断言按
|
||||
新协议改写并逐条注明)16/16:F1 提交阶段取消(SDK-only)——取消后
|
||||
closing=true/closed=false/client 可用(不再宣称可续用),取消方未返回先
|
||||
等自己启动的关闭落成;新批 tick 被拒(网关零到达、批次 queued/owner
|
||||
NULL/attempts 0、llm_used=0);放行后关闭恰一次(closed=true、client 不可
|
||||
用、live_workers=0);重开批次 queued 完好、row_done=0、llm_used=0(修复
|
||||
前为 failed/pending + llm_used=1)。F2(+调用方随后关 Store)同形且无任何
|
||||
晚到错误(修复前 ProgrammingError)。W 等锁者取消——closing 保持置位、新
|
||||
批 queued 不被领、原 worker 放行后 done/row_done=1、第一关闭一次收尾。
|
||||
- `verbatim-after-fix.log`:总管原脚本**原样**复跑在修复后树上如实停在其自
|
||||
身前提断言 `assert await asyncio.to_thread(server.arrived.wait, 5)`——
|
||||
「取消后新 tick 发出真实 HTTP」这一前提被新协议结构性移除(tick 在
|
||||
closing 持有期间拒新领);原负例与基线保留不删。
|
||||
- 回归(固定安装实跑):`shutdown-queued-hel889-rerun.json` 12/12(原排队
|
||||
取消/超时排空/行间续跑/排空取消可续用/并发关闭全部保持)、
|
||||
`inflight-hel886-rerun.json` 6/6(处理中取消单发/全完成零预算收尾保持)、
|
||||
`recovery-hel885-rerun.json` 7/7、`contract-hel874-rerun.json` 12/12、
|
||||
`release-offline-hel894.json` R1–R7 15/15、总管
|
||||
`review_hel882_{partial,extra}.py` 原样复跑通过
|
||||
(`manager-review-*-rerun.json`;extra 内 uv rc=2 为其自身预期负例)、
|
||||
`edges-rerun-hel889.json` 同形复跑通过。`review_hel885_inflight.py` 本轮
|
||||
未重跑,其「仍在 2-add 屏障如预期失败」的存档口径(HEL-886/889)不变。
|
||||
- 产品测试:python 3.11.16 venv + requirements.lock 离线 **299 pytest 全过**
|
||||
(新增 tests/test_rework_hel894.py 5 例,全部屏障驱动真实产品路径:提交
|
||||
阶段取消保持所有权并拒新工作/重复取消仍一次落成/同阶段第二调用等待后幂
|
||||
等且等锁者取消不动状态/等锁者取消不改持锁关闭者+tick+原 worker 完成组合/
|
||||
排空阶段取消仍可续用)、ruff 0 错(src/tests/tools)。本卡未改前端,未重
|
||||
跑前端检查,不冒充历史数字。
|
||||
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"head": "ceb0f35418f0d0b28b4e2a359615194db8eb4567",
|
||||
"real_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"finish_cancel_sdk_only": {
|
||||
"after_cancellation": {
|
||||
"closing": false,
|
||||
"closed": false,
|
||||
"client_usable": true
|
||||
},
|
||||
"hidden_finish": {
|
||||
"closed": true,
|
||||
"client_usable": false,
|
||||
"live_workers": 1,
|
||||
"batch": {
|
||||
"state": "pending",
|
||||
"reason": null,
|
||||
"owner": "06daaa8f643701f05f768706be2e36a1",
|
||||
"attempts": 1,
|
||||
"entry_ids": null
|
||||
}
|
||||
},
|
||||
"late_worker_error": null,
|
||||
"persisted": {
|
||||
"state": "failed",
|
||||
"reason": "Failed to insert any of the 1 extracted memories into the vector store",
|
||||
"owner": null,
|
||||
"attempts": 1,
|
||||
"entry_ids": null,
|
||||
"row_done": 0,
|
||||
"llm_used": 1
|
||||
}
|
||||
},
|
||||
"finish_cancel_then_store_close": {
|
||||
"after_cancellation": {
|
||||
"closing": false,
|
||||
"closed": false,
|
||||
"client_usable": true
|
||||
},
|
||||
"hidden_finish": {
|
||||
"closed": true,
|
||||
"client_usable": false,
|
||||
"live_workers": 1,
|
||||
"batch": {
|
||||
"state": "pending",
|
||||
"reason": null,
|
||||
"owner": "faa9f8c3c78e33be96ffdb4469bda941",
|
||||
"attempts": 1,
|
||||
"entry_ids": null
|
||||
}
|
||||
},
|
||||
"late_worker_error": "ProgrammingError: Cannot operate on a closed database.",
|
||||
"persisted": {
|
||||
"state": "pending",
|
||||
"reason": null,
|
||||
"owner": "faa9f8c3c78e33be96ffdb4469bda941",
|
||||
"attempts": 1,
|
||||
"entry_ids": null,
|
||||
"row_done": 0,
|
||||
"llm_used": 1
|
||||
}
|
||||
},
|
||||
"waiter_cancel": {
|
||||
"active_close_pending": true,
|
||||
"signal_after_waiter_cancel": false,
|
||||
"new_batch_before_original_worker_released": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 1,
|
||||
"entry_ids": "[\"210297a0-b8c2-4b12-ac9c-eeeaa1eb623f\"]"
|
||||
},
|
||||
"original_batch": {
|
||||
"state": "pending",
|
||||
"reason": null,
|
||||
"owner": "86b6785682c351b41c4691168752b058",
|
||||
"attempts": 1,
|
||||
"entry_ids": null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
Failed to load spaCy lemma model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to insert memory 83789ef3-dbc1-486b-aa09-b5a50a16b507: QdrantLocal instance is closed. Please create a new instance.
|
||||
semantic batch 1 failed: Failed to insert any of the 1 extracted memories into the vector store
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to insert memory 758c3b89-1564-4908-a125-8262e65da177: QdrantLocal instance is closed. Please create a new instance.
|
||||
semantic batch 1 failed: Failed to insert any of the 1 extracted memories into the vector store
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load spaCy full model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
{
|
||||
"head": "ceb0f35418f0d0b28b4e2a359615194db8eb4567",
|
||||
"real_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"finish_cancel_sdk_only": {
|
||||
"after_cancellation": {
|
||||
"closing": false,
|
||||
"closed": false,
|
||||
"client_usable": true
|
||||
},
|
||||
"hidden_finish": {
|
||||
"closed": true,
|
||||
"client_usable": false,
|
||||
"live_workers": 1,
|
||||
"batch": {
|
||||
"state": "pending",
|
||||
"reason": null,
|
||||
"owner": "06daaa8f643701f05f768706be2e36a1",
|
||||
"attempts": 1,
|
||||
"entry_ids": null
|
||||
}
|
||||
},
|
||||
"late_worker_error": null,
|
||||
"persisted": {
|
||||
"state": "failed",
|
||||
"reason": "Failed to insert any of the 1 extracted memories into the vector store",
|
||||
"owner": null,
|
||||
"attempts": 1,
|
||||
"entry_ids": null,
|
||||
"row_done": 0,
|
||||
"llm_used": 1
|
||||
}
|
||||
},
|
||||
"finish_cancel_then_store_close": {
|
||||
"after_cancellation": {
|
||||
"closing": false,
|
||||
"closed": false,
|
||||
"client_usable": true
|
||||
},
|
||||
"hidden_finish": {
|
||||
"closed": true,
|
||||
"client_usable": false,
|
||||
"live_workers": 1,
|
||||
"batch": {
|
||||
"state": "pending",
|
||||
"reason": null,
|
||||
"owner": "faa9f8c3c78e33be96ffdb4469bda941",
|
||||
"attempts": 1,
|
||||
"entry_ids": null
|
||||
}
|
||||
},
|
||||
"late_worker_error": "ProgrammingError: Cannot operate on a closed database.",
|
||||
"persisted": {
|
||||
"state": "pending",
|
||||
"reason": null,
|
||||
"owner": "faa9f8c3c78e33be96ffdb4469bda941",
|
||||
"attempts": 1,
|
||||
"entry_ids": null,
|
||||
"row_done": 0,
|
||||
"llm_used": 1
|
||||
}
|
||||
},
|
||||
"waiter_cancel": {
|
||||
"active_close_pending": true,
|
||||
"signal_after_waiter_cancel": false,
|
||||
"new_batch_before_original_worker_released": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 1,
|
||||
"entry_ids": "[\"210297a0-b8c2-4b12-ac9c-eeeaa1eb623f\"]"
|
||||
},
|
||||
"original_batch": {
|
||||
"state": "pending",
|
||||
"reason": null,
|
||||
"owner": "86b6785682c351b41c4691168752b058",
|
||||
"attempts": 1,
|
||||
"entry_ids": null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
{
|
||||
"head": "HEL-894 close ownership under cancellation",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"boundary": "deterministic 512 vectors, non-loopback forbidden",
|
||||
"protocol": "drain-phase cancel re-arms; finish-phase cancel keeps markers, refuses ticks, lands the layer-owned close exactly once; lock waiters never touch the owner's state",
|
||||
"checks": [
|
||||
{
|
||||
"name": "F1 cancelled caller does not return before its close lands",
|
||||
"ok": true,
|
||||
"detail": ""
|
||||
},
|
||||
{
|
||||
"name": "F1 markers stay: closing set, not yet closed, client untouched",
|
||||
"ok": true,
|
||||
"detail": "{\"closing\": true, \"closed\": false, \"client_usable\": true}"
|
||||
},
|
||||
{
|
||||
"name": "F1 tick during closing claims nothing and sends nothing",
|
||||
"ok": true,
|
||||
"detail": "{\"gateway_arrived\": false, \"gateway_posts\": 0, \"llm_used\": 0, \"batch\": {\"state\": \"queued\", \"reason\": null, \"owner\": null, \"attempts\": 0, \"entry_ids\": null}}"
|
||||
},
|
||||
{
|
||||
"name": "F1 layer-owned close lands exactly once, no live workers",
|
||||
"ok": true,
|
||||
"detail": "{\"closed\": true, \"client_usable\": false, \"live_workers\": 0, \"close_calls\": 1, \"closing_still_set\": true, \"batch\": {\"state\": \"queued\", \"reason\": null, \"owner\": null, \"attempts\": 0, \"entry_ids\": null}}"
|
||||
},
|
||||
{
|
||||
"name": "F1 no late error",
|
||||
"ok": true,
|
||||
"detail": "None"
|
||||
},
|
||||
{
|
||||
"name": "F1 reopened batch intact and unbilled",
|
||||
"ok": true,
|
||||
"detail": "{\"state\": \"queued\", \"reason\": null, \"owner\": null, \"attempts\": 0, \"entry_ids\": null, \"row_done\": 0, \"llm_used\": 0}"
|
||||
},
|
||||
{
|
||||
"name": "F2 cancelled caller does not return before its close lands",
|
||||
"ok": true,
|
||||
"detail": ""
|
||||
},
|
||||
{
|
||||
"name": "F2 markers stay: closing set, not yet closed, client untouched",
|
||||
"ok": true,
|
||||
"detail": "{\"closing\": true, \"closed\": false, \"client_usable\": true}"
|
||||
},
|
||||
{
|
||||
"name": "F2 tick during closing claims nothing and sends nothing",
|
||||
"ok": true,
|
||||
"detail": "{\"gateway_arrived\": false, \"gateway_posts\": 0, \"llm_used\": 0, \"batch\": {\"state\": \"queued\", \"reason\": null, \"owner\": null, \"attempts\": 0, \"entry_ids\": null}}"
|
||||
},
|
||||
{
|
||||
"name": "F2 layer-owned close lands exactly once, no live workers",
|
||||
"ok": true,
|
||||
"detail": "{\"closed\": true, \"client_usable\": false, \"live_workers\": 0, \"close_calls\": 1, \"closing_still_set\": true, \"batch\": {\"state\": \"queued\", \"reason\": null, \"owner\": null, \"attempts\": 0, \"entry_ids\": null}}"
|
||||
},
|
||||
{
|
||||
"name": "F2 no late error",
|
||||
"ok": true,
|
||||
"detail": "None"
|
||||
},
|
||||
{
|
||||
"name": "F2 reopened batch intact and unbilled",
|
||||
"ok": true,
|
||||
"detail": "{\"state\": \"queued\", \"reason\": null, \"owner\": null, \"attempts\": 0, \"entry_ids\": null, \"row_done\": 0, \"llm_used\": 0}"
|
||||
},
|
||||
{
|
||||
"name": "W owner close still pending, waiter cancel keeps the signal",
|
||||
"ok": true,
|
||||
"detail": "{\"active_close_pending\": true, \"signal_after_waiter_cancel\": true}"
|
||||
},
|
||||
{
|
||||
"name": "W new batch refused during the owner's close",
|
||||
"ok": true,
|
||||
"detail": "{\"state\": \"queued\", \"reason\": null, \"owner\": null, \"attempts\": 0, \"entry_ids\": null}"
|
||||
},
|
||||
{
|
||||
"name": "W original batch still actively owned while blocked",
|
||||
"ok": true,
|
||||
"detail": "{\"state\": \"pending\", \"reason\": null, \"owner\": \"3bd306ef54be3f3e7661b8a4065ebb68\", \"attempts\": 1, \"entry_ids\": null}"
|
||||
},
|
||||
{
|
||||
"name": "W original worker completes, one close, new batch untouched",
|
||||
"ok": true,
|
||||
"detail": "{\"original_batch\": {\"state\": \"done\", \"reason\": null, \"owner\": null, \"attempts\": 1, \"entry_ids\": \"[\\\"564af813-b7a2-4d11-b75e-2819f3a8bdb8\\\"]\"}, \"new_batch_still\": {\"state\": \"queued\", \"reason\": null, \"owner\": null, \"attempts\": 0, \"entry_ids\": null}, \"row_done\": 1, \"gateway_posts_after_release\": 1, \"client_closed\": true}"
|
||||
}
|
||||
],
|
||||
"finish_cancel_sdk_only": {
|
||||
"caller_done_after_cancel": false,
|
||||
"after_cancellation": {
|
||||
"closing": true,
|
||||
"closed": false,
|
||||
"client_usable": true
|
||||
},
|
||||
"tick_during_closing": {
|
||||
"gateway_arrived": false,
|
||||
"gateway_posts": 0,
|
||||
"llm_used": 0,
|
||||
"batch": {
|
||||
"state": "queued",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 0,
|
||||
"entry_ids": null
|
||||
}
|
||||
},
|
||||
"finish_landed": {
|
||||
"closed": true,
|
||||
"client_usable": false,
|
||||
"live_workers": 0,
|
||||
"close_calls": 1,
|
||||
"closing_still_set": true,
|
||||
"batch": {
|
||||
"state": "queued",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 0,
|
||||
"entry_ids": null
|
||||
}
|
||||
},
|
||||
"late_worker_error": null,
|
||||
"persisted": {
|
||||
"state": "queued",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 0,
|
||||
"entry_ids": null,
|
||||
"row_done": 0,
|
||||
"llm_used": 0
|
||||
}
|
||||
},
|
||||
"finish_cancel_then_store_close": {
|
||||
"caller_done_after_cancel": false,
|
||||
"after_cancellation": {
|
||||
"closing": true,
|
||||
"closed": false,
|
||||
"client_usable": true
|
||||
},
|
||||
"tick_during_closing": {
|
||||
"gateway_arrived": false,
|
||||
"gateway_posts": 0,
|
||||
"llm_used": 0,
|
||||
"batch": {
|
||||
"state": "queued",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 0,
|
||||
"entry_ids": null
|
||||
}
|
||||
},
|
||||
"finish_landed": {
|
||||
"closed": true,
|
||||
"client_usable": false,
|
||||
"live_workers": 0,
|
||||
"close_calls": 1,
|
||||
"closing_still_set": true,
|
||||
"batch": {
|
||||
"state": "queued",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 0,
|
||||
"entry_ids": null
|
||||
}
|
||||
},
|
||||
"late_worker_error": null,
|
||||
"persisted": {
|
||||
"state": "queued",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 0,
|
||||
"entry_ids": null,
|
||||
"row_done": 0,
|
||||
"llm_used": 0
|
||||
}
|
||||
},
|
||||
"waiter_cancel": {
|
||||
"active_close_pending": true,
|
||||
"signal_after_waiter_cancel": true,
|
||||
"new_batch_during_closing": {
|
||||
"state": "queued",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 0,
|
||||
"entry_ids": null
|
||||
},
|
||||
"original_batch_while_blocked": {
|
||||
"state": "pending",
|
||||
"reason": null,
|
||||
"owner": "3bd306ef54be3f3e7661b8a4065ebb68",
|
||||
"attempts": 1,
|
||||
"entry_ids": null
|
||||
},
|
||||
"after_release": {
|
||||
"original_batch": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 1,
|
||||
"entry_ids": "[\"564af813-b7a2-4d11-b75e-2819f3a8bdb8\"]"
|
||||
},
|
||||
"new_batch_still": {
|
||||
"state": "queued",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 0,
|
||||
"entry_ids": null
|
||||
},
|
||||
"row_done": 1,
|
||||
"gateway_posts_after_release": 1,
|
||||
"client_closed": true
|
||||
}
|
||||
},
|
||||
"failures": []
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
semantic shutdown drain: 1 live batch worker(s) still running after 0s — close signal set, waiting for their bounded exit before any resource closes (a live worker is never treated as dead)
|
||||
Failed to load spaCy lemma model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
Failed to load BM25 encoder: verify boundary forbids non-loopback connection: 198.18.7.201
|
||||
Failed to load spaCy full model: spaCy is not installed. Install it with: pip install mem0ai[nlp]
|
||||
@@ -0,0 +1,172 @@
|
||||
{
|
||||
"started": "2026-10-26T17:39:43Z",
|
||||
"embedding_boundary": "deterministic 512-dimensional vectors installed before TextEmbedding construction; no model loaded; zero real embeddings",
|
||||
"external_network": "socket.connect rejects all non-loopback hosts",
|
||||
"install": {
|
||||
"python": "3.10.12",
|
||||
"mem0ai": "2.2.1",
|
||||
"qdrant-client": "1.20.0",
|
||||
"fastembed": "0.9.1",
|
||||
"mem0_path": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/mem0",
|
||||
"module_sha256": {
|
||||
"memory/main.py": "f329ca4a17af1fbefb32ac10d4cc7ae7ddbdb7435a87eb74c30915cff98b8b7c",
|
||||
"memory/storage.py": "5f5ecdadd2b260fd14f36a0f338336eb760275465dd1056a99a47e1ef07702bd",
|
||||
"embeddings/fastembed.py": "3ec2ae87df5d0bebedb8b5471366c3412975847458499f66097f7b8c9ac8c9b8",
|
||||
"llms/openai.py": "37e6d3c22b55df0731740f4e0868b95bd480ce44363227fe53461db328c5f67e",
|
||||
"vector_stores/qdrant.py": "da570579d5cad69e6f123fdfe32b725235a25604e70047666bec763ba6e2bf43"
|
||||
},
|
||||
"source_tarball_sha256": "9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23"
|
||||
},
|
||||
"adapter_has_llm_guard": true,
|
||||
"checks": [
|
||||
{
|
||||
"name": "C1_construct",
|
||||
"ok": true,
|
||||
"detail": "client_path=sdk-vector-store-client"
|
||||
},
|
||||
{
|
||||
"name": "C2_add_anchor_payload",
|
||||
"ok": true,
|
||||
"detail": "created=True payload_keys=['created_at', 'data', 'hash', 'input_sha256', 'src_group', 'text_lemmatized', 'turn_ids', 'turn_roles']"
|
||||
},
|
||||
{
|
||||
"name": "C2_add_user_scope",
|
||||
"ok": true,
|
||||
"detail": "user_id present in stored payload"
|
||||
},
|
||||
{
|
||||
"name": "C2_add_ledger_shape",
|
||||
"ok": true,
|
||||
"detail": "embed_items=3 llm_calls=1"
|
||||
},
|
||||
{
|
||||
"name": "C3_extraction_input_set",
|
||||
"ok": true,
|
||||
"detail": "old_memory=True new_row=True history_carry=True"
|
||||
},
|
||||
{
|
||||
"name": "C3b_unshared_canary_never_outbound",
|
||||
"ok": true,
|
||||
"detail": "queued_public_batches=2 leaked=False anchor_taint=False"
|
||||
},
|
||||
{
|
||||
"name": "C4_search_filters",
|
||||
"ok": true,
|
||||
"detail": "hits=3"
|
||||
},
|
||||
{
|
||||
"name": "C4_search_single_debit",
|
||||
"ok": true,
|
||||
"detail": "embed_delta=1 (pre-fix=2)"
|
||||
},
|
||||
{
|
||||
"name": "C6_concurrency_cap",
|
||||
"ok": true,
|
||||
"detail": "outcomes=['ok', 'refused'] used=3 cap=3"
|
||||
},
|
||||
{
|
||||
"name": "C7_reopen_after_close",
|
||||
"ok": true,
|
||||
"detail": "hits_after_reopen=3"
|
||||
},
|
||||
{
|
||||
"name": "C5_embed_zero_refuses",
|
||||
"ok": true,
|
||||
"detail": "err=semantic_embed_budget_not_approved llm_sent=0"
|
||||
},
|
||||
{
|
||||
"name": "C5_llm_zero_refuses",
|
||||
"ok": true,
|
||||
"detail": "err=semantic_llm_budget_not_approved llm_sent=0"
|
||||
}
|
||||
],
|
||||
"embed_rows_after_first_add": [
|
||||
{
|
||||
"kind": "authority_prefix",
|
||||
"items": 0,
|
||||
"note": "authority:synthetic-verify-prefix"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
}
|
||||
],
|
||||
"final_embed_rows": [
|
||||
{
|
||||
"kind": "authority_prefix",
|
||||
"items": 0,
|
||||
"note": "authority:synthetic-verify-prefix"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:batch:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:add"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
},
|
||||
{
|
||||
"kind": "dense",
|
||||
"items": 1,
|
||||
"note": "product:search"
|
||||
}
|
||||
],
|
||||
"final_llm_used": 3,
|
||||
"finished": "2026-10-26T17:39:45Z",
|
||||
"gateway_requests_total": 3,
|
||||
"failures": [],
|
||||
"temp_dir": "/tmp/multica-task-2026294148/hel874-real-sdk-ubq10yph"
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"head": "HEL-889 same-shape rerun of review_hel886_edges.py (fixed tree)",
|
||||
"real_llm": 0,
|
||||
"real_embeddings": 0,
|
||||
"queued_cancel": {
|
||||
"batch": {
|
||||
"state": "done",
|
||||
"owner": null,
|
||||
"attempts": 1
|
||||
},
|
||||
"registered_workers": 0,
|
||||
"stored_entries": 1,
|
||||
"row_done": 1,
|
||||
"executor_queue_size": 0
|
||||
},
|
||||
"shutdown_timeout": {
|
||||
"aclose_returned_while_worker_alive": false,
|
||||
"batch_before_store_close": {
|
||||
"state": "done",
|
||||
"entry_ids": "[\"a6eb8633-c759-422f-819e-6a521c583a45\"]"
|
||||
},
|
||||
"late_worker_error": null,
|
||||
"persisted_batch": {
|
||||
"state": "done",
|
||||
"entry_ids": "[\"a6eb8633-c759-422f-819e-6a521c583a45\"]"
|
||||
},
|
||||
"persisted_row_done": 1,
|
||||
"llm_used": 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"head": "HEL-886 inflight lifecycle",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"boundary": "deterministic 512 vectors, non-loopback forbidden",
|
||||
"checks": [
|
||||
{
|
||||
"name": "L1_cancel_single_send_and_full_record",
|
||||
"ok": true,
|
||||
"detail": "arrived=1 concurrent=1 http_total=1 points=1 batch_live={'state': 'pending', 'owner': '93272596c381b3957a60f11caed4a7d2'} batch_concurrent=pending final={'state': 'done', 'reason': None, 'entry_ids': '[\"c3dc3b82-6d22-4cac-9076-75b2b2871a68\"]'} row_done=1"
|
||||
},
|
||||
{
|
||||
"name": "L2_lease_single_send_and_full_record",
|
||||
"ok": true,
|
||||
"detail": "arrived=1 concurrent=1 http_total=1 points=1 batch_live={'state': 'pending', 'owner': 'c6df32af28769cf791c6b7d25f3d8ec1'} batch_concurrent=pending final={'state': 'done', 'reason': None, 'entry_ids': '[\"5d38b54e-cc85-4cd1-b214-1aec5902140a\"]'} row_done=1"
|
||||
},
|
||||
{
|
||||
"name": "L3_shutdown_drains_live_worker_before_close",
|
||||
"ok": true,
|
||||
"detail": "arrived=1 waited=0.25s row_done_at=2000000 batch={'state': 'done', 'finished_at': 2000000}"
|
||||
},
|
||||
{
|
||||
"name": "L4a_all_done_finalizes_without_new_budget",
|
||||
"ok": true,
|
||||
"detail": "before={'state': 'done', 'entry_ids': '[\"0a4a0c10-0187-47e0-98c2-82e2d52ebb87\"]'} after={'state': 'done', 'reason': None, 'entry_ids': '[\"0a4a0c10-0187-47e0-98c2-82e2d52ebb87\"]'} http=1 llm_used=1"
|
||||
},
|
||||
{
|
||||
"name": "L4b_partial_keeps_the_hard_gate",
|
||||
"ok": true,
|
||||
"detail": "after={'state': 'queued', 'reason': 'semantic_llm_budget_exhausted'} http=0"
|
||||
},
|
||||
{
|
||||
"name": "L5_dead_owner_lease_still_recovers",
|
||||
"ok": true,
|
||||
"detail": "recovered={'state': 'failed', 'reason': 'lease_expired'} after={'state': 'done'} http=2"
|
||||
}
|
||||
],
|
||||
"gateway_requests_total": 7,
|
||||
"failures": []
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"head": "59fb06304fb9f230837b43e431e2a1cc303e16c9",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"midnight_settlement": {
|
||||
"daily_cap": 100,
|
||||
"old_hold": 90,
|
||||
"actual": 10,
|
||||
"new_day_used_before_next_request": 0,
|
||||
"request_estimate": 150,
|
||||
"over_cap_request_allowed": false,
|
||||
"rows": [
|
||||
{
|
||||
"day": "2026-10-26",
|
||||
"scene": "dialogue_main",
|
||||
"used": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
"aged_unknown_reservation": {
|
||||
"cap": 1,
|
||||
"used_before": 1,
|
||||
"second_allowed": false,
|
||||
"first_hold_id": 1,
|
||||
"second_hold_id": null,
|
||||
"used_after": 1
|
||||
},
|
||||
"release_documented_download_command": {
|
||||
"command": "uv pip download --help",
|
||||
"returncode": 2,
|
||||
"stderr": "error: unrecognized subcommand 'download'\n\nUsage: uv pip [OPTIONS] <COMMAND>\n\nFor more information, try '--help'."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"head": "59fb06304fb9f230837b43e431e2a1cc303e16c9",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"boundary": "deterministic 512 vectors, non-loopback forbidden",
|
||||
"missing_authority_constructed_with_positive_caps": false,
|
||||
"public_batch_enqueued": 1,
|
||||
"batch_after_first_tick": {
|
||||
"state": "queued",
|
||||
"attempts": 0,
|
||||
"reason": "LLM extraction failed: semantic_llm_budget_exhausted",
|
||||
"entry_ids": null
|
||||
},
|
||||
"loopback_requests_before_resume": 1,
|
||||
"stored_points_after_first": [
|
||||
{
|
||||
"src_group": "chat_public:mem0:synthetic:1bc8667410",
|
||||
"turn_ids": [
|
||||
"synthetic"
|
||||
],
|
||||
"turn_roles": [
|
||||
[
|
||||
"synthetic",
|
||||
"companion",
|
||||
2
|
||||
]
|
||||
],
|
||||
"input_sha256": "840dde45eec07416f21c56cefb0f93f4e71af3edc3100c332f84bb1266637862",
|
||||
"user_id": "cybergril",
|
||||
"data": "合成提取记忆HEL874-1",
|
||||
"text_lemmatized": "合成提取记忆HEL874-1",
|
||||
"hash": "69195446402294f6e77f8ee993ce3456",
|
||||
"created_at": "2026-10-26T17:41:21.075520+00:00",
|
||||
"updated_at": "2026-10-26T17:41:21.075520+00:00"
|
||||
}
|
||||
],
|
||||
"claimed_completed_message_ids": [
|
||||
2
|
||||
],
|
||||
"batch_after_resume": {
|
||||
"state": "done",
|
||||
"attempts": 1,
|
||||
"reason": null,
|
||||
"entry_ids": "[\"2e53e588-eb73-4b9a-8b43-05cc73cb3ca9\", \"20318cc2-8400-4e09-916f-28f00bbf23ce\"]"
|
||||
},
|
||||
"loopback_requests_after_resume": 2,
|
||||
"unsent_user_row_actually_sent": true
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"head": "HEL-885 rework",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"boundary": "deterministic 512 vectors, non-loopback forbidden",
|
||||
"checks": [
|
||||
{
|
||||
"name": "S0_positive_caps_without_authority_fail_closed",
|
||||
"ok": true,
|
||||
"detail": "missing=ledger_authority_required empty=ledger_authority_required"
|
||||
},
|
||||
{
|
||||
"name": "S1_p1_first_tick_partial",
|
||||
"ok": true,
|
||||
"detail": "state=queued reason=LLM extraction failed: semantic_llm_budget_exhausted http=1 completed=[2] points=1 per_row_anchor=True"
|
||||
},
|
||||
{
|
||||
"name": "S2_p1_restart_sends_only_missing_row",
|
||||
"ok": true,
|
||||
"detail": "state=done http=1 user_sent=True completed=[1, 2] entry_ids=[\"1b89de13-2e94-4ebd-b5d1-4275b38b1562\", \"bc359f78-1e05-4b10-a067-2b85363db02c\"]"
|
||||
},
|
||||
{
|
||||
"name": "S3a_lease_expired_batch_recovers",
|
||||
"ok": true,
|
||||
"detail": "recovered={'state': 'failed', 'reason': 'lease_expired'} state_after=done http=2"
|
||||
},
|
||||
{
|
||||
"name": "S3b_concurrent_ticks_single_send",
|
||||
"ok": true,
|
||||
"detail": "http=2 states=['done']"
|
||||
},
|
||||
{
|
||||
"name": "S4a_aged_unknown_llm_reservation_kept",
|
||||
"ok": true,
|
||||
"detail": "second_allowed=False used_after_restart=1"
|
||||
},
|
||||
{
|
||||
"name": "S4b_aged_unknown_embed_reservation_kept",
|
||||
"ok": true,
|
||||
"detail": "second_allowed=False used=1 after_release=0"
|
||||
}
|
||||
],
|
||||
"gateway_requests_total": 6,
|
||||
"failures": []
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"started": "2026-10-26T17:40:19Z",
|
||||
"venv_python": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-874-db7635e6b65e/workdir/CyberGril/.venv311/bin/python",
|
||||
"venv_python_version": "3.11.16",
|
||||
"checks": [
|
||||
{
|
||||
"name": "R1_checkout_fresh_dir",
|
||||
"ok": true,
|
||||
"detail": "cloned tracked source into /tmp/multica-task-2026294148/hel882-release-fq8t_q6_/cybergril-hel874/current"
|
||||
},
|
||||
{
|
||||
"name": "R2_service_imports_product",
|
||||
"ok": true,
|
||||
"detail": ""
|
||||
},
|
||||
{
|
||||
"name": "R6_mem0_coimport_status_recorded",
|
||||
"ok": true,
|
||||
"detail": "import mem0 → ModuleNotFoundError: No module named 'mem0' (resolution path in deploy/hel874-release.md §4; no download in this check)"
|
||||
},
|
||||
{
|
||||
"name": "R7_pip_download_command_exists",
|
||||
"ok": true,
|
||||
"detail": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/bin/python -m pip download --help → rc=0"
|
||||
},
|
||||
{
|
||||
"name": "R7_removed_uv_pip_download_still_absent",
|
||||
"ok": true,
|
||||
"detail": "uv pip download --help → rc=2 (kept absent on purpose; do not re-document it)"
|
||||
},
|
||||
{
|
||||
"name": "R2_service_serves_health",
|
||||
"ok": true,
|
||||
"detail": "version=R4-assembly semantic=stub"
|
||||
},
|
||||
{
|
||||
"name": "R3_create_status_recorded",
|
||||
"ok": true,
|
||||
"detail": "create status=201(409=前次运行的档案仍在,也是保留数据的表现)"
|
||||
},
|
||||
{
|
||||
"name": "R3_dialogue_refused_honestly",
|
||||
"ok": true,
|
||||
"detail": "http=200 events=b'data: {\"t\": \"chat.error\", \"epoch\": 1, \"turn_id\": \"00000000-0000-4000-8000-000000000001\", \"phi\": \"\\xe5\\xa5\\xb9\\xe8\\xbf\\x99\\xe4\\xbc\\x9a\\xe5\\x84\\xbf\\xe6\\xb2\\xa1\\xe6\\x8e\\xa5\\xe4\\xb8\\x8a\\xe8\\xaf'"
|
||||
},
|
||||
{
|
||||
"name": "R4_service_restarts_on_same_data",
|
||||
"ok": true,
|
||||
"detail": "epoch=2"
|
||||
},
|
||||
{
|
||||
"name": "R5_epoch_bumped_on_reopen",
|
||||
"ok": true,
|
||||
"detail": "1 → 2"
|
||||
},
|
||||
{
|
||||
"name": "R4_companion_created_offline",
|
||||
"ok": true,
|
||||
"detail": "status=409"
|
||||
},
|
||||
{
|
||||
"name": "R4_dialogue_stream_http",
|
||||
"ok": true,
|
||||
"detail": "status=200 events=b'data: {\"t\": \"chat.delta\", \"epoch\": 2, \"turn_id\": \"00000000-0000-4000-8000-000000000002\", \"delta\": \"\\xe6\\x89\\xb9\"}\\n\\ndata: {\"t\": \"c'"
|
||||
},
|
||||
{
|
||||
"name": "R4_messages_roundtrip",
|
||||
"ok": true,
|
||||
"detail": "roles=['user', 'user', 'companion']"
|
||||
},
|
||||
{
|
||||
"name": "R5_data_dir_preserved",
|
||||
"ok": true,
|
||||
"detail": "['companion.db']"
|
||||
},
|
||||
{
|
||||
"name": "R5_companion_row_survives_restart",
|
||||
"ok": true,
|
||||
"detail": "companion rows=1"
|
||||
}
|
||||
],
|
||||
"mem0_import_in_release_venv": "ModuleNotFoundError: No module named 'mem0'",
|
||||
"removed_uv_pip_download_rc": 2,
|
||||
"failures": [],
|
||||
"finished": "2026-10-26T17:40:22Z"
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
{
|
||||
"head": "HEL-889 shutdown ownership & queued-cancel",
|
||||
"real_llm_requests": 0,
|
||||
"real_embeddings": 0,
|
||||
"boundary": "deterministic 512 vectors, non-loopback forbidden",
|
||||
"checks": [
|
||||
{
|
||||
"name": "Q1 queued snapshot shows phantom-liveness",
|
||||
"ok": true,
|
||||
"detail": ""
|
||||
},
|
||||
{
|
||||
"name": "Q1 cancel releases registry and requeues",
|
||||
"ok": true,
|
||||
"detail": "{\"registered_workers\": 0, \"batch\": {\"state\": \"queued\", \"reason\": \"queued_worker_cancelled\", \"owner\": null, \"attempts\": 0, \"entry_ids\": null}, \"row_done\": 0, \"stored_entries\": 0}"
|
||||
},
|
||||
{
|
||||
"name": "Q1 in-process recovery completes without restart",
|
||||
"ok": true,
|
||||
"detail": "{\"batch\": {\"state\": \"done\", \"reason\": null, \"owner\": null, \"attempts\": 1, \"entry_ids\": \"[\\\"stub-1\\\"]\"}, \"registered_workers\": 0, \"stored_entries\": 1, \"row_done\": 1}"
|
||||
},
|
||||
{
|
||||
"name": "S1 aclose does NOT return while the worker is alive",
|
||||
"ok": true,
|
||||
"detail": ""
|
||||
},
|
||||
{
|
||||
"name": "S1 worker completes and flushes before store close",
|
||||
"ok": true,
|
||||
"detail": "{\"state\": \"done\", \"reason\": null, \"owner\": null, \"attempts\": 1, \"entry_ids\": \"[\\\"66f72048-0526-4790-8de0-946ee4fe9251\\\"]\"}"
|
||||
},
|
||||
{
|
||||
"name": "S1 no late-worker error after honest drain",
|
||||
"ok": true,
|
||||
"detail": ""
|
||||
},
|
||||
{
|
||||
"name": "S1 durable reopen: done + row_done=1 + llm_used=1",
|
||||
"ok": true,
|
||||
"detail": "{\"state\": \"done\", \"reason\": null, \"owner\": null, \"attempts\": 1, \"entry_ids\": \"[\\\"66f72048-0526-4790-8de0-946ee4fe9251\\\"]\", \"row_done\": 1, \"llm_used\": 1, \"gateway_posts\": 1}"
|
||||
},
|
||||
{
|
||||
"name": "S2 shutdown between rows requeues with first row proven",
|
||||
"ok": true,
|
||||
"detail": "{\"state\": \"queued\", \"reason\": \"shutdown_between_rows\", \"owner\": null, \"attempts\": 0, \"entry_ids\": null, \"row_done\": 1, \"gateway_posts_during_shutdown\": 1}"
|
||||
},
|
||||
{
|
||||
"name": "S2 resume sends ONLY the missing row and completes",
|
||||
"ok": true,
|
||||
"detail": "{\"state\": \"done\", \"reason\": null, \"owner\": null, \"attempts\": 1, \"entry_ids\": \"[\\\"ccfcad6e-69f4-41c9-830a-b357290ce82d\\\", \\\"085a5ea8-622e-4f5c-8862-8ea20d4b2b23\\\"]\", \"row_done\": 2, \"gateway_posts_resume\": 2}"
|
||||
},
|
||||
{
|
||||
"name": "S3 cancelled aclose closes nothing and re-arms",
|
||||
"ok": true,
|
||||
"detail": ""
|
||||
},
|
||||
{
|
||||
"name": "S3 worker finishes done and a later aclose closes for real",
|
||||
"ok": true,
|
||||
"detail": ""
|
||||
},
|
||||
{
|
||||
"name": "S4 concurrent aclose drains once and closes once",
|
||||
"ok": true,
|
||||
"detail": "{\"batch\": {\"state\": \"done\", \"reason\": null, \"owner\": null, \"attempts\": 1, \"entry_ids\": \"[\\\"bb32ccc4-10c0-4fe5-92a3-248bcc93b2f8\\\"]\", \"row_done\": 1}, \"client_usable_after_close\": false}"
|
||||
}
|
||||
],
|
||||
"queued_cancel": {
|
||||
"queued": {
|
||||
"registered_workers": 1,
|
||||
"product_executor_queue": 1
|
||||
},
|
||||
"after_cancel": {
|
||||
"registered_workers": 0,
|
||||
"batch": {
|
||||
"state": "queued",
|
||||
"reason": "queued_worker_cancelled",
|
||||
"owner": null,
|
||||
"attempts": 0,
|
||||
"entry_ids": null
|
||||
},
|
||||
"row_done": 0,
|
||||
"stored_entries": 0
|
||||
},
|
||||
"after_recovery": {
|
||||
"batch": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 1,
|
||||
"entry_ids": "[\"stub-1\"]"
|
||||
},
|
||||
"registered_workers": 0,
|
||||
"stored_entries": 1,
|
||||
"row_done": 1
|
||||
}
|
||||
},
|
||||
"shutdown_timeout": {
|
||||
"deadline": {
|
||||
"aclose_returned_while_worker_alive": false,
|
||||
"batch_before_any_close": {
|
||||
"state": "pending",
|
||||
"reason": null,
|
||||
"owner": "8956b9e9abade1c5e34a3ecc6986b6f2",
|
||||
"attempts": 1,
|
||||
"entry_ids": null
|
||||
},
|
||||
"client_usable": true
|
||||
},
|
||||
"before_store_close": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 1,
|
||||
"entry_ids": "[\"66f72048-0526-4790-8de0-946ee4fe9251\"]"
|
||||
},
|
||||
"late_worker_error": null,
|
||||
"persisted": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 1,
|
||||
"entry_ids": "[\"66f72048-0526-4790-8de0-946ee4fe9251\"]",
|
||||
"row_done": 1,
|
||||
"llm_used": 1,
|
||||
"gateway_posts": 1
|
||||
}
|
||||
},
|
||||
"between_rows": {
|
||||
"after_shutdown": {
|
||||
"state": "queued",
|
||||
"reason": "shutdown_between_rows",
|
||||
"owner": null,
|
||||
"attempts": 0,
|
||||
"entry_ids": null,
|
||||
"row_done": 1,
|
||||
"gateway_posts_during_shutdown": 1
|
||||
},
|
||||
"after_resume": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 1,
|
||||
"entry_ids": "[\"ccfcad6e-69f4-41c9-830a-b357290ce82d\", \"085a5ea8-622e-4f5c-8862-8ea20d4b2b23\"]",
|
||||
"row_done": 2,
|
||||
"gateway_posts_resume": 2
|
||||
}
|
||||
},
|
||||
"cancelled_aclose": {
|
||||
"after_cancel": {
|
||||
"client_usable": true,
|
||||
"batch": {
|
||||
"state": "pending",
|
||||
"reason": null,
|
||||
"owner": "19c694186e6d25be43d24f3f0d0e2128",
|
||||
"attempts": 1,
|
||||
"entry_ids": null
|
||||
},
|
||||
"closing_cleared": true
|
||||
},
|
||||
"after_worker": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 1,
|
||||
"entry_ids": "[\"4649609e-db3b-4e2d-b473-dc30988a744c\"]"
|
||||
},
|
||||
"client_usable_after_real_close": false
|
||||
},
|
||||
"concurrent_aclose": {
|
||||
"batch": {
|
||||
"state": "done",
|
||||
"reason": null,
|
||||
"owner": null,
|
||||
"attempts": 1,
|
||||
"entry_ids": "[\"bb32ccc4-10c0-4fe5-92a3-248bcc93b2f8\"]",
|
||||
"row_done": 1
|
||||
},
|
||||
"client_usable_after_close": false
|
||||
},
|
||||
"failures": []
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
Traceback (most recent call last):
|
||||
File "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-894-8e6b450e162c/workdir/after/review_hel889_cancel.py", line 127, in <module>
|
||||
result = asyncio.run(main())
|
||||
File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
|
||||
return loop.run_until_complete(main)
|
||||
File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
|
||||
return future.result()
|
||||
File "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-894-8e6b450e162c/workdir/after/review_hel889_cancel.py", line 121, in main
|
||||
'finish_cancel_sdk_only': await finish_cancel(root / 'sdk-only', False),
|
||||
File "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-894-8e6b450e162c/workdir/after/review_hel889_cancel.py", line 43, in finish_cancel
|
||||
assert await asyncio.to_thread(server.arrived.wait, 5)
|
||||
AssertionError
|
||||
Exception ignored in: <function QdrantClient.__del__ at 0x7f6512cc1f30>
|
||||
Traceback (most recent call last):
|
||||
File "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/qdrant_client/qdrant_client.py", line 172, in __del__
|
||||
File "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-894-8e6b450e162c/workdir/after/review_hel889_cancel.py", line 29, in gated_close
|
||||
File "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/qdrant_client/qdrant_client.py", line 181, in close
|
||||
File "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/qdrant_client/local/qdrant_local.py", line 113, in close
|
||||
ImportError: sys.meta_path is None, Python is likely shutting down
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"checks": [
|
||||
{
|
||||
"name": "H0_service_up",
|
||||
"ok": true,
|
||||
"detail": "port 8395"
|
||||
},
|
||||
{
|
||||
"name": "H1_404_before_companion",
|
||||
"ok": true,
|
||||
"detail": "status=404"
|
||||
},
|
||||
{
|
||||
"name": "H2_first_page_contract",
|
||||
"ok": true,
|
||||
"detail": "len=50 ascending=True next_before=278 epoch=1"
|
||||
},
|
||||
{
|
||||
"name": "H3_full_backward_walk",
|
||||
"ok": true,
|
||||
"detail": "rows=327 unique=327 pages=2 backward_pages=True last_next_before=None"
|
||||
},
|
||||
{
|
||||
"name": "H4_public_allowlist_proactive_flag",
|
||||
"ok": true,
|
||||
"detail": "rows=327 proactive=[3, 326, 327] expected=[3, 326, 327] bad_keys=[]"
|
||||
},
|
||||
{
|
||||
"name": "H5_fetchafter_gap_semantics",
|
||||
"ok": true,
|
||||
"detail": "call1: gap=True pages=5 handed=0; call2: gap=False through=327/327 handed_ids=[326, 327]"
|
||||
},
|
||||
{
|
||||
"name": "H6_epoch_stable_404_shape",
|
||||
"ok": true,
|
||||
"detail": "epoch=1/1 unknown=404"
|
||||
}
|
||||
],
|
||||
"failures": [],
|
||||
"service_stderr_tail": "INFO: Started server process [1535952]\nINFO: Waiting for application startup.\nINFO: Application startup complete.\nINFO: Uvicorn running on http://127.0.0.1:8395 (Press CTRL+C to quit)\nINFO: Shutting down\nINFO: Waiting for application shutdown.\nINFO: Application shutdown complete.\nINFO: Finished server process [1535952]\n"
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 434 KiB |
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"ok": true,
|
||||
"checks": 7,
|
||||
"fail_count": 0
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 437 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 231 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 231 KiB |
@@ -0,0 +1,72 @@
|
||||
# HEL-896 证据目录:浏览器误报勘误与修正后证据、分账切换验证、SDK 余缺清单、发布快照验证
|
||||
|
||||
本目录只增不改;HEL-895 的原始证据仍完整保留在 `docs/rework/hel895/`。基准 v1.11
|
||||
(60 项),读取提交 `04eff61`(分支 `hel-870-assembly`,PR#11 追加前的候选 head)。
|
||||
|
||||
## 1. 浏览器验证误报勘误(browser/ 与 browser-before/)
|
||||
|
||||
总管独立复审(HEL-896 附件)证实原 `web/tests/hel895_real.mjs` 的 B3 是误报,本卡
|
||||
复现并修正,未改任何产品/界面代码:
|
||||
|
||||
- **原缺陷**:B3 断言为 `!!stage || !!unreadHit`,其中 `unreadHit` 只要页面有任意
|
||||
可见 button 就成立;点击目标是全页面第一个含「回」的按钮——实际点到首页的
|
||||
「日子与回忆」导航按钮(`web/src/app/RealApp.tsx` 的 `.away`),跳到日子页,
|
||||
留言从未进入交谈。原日志 seq133 `PASS B3_stage_plays_reachout — stage=false`
|
||||
与截图仍停在日子页即为该误报。
|
||||
- **勘误前复现(browser-before/)**:逐字原脚本(`hel895_real.mjs.original`,
|
||||
取自 `04eff61`)本机重跑,7/7 全 PASS 且 B3 明细 `stage=false`——证明 7/7 结论
|
||||
含误报,作废;`real-browser.json` 的 `ok:true` 不再作为舞台行为证据。
|
||||
- **修正后(browser/,同 head、同一界面构建)**:
|
||||
- B2 精确断言首页信件卡(`button.letter:not(.read)`)携带她的原话 + 交谈导航
|
||||
未读标记(aria-label「她有 1 句新留言」)。
|
||||
- B3a/B3b 精确交互:导航到「日子」→ 等待可见留言卡(`aside.reach.show`、
|
||||
aria-hidden=false、非零宽度、内容恰为该留言)→ 只点该卡 `.acts` 内文本为
|
||||
「回她」的按钮 → 严格断言交谈舞台 `.conversation .sr` 含留言原文、未读清零。
|
||||
无任何兜底;`stage=true` 为真实舞台文本。
|
||||
- B4/B5 去重证据改为服务器消息 ID(记录面板 `data-mid`):全记录 0 重复、
|
||||
缺口留言与现场留言各恰好出现 1 次;B5 先断言 `/api/health` epoch 恰 +1
|
||||
(实测 1→2)再复核记录仍无重复。
|
||||
- B6 改为注入计数器统计页面自身 Permissions API / getUserMedia 调用次数
|
||||
(实测 query=0、getUserMedia=0;权限状态 prompt 仅作旁证)。
|
||||
- B7 新增:服务以临时回环 `models.yaml`(`CYBERGRIL_MODELS`,全部 cap=0)+
|
||||
`sitecustomize` socket 守卫启动,任何非回环 connect 直接拒绝并计数——实测
|
||||
非回环连接尝试 0、非回环 DNS 查询 0(netguard 持久报告,进程退出方式无关)。
|
||||
原「零非回环出站」在本 head 缺隔离证据的说法同时作废。
|
||||
- **运行环境说明**:Chromium 需捆绑运行库(LD_LIBRARY_PATH 指向本工作区
|
||||
`browser-libs/`,来源 hel-740 的 `.browser-libs` 拷贝)+ Playwright chromium-1248
|
||||
chrome 二进制;产品服务用既有 3.11.16 venv(依赖 requirements.lock,离线)。
|
||||
精确命令见 `browser/run-commands.txt`。
|
||||
- **产品行为结论**:精确操作下舞台真实播放留言、未读真实清零、去重与 epoch
|
||||
语义真实成立——是原测试缺陷,不是界面缺陷;未发现需报总管的产品行为问题,
|
||||
亦未改原作者任何界面文件。
|
||||
|
||||
## 2. A/B/C 分账与阶段切换验证(budget-split/)
|
||||
|
||||
`tools/verify_budget_split_hel896.py`,仅临时库+替身,输出 `budget-split.json`(8/8)。
|
||||
执行口径与封账/授权/停续语义见 `docs/budget-package-hel895.md` §6。
|
||||
|
||||
## 3. SDK 离线余缺清单(sdk-audit/)
|
||||
|
||||
`tools/audit_sdk_offline_hel896.py`,只读审计,输出 `sdk-audit.json` + `summary.md`
|
||||
(闭包 55/缺 41:纯 28+原生 13;缓存立即可复用 3 项;mem0 固定源码 tarball SHA256
|
||||
与 `deploy/mem0-stack.lock` 一致;pip 26.2.2 事件缓存取证与勘误)。
|
||||
|
||||
## 4. 发布归档/回退一致性快照验证(release-snapshot/)
|
||||
|
||||
`tools/release_snapshot_hel896.py` 自测输出 `selftest.json`(4/4);WAL 丢失复现
|
||||
(在线裸复制 `no such table: companion`)与 backup API / 停服+checkpoint 双口径修复
|
||||
证据。发布照抄命令见 `deploy/hel895-release-candidate.md` §5。
|
||||
|
||||
## 5. 本卡回归与复跑(regression/ 及 browser/、http-pagination-rerun.json)
|
||||
|
||||
- 固定 SDK 回归(hel857-venv 3.10.12,替身+回环,本卡在 04eff61+ 本卡修订 head
|
||||
重跑,报告全部存 regression/):verify_mem0_real_hel874 12/12、
|
||||
verify_semantic_recovery_hel885 7/7、verify_inflight_lifecycle_hel886 6/6、
|
||||
verify_close_ownership_hel894 16/16、verify_shutdown_queued_hel889 12/12。
|
||||
- check_release_offline_hel882 R1–R7 全过;真实 HTTP 分页合同 7/7 复跑存
|
||||
`http-pagination-rerun.json`(原 `docs/rework/hel895/http-pagination.json` 原样
|
||||
保留未改)。
|
||||
- pytest 299/299、ruff 干净(src/tests/tools)、web typecheck 0 错 / lint 0 错
|
||||
1 条既有警告 / vitest 55/55 / build 通过(CSS 仍 `index-DdDYzR5S.css`,视觉零改动)。
|
||||
- 三目标需求检查各含/不含 `handoff-HEL-896.json` 共 6/6(origin/main、
|
||||
origin/agent/agent/de8874a3803b、152479b)。
|
||||
@@ -0,0 +1,109 @@
|
||||
// HEL-895 real-server browser check: drives headless Chromium over CDP against the REAL product API
|
||||
// (python -m cybergril.api serving web/dist), not the mock. New reach-out rows are injected straight
|
||||
// into the temp SQLite DB (WAL) the way the server's own writer would, so the page's poll, the inbox
|
||||
// cursor gap logic and the unread/stage behaviour are all exercised over real HTTP.
|
||||
// Usage: node tests/hel895_real.mjs <chrome> <venvPython> <repoRoot> <outDir> [port]
|
||||
import { spawn } from 'node:child_process';
|
||||
import { mkdirSync, writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
|
||||
const [chrome, venv, root, outArg, portArg] = process.argv.slice(2);
|
||||
const port = Number(portArg || 8398), base = `http://127.0.0.1:${port}`, cdpPort = 9498;
|
||||
const out = join(root, outArg || 'docs/rework/hel895');
|
||||
mkdirSync(out, { recursive: true });
|
||||
const work = join(tmpdir(), `hel895-real-${Date.now()}`);
|
||||
mkdirSync(work, { recursive: true });
|
||||
const dbPath = join(work, 'companion.db');
|
||||
const results = [];
|
||||
const check = (name, ok, detail = '') => { results.push(ok); console.log(`${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`); };
|
||||
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
|
||||
// ── Seed: companion + 5 history rows (one old reach-out) — the same shape as the HEL-875 mock history.
|
||||
const T0 = Date.UTC(2026, 9, 25, 12);
|
||||
const identity = { id: '1', name: '栖', birth: '2000-03-14', hour: '子时', relation: '朋友', nickname: '', boundary: '', color: { pillars: { year: '庚辰', month: '己卯', day: '丙午', hour: '戊子' }, traits: 'HEL-895 真实服务浏览器检查合成档案' } };
|
||||
let seeded = 0;
|
||||
const seedRow = (role, text, turn, proactive = 0) => { seeded++; db.prepare('INSERT INTO chat_message(ts,role,text,turn_id,status,proactive) VALUES(?,?,?,?,?,?)').run(T0 + seeded * 60e3, role, text, turn, 'sent', proactive); };
|
||||
let db = new DatabaseSync(dbPath);
|
||||
db.exec('PRAGMA journal_mode=WAL');
|
||||
// Wait for the service to create the schema first (started below), then seed.
|
||||
const env = { ...process.env, CYBERGRIL_DB: dbPath, PYTHONPATH: join(root, 'src'), CYBERGRIL_R2_ENABLED: '0', CYBERGRIL_SEMANTIC: 'off', CYBERGRIL_PORT: String(port), CYBERGRIL_WEB: join(root, 'web', 'dist') };
|
||||
let svc = spawn(venv, ['-m', 'cybergril.api'], { cwd: root, env, stdio: ['ignore', 'ignore', 'pipe'] });
|
||||
const up = async () => { for (let i = 0; i < 120; i++) { try { const r = await fetch(`${base}/api/messages`); if (r.status === 404) return true; } catch { await sleep(500); } } return false; };
|
||||
if (!await up()) { console.error('service did not come up'); process.exit(1); }
|
||||
db.exec('PRAGMA journal_mode=WAL');
|
||||
db.prepare('INSERT INTO companion VALUES(1,?)').run(JSON.stringify(identity));
|
||||
seedRow('user', '下雨天的时候,你会想听什么?', 't-1');
|
||||
seedRow('companion', '会想把窗开一条缝,听雨落在铁皮檐上。', 't-1');
|
||||
seedRow('companion', '昨晚的月亮很亮,想起你说过喜欢夜里散步。', 'reach-old', 1);
|
||||
seedRow('user', '今天加班到九点,好累。', 't-2');
|
||||
seedRow('companion', '那先别急着做别的,喝口热水。', 't-2');
|
||||
const restart = async () => { svc.kill('SIGTERM'); await new Promise(r => svc.on('exit', r)); svc = spawn(venv, ['-m', 'cybergril.api'], { cwd: root, env, stdio: ['ignore', 'ignore', 'pipe'] }); for (let i = 0; i < 120; i++) { try { await fetch(`${base}/api/health`); return; } catch { await sleep(500); } } };
|
||||
|
||||
// ── Chromium over CDP (same driver style as tests/hel875_shots.mjs).
|
||||
const profile = join(tmpdir(), `hel895-chrome-${Date.now()}`);
|
||||
const browser = spawn(chrome, ['--headless=new', `--remote-debugging-port=${cdpPort}`, `--user-data-dir=${profile}`, '--no-first-run', '--hide-scrollbars', '--force-color-profile=srgb', 'about:blank'], { stdio: 'ignore' });
|
||||
let targets;
|
||||
for (let i = 0; i < 60 && !targets; i++) { try { targets = await (await fetch(`http://127.0.0.1:${cdpPort}/json`)).json(); } catch { await sleep(250); } }
|
||||
const ws = new WebSocket(targets.find(t => t.type === 'page').webSocketDebuggerUrl);
|
||||
await new Promise(r => ws.addEventListener('open', r, { once: true }));
|
||||
let seq = 0; const waiting = new Map();
|
||||
ws.addEventListener('message', ({ data }) => { const m = JSON.parse(data); if (m.id && waiting.has(m.id)) { const { ok, ko } = waiting.get(m.id); waiting.delete(m.id); m.error ? ko(new Error(m.error.message)) : ok(m.result); } });
|
||||
const cdp = (method, params = {}) => new Promise((ok, ko) => { const id = ++seq; waiting.set(id, { ok, ko }); ws.send(JSON.stringify({ id, method, params })); });
|
||||
const js = async expr => { const r = await cdp('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) throw new Error(r.exceptionDetails.exception?.description || 'js error'); return r.result.value; };
|
||||
const shot = async name => { const { data } = await cdp('Page.captureScreenshot', { format: 'png' }); writeFileSync(join(out, `real-${name}.png`), Buffer.from(data, 'base64')); };
|
||||
const read = async () => { await js('document.dispatchEvent(new Event("visibilitychange")), true'); };
|
||||
const waitFor = async (fn, ms, step = 400) => { for (const end = Date.now() + ms; Date.now() < end;) { const v = await fn(); if (v) return v; await sleep(step); } return null; };
|
||||
|
||||
try {
|
||||
// B1 — the built web boots against the real service.
|
||||
await cdp('Page.navigate', { url: `${base}/` }); await sleep(2500);
|
||||
const ready = await js(`!!document.querySelector('.conversation, .stage, [class*=talk], [class*=days]') && !document.body.innerText.includes('服务尚未接通')`);
|
||||
check('B1_real_service_boot', ready, await js(`document.body.innerText.slice(0, 40)`));
|
||||
await shot('boot');
|
||||
|
||||
// B2 — a reach-out lands while the page is open on 日子: the card speaks, unread marks.
|
||||
seedRow('companion', '窗外的雨停了,想跟你说一声。', `reach-live-${Date.now()}`, 1);
|
||||
await read();
|
||||
const card = await waitFor(() => js(`!![...document.querySelectorAll('*')].find(e => e.children.length === 0 && e.textContent.includes('窗外的雨停了'))`), 30000);
|
||||
check('B2_reach_card_over_real_http', !!card);
|
||||
await shot('card');
|
||||
|
||||
// B3 — replying opens the conversation; the new message plays on the stage.
|
||||
const unreadHit = await waitFor(() => js(`[...document.querySelectorAll('button, .unread, [class*=unread]')].some(e => e.offsetParent !== null)`), 5000);
|
||||
if (await js(`[...document.querySelectorAll('button')].some(b => b.textContent.includes('回'))`)) { await js(`[...document.querySelectorAll('button')].find(b => b.textContent.includes('回'))?.click(), true`); await sleep(1500); }
|
||||
const stage = await waitFor(() => js(`(document.querySelector('.conversation .sr')?.textContent || '').includes('窗外的雨停了')`), 8000);
|
||||
check('B3_stage_plays_reachout', !!stage || !!unreadHit, `stage=${!!stage}`);
|
||||
await shot('stage');
|
||||
|
||||
// B4 — long absence: 260 bulk rows land ABOVE one new reach-out (gap regression, HEL-880):
|
||||
// the bounded first read (5×50) must not skip her; the next read closes the gap and hands her over.
|
||||
seedRow('companion', '刚看到一只猫从窗台跳下去。', `reach-gap-${Date.now()}`, 1);
|
||||
for (let i = 0; i < 260; i++) seedRow(i % 2 ? 'companion' : 'user', `别处的第${i + 1}句`, `bulk-${Date.now()}-${i}`);
|
||||
await read();
|
||||
const gapDone = await waitFor(async () => {
|
||||
await read();
|
||||
return js(`[...document.querySelectorAll('*')].some(e => e.children.length === 0 && e.textContent.includes('刚看到一只猫'))`);
|
||||
}, 40000, 1500);
|
||||
check('B4_gap_read_hands_reachout_exactly_once', !!gapDone);
|
||||
await shot('gap');
|
||||
const dup = await js(`[...document.querySelectorAll('*')].filter(e => e.children.length === 0 && e.textContent.includes('刚看到一只猫')).length`);
|
||||
check('B4b_no_duplicate_after_gap', dup >= 1 && dup <= 2, `matches=${dup}`);
|
||||
|
||||
// B5 — server restart (epoch+1): the same rows must not be added again.
|
||||
await restart(); await sleep(800); await read(); await sleep(2500);
|
||||
const dup2 = await js(`[...document.querySelectorAll('*')].filter(e => e.children.length === 0 && e.textContent.includes('刚看到一只猫')).length`);
|
||||
check('B5_restart_epoch_no_dup', dup2 >= 1 && dup2 <= 2, `matches=${dup2}`);
|
||||
|
||||
// B6 — no permission was ever asked for.
|
||||
const mic = await js(`navigator.permissions.query({name:'microphone'}).then(r => r.state)`);
|
||||
const cam = await js(`navigator.permissions.query({name:'camera'}).then(r => r.state)`);
|
||||
check('B6_zero_permission_requests', mic === 'prompt' && cam === 'prompt', `mic=${mic} cam=${cam}`);
|
||||
} finally {
|
||||
writeFileSync(join(out, 'real-browser.json'), JSON.stringify({ ok: results.every(Boolean), checks: results.length, fail_count: results.filter(v => !v).length }, null, 1));
|
||||
try { browser.kill(); } catch { /* already gone */ }
|
||||
try { svc.kill('SIGTERM'); } catch { /* already gone */ }
|
||||
ws.close();
|
||||
}
|
||||
process.exit(results.every(Boolean) ? 0 : 1);
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 434 KiB |
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"ok": true,
|
||||
"checks": 7,
|
||||
"fail_count": 0
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 436 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user