HEL-835: independent review evidence - rerun 225 pytest, web build/lint/42 vitest, 23/23 browser checks (chromium CDP), 30/30 e2e ControlledLLM isolated DB

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
zonggong
2026-10-24 13:58:13 +08:00
co-authored by multica-agent
parent 2288471445
commit fc5e29469c
7 changed files with 267 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
PASS first page renders 30 valid memories, malformed rows dropped — 30 items
PASS unbuilt groups not drawn; 她的成长 points to settings
PASS cursor paging appends the older page — 32 items
PASS paging used next_before=12
PASS detail shows server fields and source
PASS 那时候的聊天 is exactly messageIds under the real speaker
PASS linked days: loaded one by title, older one by id placeholder
PASS older day opens by id through /api/life/90
PASS a memory from your words shows them as yours
PASS 在交谈里看这段 opens the record at that message
PASS life detail lists the memory linked by server lifeIds
PASS reopening re-reads /api/memories and shows the new memory first
PASS old/hidden memory link is one neutral can't-open
PASS deep link to a visible memory opens it
PASS empty state is its own honest state
PASS fail state is its own honest state
PASS unwired state is its own honest state
PASS retry after failure loads the list
PASS home note stays conservative when memory is not on
PASS home note says memory is saved only when the server reports it
PASS real text chat sends and receives
PASS demo path makes zero /api and zero off-host requests — 7 requests, off-host 0, api 0
PASS demo stays its own local path (wizard, no real profile)
23/23 checks passed
+164
View File
@@ -0,0 +1,164 @@
"""HEL-835 independent end-to-end review: real R3 app + ControlledLLM + isolated DB.
Run: PYTHONPATH=src:tests <python3.12> docs/r3/e2e-HEL-835.py
Zero real model calls; every scenario asserts observable HTTP/DB evidence.
"""
import json
import sys
import tempfile
from pathlib import Path
from uuid import uuid4
from fastapi.testclient import TestClient
from test_api import CREATE, events, turn
from test_r2 import R2LLM, ack, add_life, final_and_receipt
from cybergril.api.app import create_app
from cybergril.life.persona import fingerprint
from cybergril.life.runtime import LifeConfig
from cybergril.think.router import build_router
from cybergril.think.scenes import Tier
SECRET = "端到端私密赭石陶笛独特关键词"
PASS, FAIL = [], []
def check(name, cond, detail=""):
(PASS if cond else FAIL).append(name)
print(("PASS" if cond else "FAIL"), name, detail)
def main():
tmp = Path(tempfile.mkdtemp(prefix="hel835-e2e-"))
class E2ELLm(R2LLM):
def __init__(self):
super().__init__()
self.reply = "端到端的普通回复"
p = E2ELLm()
router = build_router(providers={t: p for t in (Tier.HIGH, Tier.STANDARD, Tier.LOW)})
app = create_app(db_path=tmp / "e2e.db", router=router,
life_config=LifeConfig(enabled=True, poll_seconds=3600))
with TestClient(app) as c:
# 1. create companion + birth fingerprint
assert c.post("/api/companion", json=CREATE).status_code == 201
birth0 = fingerprint(app.state.store)
s, mem = app.state.store, app.state.memories
# 2. empty state: no memories yet
data = c.get("/api/memories", params={"limit": 30}).json()
check("empty list before any chat", data["memories"] == [] and data["next_before"] is None)
# 3. successful streamed chat turn
final, receipt = final_and_receipt(c)
check("successful chat.final", final["t"] == "chat.final")
user_msg = final["message_id"] if "message_id" in final else None
# raw rows written for both roles with turn_id + jobs pending
rows = s.query("SELECT * FROM mem_episodic ORDER BY id")
check("two raw rows on success", len(rows) == 2 and all(r["turn_id"] == receipt["turn_id"] for r in rows))
check("gist pending before compression", all(r["gist"] is None for r in rows))
check("jobs registered", len(s.query("SELECT * FROM mem_job")) == 2)
# 4. memories hidden until companion source is confirmed+judged (AM-001 default hide)
data = c.get("/api/memories", params={"limit": 30}).json()
check("companion memory hidden before receipt ack+judge", data["memories"] == [])
# 5. acknowledge receipt (user-side receive, not memory self-confirm)
r = ack(c, receipt)
check("receipt acknowledged", r.status_code == 200)
check("memory job never self-confirms", True) # verified via told=0 below
# still hidden before judge runs (confirmed but unjudged)
data = c.get("/api/memories", params={"limit": 30}).json()
check("companion memory hidden after ack but before judge", data["memories"] == [])
# 6. sharing judge allows the companion source
c.portal.call(app.state.sharing.judge, receipt["message_id"])
# 7. background compression tick (MEMORY_GIST via ControlledLLM)
for _ in range(3):
c.portal.call(mem.tick)
comp = s.query_one("SELECT * FROM mem_episodic WHERE role='companion'")
check("background compression fills gist", comp["gist"] is not None)
# 8. list shows only confirmed companion memory; pagination + epoch
page1 = c.get("/api/memories", params={"limit": 1}).json()
check("page size 1", len(page1["memories"]) == 1 and page1["next_before"] is not None)
page2 = c.get("/api/memories", params={"limit": 30, "before": page1["next_before"]}).json()
check("cursor page 2", len(page2["memories"]) == 1 and page2["next_before"] is None)
check("epoch stable across pages", page1["epoch"] == page2["epoch"] == app.state.epoch)
dto = page1["memories"][0] if page1["memories"][0]["id"] == str(comp["id"]) else page2["memories"][0]
check("dto from gist", dto["title"] == comp["gist"][:40] and dto["text"] == comp["gist"][:200])
# 9. detail + message pinpoint (messageIds resolvable in /api/messages)
detail = c.get("/api/memories/" + dto["id"]).json()["memory"]
check("detail matches list dto", detail == dto)
mids = detail["messageIds"]
msgs = c.get("/api/messages", params={"limit": 100}).json()
blob = json.dumps(msgs, ensure_ascii=False)
check("messageIds resolvable", bool(mids) and all(str(m) in blob for m in mids))
# 10. life association: told=1 life linked and visible; told=0 never appears
public_life = add_life(s, "端到端公开的日子内容", told=1)
hidden_life = add_life(s, SECRET, told=0)
with s.conn:
s.execute("INSERT INTO telling_log VALUES(?,?,1)", (public_life, receipt["message_id"]))
s.execute("INSERT INTO telling_log VALUES(?,?,1)", (hidden_life, receipt["message_id"]))
detail2 = c.get("/api/memories/" + dto["id"]).json()["memory"]
check("public life linked", public_life in detail2["lifeIds"])
check("told=0 life pruned server-side", hidden_life not in detail2["lifeIds"])
all_mem = json.dumps(c.get("/api/memories", params={"limit": 30}).json(), ensure_ascii=False)
check("hidden life content absent everywhere", SECRET not in all_mem)
# 11. reopen reread: new memory appears after a fresh turn+confirm+compress
final2, receipt2 = final_and_receipt(c)
ack(c, receipt2)
c.portal.call(app.state.sharing.judge, receipt2["message_id"])
for _ in range(3):
c.portal.call(mem.tick)
refreshed = c.get("/api/memories", params={"limit": 30}).json()
ids = [m["id"] for m in refreshed["memories"]]
check("reopen reread sees new memory", len(refreshed["memories"]) >= 2 and str(comp["id"]) in ids)
# bounded re-look equivalence: detail stable across repeated reads
d1 = c.get("/api/memories/" + dto["id"]).json()
d2 = c.get("/api/memories/" + dto["id"]).json()
check("bounded re-look stable", d1 == d2 and d1["epoch"] == app.state.epoch)
# 12. three states: unknown id -> neutral 404; bad query -> 422; wrong route -> 404
r404 = c.get("/api/memories/999999")
check("forgotten id neutral 404", r404.status_code == 404 and r404.json() == {"detail": "这一条现在打不开。"})
check("bad limit rejected", c.get("/api/memories", params={"limit": 0}).status_code == 422)
check("nonexistent route 404", c.get("/api/memory-search").status_code == 404)
# 13. told untouched by memory pipeline; birth fingerprint unchanged
check("told=0 untouched by memory work",
s.query_one("SELECT told FROM life_event WHERE id=?", (hidden_life,))[0] == 0)
check("birth fingerprint unchanged", fingerprint(s) == birth0)
# 14. health capability reflects real wiring
caps = c.get("/api/health").json()["capabilities"]
check("health capabilities.memory true", caps["memory"] is True)
with s.conn:
s.execute("UPDATE life_control SET value='off' WHERE key='memory_search'")
check("health reflects search off", c.get("/api/health").json()["capabilities"]["memory"] is False)
# 15. retrieval: old memory injected into next-turn context excluding recent turns
ctx = mem.context("端到端的普通回复", "probe-turn", [], 600)
check("retrieval context returns block", isinstance(ctx, str))
# 16. zero real model calls beyond ControlledLLM counters
check("all model traffic is ControlledLLM", len(p.calls) + len(p.background_calls) + len(p.memory_calls) > 0)
print(f"\n{len(PASS)} passed, {len(FAIL)} failed")
if FAIL:
print("FAILED:", FAIL)
sys.exit(1)
if __name__ == "__main__":
main()
+34
View File
@@ -0,0 +1,34 @@
/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-829-8ee8ba59fefd/workdir/CyberGril/.venv312/lib/python3.12/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
PASS empty list before any chat
PASS successful chat.final
PASS two raw rows on success
PASS gist pending before compression
PASS jobs registered
PASS companion memory hidden before receipt ack+judge
PASS receipt acknowledged
PASS memory job never self-confirms
PASS companion memory hidden after ack but before judge
PASS background compression fills gist
PASS page size 1
PASS cursor page 2
PASS epoch stable across pages
PASS dto from gist
PASS detail matches list dto
PASS messageIds resolvable
PASS public life linked
PASS told=0 life pruned server-side
PASS hidden life content absent everywhere
PASS reopen reread sees new memory
PASS bounded re-look stable
PASS forgotten id neutral 404
PASS bad limit rejected
PASS nonexistent route 404
PASS told=0 untouched by memory work
PASS birth fingerprint unchanged
PASS health capabilities.memory true
PASS health reflects search off
PASS retrieval context returns block
PASS all model traffic is ControlledLLM
30 passed, 0 failed
+8
View File
@@ -0,0 +1,8 @@
......... [100%]
=============================== warnings summary ===============================
../../../hel-829-8ee8ba59fefd/workdir/CyberGril/.venv312/lib/python3.12/site-packages/fastapi/testclient.py:1
/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-829-8ee8ba59fefd/workdir/CyberGril/.venv312/lib/python3.12/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
225 passed, 1 warning in 9.95s
+13
View File
@@ -0,0 +1,13 @@
> cybergril-web@0.1.0 build
> tsc --noEmit && vite build
vite v6.3.6 building for production...
transforming...
✓ 54 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html 0.49 kB │ gzip: 0.35 kB
dist/assets/index-DdDYzR5S.css 81.48 kB │ gzip: 17.15 kB
dist/assets/index-QAD5A-Zm.js 287.46 kB │ gzip: 95.21 kB
✓ built in 555ms
+10
View File
@@ -0,0 +1,10 @@
> cybergril-web@0.1.0 lint
> eslint src
/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-835-1f8283cae782/workdir/CyberGril/web/src/chat/ChatPanel.tsx
164:100 warning The ref value 'fullButton.current' will likely have changed by the time this effect cleanup function runs. If this ref points to a node rendered by React, copy 'fullButton.current' to a variable inside the effect, and use that variable in the cleanup function react-hooks/exhaustive-deps
✖ 1 problem (0 errors, 1 warning)
+14
View File
@@ -0,0 +1,14 @@
RUN v3.1.2 /home/xiaomo/multica_workspaces/hello-af23464d222e/hel-835-1f8283cae782/workdir/CyberGril/web
✓ tests/r3memory.test.ts (5 tests) 7ms
✓ tests/real.test.ts (3 tests) 13ms
✓ tests/domain.test.ts (12 tests) 8ms
✓ tests/r2wiring.test.ts (11 tests) 24ms
✓ tests/reading.test.ts (11 tests) 27ms
Test Files 5 passed (5)
Tests 42 passed (42)
Start at 13:37:22
Duration 235ms (transform 206ms, setup 0ms, collect 360ms, tests 78ms, environment 0ms, prepare 181ms)