Compare commits

...
Author SHA1 Message Date
总管andmultica-agent 18aef2a459 HEL-898: record independent 118 tests and 53 browser replay checks
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 07:14:21 +08:00
总管andmultica-agent 9254c17316 HEL-898: preserve director three-fix rework verbatim on b0dcb17
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 07:09:23 +08:00
总管andmultica-agent b0dcb17e1a HEL-879 keep pending frontend acceptance explicit in R5 package
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 06:49:33 +08:00
总管andmultica-agent 9756db3204 HEL-879 clarify dependency inventory and evidence scope; clean bridge lint
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 06:48:37 +08:00
hel-879-workerandmultica-agent 11fd0e2dc2 HEL-879 R5 test package v2 (materials rework per manager's 4-item list; zero execution): (1) dependencies split into two tracks - R5 voice-only chain verified present (LiveKit 7880 plain ws / FunASR / agent venv livekit-agents 1.8.8 / CosyVoice weights, GPU 30520MiB free) vs FULL unified experience honestly listed as 41-item closure truly-missing (mem0ai fixed-source wheel offline-built sha256 698284b6 352586B, greenlet/pillow/truststore cache-reusable, 37 pending download authorization, hatchling resolved; existing .12 product venvs probed have NO mem0 and are system 3.12.3 - 'zero missing' does not hold for the full experience; fastembed models actually 190.4MB+0.05MB, hel895 doc's ~92MB flagged as erratum); (2) trusted HTTPS+WSS rewritten with evidence - retracted 'one self-signed click covers LiveKit WSS 7880' (7880 is plain ws shared by generic worker and 8090 demo, 7881 is not an HTTP endpoint, the 8090 https page itself hands browsers ws:// = mixed content with no proof it ever worked); proposed dedicated 8363 TLS bridge deploy/voice/wss_bridge.example.py (asyncio+ssl stdlib, zero new deps; local smoke TLS1.3 + byte transparency pass, wss-bridge-smoke.log), livekit_url=wss://...:8363, per-device per-port trust steps, ignore-cert switches explicitly banned, iOS/Android behavior marked pending real device; (3) budget per-category - acquire() is an atomic pre-send hold so failures/retries/interrupts/background all count (LLM call count != successful chat lines); LLM+embedding = code-enforced hard gates, ASR/TTS/session-creation honestly listed as ops-only caps (window<=2h, calls<=3, TTS<=200 segments; session-cap listed as engineering follow-up); proposed caps do not self-activate, caps stay 0; (4) engineering params decided by engineering - 8360/8361/8363/8082 and /opt/voice-agent-cybergril + /opt/cybergril-voice-certs all verified conflict-free read-only; D1 rewritten to the real 11 voice IDs with zero-synthesis reference-audio audition plan (8090 panel has NO audition and mutates shared state - v1 claim retracted; generic demo 'rain' is not this user's choice). .12 facts from read-only SSH probes, evidence sheet r5-facts-20261027.md (secrets redacted); web/src and src zero changes; 4x requirements checks pass (origin/main and f92a86f, with/without handoff); handoff appended; zero real LLM/embed/ASR/TTS/mic, zero download/install/deploy/merge, real caps stay 0
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 06:44:42 +08:00
zonggongandmultica-agent f92a86f5d8 HEL-879 unified integration review: actual fc905fe worker frames (generated on the .12 real SDK, read-only SSH stdin) fed through the preserved HEL-898 frontend - fixtures + 9 vitest cases pass (incremental->final->record takeover, phi/no-receipt/interrupt/proactive-during-playout lifecycles, user-interim/hush/drop-reopen boundaries, replay/late/out-of-order frames all dropped); seq namespace analysis (job=room=dispatch=controller call, one entrypoint per job, reconnect keeps the bus, single dispatch site per fresh room - no same-room second-job reset path; residual gap: agent-left zombie UI, for the author); DOCUMENTED GAP: closed-64 squeeze lets a duplicate high-seq turn_start re-enter a retired speech (not producible by the current worker; unbounded-Set fix suggested for the author); browser layer NOT independently re-run (no GUI system libs locally, zero-install policy) - hel879_unified_shots.mjs harness committed for Edge replay, author's 61/61 + theme screenshots remain the interface evidence; regression pytest 352/352, vitest 108/108, typecheck/build/lint(0 err/1 known warning)/ruff clean, requirements checks 4/4 (origin/main and fc905fe, with/without updated handoff-HEL-879); R5 real-test package prepared for the main card (r5-test-package.md) with zero execution; mock &turn/&id params are additive test-infra extensions, default semantics unchanged
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 06:27:27 +08:00
总管andmultica-agent fb7ea846ff HEL-898: record exact source preservation and independent offline checks
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 05:54:01 +08:00
总管andmultica-agent 46ac0a7a20 HEL-898: preserve director v2 subtitle wiring verbatim on fc905fe
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 05:53:00 +08:00
zonggongandmultica-agent fc905fe6bd HEL-879 timing closure: the three contract-approval findings fixed as implementation-timing only (v2 envelope fields/event kinds untouched, web/src untouched, no re-audit of passed binding/backup/budget work) - (1) open_session re-kicks the cleanup owner AFTER registration+persist: a real dispatch await yields the loop and the pre-dispatch kick's cleaner already exited with nothing to watch (manager repro: 20ms real dispatch await, TTL 25ms, 120ms of silence -> DELETE=0, expired registration still present, cleaner=None; fixed: exactly one DELETE, registration settled, no dangling coroutine - the bounded auto-expiry/shared-cap mechanism itself is unchanged), new regressions reproduce first on the old code with a genuinely-yielding dispatch stand-in (asyncio.sleep, not the instantly-returning MockTransport) plus an earlier-deadline wake negative; (2) speak_proactive_row takes the handle the real sync AgentSession.say returns (iscoroutine only unwraps await-style stand-ins - the real SpeechHandle itself defines __await__, so awaiting it would wait out the whole playout), publishes proactive_start IMMEDIATELY, registers the handle's own end/cancel, then waits out that one row (sequential rows kept); .12 real-SDK probe: start visible while handle_done=false, end only after, cancel-only on interrupt, v2 fields identical; (3) _after_playout gates assistant_final on the completed+legal-same-turn-receipt conditions BEFORE anything is sent - incomplete streams, missing/partial receipts and stale epochs close with turn_cancel only (reason incomplete/no-receipt), never final-then-unack; the complete+receipt path keeps BOTH final and ack; phi/interrupt/failure/unattributed fail-closed unchanged; real-SDK check extended to 14/14 ALL PASS on .12 (proactive timing, proactive interrupt, no-receipt no-final negatives all driving the actual worker module's registered callbacks), before/after evidence in docs/rework/hel879/timing-fix/ and sdk-check-hel879-timing.log, reusable probe generator scripts/gen_hel879_timing_probe.py; regression at this head: pytest 352/352 x3 consecutive runs, ruff(src/tests/tools) clean, verify_hel877_voice_chain 8/8, requirements checks 4/4 (origin/main and c121c42, each with/without handoff-HEL-879.json, handoff appended with this round's scope); zero real LLM/embed/ASR/TTS, zero downloads, .12 touched read-only via SSH stdin only, not deployed, nothing merged, PR#11 and hel-870-assembly untouched, real caps stay 0
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 05:31:05 +08:00
zonggongandmultica-agent 3228b1e0d7 HEL-879 remaining closure: auto-expiry cleanup ownership, exact SDK speech-turn attribution, and the versioned v2 subtitle contract for HEL-898
A. Room lifecycle (总管 review items A-1/A-2): the single cleanup owner now watches
   ACTIVE session deadlines as well as debts - a session nobody touches expires into
   the ledger and has its room really deleted with zero further requests (old code:
   _cleanup_loop only scanned debts, expired rooms were never deleted until some
   request ran _prune). Explicit end and the background sweep share ONE persisted
   attempt/age cap and abandoned state - _sessions no longer bypasses it (the
   manager measured 2 background attempts + 3 more interactive deletes = 5); an
   abandoned debt raises CleanupAbandoned -> 503 with room/attempts/reason instead
   of pretending done-404, success clears both _sessions and _debts, restart
   continues the same persisted cap, close/cancel leaves no dangling coroutine, and
   only this instance's own rooms are ever touched. New wake event makes a fresh
   debt retry immediately instead of waiting out a deadline sleep.

B. Queued speech turns bind EXACTLY (总管 interleaving note_handle(H1)->H2->
   note_stream(S1)->S2 gave H1=None, H2='turn-one'): CyberGrillLLM.chat now reads
   the SDK's own agents_speech_handle ContextVar (livekit-agents 1.8.8 sets it in
   _create_speech_task around every speech pipeline; source-verified on .12 and
   asserted at runtime) so each ChatApiLLMStream carries speech_handle/speech_id of
   the very handle whose generation created it. TurnLedger binds only on that exact
   attribution - a newer speech NEVER retires an older pending handle, say/proactive
   generations bind nobody, missing attribution fails closed (no ack, no subtitle
   turn), interrupt/failure discards only its own binding.

C. Versioned subtitle contract v2 for the visual author's wiring card (HEL-898):
   every cg.subtitle payload gains {v:2, seq} (session-monotonic), turn identity
   (speech_id, turn_id, same-source message_id for proactive rows) and explicit
   lifecycle events - turn_start/assistant_interim/assistant_final/turn_end/
   turn_cancel(reason) plus proactive_start/proactive_end/proactive_cancel that
   ride the say SpeechHandle instead of the receipt branch. Accumulators are
   isolated per speech_id (a late delta after cancel is dropped producer-side);
   seq + speech_id give the frontend two independent stale-rejection rules. Full
   executable contract: docs/voice-subtitle-contract-v2.md with recorded samples in
   docs/rework/hel879/subtitle-samples/ (scripts/gen_subtitle_samples_hel879.py).
   web/src untouched.

Verification: the .12 real-SDK check now LOADS THE ACTUAL WORKER MODULE and drives
the callbacks IT registered on a real AgentSession (no hand-copied functions) -
both-queued delayed generations bind exactly, a say sandwiched between user turns
steals nothing, unattributed generations fail closed, interrupted turns cancel
without final/receipt (11/11 ALL PASS, source-level proof of the ContextVar wiring
included; evidence docs/rework/hel879/sdk-check-hel879-remaining.log). The old r2
lifespan test flake is root-caused (clock freeze and block were two separate portal
calls; a 10ms life tick could slip between them and claim the frozen day unblocked)
and fixed atomically without raising timeouts or dropping assertions - 12/12 solo,
full suite 350/350 three times in a row on .venv311; ruff back to the single
pre-existing error; voice chain 8/8; release offline R1-R7 with zero failures;
requirement checks 4/4 (origin/main and c121c42, each with/without handoff).
Zero real LLM/embed/ASR/TTS calls, zero downloads/installs, nothing deployed or
merged, real caps stay 0.

Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 05:10:52 +08:00
zonggongandmultica-agent b4dadd82b3 HEL-879: refresh release-snapshot selftest (12/12), budget-split (10/10) and sdk-audit evidence at the exact rework head so the recorded code_commit matches the code it verifies
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 04:27:27 +08:00
zonggongandmultica-agent 7c73d392b9 HEL-879 rework materials: hatchling cache correction with the offline mem0ai wheel actually built from the fixed source (sha256 698284b6..., zero downloads/installs, no 3.10 mixing, recorded in release-artifacts.json), hel895 release doc §0 now separates the historical HEL-895/896 candidate from the current hel-879-assembly candidate and §4/§5 carry the v3 snapshot semantics, budget §6.2.4 amended - a sealed library is never reopened by raising total_requests (contradiction removed), runbook §3.3/3.5/3.6/4.4/4.5 corrected (bounded cleanup ledger replaces the unproven rooms-die-naturally claim; assistant_interim kind; real-but-default-off proactive seam; single-owner dual launcher), one comment-only correction in web/src/voice/session.ts removing the unproven server-auto-cleanup claim (no logic/style change; CSS and JS bundle hashes byte-stable), CG-011 + her incremental subtitles now have a real minimal backend seam (voice/seam.py + worker/llm_chat_api wiring, proactive default off) with the precise frontend interface and acceptance list in docs/rework/hel879/voice-seam-interfaces.md, real-SDK check extended to 10 items (worker callback path on real SpeechHandle/SpeechCreatedEvent; interim seam through real LLMStream) and re-run on .12 ALL PASS, handoff-HEL-879.json appended with the rework scope; regression at this head: pytest 341/341, ruff clean except 1 pre-existing error identical at 7f0a739, web typecheck 0 errors / lint 0 errors 1 pre-existing warning / vitest 88/88 / build passes (CSS index-BpZaybfD.css unchanged), voice chain 8/8, budget split 10/10, release offline R1-R7, http pagination 7/7, real-service browser 9/9 with zero non-loopback outbound, pinned-SDK 16/16+12/12+6/6+7/7+12/12, requirements checks 4/4 (origin/main and c121c42, each with/without handoff)
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 04:27:04 +08:00
zonggongandmultica-agent 78a5cfd8d7 HEL-879 rework: fix the four manager-review edges - (1) dual-port launcher owns the app lifecycle exactly once: uvicorn listeners run with lifespan=off, either listener ending stops the other BEFORE the shared store closes, cancellation cleans up listeners first, and create_app's lifespan gains a fail-closed double-entry guard (the old shape silently re-initialized Store/epoch/life/chat_lock on the second entry); (2) room ownership survives delete failures: end_session keeps the session in a bounded persisted cleanup ledger until the remote delete lands (retry/idempotent, LiveKit 404 = already gone = settled), expiry moves rooms into that ledger instead of silently dropping them (the manager measured zero DELETE attempts on expired ends), graceful shutdown does not hang up live calls, restart compensates only this instance's own recorded rooms bounded by attempts/TTL+grace with abandonment recorded, and the router answers 503 (not 404) while a room is still owned so the browser keeps its owed DELETE; (3) TurnLedger binds each speech handle to its own generation stream explicitly (oldest pending handle gets the stream the SDK starts; a newer speech retires an older generation-less handle) - a handle without a generation can no longer steal the next turn's stream/receipt, the worker discards interrupted/failed bindings fail-closed before consuming; (4) release snapshot v3: verify requires three-way count equality (manifest source = manifest snapshot = restored actual; rows_match is an extra constraint, never an OR bypass), the rollback drill actually restores by source path into a persistent isolated tree and opens the restored DBs there, same-basename DBs get collision-free archive names, duplicate sources are refused; new regression file tests/test_voice_rework_hel879.py 14/14 reproducing every edge first, full pytest 338/338
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 04:10:23 +08:00
zonggongandmultica-agent 7f0a739213 HEL-879: refresh release-snapshot selftest (9/9) and budget-split (10/10) evidence at the exact final head 7f66015 so the recorded code_commit matches the code it verifies
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 03:37:07 +08:00
zonggongandmultica-agent 7f66015ac8 HEL-879 unified voice candidate assembled and verified on hel-879-assembly (isolated branch; PR#11 and hel-870-assembly untouched) - the merge brings exactly the audited HEL-877 voice backend (aba2aeb: src/cybergril/voice module with zero new dependencies, byte-identical to the author's head) and the HEL-897-verified voice frontend (c61d9c9: web/src byte-identical to the original author's head - the CSS hash change to index-BpZaybfD.css is entirely the author's own components.css +21 lines, no visual rework by this card); the one integration adjustment is test-harness alignment, not a product change: the voice adapter fixtures and verify_hel877_voice_chain.py built their router without a budget config, which the mainline's HEL-882 hard gate (guard.acquire before every real send) now correctly refuses at cap 0 - they now use the same tests/conftest.engineering_budget() every mainline test uses, no gate weakened, no src change on either author's side; full battery rerun on the assembled head: pytest 324/324 (299 mainline + 25 voice), ruff clean, web typecheck 0 errors / lint 0 errors 1 pre-existing warning / vitest 88/88 / build passes, real-server browser 9/9 with the HEL-896 precise-interaction revision (reach card over real HTTP, stage plays the reach-out, unread clears, message-id dedup, restart epoch+1, zero permission API calls, zero non-loopback outbound - evidence docs/rework/hel879/browser/), verify_hel877_voice_chain 8/8 offline full-chain, the .12 livekit-agents 1.8.8 real-SDK stdin check regenerated from THIS head's sources 8/8 ALL PASS (zero writes zero network), check_release_offline R1-R7 passes, pinned-SDK regressions on hel857-venv all green (close-ownership 16/16, shutdown-queued 12/12, inflight 6/6, semantic-recovery 7/7, mem0-real 12/12 - the seal change is exercised through the same PersistentBudget paths), requirement checks 4/4 (origin/main and c121c42, each with and without handoff-HEL-879.json); release-artifacts.json records the exact release code commit (01ff919), the tarball sha256 measured equal to the lock, and the cache-reusable wheels reverified with content digests after the disk-recovery cache incident; honest gaps kept open: real-call acceptance untested and unauthorized (D1 voice, trusted HTTPS device path, budgets), LiveKit room/worker release after API restart still lacks real-environment evidence (_prune only drops the in-memory registry, end_session does delete_room), incremental subtitles remain a contract gap not a completed feature, CG-011 proactive voice stays an explicit no-op; zero real LLM/embed/ASR/TTS, zero downloads, no mic, no service started or stopped on any shared machine, not deployed, nothing merged, authoritative ledgers/freezes/HEL-835 gate untouched, real caps stay 0
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 03:36:37 +08:00
zonggong 8afdabc7d1 HEL-879 assembly: merge the HEL-897-verified voice frontend (c61d9c9, hel-878-voice-wiring) - original author's six-file visual base byte-preserved with the late-cleanup/interleaved-start fix (761a2331) already independently re-verified 88/88 by the manager; no visual rework, no backend changes on this side 2026-10-27 03:27:07 +08:00
zonggong 48252b1dcd HEL-879 assembly: merge the audited HEL-877 voice backend (aba2aeb) onto the HEL-879 release-closure mainline - backend-only voice module (session/token router, chat_api_client, llm_chat_api, receipt, stt/tts adapters, worker) with zero new dependencies, provenance-preserved, e978e87 fully contained in both parents; web/ untouched by this side 2026-10-27 03:27:03 +08:00
zonggongandmultica-agent 01ff919859 HEL-879 release closure: the four HEL-896 audit findings become enforced behavior - (1) archive consistency: tools/release_snapshot_hel896.py v2 checks the wal_checkpoint(TRUNCATE) return (busy!=0 = hard failure, no deliverable tar, half-built dir removed - 'stopped service first' is a precondition, never a reason to ignore an actual failed result), aborts packaging on any source-vs-snapshot table-count mismatch instead of recording rows_match=False and shipping anyway, and verify_archive now rejects legacy packages that recorded rows_match=False while mapping a real rollback drill (unpack to an isolated dir, open every DB, check rows AND hashes against both recorded source and snapshot counts, emit a source-path rollback_plan - no more tar-internal self-attestation); selftest 9/9 reproduces every old failure shape first (naive WAL copy loses companion, held-reader checkpoint=(1,9,8) leaves the copied image malformed/missing the events row, the manager's rows_match=False legacy package verifying ok) then proves each fix (busy fails with zero artifacts, stale-snapshot mismatch fails with zero artifacts, online mode refuses --file dirs containing SQLite by magic-header scan, stop mode recursively takes embedded SQLite like history_root/mem0_public_history.db through checkpoint+per-table verification, verify maps source paths and passes); (2) early-finish seal: PersistentBudget gains sealed()/seal() writing sealed/sealed_requests/sealed_tokens/sealed_at atomically into budget_total beside the real usage (never faking 55, never zeroing), acquire checks the seal FIRST so every category and every remaining allowance rejects budget_sealed:51/55, the marker survives restart and backup-API archive/restore, reseal is idempotent, and there is deliberately no unseal path (re-test = new approval + new instance per budget-package 6.2.4); tools/seal_budget_hel879.py makes the closure an explicit auditable operator command with a post-seal rejection self-check; verify_budget_split_hel896.py gains S6 (51/55 experience chain completes -> seal keeps usage 51 -> 52nd and cross-category rejected with dialogue daily still 19/60 proving it is the seal not the caps, mistaken restart rejected, archive restore rejected, marker requests=51 intact) now 10/10, HEAD made dynamic instead of hardcoded 04eff61; budget-package-hel895.md 6.2 rewritten to the executable seal order (stop -> seal -> consistency snapshot); (3) single install entry: deploy/hel895-release-candidate.md 2 is now the only copy-paste install (uv --offline, npm --offline, cache-miss = stop and record, network only inside an authorized release window with per-item sha256) and 4 is the only fixed-SDK entry as a five-step chain (verify tarball sha256 9dcef930...dda23 against mem0-stack.lock -> build the wheel from the fixed source with hatchling honestly listed as the missing build backend -> cp311 dependency prep with greenlet/pillow/truststore confirmed cache-reusable on re-run -> local-wheels-only --no-index install where mem0ai must come from the fixed-source wheel, the lock line is version-only -> same-process co-import plus the 92MB fastembed cache placement); deploy/hel874-release.md 2/4 carry superseded banners pointing here, mem0-stack.lock no longer routes installs through the index mem0ai==2.2.1 wheel; (4) exact-commit honesty: audit_sdk_offline_hel896.py derives head from git rev-parse and accepts an output dir, fresh evidence written to docs/rework/hel879/sdk-audit/ (hel896 evidence left untouched) recording closure 55 / missing 41 (28 pure + 13 native) / cache-reusable [greenlet, pillow, truststore] / tarball-matches-lock at c121c42; regression on this head: pytest 299/299, ruff clean (src/tests/tools), release-snapshot selftest 9/9, budget-split 10/10 including the new S6 negatives; zero provider calls, zero downloads, authoritative ledgers/freezes/HEL-835 gate untouched, not deployed, PR#11 untouched
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 03:26:30 +08:00
zonggongandmultica-agent c121c42e39 HEL-896 release completion: the A/B/C budget becomes an executable two-instance split, the real-browser false positive is fixed precisely (product untouched), the SDK gap list is corrected to the honest 41-distribution closure with the pip-download erratum, and release archiving becomes a consistency snapshot - (1) budget split: the manager's independent review reproduced on a temp store that writing A's total_requests=55 into the shared product ledger blocks B's mem0_extract and C's dialogue_main with budget_total_requests_exhausted:55/55 while budget_total/request_usage/token_usage have no stage or authorization-source column; docs/budget-package-hel895.md gains §6 making the split executable: A lives ONLY on the isolated 8360 team-test instance with its own DB and models.yaml (55/500000 one-shot totals plus same-value daily category caps), the user instance NEVER carries one-shot totals (its budget_total rows are record-only, no gate), A is sealed by stop+consistent-snapshot archiving with budget_total never zeroed and restart/archive proving the seal holds, old test data has no import path into the user instance (release checklist forbids copying the 8360 data dir), B starts only after baiqizhi approval writes llm_cap/embed_cap per the 500+180 formula with the authority frozen prefix, the real-Mem0 smoke is counted INSIDE B's 60/180, B cap-hit queues batches (no retry burn, no lost rows, FTS fallback, chat uninterrupted) and first-week end requires a fresh approval to continue, and §6.4 gives one responsible limited recommendation (A 55/500k isolated, B 60 LLM/180 embeds, C daily table) plus errata for the two unsafe original claims ('A shares C's daily account' and 'WAL single file copies whole'); tools/verify_budget_split_hel896.py 8/8 on temp stores and stand-ins only (S0 exact-shape repro of the manager's finding, S1 team instance consumes the §1 step plan to 55 then hard-stops with the tightest gate winning - life daily 8/8 rejects before the total, S2 seal survives restart and backup-API archive, S3 user instance B/C unblocked after A with dialogue 60/semantic 40/daily-token stacking and rejections booking nothing, S4 the misconfigured-total guard on the user instance, S5 repo configs and authoritative facts untouched, real caps all stay 0); (2) browser false positive fixed without touching any product file: web/tests/hel895_real.mjs B3 used '!!stage || any visible button' and clicked the FIRST button containing 回 which is the 日子与回忆 home nav button, so stage=false still PASSed and the screenshot stayed on days - the verbatim original rerun reproduces the false 7/7 with B3 stage=false (docs/rework/hel896/browser-before/), and the revised test passes 9 checks by precise interaction: B2 asserts the letter card carries her exact words plus the talk-nav unread aria-label, B3a waits for the VISIBLE reach card (aside.reach.show, aria-hidden=false, non-zero box, exact words) over days, B3b clicks only THAT card's 回她 button and strictly asserts the conversation stage .conversation .sr contains the reach-out text with the unread mark cleared, B4/B5 prove dedup by server message id (data-mid in the record panel: zero duplicates, gap and live reach-outs each exactly once), B5 asserts /api/health epoch bumps exactly +1 (1->2) before rechecking, B6 counts the page's OWN permissions.query/getUserMedia calls from first document load (0/0, instrumented via Page.addScriptToEvaluateOnNewDocument after Page.enable), B7 runs the service under a temp loopback models config (CYBERGRIL_MODELS, all caps 0) plus a sitecustomize socket guard that REJECTS and eagerly reports non-loopback connects (0 attempts, 0 non-loopback DNS lookups, report survives signal death) - under precise interaction the stage really plays, unread really clears, dedup and epoch semantics really hold: it was a test defect, not a product defect, so no interface change and nothing to escalate; (3) SDK honesty: tools/audit_sdk_offline_hel896.py (read-only, zero downloads/installs/inference) computes the 3.11-Linux closure from the installed 3.10 pinned metadata - 55 dists, 41 missing from the product venv (28 pure + 13 native incl. grpcio/numpy/onnxruntime/tokenizers/sqlalchemy/protobuf), all three top-level pins are py3-none-any (no 'cp311 wheel' needed for them), mem0ai's installed direct_url is the local fixed source hel857-src and hel857-data/mem0-2c81af3.tar.gz hashes exactly to the lock's 9dcef930...dda23 (an index mem0ai==2.2.1 wheel proves nothing), the wheel built during hel857 lived only in an ephemeral cache and hatchling is not offline-available, exactly 3 of the 41 are cache-reusable today for 3.11 (greenlet 3.5.6 cp311, pillow 12.3.0 cp311, truststore 0.10.5 py3-none-any - version+tag verified), the fastembed model cache is measured (Qdrant/bge-small-zh-v1.5 + bm25, ~92MB, placement = instance data dir copy), and the seq48-51 pip event is settled by cache forensics: uv's pip-26.2.2 wheel entry (files.pythonhosted.org, sha256 fd7f4dfa...) was CREATED 2026-10-27 02:10:04 inside the HEL-895 run window as the ONLY new cache entry, so that step DID fetch from the network - the prior card's zero-download claim is corrected to 'download' by the conservative reading (byte-level download vs conditional request is indistinguishable from cache evidence) while the lock install itself added no new cache entries; deploy/hel895-release-candidate.md §4 is rewritten to this accurate list, all future installs forced offline/no-index; (4) release archiving: tools/release_snapshot_hel896.py implements the two safe modes (stop+wal_checkpoint(TRUNCATE)+copy, or online SQLite backup API), writes per-file sha256 manifests carrying the exact code commit, per-table row-count verification and a verify-mode rollback drill; its selftest reproduces the manager's WAL finding (online naive copy of the main .db: 'no such table: companion') and proves both fixed modes complete (backup API and checkpoint copies both hold companion=1/chat_message=1, archive hashes+rows verify, commit 04eff61 recorded); §5 of the release doc now carries the copy-paste commands covering the product DB, semantic vector root, history root and the authoritative embed ledger, archives are never deleted, and the section explicitly does NOT claim R6 complete backup; regression on this head: pytest 299/299 (3.11.16 venv + requirements.lock offline), ruff clean (src/tests/tools), web typecheck 0 errors / lint 0 errors 1 pre-existing warning / vitest 55/55 / build passes with CSS still index-DdDYzR5S.css, pinned-SDK reruns all green (close-ownership 16/16, shutdown-queued 12/12, inflight 6/6, semantic-recovery 7/7, mem0-real 12/12, reports in docs/rework/hel896/regression/), check_release_offline R1-R7 passes, real-HTTP pagination 7/7 rerun saved as docs/rework/hel896/http-pagination-rerun.json with the original hel895 evidence byte-untouched, requirement checks 6/6 (origin/main, de8874a3803b, 152479b, each with and without handoff-HEL-896.json); handoff-HEL-896.json written; zero real LLM, zero real embeddings, zero new downloads beyond existing caches, authoritative ledgers and freezes (39/60, 498/500, 2 frozen, HEL-835 gate) untouched, not deployed, PR#11 appended-not-merged, no visual changes, R5/R6/R7 honestly deferred
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 03:06:23 +08:00
zonggongandmultica-agent 04eff611e7 HEL-895 release candidate: the approved kernel 152479b + the HEL-881-passed proactive-inbox frontend bf234570 assembled into one auditable head, with the full post-synthesis battery, the release package and the one-approval budget execution package - integration is a clean merge (the mainline 31970c4..152479b never touched web/, so the merge brings exactly the visual director's six frontend files plus handoff-HEL-875/880.json; web/ is verbatim bf234570 and src/tests/tools/config are verbatim 152479b, all HEL-874..894 backend fixes preserved, no redesign, no visual decision, the unpushed b48d28b test merge superseded); verification rerun on the merged head: 299 python tests pass on the 3.11.16 venv + requirements.lock offline, ruff clean (src/tests/tools), web typecheck 0 errors / lint 0 errors 1 pre-existing warning / vitest 55/55 / build passes with CSS still index-DdDYzR5S.css (visual untouched), pinned-SDK regressions on hel857-venv (3.10.12, deterministic 512-dim stand-in, loopback-only, temp ledgers): 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 (R6 honestly records the 3.11 mem0 gap); NEW real-HTTP pagination contract 7/7 (tools/verify_http_pagination_hel895.py: the real product service seeded with 327 rows proves the reverse-page + limit+1 probe + next_before + epoch contract the approved frontend depends on, the public field allowlist with proactive exactly on reach-out rows, and a mechanical port of inbox.ts fetchAfter proving the HEL-880 gap semantics against the real backend - 5-page cap leaves the gap, the next call closes it and hands the two new reach-outs exactly once); NEW real-server browser 7/7 (web/tests/hel895_real.mjs: Chromium CDP against the real API + built dist - real boot, reach card arriving over real HTTP, stage playback, the 260-row long-absence gap regression, SIGTERM restart epoch+1 with no duplicate rows, zero permission requests; evidence docs/rework/hel895/); deliverables: deploy/hel895-release-candidate.md (exact head, manifest, install reference, health matrix, disable/rollback, the exact 3.11 fixed-SDK gap verified offline - product venv imports fine, no mem0/qdrant/fastembed wheels in any local cache, hel857-venv 3.10 fixed stack untouched and un-mixed, zero downloads, resolution path unchanged per hel874-release.md §4) and docs/budget-package-hel895.md (the A/B/C tables as one approvable package: per-send four-gate stacking semantics, the cross/duplicate/missed accounting verdict - A totals stack on daily category caps by design, B extraction LLMs are dual-counted in llm_ops cumulative AND the semantic daily gate by design, embeds count only as items, all four request categories plus the global token pool cover every outbound site, failures/cancels conservatively billed with no refunds, cap-hit stop semantics and evidence - all still cap=0 defaults, the 39/60 and 498/500 freezes and the HEL-835 gate untouched); requirement checks 6/6 (origin/main, origin/agent/agent/de8874a3803b and the actual mainline 152479b, each with and without handoff-HEL-895.json); zero real LLM, zero real embeddings, zero downloads, authoritative ledgers and freezes unwritten, not deployed, PR#11 appended-not-merged, no visual changes, R5/R6/R7 honestly deferred
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 02:23:08 +08:00
zonggongandmultica-agent 68f3eefeca HEL-895: mechanically integrate the approved proactive-inbox frontend (bf23457, HEL-881 passed) onto the unified mainline 152479b
The mainline 31970c4..152479b never touched web/ (verified: git diff 31970c4 152479b -- web/ is empty), so this merge brings in exactly the two HEL-875/880 commits and nothing else: the visual director's six frontend files verbatim (web/src/adapters/inbox.ts, real.ts, app/RealApp.tsx, chat/ChatPanel.tsx + tests) with CSS untouched, plus handoff-HEL-875.json and handoff-HEL-880.json for governance provenance. No backend file changes, no redesign, no visual decisions made here; all HEL-874..894 backend fixes stay byte-identical.

Containment verified before merging: bf23457 is NOT an ancestor of 152479b (1ae243f + bf23457 were the only mainline-missing work); the old b48d28b test merge was never pushed and is superseded by this fresh integration at the current head.

Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 01:55:45 +08:00
zonggongandmultica-agent 152479b350 HEL-894 rework: a cancelled close owns nothing it did not start — aclose cancellation is now a two-phase ownership protocol - the manager's independent pinned-SDK review of ceb0f35 reproduced two combo holes in the HEL-889 close path: (1) cancelling aclose AFTER backend.aclose began made the shielded finish keep running unowned in the background while the outer except already released the lock and cleared closing — the layer was reported reusable (closing=false/closed=false/client usable), a new tick claimed a batch onto real HTTP, then the orphaned finish closed Qdrant under it (SDK-only branch: batch failed on 'QdrantLocal instance is closed' with row_done=0/llm_used=1; with the caller closing the store afterwards the late worker died on ProgrammingError 'Cannot operate on a closed database' and the reopened batch sat pending), and (2) cancelling a SECOND aclose that was still WAITING for the close lock ran the same except and cleared the FIRST closer's flag (first close still pending but signal=false, a new batch ran to done while the original worker was still on the wire). SemanticMemory.aclose now states the revocation boundary explicitly: the DRAIN phase (nothing closed yet) stays revocable — a cancelled caller leaves everything open, resets the closing signal and the layer remains usable (HEL-889 semantics preserved); the FINISH phase (backend.aclose begun) is irreversible — a cancelled caller keeps the closing markers (a half-closed layer is never reported usable, ticks keep refusing), keeps the lock, and waits — repeated cancels only re-shield — for the layer-owned close task it started (self._close_task, created exactly once with asyncio.create_task and stored on the layer, never dropped to the background) to land before propagating CancelledError, so resources close exactly once; _finish_close also joins this layer's OWN executor via asyncio.to_thread so a cancelled caller can never block the event loop on a synchronous shutdown wait (caller-injected executors stay caller-owned), and signal reset is ownership-gated by owns_close: only the call that actually acquired the lock and set the signal may clear it, and only while no close task exists — a lock waiter cancelled on acquisition touches nothing; module docstring, aclose docstring and the app lifespan comment state the same contract; verification: 299 python tests pass on 3.11.16 venv + requirements.lock offline (5 new tests/test_rework_hel894.py cases, all barrier-driven real product paths: finish-phase cancel keeps ownership and refuses new work with zero sends and zero budget / repeated cancels during finish land the close exactly once / a same-phase second aclose waits then returns idempotently while a third cancelled lock-waiter touches nothing / waiter-cancel + owner draining + refused new tick + original worker completing combo / drain-phase cancel still re-arms for reuse), ruff clean (src/tests/tools), no frontend changes (frontend checks not re-run, stated honestly); pinned mem0 2.2.1 on the deterministic boundary (512-dim stand-in before TextEmbedding construction, loopback-only): new tools/verify_close_ownership_hel894.py 16/16 reruns the manager's two negatives in the same shape with assertions restated for the new protocol (F1 SDK-only: after the cancel closing=true/closed=false/client usable and the cancelled caller does not return before its close lands; the new tick is refused with zero gateway arrivals, batch queued/owner NULL/attempts 0 and llm_used=0; after the gate the close lands exactly once with live_workers=0 and the reopened batch is intact queued/row_done=0/llm_used=0 — before the fix the same run gave closing=false, a claimed batch, QdrantLocal instance is closed, failed/pending with row_done=0/llm_used=1; F2 + store-close branch: same shape and no late error — before: ProgrammingError; W waiter cancel: closing stays set, the new batch stays queued, the original worker completes done/row_done=1 after release and the owner's close then lands once); the manager's review_hel889_cancel.py re-run VERBATIM on the pristine ceb0f35 first reproduced both defects exactly (docs/rework/hel894/before-ceb0f35.json matches the manager's independent JSON), and on the fixed tree stops honestly at its own premise assert server.arrived — 'a finish-phase-cancelled layer still starts new real HTTP' is structurally gone because tick refuses while closing is held (verbatim-after-fix.log; original negative and baseline kept); regressions all green: verify_shutdown_queued_hel889.py 12/12 (queued cancel, timeout drain, between-rows resume, drain-cancel re-arm, concurrent close all preserved), 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, both manager review_hel882_{partial,extra}.py scripts re-run verbatim pass (extra's uv rc=2 is its own expected negative), HEL-889 same-shape rerun review_hel886_edges_rerun_hel889.py passes; crash semantics unchanged and still honest (unknown wire outcomes billed, proof-gated retries, strict-once not claimed); runbook §4 gains the HEL-894 cancellation-ownership paragraph, docs/requirements/handoff-HEL-894.json written, dual-target requirement checks 4/4 (origin/main and origin/agent/agent/de8874a3803b, with and without handoff); zero real LLM, zero real embeddings, zero non-loopback outbound, authoritative ledgers and freezes untouched, not deployed, PR not merged, no visual changes
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 01:43:54 +08:00
zonggongandmultica-agent ceb0f35418 HEL-889 evidence: add the same-shape rerun JSON referenced by docs/rework/hel889/README.md
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 01:24:31 +08:00
zonggongandmultica-agent 440d95cad8 HEL-889 rework: shutdown never closes under a live worker, and a queued-cancelled worker is released by the authoritative future state — SemanticMemory now runs batch workers on its OWN dedicated ThreadPoolExecutor and keeps each work item's concurrent.futures.Future: cancelling the awaiting tick propagates to that future, and its post-cancel state is the deterministic queued/started oracle (the manager's review_hel886_edges.py reproduced the old false-liveness on the shared default executor: register _live, await to_thread, executor busy -> cancel tick cancels the QUEUED item, _run and its finally never run, _live=1 forever with the batch pending and recovery permanently skipping a worker that does not exist, restart the only way out; now a CANCELLED future proves nothing ever ran: _release_never_started removes the registry entry idempotently and returns the batch to queued with its attempt restored and zero sends, while a running/finished future keeps HEL-886 semantics — the worker thread owns the batch fate, single send, no reclaim, no resend), and aclose becomes a real ownership protocol instead of a bounded promise it could break: a closing flag makes tick refuse new claims and makes both backends' _add_group_sync check should_stop BEFORE each row's wire work (a _ShutdownAbort at a boundary keeps finished rows' sem_row_done proofs and requeues only the unsent remainder — never a mid-row cut, and a single-row batch still completes done); never-started workers are cancelled out of the executor queue under the same authoritative CANCELLED proof; started workers finish their current row and flush, and aclose WAITS for every live worker to actually exit before backend/executor close — drain_timeout only bounds the polite phase (after expiry: honest warning, keep waiting; a live worker is never treated as dead), bounded in practice because Mem0Backend now sets the gateway OpenAI client's timeout (gateway_timeout config, default 60s) instead of the 600s default that made the drain effectively open-ended; a cancelled aclose closes NOTHING (shielded finish, closing signal reset, layer stays usable), concurrent aclose calls serialize on an asyncio.Lock and are idempotent, and the app lifespan comment now states the ownership contract — the manager's fixed-SDK reproduction of the OLD timeout branch (loopback gateway accepts the first HTTP then withholds the answer, aclose(drain_timeout=0.05) returns with the worker alive, app closes the store, late worker dies on 'QdrantLocal instance is closed'/'Cannot operate on a closed database', reopen shows pending/row_done=0/llm_used=1) is closed: aclose does not return while the worker is alive, the worker's writes land BEFORE the app-order store.close(), and the reopened store shows done + row_done=1 + full entry_ids with llm_used=1 and zero late errors; crash semantics unchanged and still honest (unknown wire outcomes billed, proof-gated retries, strict-once not claimed); verification: 294 python tests pass on 3.11.16 venv + requirements.lock offline (7 new tests/test_rework_hel889.py cases, all barrier-driven real product paths: queued-cancel release + in-process recovery via injected single-worker executor / shutdown-timeout never closes under the live worker and flushes before app-order store close / between-rows abort requeues and the next process sends ONLY the missing row / shutdown-before-start zero sends then resumes / cancelled aclose leaves everything open / concurrent aclose serializes / closed layer refuses new claims), ruff clean (src/tests/tools), no frontend changes (frontend checks not re-run, stated honestly); pinned mem0 2.2.1 on the deterministic boundary (512-dim stand-in before TextEmbedding construction, loopback-only): new tools/verify_shutdown_queued_hel889.py 12/12 (Q1 queued-cancel phantom released and recovered in-process with the manager's 41s lease probe, S1 fixed-SDK shutdown-timeout branch, S2 between-rows abort + missing-row-only resume proven by gateway POST counts, S3 cancelled aclose, S4 concurrent aclose), the manager's review_hel886_edges.py re-run VERBATIM on the pristine f1b50ad first reproduced both defects exactly (docs/rework/hel889/edges-verbatim-before-fix-f1b50ad.json matches the manager's independent JSON), the same-shape rerun review_hel886_edges_rerun_hel889.py now passes both scenarios with every original result key (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) while the verbatim original stops at its own premise assert executor._work_queue.qsize()==1 — batch workers no longer ride the loop default executor, the encoded defect is structurally gone (original negative and baseline kept); regressions all green: verify_inflight_lifecycle_hel886.py 6/6 (single-send preserved), verify_semantic_recovery_hel885.py 7/7, verify_mem0_real_hel874.py 12/12, check_release_offline_hel882.py R1-R7 15/15, both manager review_hel882_{partial,extra}.py scripts re-run verbatim pass, review_hel885_inflight.py still fails at its 2-add barrier exactly as documented in HEL-886; runbook §4 gains the HEL-889 ownership/queued-cancel semantics, config/semantic.example.yaml documents gateway_timeout, docs/requirements/handoff-HEL-889.json written, dual-target requirement checks 4/4 (origin/main and origin/agent/agent/de8874a3803b, with and without handoff); zero real LLM, zero real embeddings, zero non-loopback outbound, authoritative ledgers and freezes untouched, not deployed, PR not merged, no visual changes
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 01:24:11 +08:00
zonggongandmultica-agent f1b50ad91a HEL-886 rework: a live worker is never treated as dead, and a fully-completed batch finalizes without new budget - SemanticMemory gains an in-process live-worker registry (batch_id -> _LiveWorker, registered BEFORE the thread starts, so the batch is provably active from the moment the claim commits) and the batch's whole execution (row building, anchor verify, resume set, real adds, terminal sem_batch write) moves INTO the worker thread function, because asyncio.to_thread cannot interrupt a running call: cancelling the tick cancels only the waiter, and the old finally that marked the still-running batch failed and cleared its owner was the exact double-send the manager reproduced live with the pinned SDK (first HTTP accepted but unanswered -> cancel tick -> failed written while the add was on the wire -> second tick re-claimed and sent the same sentence again: 2 HTTP, 2 vectors, 1 row_done entry, the late worker's result dropped); recover() now fails only expired pending batches WITHOUT a live worker, so a lease past 40s with the thread still inside its add is never reclaimed and concurrent ticks cannot re-send an active row (same 1-HTTP/1-vector/1-record outcome on the manager's clock-injection lease variant), while a genuinely dead owner (crash/restart, no registered worker) keeps the honest conservative recovery: unknown wire outcome, ledger holds stay billed (HEL-885), the retry sends only rows without a completion proof, and strict-once is NOT claimed for arbitrary crashes without remote idempotency (module docstring + runbook say so in words); new SemanticMemory.aclose() drains live workers (bounded wait, honest warning on timeout) BEFORE backend/store close so shutdown never orphans durable writes, and the app lifespan now drains through it; Mem0Backend/StubBackend extract the sync core _add_group_sync (async add_group contract unchanged) where the completion set (vector-store anchors + sem_row_done + skip) is proven BEFORE any budget gate: a batch whose every row is already complete returns done with ZERO new HTTP/embed/LLM after restart (the manager's third example: llm_cap=1 fully spent, row really completed, 'row persisted, final status did not' crash window -> was queued/semantic_llm_budget_exhausted, now done with entry_ids intact, cap unchanged, nothing refunded), while any missing row keeps the hard fail-closed gate - finalization never buys headroom; verification: 287 python tests pass on 3.11.16 venv + requirements.lock offline (6 new tests/test_rework_hel886.py cases: in-flight cancel never double-sends / expired lease with live worker never reclaims + concurrent ticks single-send / dead-owner lease still recovers / shutdown drains the live worker before close proven by post-aclose durable writes / all-rows-done restart finalizes with zero new HTTP / partially-done keeps refusing), ruff clean (src/tests/tools), web tsc + 42 vitest + build via npm ci --offline, pinned mem0 2.2.1 on the deterministic boundary (512-dim stand-in installed before TextEmbedding construction, loopback-only): the manager's review_hel885_inflight.py re-run VERBATIM now fails exactly at its worker_done 2-add barrier (the assertion that a second send must happen; before the fix the same script passed with 2 HTTP/2 vectors - baseline JSON kept), the same-shape rerun with the barrier at ONE completed add passes both scenarios with 1 HTTP/1 vector/1 sem_row_done, batch pending while in flight, done with full entry_ids, and the all-done restart finalizes done with zero new HTTP and llm_used still 1; new tools/verify_inflight_lifecycle_hel886.py 6/6 (L1 cancel single-send full record, L2 lease-expired-live no reclaim, L3 shutdown drain before close, L4a all-done finalize without budget, L4b partial keeps the hard gate, L5 dead-owner recovery still works); regressions all green: verify_semantic_recovery_hel885.py 7/7, verify_mem0_real_hel874.py 12/12, check_release_offline_hel882.py R1-R7 15/15, both manager review_hel882_{partial,extra}.py scripts re-run verbatim pass; dual-target requirement checks 4/4 with handoff-HEL-886.json; zero real LLM, zero real embeddings, zero non-loopback outbound, authoritative ledgers and freezes untouched, not deployed, PR not merged, no visual changes
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 00:30:47 +08:00
zonggongandmultica-agent 78b6267d3a HEL-885 rework: per-row progress matches what was really sent, cross-day and unknown-outcome accounting stay conservative, positive caps require the ledger authority, and the release dependency-fill command actually exists - every Memory.add now carries an anchor for exactly its own row (the HEL-882 whole-batch anchor let the first stored vector mark unsent rows complete; the manager's real-SDK partial reproduction showed the user's original words never leaving the process), a row finishing with a legitimately EMPTY extraction gets a durable sem_row_done record (r4.sql additive table) written inside the worker thread the moment its add completes, resume skips = vector-store anchors ∪ sem_row_done so no row is lost, none falsely complete, and finished work is never re-sent across budget-refusal/crash/restart/lease/concurrency, entry_ids merge across partial attempts, and the swallowed-failure detection is per-row; PersistentBudget.settle/release book on hold.day (the old write to state.day manufactured -80 used on the new day and re-opened headroom the reservation day had already spent; token_usage/request_usage refund the paying day, budget_total stays global) and the empty-budget_total TypeError crash in total_requests_used/total_tokens_used is fixed - the old one-shot-total test passed only because that crash surfaced as chat.error, it now books the total honestly; _atomic_reserve no longer purges aged reserve: rows (a crashed worker's hold is an UNKNOWN outcome - an hour-old timeout is not proof the call never happened; unknown consumption stays billed across restarts, only a provably-unsent release() ever removes a hold, embed_ops and llm_ops share the fix); Mem0Backend refuses construction before importing mem0 when any cap>0 lacks a (non-empty dict) authority block - ledger_authority_required - or when the prefix check fails, closing the fresh-empty-ledger bypass the manager reproduced live, config/semantic.example.yaml ships the authority block as REQUIRED with the explicit cumulative formula (embed_cap=500+approved-increment e.g. 680, embed_reserve=2 frozen untouched, min_used=498; never write 180 as the cumulative cap of a 498-used account) mirrored into docs/budget-plan-hel874.md §2 with the HEL-885 accounting rules; deploy/hel874-release.md §4 replaces the nonexistent uv pip download (rc=2, re-verified) with python3 -m pip download --only-binary :all: --platform manylinux2014_x86_64 --python-version 3.11 --implementation cp --abi cp311 + sha256 manifest + uv pip install --no-index --find-links offline merge + same-process co-import verdict, honest missing-materials list unchanged (no 3.11 wheels cached, zero downloads this card, no version swaps), and tools/check_release_offline_hel882.py gains R7 probing both command surfaces offline; verification: 281 python tests pass on 3.11.16 venv + requirements.lock offline (9 new tests/test_rework_hel885.py cases: partial batch claims only the sent row / empty-extraction row completes and is never resent / process restart sends only the missing row / concurrent ticks never double-send / cross-day settle and release book on the reservation day / aged unknown llm+embed reservations never refunded / missing-empty-malformed-wrong authority all fail closed; test_resume_never_loses_or_duplicates_rows rewritten to drive the REAL product path instead of hand-planting single-row anchors the product never wrote), ruff clean (src/tests/tools), web tsc + 42 vitest + build via npm ci --offline, pinned mem0 2.2.1 install on the deterministic boundary: contract re-run 12/12 (harness now hands a synthetic authority prefix - production defaults not relaxed), new tools/verify_semantic_recovery_hel885.py 7/7 (S0 authority fail-closed, S1 per-row partial with exactly 1 HTTP, S2 restart sends only the missing row proven by HTTP count since mem0 prompts legitimately carry old rows in rolling history, S3 lease recovery + concurrent ticks single-send, S4a/S4b unknown reservations kept), both manager review scripts re-run VERBATIM against this head (partial: completed=[2] only, resume done with 2 entries, HTTP=2, user words actually sent; extra: new-day used=0, over-cap request refused, aged reservation second_allowed=false, uv probe still rc=2), release offline check R1-R7 green, dual-target requirement checks 4/4 with handoff-HEL-885.json; zero real LLM, zero real embeddings, zero non-loopback outbound, authoritative ledgers and freezes untouched, not deployed, PR not merged, no visual changes
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 00:01:44 +08:00
zonggongandmultica-agent 59fb06304f HEL-882 evidence: manager review scripts re-run verbatim against e291058 (api repro blocked - 0 provider calls, used unchanged, honest chat.error; sdk repro 1 - exactly 1 loopback HTTP, llm_ops=1, max_retries=0; sdk repro 2 - mid-add swallow now requeues without burning attempts and resumes done with real entry ids after the cap is raised; sdk repro 3 - stub coverage no longer blocks the real rebuild, enqueue 1→tick→done→search hit→no resend proven live by the add-on), deterministic-boundary pinned-install contract re-run 12/12 with final ledger rows persisted before temp cleanup, and the release offline check green across checkout/import/health/fail-closed caps/approved-caps run/data preservation plus the honest mem0 3.11 co-import gap; zero real LLM, zero real embeddings, zero non-loopback outbound, authoritative ledgers untouched
Co-authored-by: multica-agent <github@multica.ai>
2026-10-26 23:30:36 +08:00
zonggongandmultica-agent e291058754 HEL-882 rework: execution-table caps become real hard gates at the wire, SDK hidden retries and mid-add failures recover without loss, the 8360 release package runs offline, and the HEL-874 embedding evidence is corrected - every real provider exit (create/dialogue/life/gist/judgment/growth/proactive phrasing + mem0's internal extraction LLM) now persistently pre-allocates request count (per-category daily + one-shot totals in request_usage/budget_total, BEGIN IMMEDIATE) and a conservative token estimate before sending, refuses at the tightest cap (BudgetStop, no more downgrade-and-keep-calling) and survives restarts; known usage settles to actual, unknown/disconnect/failure keeps the full reservation; ledger guards gain per-day tops (embed_daily/llm_daily) and verify_ledger_authority rejects a fresh file bypassing the frozen 498/500 prefix; the mem0 SDK client's hidden OpenAI retries are disabled (max_retries=0) so every actual send is gated and answered failures (even 5xx) settle as used with only httpx.ConnectError-grade unsent requests released; embed refusals the SDK swallows into empty adds are re-raised as budget errors so the batch re-queues without burning an attempt and resumes when the cap returns, add_group resumes at vector-store level (src_group anchors: only missing rows resent, crash/restart/concurrency never duplicate), sem coverage is per-backend (stub batches no longer block a real rebuild, real done batches never resend) with backend-scoped batch keys; the release package is offline-executable: fresh-dir git clone, cybergril service account, PYTHONPATH in the unit so the lock-only venv imports the product, shipped caps explicitly 0 (=refuse by design until approved), deploy/mem0-stack.lock + accurate 3.11 wheel gap with a no-download path (hel857-venv is 3.10; never present stub/FTS as the real memory experience), and tools/check_release_offline_hel882.py verifies checkout/import/health/fail-closed/approved-run/data-preservation on loopback with echo stand-ins; evidence corrected per manager review: docs/rework/hel874/erratum-hel882.json + README appendix record that e978e87's verification did real local embeddings from cache (provable lower bound 12, exact total unrecoverable, temp sandbox gone) while zero-real-LLM/zero-download/zero-outbound stand, old JSONs and authoritative ledgers untouched, and tools/verify_mem0_real_hel874.py now installs the deterministic 512-dim stand-in before TextEmbedding construction plus non-loopback socket rejection (zero model loads) with final_embed_rows persisted before temp cleanup; verification: 272 python tests pass on 3.11.16 venv + requirements.lock offline (16 new: manager api repro blocked with 0 provider calls, sdk repro 1-HTTP/max_retries=0/llm_ops=1, mid-add swallow requeued-then-resumed, resume-no-duplicate, stub-to-mem0 rebuild, daily embed cap, unified mem0-LLM gate, authority refusal, restart-persistent counters, tightest-constraint settle/release), ruff clean (src/tests/tools), web tsc + 42 vitest + build via npm ci --offline, pinned-install deterministic re-run 12/12, manager review scripts re-run evidence attached in delivery, dual-target requirement checks 4/4 with handoff-HEL-882.json; zero real calls, zero real embeddings, zero non-loopback outbound, authoritative ledgers and freezes untouched, not deployed, PR not merged, no visual changes
Co-authored-by: multica-agent <github@multica.ai>
2026-10-26 23:20:18 +08:00
施工员2号 aba2aebeb0 HEL-877: wire the audited .12 voice base into the one CyberGril chat chain as backend-only code (no deploy, no real voice call) - new independent src/cybergril/voice module: session+router add POST /api/voice/session / DELETE end / GET honest status on the unified instance (server-side room + dedicated cybergril-voice-agent dispatch + short-TTL join token via zero-new-dependency livekit_wire HS256/Twirp matching the installed SDK's wire format verified claim-for-claim on the .12 agent venv stdin-only; keys never reach the browser; unconfigured/no-profile/TTS-down refuse honestly and capabilities.voice stays false), chat_api_client speaks the exact real.ts RealChatAdapter protocol (per-turn health epoch, bounded 409 resync, robust incremental SSE parsing across multibyte/CRLF/multi-data chunk splits, fixed turn_id idempotency, immediate HTTP-stream close on barge-in without waiting for the next ASR final), llm_chat_api is a livekit llm.LLM adapter whose only backend is POST /api/chat so persona/mood/dyad/memory/life-sharing/budget gates and the one same-turn chat_lock govern text and voice identically (phi spoken verbatim via ChatChunk, never archived, no receipt), receipt.py confirms a sharing receipt only after real SDK playback completion with every negative (interrupted/failed/mid-playout/phi/incomplete/stale-epoch) fail-closed and exactly once per turn, stt_funasr/tts_cosyvoice reuse the proven .12 adapters verbatim with provenance, worker.py assembles the dedicated worker (independent voice_state_cybergril.json so D1 stays undecided, no second persona, cg.subtitle data-channel contract for the visual lead, CG-011 proactive seam left as an explicit no-op hook, built-in health port 8082); app.py keeps a minimal registration with voice_service injection for isolated tests; deployment drafts only (config/voice.example.yaml, worker.env.example, restart_cybergril.sh.example with exact-pattern process matching, serve_dual_8360_8361.py keeping HTTP 8360 and the HTTPS voice entry on ONE process sharing state/locks, runbook-hel877-voice.md contract+startup+rollback incl. the >=6GB GPU precondition and the do-not-copy historical fuser warning) - verification: 281 pytest pass on 3.11 venv + requirements.lock after merging the chief engineer's mid-flight HEL-874 rework e978e87 in full (parallel work preserved), verify_hel877_voice_chain.py 8/8 offline full-chain incl. socket-level barge-in fail-close and text/voice mutual same-gate cancellation, real-SDK check on the .12 livekit-agents 1.8.8 venv 8/8 ALL PASS via SSH stdin with zero writes zero network, dual-port single-process smoke on loopback 18360/18361 with a throwaway self-signed cert then stopped, ruff clean on all new code, dual-target requirement checks pass vs origin/hel-870-assembly (current tip e978e87) and origin/main with handoff-HEL-877.json; zero real LLM/embed/TTS/ASR requests, no mic/camera, no downloads/installs, no service start/stop, no 4080 access, no third-account or profile writes, web/ untouched, not merged, not deployed
Co-authored-by: multica-agent <github@multica.ai>

## HEL-877 交付摘要(R5 后台施工,未部署未合并)

- 新独立模块 src/cybergril/voice/:专属 livekit worker(agent_name=cybergril-voice-agent,复用 .12 FunASR/CosyVoice/silero 底座,不 fork 不另搭平台,无第二人格/记忆,不直连 LLM);llm_chat_api 适配器与文字前端 real.ts 同协议走 POST /api/chat(epoch 409 有界重同步、SSE 分片、turn_id 幂等、打断即断流);/api/voice/session 建房+专属派发+短时限 token 全服务端(密钥不进浏览器;缺配置/无档案/TTS 停机诚实拒绝;capabilities.voice 不伪造);receipt 仅在 SDK 真实播放完成后确认(打断/失败/φ 轮/旧 epoch 全 fail-closed)。
- 部署草案:/opt/voice-agent-cybergril 目录、worker 健康端口 8082、HTTP 8360 + HTTPS 8361 同进程双入口(deploy/voice/serve_dual_8360_8361.py,共用同一状态/锁)、启动顺序含 TTS GPU≥6GB 前置与回滚;本卡未执行任何启停。契约(开关/字幕/失败/打断/挂断 + CG-011 主动开口接缝)见 docs/runbook-hel877-voice.md §3。
- 自测(零真实请求):281 pytest 全过(含语音 25 例:socket 级打断断流、SSE 边界、重放零调用、文字/语音同门互打断、回执全负例、token 房间隔离);scripts/verify_hel877_voice_chain.py 8/8;.12 agent venv(livekit-agents 1.8.8)真 SDK 检查 8/8(stdin-only 零写入零网络);双端口同进程本地冒烟通过;ruff 干净;需求检查双目标通过(含 handoff-HEL-877.json)。
- 施工中总工 HEL-874(e978e87)推上同分支,本卡已完整并入,不覆盖并行成果。未验证如实列明:端到端语音(TTS 停机/GPU 3.9GB<5.6GB)、真 LiveKit server 的 dispatch/delete、移动端实机——待接线/审核卡真机验收,未经真实验收不称 R5 完成。
2026-10-26 22:42:19 +08:00
施工员2号 a324fd57b7 Merge remote-tracking branch 'origin/hel-870-assembly' into agent/2/6a08a4c18f23 2026-10-26 22:39:06 +08:00
zonggongandmultica-agent e978e878df HEL-874 rework: bounded verbatim readback reaches the real provider input, real pinned Mem0 2.2.1 SDK contract verified live and the adapter's five wiring defects fixed, budget gates made provable (atomic reserve/settle embed ledger + independent llm_ops cap), definite-cap real-check execution table, 8360 isolated release package prepared (not deployed), 2026-10-26 user originals recorded source-only - semantic.context now quotes chain-verified archive rows (original words + role + when, <=200-char window with visible trim marker, summary demoted to a one-line pointer, same min(600,budget) cap) and episodic.context flips from gist-first-120-chars to verbatim-first (<=240) + role + date, both proven by tests/test_readback_hel874.py capturing the actual messages handed to the provider through the real API path: a detail absent from the gist yet quoted verbatim from a turn pushed outside the recent-20 window, the same for a semantic chain hit, and an unshared companion row never leaking through FTS, semantic or any anchor (AM-001); tools/verify_mem0_real_hel874.py runs the FIXED install itself (mem0ai 2.2.1 2c81af3, real embedded qdrant, real fastembed from the existing cache, deterministic 127.0.0.1 fake gateway, temp ledgers only): before=31970c4 dies at construction (second QdrantClient on the same path rejected by the SDK - recorded), and source-level findings confirmed live and fixed - unscoped search raises ValueError in 2.2.1 (add now carries user_id scope, search passes filters+top_k since limit is silently ignored), payload reads ride the SDK's own vector_store.client, the old outer search debit double-counted (wrapper is now the single accounting point), the SDK's internal extraction LLM was ungated (LlmLedgerGuard with its own llm_ops table, cap default 0) and check-then-debit could overshoot under concurrency (EmbedLedgerGuard now reserves atomically in BEGIN IMMEDIATE, releases on failure/cancel, purges stale holds); after fixes all 12 checks pass (construction/anchor payload/user scope/1 LLM + 2-3 embeds per add measured/extraction input set provably only prior gated batches incl. old memories and rolling history/unshared canary zero outbound/search single debit/concurrency never over cap/close-reopen intact/both zero caps refuse with ZERO outbound LLM requests), evidence in docs/rework/hel874/; mood sensitivity returns to the original repo's continuous rule 1.35-0.70*strength (bazi.sensitivity_from_strength, foundation commit 1012d8b) replacing HEL-870's invented 1.25/0.8 step and is labeled an engineering heuristic, never user-approved bazi science (external agent formulas remain not adopted); docs/budget-plan-hel874.md gives definite caps for the one-time team check (55 requests/0 embeds), first-week semantic activation (<=60 extract LLM / <=180 embeds) and user daily use with code-level hard-gate facts and authorization sources (old 20-30/100-200 were estimates, not approved quota; frozen 39/60 and 498/500 untouched and never挪用); deploy/hel874-release.md + cybergril-hel874-8360.service.example prepare the isolated fresh-empty-data-dir release with health capability matrix and data-preserving rollback (NOT executed, PR#11 not merged, no user-facing loop, no experience address); sources/comment-evidence-full-experience-20261026.json adds the two 2026-10-26 user originals (01a17806, 01a1780e full-experience-then-iterate) with authorship verified and manifest appended only - baseline/current/coverage/changes untouched; verification: 256 python tests pass on 3.11.16 venv + requirements.lock offline (3 new real-provider-input readback cases incl. AM-001 negative, mem0 fake rebuilt to mirror the real 2.2.1 surfaces, 9 cases), ruff clean, web tsc + 42 vitest + build pass, local 8360 stub smoke (loopback, isolated temp DB) health R4-assembly all green then stopped, real-SDK script re-run 12/12 after final edits, dual-target requirement checks 4/4 pass with handoff-HEL-874.json; zero real model calls, zero real embeddings, zero outbound (fake gateway on loopback + existing local model cache only), authoritative ledgers and freezes untouched, no existing services or profiles touched
Co-authored-by: multica-agent <github@multica.ai>
2026-10-26 22:13:45 +08:00
施工员2号andmultica-agent 23415fbcd0 HEL-876: voice-base read-only verification + R5/R6 readiness materials - docs/voice-r5-readiness-HEL876.md records the 2026-10-26 bounded read-only audit of 192.168.200.12 with timestamps: LiveKit/FunASR/8090 alive with health evidence, 8081 corrected as the voice-agent livekit worker (not a legacy lip-sync piece), CosyVoice TTS 9880 gracefully stopped since 2026-10-24 22:17 and NOT running, MuseTalk 8010 unit gone, ComfyUI 8188 holding 26.6GB of the 32GB GPU leaving 3.9GB free < ~5.6GB TTS requirement (the only hard resource gap, coordination not unilateral action), voice catalog (11 voices, rain selected) with whitelisted config field names only and no credentials read or printed, .11 gateway model-list probe and open-meteo outbound both 200; the R5 seam is fixed at src/cybergril/api/app.py:488 - the worker must reuse the same chat chain via a livekit LLM adapter speaking the identical RealChatAdapter protocol (epoch sync/serialized turns/barge-in cancel/idempotent replay/receipt round-trip), proven offline with zero model calls by scripts/verify_hel876_voice_seam.py (six checks all pass on 3.11 venv + requirements.lock installed from the local uv offline cache, ruff clean, explicitly NOT claimed as end-to-end voice verification); R6 seams located read-only: city placeholder ManagePanel.tsx:182 with no backend, no weather client (outbound now proven), demo-only material import :178/:183, full-backup boundary = companion.db + vec_public + mem0_public_history.db + embed ledger where copying the single db file is not a complete backup and Persona.fingerprint is the same-her check; three-column honest conclusions (verified / unverified / actually-missing) plus six zero-cost work items ready for dispatch; no product code touched (src/ web/ unchanged), no new product PR, candidate branch not merged, dual-target requirement checks 4/4 pass with handoff-HEL-876.json
Co-authored-by: multica-agent <github@multica.ai>
2026-10-26 21:42:41 +08:00
294 changed files with 30918 additions and 356 deletions
+1
View File
@@ -1,2 +1,3 @@
__pycache__/
*.pyc
web/node_modules
+10
View File
@@ -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 硬顶(待批,由总管填)
# =============================================================================
# 语音链路
+43 -6
View File
@@ -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
+31
View File
@@ -0,0 +1,31 @@
# CyberGril 语音会话端点脱敏配置示例(HEL-877,R5 后台施工)
#
# 复制为 config/voice.yaml(或用 CYBERGRIL_VOICE_CONFIG 指定路径)后由总管
# 按部署值填写;api_key/api_secret 只写服务器本地文件,不进仓库、不进浏览器、
# 不进任何日志。环境变量 CYBERGRIL_VOICE_* 可覆盖同名字段。
#
# 没有这份文件 = 本实例未配置语音:POST /api/voice/session 如实 503 拒绝,
# /api/health 的 capabilities.voice 保持 false,不伪造已接通。
# LiveKit SFU(.12 已有 docker voice-livekit;浏览器接入地址)
# 注意:7880 是纯 ws 且被通用 worker/8090 共享,不得改 TLS。页面走 HTTPS 时
# 浏览器连 ws:// 属混合内容会被拦——R5 方案是独立的 TLS 桥(拟议,未部署):
# deploy/voice/wss_bridge.example.py,livekit_url 用 wss://192.168.200.12:8363
# (详见 docs/rework/hel879/unified-review/r5-test-package.md §3)。
livekit_url: ws://192.168.200.12:7880
# 服务端 Twirp 调用地址(本机回环即可)
livekit_internal: http://127.0.0.1:7880
# LiveKit API 凭据(与 .12 /opt/voice-agent/.env 中 LIVEKIT_KEYS 同源;此处只放占位符)
api_key: <LIVEKIT_API_KEY_PLACEHOLDER>
api_secret: <LIVEKIT_API_SECRET_PLACEHOLDER>
# 专属 worker 的 agent_name:与通用 cascade-voice-agent 区分派发,互不争抢
agent_name: cybergril-voice-agent
# CosyVoice TTS 健康探测(TTS 不在就诚实拒绝开通话,不假装能说话)
tts_health_url: http://127.0.0.1:9880/health
# 浏览器接入 token 有效期(秒):只够加入房间,短时限
token_ttl_seconds: 300
# 会话登记有效期(秒):房间早空/挂断后登记自然过期
session_ttl_seconds: 7200
@@ -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
+156
View File
@@ -0,0 +1,156 @@
# 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. 安装命令(发布卡执行)
> **已被取代(HEL-879,2026-10-27)**:本节安装命令缺 `--offline`/`--no-index`
> 强制项。唯一照抄入口改为 `deploy/hel895-release-candidate.md` §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-879,2026-10-27)**:本节 4.1/4.2 按索引拉 `mem0ai==2.2.1`
> 同名 wheel,不能保证固定源码身份 2c81af3;固定 SDK 激活的唯一照抄入口改为
> `deploy/hel895-release-candidate.md` §4(固定 tarball 哈希 → 固定源码构建轮 →
> cp311 依赖准备 → 仅本地轮安装 → 同进程 co-import)。以下保留为历史证据。
**当前事实(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。
+233
View File
@@ -0,0 +1,233 @@
# HEL-895 · 统一发布候选(离线核验过,未执行部署)
> **HEL-879 发布工具收口(2026-10-27,隔离候选分支 `hel-879-assembly`)**:按总管
> 对 HEL-896 的独立复核完成四项有限收口——①归档一致性:checkpoint 返回值必查、
> 源/快照行数不一致或 checkpoint busy 一律 CLI 非 0 且不形成可交付包,verify 拒绝
> 记录了 `rows_match=False` 的旧包(§5);②目录内真实 SQLite(history_root 的
> `mem0_public_history.db` 等)停服口径走 checkpoint+逐表核对,在线模式只允许显式
> 单库 backup API、含 SQLite 的目录直接拒绝;③提前封账:`tools/seal_budget_hel879.py`
> 显式封账(真实用量保留、跨重启/归档拒绝,负例 S6);④安装入口唯一化:本文件 §2/§4
> 为唯一照抄入口(全 offline/no-index;mem0ai 只认固定源码轮),`deploy/hel874-release.md`
> §2/§4 已标注被取代。产物清单与精确提交见 `docs/rework/hel879/sdk-audit/` 与
> `docs/rework/hel879/release-artifacts.json`。**不要用旧父提交(04eff61/152479b)
> 的说明冒充本候选 head:以交付评论与 handoff JSON 记录的精确提交为准。**
> **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 与整合方式
> **HEL-879 返修勘误(2026-10-27)**:本节以下原文记录的是 **HEL-895/896 历史
> 候选**(分支 `hel-870-assembly`,PR #11 未合并),其 299 pytest / 55 vitest 等
> 验证数字属于那个历史 head,**不是当前发布候选**。当前统一发布候选是分支
> `hel-879-assembly`(在 7f0a739 之后正常追加返修提交,不强推、不并入 PR #11),
> 其精确 head、pytest/vitest/build 全量数字与证据以**交付评论与
> `docs/requirements/handoff-HEL-879.json` 记录为准**;本文件的安装/检查入口
> (§2/§4/§5/§6)继续适用于当前候选,产物清单见
> `docs/rework/hel879/sdk-audit/` 与 `docs/rework/hel879/release-artifacts.json`
> (`rework_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. 安装命令(发布卡执行;本节是唯一照抄入口——HEL-879 收口)
```bash
# 全新目录,逐条照抄;任何一步失败即停。全部安装强制 offline/no-index:
# 缓存命中即装,缓存缺失即停并留证;联网补齐只发生在获批的发布窗口内,
# 逐项 sha256 留证(seq48–51 勘误教训:不写 offline 的安装发生过网络获取)。
# 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:全新空目录 clone(空目录里没有仓库,不能直接 git fetch)
git clone ssh://git@192.168.200.36:222/leefer/CyberGril.git /opt/cybergril-hel874/current
git -C /opt/cybergril-hel874/current checkout <交付评论/handoff JSON 记录的精确head>
# 2.3 依赖与前端(uv/npm 一律 offline;缓存缺失即停)
cd /opt/cybergril-hel874/current
uv venv --python 3.11 .venv311
uv pip install --python .venv311/bin/python --offline -r requirements.lock
(cd web && npm ci --offline && npm run build)
# 2.4 服务单元(单元内已带 PYTHONPATH=<current>/src:requirements.lock 只装依赖,
# 产品代码经 PYTHONPATH 进程内可 import)
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
```
发布前必过检查按 §6 与 `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-879 收口)
**本节取代 `deploy/hel874-release.md` §4(历史文件,已标注被取代,不得再照抄)。**
旧 4.1/4.2 按 `deploy/mem0-stack.lock` 的 `mem0ai==2.2.1` 向索引拉同名 wheel——
**索引同名 wheel 不能证明固定源码身份 2c81af3**,这条路已废除。入口只有一条:
**固定 tarball 哈希 → 固定源码构建轮 → 目标 cp311 依赖准备 → 仅本地轮安装 → 同进程 co-import。**
- **第 0 步 · 产物清单核对**:`docs/rework/hel879/sdk-audit/sdk-audit.json`(本候选
精确提交上重跑)与 `docs/rework/hel879/release-artifacts.json` 记录闭包 55 项、
缺 41 项、缓存可复用 3 项与逐项哈希;发布卡先核对清单与目标提交一致。
事实口径(HEL-896 审计核定,HEL-879 重跑仍成立):三个顶层固定版本
(mem0ai 2.2.1 / qdrant-client 1.20.0 / fastembed 0.9.1)的 wheel 均为
`py3-none-any` 纯 Python;闭包按 3.11 Linux markers+extras 共 **55 项,产品
3.11 venv 缺 41 项**(28 纯 Python + 13 原生:grpcio/numpy/onnxruntime/
tokenizers/sqlalchemy/protobuf 等),这是闭包清单不是已完成的 cp311 下载结果。
seq48–51 pip 事件勘误保留:不写 offline 的安装发生过网络获取(uv 缓存取证),
故本入口全程 `--offline`/`--no-index`。
- **第 1 步 · 固定 tarball 哈希核对**(零下载):
```bash
sha256sum <hel857-data>/mem0-2c81af3.tar.gz
# 必须等于 deploy/mem0-stack.lock 记录的
# 9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23
```
不一致即停;不得改用索引 `mem0ai==2.2.1` wheel 冒充固定源(audit 已证明两者
身份不可互证)。
- **第 2 步 · 固定源码构建轮(HEL-879 返修:已完成可离线构建)**:此前此处写
「hatchling 当前不在任何本机缓存、待补」——经总管只读核查与本卡复核**勘误**:
uv wheels-v6/wheels-v7 均有 hatchling **1.32.4** 缓存(`release-artifacts.json`
`build_backend` 节逐路径留证)。固定源 tarball 已在 3.11.16 + `uv build --wheel
--offline` 下**离线构建出轮**(零下载、零安装、不混 3.10 site-packages):
`mem0ai-2.2.1-py3-none-any.whl`,
sha256 `698284b6083ab8b7761454d65876a6d1bb983d278ad21a7bee38d760cb20e9db`。
发布卡在目标机重跑同一命令并核对该哈希:
```bash
tar xzf <hel857-data>/mem0-2c81af3.tar.gz && cd mem0-2c81af3*/
uv build --wheel --offline --python 3.11 -o wheels/
sha256sum wheels/mem0ai-2.2.1-py3-none-any.whl
```
- **第 3 步 · 目标 cp311 依赖准备**:41 项缺项中**缓存今天即可复用 3 项**
(greenlet 3.5.6 cp311、pillow 12.3.0 cp311、truststore 0.10.5 py3-none-any,
uv wheels-v6/v7 缓存实测,HEL-879 重跑核实仍命中);其余 37 项
**由总管获批后发布窗口拉取**(`python3 -m pip download --only-binary :all:
--implementation cp --python-version 3.11 --platform manylinux2014_x86_64
--abi cp311 -r <缺项清单> -d wheels/`),逐项 sha256 留证;本卡零下载、零联网。
某固定版本若无 cp311 wheel,按 lock 注释如实上报缺项,不换版本号、不混 sdist。
- **第 4 步 · 仅本地轮安装**(--no-index 全程,目录里只有上两步产物):
```bash
uv pip install --python .venv311/bin/python --no-index \
--find-links <本地wheels目录> mem0ai==2.2.1 qdrant-client==1.20.0 fastembed==0.9.1 <41缺项>
```
`deploy/mem0-stack.lock` 的 mem0ai 行只作版本记录;安装来源必须是第 2 步的
固定源码轮(同版本号不同来源不互认)。
- **第 5 步 · 同进程 co-import 与模型落位**:
```bash
.venv311/bin/python -c "import cybergril.api, mem0, qdrant_client, fastembed; print('co-import ok')"
```
fastembed 模型缓存(hel857-data/fastembed-cache,Qdrant/bge-small-zh-v1.5 +
bm25 约 92MB,实测存在)拷入实例数据目录,真实 mem0 模式 cache_dir 指向它。
产品 venv 目前 `import mem0/qdrant_client/fastembed` 均缺、产品代码导入不受影响;
在 wheel 落位且 co-import 通过前,不得以 stub/FTS 冒充真实 Mem0 体验就绪。
真缺的下载(37 项运行闭包;hatchling 已勘误为缓存命中并完成离线构建,不在缺单)
仍待主卡授权,本卡不为补命令联网。
## 5. 停用与保留数据回退
与 `deploy/hel874-release.md` §5 相同:`CYBERGRIL_SEMANTIC=off|stub` 停语义层
(表保留、按后端区分续跑);`CYBERGRIL_R2_ENABLED=0` 停生活/主动联系;整实例
`systemctl stop` → 数据目录原样保留 → 切回上一 release/上一提交(R4 迁移全部
增量新表,旧代码可安全打开同一数据库);彻底撤销留数据档不删。
**HEL-896 归档/回退修订 + HEL-879 收口(2026-10-27,v3 返修)**:在线库只复制主
`.db` 会丢 WAL 里已提交的资料(总管复现件连 `companion` 表都不在)。发布归档一律
二选一:**停服务 + `PRAGMA wal_checkpoint(TRUNCATE)` 后整目录归档**(推荐),或
**在线用 SQLite backup API 对显式单库做一致性快照**。工具已按收口语义强制执行
(v3;v2 的两处已证缺陷由总管独立复核提出并修复):
- checkpoint 返回 busy≠0 或源/快照行数不一致 → CLI 非 0、不产生可交付归档
(「先停服」是前提,不是忽略失败结果的理由);
- 目录内真实 SQLite(history_root 的 `mem0_public_history.db`、向量目录内库等,
按文件头魔数递归识别)在停服口径一并 checkpoint+逐表核对;
- 在线模式只接受显式 `--db`(backup API);传入含 SQLite 的 `--file` 目录直接拒绝;
- `verify` 要求**三个计数直接相等**(manifest 源行数 = manifest 快照行数 = 恢复
实测;`rows_match` 只是额外约束——v2 的 `rows_match is True or …` 允许记录值
绕过源计数比较,源 2/快照 1 仍 ok,已废除);回退演练按源路径映射**实际恢复**
到归档旁的持久独立目标树、打开恢复库核对行数与哈希,输出 rollback_plan;
- 归档侧目标名无冲突映射:不同目录的同名 `ledger.db` 不再互相覆盖(v2 后写覆盖
前写仍先交成功包);同一源重复传入直接拒绝。
照抄命令:
```bash
# 覆盖范围:产品库(CYBERGRIL_DB)、语义向量目录 vector_root、推导史 history_root、
# 权威嵌入账 ledger_path(按部署实例 config/semantic.yaml 实际路径逐项代入)
# 停服口径(推荐;目录内 SQLite 一并一致性处理):
.venv311/bin/python tools/release_snapshot_hel896.py snapshot \
--name <release-或阶段名> --db <产品库> [--db <权威账>] \
--file <vector_root> --file <history_root> --out /opt/<实例>/archives/
# 在线兜底口径:仅显式单库(backup API),不得传含 SQLite 的目录:
.venv311/bin/python tools/release_snapshot_hel896.py snapshot --name <名> \
--db <产品库> --out /opt/<实例>/archives/ --online
.venv311/bin/python tools/release_snapshot_hel896.py verify --archive <产物>.tar.gz
```
产物含逐文件 SHA256 清单、**代码精确提交**(快照时刻 `git rev-parse HEAD`)、
逐表行数核对与回退演练结果;不删档,旧归档永久保留。回退=停服务 → 解包快照到
新数据目录(按 rollback_plan 的源路径映射)→ 指回上一 release/上一提交 → 起
service → `verify` 通过后交付。本节是**发布归档/回退要求**,不冒充 R6(CG-009
每日自动备份/手动完整导出)完成。
## 6. 本候选不做的事
不合并任何 PR(PR #11 保持开放待总管/白栖知决定);不部署、不交体验地址;
零真实 LLM/嵌入/下载;不触 8300–8350、共享语音口型、GPU/4080;不导入演示测试
档案为用户伴侣;不替视觉作者改稿(前端内容以原作者六文件与 CSS 可追溯为准)。
R5(HEL-878/879 语音)、R6(天气/素材/完整备份)、R7(有条件视觉观察)未接入,
界面保持诚实文案,不冒充已运行。
+13
View File
@@ -0,0 +1,13 @@
# CyberGril 语义记忆层固定栈清单(HEL-882;B 部分批准后由发布卡拉取)
# 固定源:mem0ai 2.2.1(源码提交 2c81af3bbe27d9bccb09c772d583df9e9be72ee5,
# tarball SHA256 9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23)。
# HEL-879 唯一安装入口:deploy/hel895-release-candidate.md §4(固定 tarball 哈希 →
# 固定源码构建轮 → cp311 依赖准备 → 仅本地轮安装 → 同进程 co-import)。
# 本文件的 mem0ai==2.2.1 行只作版本记录——不得按此行向索引拉同名 wheel 冒充固定源
# (旧 deploy/hel874-release.md §4 已被取代)。若某包无 3.11 wheel,发布卡须如实
# 上报缺项,不得换版本号冒充固定安装。版本来自 hel857-venv 实测安装
#(Python 3.10.12 侧 mem0ai 2.2.1 / qdrant-client 1.20.0 / fastembed 0.9.1),
# 3.11 侧需同版本 wheel。
mem0ai==2.2.1
qdrant-client==1.20.0
fastembed==0.9.1
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# CyberGril 专属语音 worker 运维脚本草案(HEL-877,本卡未执行)。
# 用法: bash /opt/voice-agent-cybergril/restart_cybergril.sh [stop|status]
# 边界: 只管本 worker(python -m cybergril.voice.worker),绝不匹配通用
# worker 的 `agent.py start`,不动 /opt/voice-agent 与 8090/8081。
set -u
BASE=/opt/voice-agent-cybergril
cd "$BASE"
VENV=/opt/voice-agent/agent/venv # 复用已装 livekit-agents 1.8.8 的 venv
PAT='cybergril.voice.worker start' # 精确匹配本 worker 命令行
for p in $(pgrep -f "$PAT"); do kill "$p" 2>/dev/null; done
sleep 2
for p in $(pgrep -f "$PAT"); do kill -9 "$p" 2>/dev/null; done
if [ "${1:-}" = "stop" ]; then echo "cybergril voice worker stopped"; exit 0; fi
if [ "${1:-}" = "status" ]; then
pgrep -af "$PAT" || echo "not running"
curl -s --max-time 3 "http://127.0.0.1:${CYBERGRIL_WORKER_HEALTH_PORT:-8082}/" \
&& echo " <- health" || echo "health port not answering"
exit 0
fi
set -a; source "$BASE/.env"; set +a
PYTHONPATH="$BASE" nohup "$VENV/bin/python" -m cybergril.voice.worker start > worker.log 2>&1 &
echo "cybergril voice worker starting (pid $!)"
sleep 18
grep -m1 "registered worker" worker.log >/dev/null && echo "OK: worker registered" \
|| { echo "FAIL:"; tail -5 worker.log; exit 1; }
+105
View File
@@ -0,0 +1,105 @@
"""统一 CyberGril 实例单进程双入口启动器(HEL-877 部署草案,本卡未执行).
HTTP 8360 与 HTTPS 8361(语音页需要 secure context 才能拿麦克风)必须共用
同一个 API 进程、同一份 app.state(epoch / chat_lock / store / 语音会话登记)。
HEL-879 收口(总管复核第 1 项):uvicorn 两个 Server 默认各自进入一次 FastAPI
lifespan,导致 epoch 1→2、Store/life/chat_lock 全部被第二次初始化替换——同一个
app 对象并不保证只初始化一次。本启动器改为:
- 应用生命周期由本启动器**独占管理且只进入一次**(`app.router.lifespan_context`);
- 两个 uvicorn Server 以 `lifespan="off"` 只负责监听,共享同一份状态;
- 任一监听退出(失败/取消/信号)先把另一个也停掉,**再**退出生命周期:
绝不出现一边还在接请求、一边已被另一个监听提前关库;循环与资源只关闭一次;
- `create_app` 的 lifespan 自带重入门卫:任何二次进入直接报错(fail-closed),
防止未来改回 auto 双 Server 时再次静默替换状态。
自签证书签发命令与回滚步骤见 docs/runbook-hel877-voice.md。
用法(部署卡执行,先 `cd` 到仓库根并激活 3.11 venv):
PYTHONPATH=src python deploy/voice/serve_dual_8360_8361.py \
--http-port 8360 --https-port 8361 \
--cert /opt/cybergril-voice-certs/cert.pem --key /opt/cybergril-voice-certs/key.pem
本地离线冒烟(不涉及任何现网/证书变更):
PYTHONPATH=src python deploy/voice/serve_dual_8360_8361.py \
--http-port 18360 --https-port 18361 --cert <本地测试证书> --key <本地测试密钥>
"""
from __future__ import annotations
import argparse
import asyncio
import contextlib
import os
import sys
import uvicorn
from cybergril.api.app import create_app
async def serve(http_port: int, https_port: int, cert: str, key: str, app=None) -> None:
# ONE app object, ONE lifespan owner: both listeners share state, locks,
# epoch and the store. The uvicorn servers never run the app lifespan
# themselves (lifespan="off") — the context manager below is its only
# owner, so initialization and shutdown each happen exactly once.
if app is None:
app = create_app()
http_server = uvicorn.Server(
uvicorn.Config(app, host="0.0.0.0", port=http_port, log_level="info",
lifespan="off")
)
https_server = uvicorn.Server(
uvicorn.Config(app, host="0.0.0.0", port=https_port, ssl_certfile=cert,
ssl_keyfile=key, log_level="info", lifespan="off")
)
async with app.router.lifespan_context(app):
servers = [asyncio.create_task(http_server.serve()),
asyncio.create_task(https_server.serve())]
try:
done, pending = await asyncio.wait(servers,
return_when=asyncio.FIRST_COMPLETED)
except asyncio.CancelledError:
# The launcher itself was cancelled: stop both listeners first
# (graceful should_exit, hard cancel only as a last resort), await
# their full exit, only then let the lifespan close (and re-raise).
# No listener is ever left running without its state.
for server in (http_server, https_server):
server.should_exit = True
for task in servers:
with contextlib.suppress(asyncio.CancelledError, Exception):
await asyncio.wait_for(asyncio.shield(task), timeout=5.0)
for task in servers:
task.cancel()
with contextlib.suppress(asyncio.CancelledError, Exception):
await task
raise
# Any listener ending stops the other FIRST; only after both listeners
# have fully exited does the lifespan context above close the loop's
# resources and the store — never the other way around.
for server in (http_server, https_server):
server.should_exit = True
for task in pending:
with contextlib.suppress(asyncio.CancelledError, Exception):
await task
for task in done:
task.result() # surface the first listener's real error, after cleanup
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--http-port", type=int, default=8360)
parser.add_argument("--https-port", type=int, default=8361)
parser.add_argument("--cert", required=True)
parser.add_argument("--key", required=True)
parser.add_argument("--db", default=os.environ.get("CYBERGRIL_DB"))
args = parser.parse_args()
if args.db:
os.environ["CYBERGRIL_DB"] = args.db
asyncio.run(serve(args.http_port, args.https_port, args.cert, args.key))
return 0
if __name__ == "__main__":
sys.exit(main())
+18
View File
@@ -0,0 +1,18 @@
# CyberGril 语音 worker 脱敏环境变量示例(HEL-877)
#
# 部署位置 /opt/voice-agent-cybergril/.env(权限 600,不进仓库)。
# 复用 /opt/voice-agent/agent/venv(livekit-agents 1.8.8)与 .12 现有
# LiveKit(7880)/FunASR(10095)/CosyVoice(9880);不新装任何依赖。
# 统一 CyberGril 实例(HTTP 8360 / HTTPS 8361 同进程双入口)
CYBERGRIL_API_BASE=http://127.0.0.1:8360
# 专属派发名(与 config/voice.yaml 的 agent_name 一致)
CYBERGRIL_AGENT_NAME=cybergril-voice-agent
# worker 注册 LiveKit 所需凭据(与通用 worker restart 脚本同源;只放占位符)
LIVEKIT_URL=ws://127.0.0.1:7880
LIVEKIT_API_KEY=<LIVEKIT_API_KEY_PLACEHOLDER>
LIVEKIT_API_SECRET=<LIVEKIT_API_SECRET_PLACEHOLDER>
# 专属健康端口(livekit-agents worker 内建 GET / 健康检查;8081 是通用 worker)
CYBERGRIL_WORKER_HEALTH_PORT=8082
# CyberGril 自己的音色状态文件(不读通用助手 voice_state.json)
CYBERGRIL_VOICE_STATE=/opt/voice-agent-cybergril/voice_state_cybergril.json
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""CyberGril R5 · WSS TLS bridge (PROPOSAL — 拟议材料,未在任何服务器执行).
背景(docs/rework/hel879/unified-review/r5-facts-20261027.md E2/E4):.12 的
LiveKit 7880 是纯 HTTP/WS 且通用 worker/8090 演示都依赖它保持明文,不能改
TLS;HTTPS 页面直连 ws:// 又是混合内容。本脚本在**新端口**终结 TLS 并把字节
流透明转发给本机 7880,使浏览器可用 ``wss://192.168.200.12:<port>`` 接入
LiveKit 信令,零新依赖(asyncio + ssl 标准库),不触碰既有服务。
- 监听(拟议):0.0.0.0:8363,证书 = 与 8361 同一张自签证书
(SAN=IP:192.168.200.12,存 /opt/cybergril-voice-certs/,600)。
- 上游:127.0.0.1:7880(仅回环,不暴露第二明文入口)。
- voice.yaml:livekit_url 改 wss://192.168.200.12:8363;
livekit_internal 保持 http://127.0.0.1:7880。
- 实现为透明 TCP TLS 终结(不解析 WebSocket/HTTP),LiveKit 信令是 7880 上的
HTTP/WebSocket,字节级转发即协议无关;WebRTC 媒体面(UDP/7881)不经本桥,
由 WebRTC 自带 DTLS/SRTP 加密。
- 每设备分别打开 https://192.168.200.12:8361 与 :8363,验证证书例外和实际
连接;不推断例外缓存范围。禁止用 --ignore-certificate-errors 冒充验收。
部署(获批后)示例:
python3 wss_bridge.example.py --listen 0.0.0.0:8363 \
--upstream 127.0.0.1:7880 \
--cert /opt/cybergril-voice-certs/cybergril-voice.pem \
--key /opt/cybergril-voice-certs/cybergril-voice.key
回滚:停本进程即可(独立进程,不影响 LiveKit/通用 worker/8090)。
本地冒烟(零网络出站):见
docs/rework/hel879/unified-review/r5-facts-20261027.md 与
docs/rework/hel879/unified-review/wss-bridge-smoke.log(一次性本地证书 +
dummy 上游回环验证字节穿透与 TLS 握手)。
"""
from __future__ import annotations
import argparse
import asyncio
import contextlib
import ssl
import sys
async def _pipe(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
try:
while True:
data = await reader.read(65536)
if not data:
break
writer.write(data)
await writer.drain()
except (ConnectionError, asyncio.CancelledError, ssl.SSLError):
pass
finally:
with contextlib.suppress(Exception):
writer.close()
async def _handle(
downstream: tuple[asyncio.StreamReader, asyncio.StreamWriter],
upstream_host: str,
upstream_port: int,
) -> None:
down_reader, down_writer = downstream
try:
up_reader, up_writer = await asyncio.open_connection(upstream_host, upstream_port)
except (ConnectionError, OSError):
down_writer.close()
return
await asyncio.gather(
_pipe(down_reader, up_writer),
_pipe(up_reader, down_writer),
return_exceptions=True,
)
async def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="TLS->plain TCP bridge (CyberGril R5 WSS proposal)")
parser.add_argument("--listen", default="0.0.0.0:8363", help="host:port to terminate TLS on")
parser.add_argument("--upstream", default="127.0.0.1:7880", help="plain upstream host:port")
parser.add_argument("--cert", required=True, help="TLS certificate PEM path")
parser.add_argument("--key", required=True, help="TLS private key path")
args = parser.parse_args(argv)
listen_host, _, listen_port = args.listen.rpartition(":")
up_host, _, up_port = args.upstream.rpartition(":")
if not listen_host or not up_host:
parser.error("listen/upstream must be host:port")
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain(args.cert, args.key)
async def handler(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
await _handle((reader, writer), up_host, int(up_port))
server = await asyncio.start_server(
handler, listen_host or "0.0.0.0", int(listen_port), ssl=ctx
)
addrs = ", ".join(str(s.getsockname()) for s in server.sockets or [])
print(f"wss-bridge(PROPOSAL) tls {addrs} -> {up_host}:{up_port}", file=sys.stderr)
async with server:
await server.serve_forever()
return 0
if __name__ == "__main__":
raise SystemExit(asyncio.run(main()))
+171
View File
@@ -0,0 +1,171 @@
# 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 结束即封账**,两种到达方式都有可执行动作(HEL-879 收口):额度用满时
`budget_total_requests_exhausted:55/55` 如实拒绝、不补发;**体验链提前走完
(如 51/55)时运行 `tools/seal_budget_hel879.py --db <团队实例库>` 显式封账**
——真实用量 51 原样写入并保留(不伪造 55、不清零),此后该库任何类别任何
余量的 `acquire` 都拒绝 `budget_sealed:51/55`,跨重启、跨 backup API 归档/
快照恢复同样拒绝(S6 负例:51/55 提前收尾、误重启、归档恢复均拒绝;重复
seal 幂等不覆盖原标记;无解除封账代码路径)。封账动作顺序:`systemctl stop`
8360 实例 → `seal_budget_hel879.py` → `tools/release_snapshot_hel896.py`
一致性快照(封账标记随库一同归档)→ 归档(数据目录 + 哈希清单)交交付评论。
`budget_total` 永不手工清零。
3. **旧测试数据不会转成用户伴侣**:测试档案、聊天、生活数据只存在于团队实例库与
归档;用户实例是全新空库,由用户自己创建档案;产品无任何「测试库导入用户库」
路径,发布卡明令禁止把 8360 数据目录复制/合并进用户实例数据目录。
4. 后续任何团队复检 = **新批准 + 全新隔离实例/新库**,绝不续在用户账上,也
**绝不在已封账的 8360 库上抬 `total_requests` 重开**(HEL-879 返修勘误:本节
旧版括注「或在 8360 实例上以新批准抬 total_requests」与「无解除封账路径」
自相矛盾——封账标记随库持久,抬 cap 不能也不得绕过它;该括注废除)。旧
8360 库及其封账标记原样归档留存,作为真实用量的证据链。
### 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,不因其余获批自动放开。
+140
View File
@@ -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 池)。
+51
View File
@@ -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 …] 四项通过"
]
}
+51
View File
@@ -0,0 +1,51 @@
{
"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 恢复后延迟"
]
}
+54
View File
@@ -0,0 +1,54 @@
{
"baseline_version": "1.11",
"read_commit": "31970c4dd3851aae67a922b03264743d504b8e97",
"task": "HEL-877:把已核清的语音底座接到统一 CyberGril 业务链(获批 R5 后台施工);新增专属 livekit worker(agent_name=cybergril-voice-agent,复用 .12 FunASR/CosyVoice/silero VAD,不 fork 不另搭平台)+ 统一实例 POST /api/voice/session 最小协议(建房/专属派发/短时限 token 服务端处理,密钥不进浏览器)+ 与文字前端同协议的 livekit LLM 适配器(POST /api/chat,SSE/epoch 409 有界重同步/turn_id 幂等/打断即断流)+ SDK 真实播放完成才确认的分享回执;零真实请求自测交可审核代码",
"requirement_ids": [
"CG-006",
"CG-010",
"CG-011",
"CG-013",
"CG-014",
"CG-015",
"CG-016",
"CG-018",
"CG-019",
"CG-020",
"CG-021",
"AM-001",
"GOV-001",
"GOV-002",
"GOV-003",
"GOV-004",
"GOV-005",
"GOV-006",
"DEL-001"
],
"deliverable": "src/cybergril/voice/ 独立模块:session.py+router.py(POST /api/voice/session、DELETE 结束删房释放麦克风/job、GET /api/voice/status 诚实开关;缺配置/无档案/TTS 不可用如实拒绝;app.py 仅最小注册且 capabilities.voice 不伪造)、livekit_wire.py(零新依赖 HS256 token+Twirp 派发/删房,与已装 SDK 同线上格式)、chat_api_client.py(与 real.ts 同协议:health epoch、SSE 分片鲁棒解析、409 一次重试、固定 turn_id、打断 finally 即断流)、llm_chat_api.py(livekit llm.LLM 适配器,只发当前用户话轮,服务端组装全部人格/记忆/关系;φ 原句经 ChatChunk 念出无回执)、receipt.py(should_ack 只有 played_to_end 且未打断未失败且带完整 receipt 且 epoch 一致才确认;TurnLedger 绑定 speech handle↔流;确认恰好一次)、stt_funasr.py/tts_cosyvoice.py(.12 已验证适配器原样复用带出处)、worker.py(agent_name=cybergril-voice-agent、独立 voice_state_cybergril.json 音色、无第二人格、cg.subtitle 字幕数据通道、CG-011 主动开口空操作接缝、专属健康端口 8082);config/voice.example.yaml 与 deploy/voice/{worker.env.example,restart_cybergril.sh.example,serve_dual_8360_8361.py} 部署草案(HTTP 8360+HTTPS 8361 同进程双入口共用同一状态/锁,runbook 含启动顺序/回滚/GPU 前置);docs/runbook-hel877-voice.md 契约+部署文档;tests/test_voice_session.py(9)+test_voice_chat_adapter.py(16)、scripts/verify_hel877_voice_chain.py、scripts/gen_hel877_sdk_check.py",
"acceptance": [
"语音轮次走统一链被证明:worker 适配器只调 POST /api/chat,文字/语音互相打断由服务端同一 chat_lock 轮次门处理(本地 socket 实测:语音打断文字轮旧行=cancelled 无幽灵行、打断取消即刻断 HTTP 流旧行 fail-closed、新轮干净)",
"完成轮重放不新增模型调用;409 旧 epoch 一次有界重同步后成功;SSE 多字节/CRLF/多行 data 分片边界解析正确;turn_id 固定使 SDK 内部重试幂等",
"分享 receipt 仅在 SDK 真实播放完成后确认:should_ack 对被打断/播放失败/未播完/φ 轮/残缺回执/旧 epoch 全负例拒绝(测试覆盖),确认恰好一次,服务端仍独立 fail-closed 校验",
"POST /api/voice/session 服务端建房+专属派发(agent_name=cybergril-voice-agent,protobuf CreateDispatch 与已装 SDK 同格式)+短时限 token(TTL≤300s,与 livekit.api.AccessToken 声明集全等,.12 真 SDK 比对通过);房间隔离(两 session 不同房、token 各自房间);DELETE 删房结束;缺配置/无档案/TTS 停机诚实拒绝且 capabilities.voice 保持 false",
"worker 不持有第二人格/记忆、不直连 LLM、不读通用助手 persona_state/voice_state(独立 voice_state_cybergril.json,D1 未拍板回落 default);CG-011 语音主动开口留明确空操作接缝并写清边界(仅已开启会话、同业务链、可打断、不新回执通道)",
"真 SDK 兼容在 .12 agent venv(livekit-agents 1.8.8)stdin-only 零写入零网络验证 8 项全过(真 LLM/LLMStream 子类、真 ChatContext、真流机制 ChatChunk+final+receipt、φ ChatChunk、token 声明集与 SDK AccessToken 全等、server token 同形、SpeechHandle/事件 API 存在、打断取消立即关流)",
"双入口同进程:本地 18360/18361 冒烟单 pid 双端口同 epoch 两端诚实拒答;HTTP 与 HTTPS 共用同一 app 对象/状态/锁,无第二实例写同一 DB",
"部署草案齐全(/opt/voice-agent-cybergril、健康端口 8082、启动顺序含 TTS GPU≥6GB 前置、回滚、禁抄历史 fuser 命令的明确警告);本卡未执行任何部署/启停/下载安装,未动 web/、三账、档案、4080、现网服务"
],
"deferred": [
"R5 真机验收:连续 3 轮、开口打断 ~1s 停声、结束停麦无残音、移动端 Safari/Chrome 麦克风——TTS 恢复(GPU 需 ≥5.6GB,现 3.9GB,ComfyUI 不停)并接线后在后续卡执行;未经真实验收不得称 R5 完成",
"视觉总监接线:语音按钮/字幕(cg.subtitle)/失败/打断/挂断按 runbook §3 契约;本卡未改 web/",
"总工独立审核与部署(runbook §4):.12 上对真 LiveKit server 的 dispatch/delete、HTTPS 证书签发与实机信任(每台手机高级→继续)待实测,不承诺每台手机点继续必然可用",
"语音内主动开口(CG-011 接缝)与 D1 音色最终决定、D3 用户设备动作、GPU 时段:总管集中与白栖知沟通",
"R6 天气/素材/完整备份承接不变;视觉观察默认关闭不变"
],
"requirement_changes": [],
"verification": [
"PYTHONPATH=src .venv311/bin/python -m pytest tests -q:281 passed(总工 HEL-874 返工后全量 256 + 本卡 25,含语音会话 9 与适配器/回执 16,全 fake HTTP/SSE/本地 127.0.0.1 socket,零真实模型/嵌入/合成/识别)",
"scripts/verify_hel877_voice_chain.py 一键全链 8 项全过(真实 app + fake LiveKit/TTS + 本地 socket:拒答序/专属派发/短时限 token/房间隔离/删房结束/多轮回执/重放零调用/409 重同步/打断 fail-closed/播放完成回执一次/capabilities.voice=false)",
"scripts/gen_hel877_sdk_check.py 生成脚本经 SSH stdin 在 .12 /opt/voice-agent/agent/venv/bin/python(livekit-agents 1.8.8、livekit 1.1.20)执行:8 项 ALL PASS(零写入、零网络、未触任何服务)",
"双入口本地冒烟:一次性自签证书(本机 openssl 生成)+ deploy/voice/serve_dual_8360_8361.py 于 18360/18361,单 pid、同 epoch、/api/voice/status 诚实、未配置 POST 503;测后进程与端口已释放",
"ruff check src tests scripts deploy:本卡代码 0 错(唯一遗留 SIM115 属既有 deploy/hel835_guard.py,非本卡);check_requirements.py 双目标(origin/hel-870-assembly 现tip e978e87 与 origin/main)含本 handoff 通过",
"读取位置记录:开工时 origin/hel-870-assembly=31970c4 与卡文一致;施工中总工推入 HEL-874(e978e87),本卡已完整 merge 保其成果不覆盖;origin/hel-876-voice-readiness=23415fb(总管读取点)已并入",
"未验证如实声明:端到端语音(TTS 停机未连麦)、真 LiveKit server 的 dispatch/delete 实测、worker 运行时行为(字幕/VAD/回声抑制仅源码级对齐 .12 已验证实现与 SDK 静态契约)、移动端实机"
]
}
File diff suppressed because one or more lines are too long
+54
View File
@@ -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 …] 四项通过"
]
}
+51
View File
@@ -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 …] 四项通过"
]
}
+51
View File
@@ -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 …] 四项通过"
]
}
+53
View File
@@ -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 …] 四项通过"
]
}
+52
View File
@@ -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 …] 四项通过"
]
}
+65
View File
@@ -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、未交体验地址"
]
}
+57
View File
@@ -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 与真实嵌入行为(本卡零真实调用)、目标发布机的实际归档执行(工具已自测,发布卡照抄)"
]
}
+47
View File
@@ -0,0 +1,47 @@
{
"baseline_version": "1.11",
"read_commit": "f92a86f5d8f839338311cdee08179049de34eb20",
"task": "HEL-898(HEL-879 接线卡,视觉总监本人编码):在已认可的交谈界面接上她的增量字幕(cg.subtitle 契约 v2)与语音内主动开口呈现,处理打断/文字接管/错误/挂断收尾;不另做聊天页、不改主题与整体动效;f92a86f 统一复核后的有限返修三项(worker 离场诚实收场、已退休 speech 不复活、短语音回复播放中可见)及回放基础设施",
"requirement_ids": [
"CG-004", "CG-006", "CG-010", "CG-011", "CG-018", "CG-020",
"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": "自总管输入包 3228b1e(INPUT-MANIFEST 逐文件 SHA-256)起的前端补丁,只动 web/src/voice/session.ts、web/src/chat/ChatPanel.tsx 与测试:1) session.ts 严格解析 v2 信封(v===2、正整数 seq、kind 白名单、角色与种类对应、她的事件必带 speech_id、assistant_final 必带 turn_id、proactive_* 必带正整数 message_id;不合格整条丢弃且不推进序号);每次通话(控制器代次)独立 seq 高水位,seq<=已应用即丢;按 speech 生命周期只接受当前打开的一轮,已关闭/被取代的 speech 不再显示,旧 speech 的高 seq 事件不能关闭新轮。VoiceView 新增 her(speech/text/state open|final|done/turnId/messageId/proactive),与用户 ASR 草稿 heard 分槽。2) turn_start 开轮,assistant_interim 整句替换,assistant_final 原子替换为落库文本并请页面回读 /api/messages,turn_end 关轮(无 final 的关轮按未成为正式回复即清),turn_cancel/error(φ)/user_final/文字接管 hush/结束/掉线/卸载/重开即时清;φ 原句仍在语音行以“未记入聊天”显示。3) proactive_start 以落库原文、同源 message_id 显示在她的位置,proactive_end 保留至记录或下一轮接替,proactive_cancel 即清;不发任何请求、不标已读、不开麦、不重 TTS、不进入正式记录。4) ChatPanel:她的语音行进入与文字回复相同的舞台槽位(当前一两句、自动续读、暂停/回看、全文),生长中沿用文字流的“最新一句先压住”规则,逐分片不重启动画;落库行到来时同一 turn 同一位置接替,行键不变不重放;被撤回的语音行以 instant 方式立即离场(不淡出);减少动态设置沿用既有逻辑。你用语音开始新一轮时与发送文字一致恢复自动呈现(原先暂停状态下她的回答不会进来)。5) 测试:voice.test.ts 改为 v2 帧并新增 11 项(录制样例三场景回放、信封负例、乱序/重放/迟到/双 handle、φ、主动开口及打断、文字接管/挂断/重开、舞台行键接替);fake-livekit.js 未带信封的帧自动套 v2;hel878_mock.mjs 增 /__proactive;hel878_shots.mjs 的她的 final 与 φ 改为 v2 生命周期;新增 hel898_shots.mjs(两主题×电脑/手机)。【f92a86f 有限返修,首轮读取 3228b1e;本轮输入包 166 文件 SHA-256 全符】1) livekit.ts 监听 RoomEvent.ParticipantDisconnected(vendor 2.22.4 原名 participantDisconnected,emit(participant, reason)),只在本房间她的 worker(按身份记住的 agent 或 isAgent)离开、且房内没有其他 agent 时报 agentLeft;房间关闭中不报。session.ts 的 RoomEvents 新增 agentLeft:仅当前代次且 agent 已接入时走既有 fail(清字幕/草稿、停麦、清播放、关房、DELETE 本次服务房,失败入欠账重试),中文提示 WORDS.agentLeft;普通参与者离场、旧房间迟到离场、用户主动挂断都不触发。2) 已关闭 speech 由 64 项窗口数组改为每次通话一个 Set,随通话代次释放;seq 严格门不变。3) 语音回复开放态改为 live=speaking:reading.pagesOf 对其最后一块只显示已成句部分(。!?!?… 及其后的收尾引号括号),未成句部分仍压住;文字流 live=generating 的规则不变。已显示的语音行之后只会在同键下增长(短句与下一句合并时):LineStage.grow 原位追加新字的既有墨迹,不重建行、不重放;减少动态时直接设文本。ChatPanel 签名对语音行计入文本,全文页“还在说…”也适用于 speaking;ReadingClock 对同键增长只延长停留时间。没有新布局或样式。4) 测试:hel879_frames 的 DOCUMENTED GAP 改为拒绝断言,并新增每通话独立用例;voice.test 新增短回复/长句/文字流不变用例;新增 hel898_agent_left.test.ts(真实 livekitTransport 适配器 + 脚本化 SDK 事件名,另有控制器负例);fake-livekit.js 增 ParticipantDisconnected/leave/lateLeave/guest;hel878_mock.mjs 增 /__pid、/__reset,监听失败即退出;hel879_unified_shots.mjs 修回放基础设施(端口空闲与 pid 确认、多段录制按当前通话单调重编号并写 seq-renumbering.json、新通话重置、每主题 /__reset 隔离、负例原样发送并断言拒绝),加三项正反例和 CDP screencast→WebM 录屏",
"acceptance": [
"假 LiveKit 有序回放 normal-turn.jsonl:用户草稿→用户终稿→turn_start→两次增量(整句替换)→final(带 turn_id)→turn_end;她的字在舞台出现时 /__rows 尚无该回复;落库后记录中只出现一次(页面只回读,不写)",
"interrupt.jsonl 与负例:turn_cancel 后她的未完成行立即离场;被取消轮之后的高 seq 增量/final/重复 turn_start 均不显示;整段录制重放不改变界面;双 handle 时旧 speech 的 turn_end/turn_cancel 不关闭新轮;无 final 的 turn_end 视为未成为正式回复并清除",
"φ:error 原句在语音行显示并标“未记入聊天”,她的增量行立即清除,随后 turn_cancel(error_phi) 不抹掉 φ 原句;不落正式记录",
"proactive.jsonl:原文一字不差显示在她的位置并保留落库行身份(id/turn),可被你的语音打断(proactive_cancel 即清);全程页面无任何非 GET 请求(无回执、无已读、无消息写入),不调用 onSpoke/onRecord",
"文字接管、结束语音、掉线、新通话:她的行即时清;旧房间迟到帧被代次挡住;新通话序号从 1 重新开始并正常显示",
"两主题(月光/雾光)电脑 1440×900 与手机 390×844 截图与连续帧:沿用已认可位置与组件,无新增组件或样式;减少动态设置下她的字照常出现、无入场动画",
"工程:tsc 0 错;eslint 0 错 1 条既有警告(ChatPanel fullButton ref,HEL-897 起即有);vitest 8 文件 99/99;vite build 通过;浏览器 hel898 61/61,HEL-878 回归 49/49;修前对照:新 voice.test.ts 在原 session.ts/ChatPanel 上 12 项失败",
"f92a86f 返修-1:worker 离场(房间仍开)时通话即时结束:她的行清除,提示“她那边的语音中断了……”,麦克风轨 ended,播放容器移除,本次 session DELETE 恰好一次;普通参与者离场不挂断;旧房间迟到离场不影响新通话;用户主动挂断只有一次 DELETE、没有离场提示;DELETE 失败入欠账并重试(单测)",
"f92a86f 返修-2:adversarial-gates 66 次挤出后,speech_A 以新 seq 重开/增量/终稿都被拒(单测与浏览器原样探针),之后新 speech 正常进入;新通话从 seq 1 开始,不受旧通话已退休 id 影响",
"f92a86f 返修-3:worker-normal 回放中“在听呢,”“…那件事”未成句时舞台不显示她的半句;第三个增量成句后、final 之前即显示整句,并在播放期间保持;final 与落库行接替为同一 DOM 行(不重建、不重放);短句与下一句合并时同节点原位增长、首字墨迹不动;长句只显示已完成的行、不整段铺满;文字流仍压住最新块",
"f92a86f 返修工程:tsc 0;eslint 0 错 1 条既有警告;vitest 10 文件 118/118;build 通过;修前对照 vitest 7 项失败;统一实际 worker 帧浏览器回放(moon/fog × 电脑/手机 + 减少动态)修后 53/53、修前 30/53(失败都落在三项及其连带);hel898_shots 61/61、hel878_shots 49/49;8 段 WebM 录屏取自真实 screencast 帧"
],
"deferred": [
"真实通话未测:真实 LiveKit/WebRTC、真实 worker 与 SDK 回调时序、真机/Safari/iOS、可信 HTTPS+WSS、R5 连续三轮/1 秒打断/停麦无残音/有限预算,均仍由 HEL-797 主卡承接;本卡全部为假 LiveKit、假音频设备与 Chromium 无头 Edge",
"CG-011 语音内主动开口仅是“会话开时念一条已落库旧留言”的最小接缝(后台默认关,本卡不启用);会话中持续主动联系、激活节奏、未读联动策略未完成,不得记为 CG-011 完成",
"后台并行小修(建房延迟后的清理唤醒、主动 say 的 start 发得过晚、无完整 receipt 错误发 final)由原总工在 HEL-879 处理;本卡按 v2 正确语义实现,最终后台 commit 出来后需按其统一复核(特别是 proactive_start 晚于音频开始时的呈现)",
"已知边界:seq 按“一次 worker job 一个序列”实现;若同一房间内 worker 重连且序号归零,前端会按契约丢弃其帧直到超过旧高水位——需后台确认 job 与房间一一对应",
"原版 Python 需求检查(scripts/check_requirements.py --base-ref origin/main,含本交接)未在本运行时执行(无 Python、无 git),须总管对实际目标分支复跑",
"D1 音色、cap=0、R6/R7、整体沉浸体验由主卡与白栖知决定;工程自测不是用户体验验收",
"f92a86f 返修:真实 SDK 下 worker 离场事件的时序(含网络重连期间 remoteParticipants 被清空但不发离场事件)只按 vendor 源码核对,未在真实房间验证",
"回放基础设施把多段录制重编号(原始帧保留在 fixtures,映射见 seq-renumbering.json),证明的是页面在“同一通话单调 seq”下的呈现与门,不证明 worker 跨 job 的 seq 行为"
],
"requirement_changes": [],
"verification": [
"读取:HEL-898 卡(启动交接与原说明)、输入包 3228b1e:docs/voice-subtitle-contract-v2.md、docs/rework/hel879/voice-seam-interfaces.md、subtitle-samples/*.jsonl、src/cybergril/voice/seam.py 与 worker.py(事件发出点)、AGENTS.md、docs/requirements/README.md/current.md(v1.11)/coverage.md/changes.md、sources/integration-plan-v1.md R5",
"工具:沿用本机既有离线工具链(HEL-834 留存 node v22.11.0;HEL-897 留存 web/node_modules,package-lock 哈希与本包一致,目录联接使用);未下载、未安装",
"web:tsc --noEmit 0;eslint src 0 错 1 既有警告;vitest run 8 文件 99/99(原 88 + 新增);vite build 通过;日志随交付包",
"浏览器:node tests/hel878_mock.mjs + node tests/hel898_shots.mjs(无头 Edge、脚本化 LiveKit 替身、Chromium 假音频设备)61/61;node tests/hel878_shots.mjs 回归 49/49;截图与连续帧随交付包",
"无模型、嵌入、ASR、TTS 请求,无真实麦克风采集,无部署、合并、服务或绑定改动;未改后台、总装分支或 PR#11",
"f92a86f 返修读取:卡内三项说明、HEL-898-统一复核返修输入-f92a86f.zip(166 文件 SHA-256 全符)、web/public/vendor/livekit-client-2.22.4.umd.min.js 中 RoomEvent.ParticipantDisconnected 的定义与 emit(participant, reason)、断开清理路径不发该事件;本包缺 docs/rework/hel879/subtitle-samples,测试运行时沿用 3228b1e 输入包同名文件(不入补丁)",
"f92a86f 返修浏览器:node tests/hel879_unified_shots.mjs <msedge> <out> 8879 9499(自带 mock,先确认端口空闲与 pid);hel898_shots/hel878_shots 由本运行自起 mock(核对 pid 后按 PID 停止)"
]
}
+60
View File
@@ -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`)。
+46
View File
@@ -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 并在清理临时目录前落盘,避免同类不可恢复"
}
+101
View File
@@ -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": []
}
+32
View File
@@ -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."
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

@@ -0,0 +1,12 @@
{
"ok": true,
"checks": 9,
"fail_count": 0,
"netguard": {
"guard_loaded": true,
"nonloopback_connect_attempts": 0,
"nonloopback_dns_lookups": 0,
"rejected": []
},
"service_epoch_isolation": "CYBERGRIL_MODELS loopback, caps 0, socket guard rejecting non-loopback"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 KiB

@@ -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\": \"a6173b4a79a84dbd6378281c17b23127\", \"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\": \"[\\\"494b27cf-6e99-4e9a-ad17-bc4c9d2f42ec\\\"]\"}, \"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": "a6173b4a79a84dbd6378281c17b23127",
"attempts": 1,
"entry_ids": null
},
"after_release": {
"original_batch": {
"state": "done",
"reason": null,
"owner": null,
"attempts": 1,
"entry_ids": "[\"494b27cf-6e99-4e9a-ad17-bc4c9d2f42ec\"]"
},
"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,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': '9a6afb62871305fe6c7a056acf0b8797'} batch_concurrent=pending final={'state': 'done', 'reason': None, 'entry_ids': '[\"9838ab55-15ab-4872-b7cd-1ba91faea2d7\"]'} 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': '8f42725681bcfda7043797c128eb0750'} batch_concurrent=pending final={'state': 'done', 'reason': None, 'entry_ids': '[\"e71ba931-650f-40a3-b571-5945829f6f7c\"]'} 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': '[\"80c8cb6b-e437-478c-a3fb-46d386ac9a07\"]'} after={'state': 'done', 'reason': None, 'entry_ids': '[\"80c8cb6b-e437-478c-a3fb-46d386ac9a07\"]'} 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,172 @@
{
"started": "2026-10-26T19:35:46Z",
"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-26T19:35:47Z",
"gateway_requests_total": 3,
"failures": [],
"temp_dir": "/tmp/multica-task-29494715/hel874-real-sdk-8dkofioe"
}
@@ -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=[\"07401cdd-4964-4c74-aab5-516ab09cf5e4\", \"4d76b52d-186c-4b21-8bfa-e81341213589\"]"
},
{
"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,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\": \"[\\\"19039615-6f32-48e6-979b-715d2cd56fcb\\\"]\"}"
},
{
"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\": \"[\\\"19039615-6f32-48e6-979b-715d2cd56fcb\\\"]\", \"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\": \"[\\\"f26d374a-858e-4dde-b2de-ae8af7057021\\\", \\\"258ab9a5-d523-45b0-ada1-d58fbda50ea7\\\"]\", \"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\": \"[\\\"e553422c-1840-4ff4-b51c-a3586cc34179\\\"]\", \"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": "4a048d364194a961365c30ee1e570233",
"attempts": 1,
"entry_ids": null
},
"client_usable": true
},
"before_store_close": {
"state": "done",
"reason": null,
"owner": null,
"attempts": 1,
"entry_ids": "[\"19039615-6f32-48e6-979b-715d2cd56fcb\"]"
},
"late_worker_error": null,
"persisted": {
"state": "done",
"reason": null,
"owner": null,
"attempts": 1,
"entry_ids": "[\"19039615-6f32-48e6-979b-715d2cd56fcb\"]",
"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": "[\"f26d374a-858e-4dde-b2de-ae8af7057021\", \"258ab9a5-d523-45b0-ada1-d58fbda50ea7\"]",
"row_done": 2,
"gateway_posts_resume": 2
}
},
"cancelled_aclose": {
"after_cancel": {
"client_usable": true,
"batch": {
"state": "pending",
"reason": null,
"owner": "a712e0367aa123b265b47e2d3223846a",
"attempts": 1,
"entry_ids": null
},
"closing_cleared": true
},
"after_worker": {
"state": "done",
"reason": null,
"owner": null,
"attempts": 1,
"entry_ids": "[\"73f87468-f546-46d7-8116-37d4fe2818b1\"]"
},
"client_usable_after_real_close": false
},
"concurrent_aclose": {
"batch": {
"state": "done",
"reason": null,
"owner": null,
"attempts": 1,
"entry_ids": "[\"e553422c-1840-4ff4-b51c-a3586cc34179\"]",
"row_done": 1
},
"client_usable_after_close": false
},
"failures": []
}
+161
View File
@@ -0,0 +1,161 @@
{
"card": "HEL-879",
"generated_by": "release-artifacts inventory (offline, zero downloads)",
"release_code_commit": "01ff91985920162cddabc5f190d146d86793627d",
"rework_head": "see delivery comment (branch hel-879-assembly, appended after 7f0a739; this inventory refreshed on that head)",
"audit_evidence": "docs/rework/hel879/sdk-audit/sdk-audit.json (head c121c42e39174e0f627f129e7f7e5700f33acf26)",
"single_install_entry": "deploy/hel895-release-candidate.md #4 (supersedes deploy/hel874-release.md #4)",
"fixed_source": {
"artifact": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-data/mem0-2c81af3.tar.gz",
"sha256": "9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23",
"lock_record": "deploy/mem0-stack.lock: 9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23",
"matches_lock": true,
"source_commit": "2c81af3bbe27d9bccb09c772d583df9e9be72ee5"
},
"closure": {
"total": 55,
"missing": 41,
"missing_pure_python": [
"backoff",
"coloredlogs",
"distro",
"fastembed",
"filelock",
"flatbuffers",
"fsspec",
"h2",
"hpack",
"httpcore2",
"httpx2",
"huggingface-hub",
"humanfriendly",
"hyperframe",
"loguru",
"mem0ai",
"mpmath",
"openai",
"portalocker",
"posthog",
"pytz",
"qdrant-client",
"requests",
"sniffio",
"sympy",
"tqdm",
"truststore",
"urllib3"
],
"missing_native": [
"charset-normalizer",
"greenlet",
"grpcio",
"hf-xet",
"jiter",
"mmh3",
"numpy",
"onnxruntime",
"pillow",
"protobuf",
"py-rust-stemmers",
"sqlalchemy",
"tokenizers"
]
},
"cache_reusable_reverified": [
{
"name": "greenlet",
"version": "3.5.6",
"cache_hits": [
{
"kind": "uv:wheels-v7",
"entry": "3.5.6-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64",
"content_digest": "d7ae14625784af8a268e8c156533b2e9d7694680e80a8466c8e5808cc3f1a005"
},
{
"kind": "uv:wheels-v6",
"entry": "3.5.6-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64",
"content_digest": "2fc3afa56933ea2cbaf6005c1e61565e769db1f202b3d8c53a67e59a2ed307f7"
}
]
},
{
"name": "pillow",
"version": "12.3.0",
"cache_hits": [
{
"kind": "uv:wheels-v7",
"entry": "12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64",
"content_digest": "02620d3560d2e91e0920c1045ed814b02a4d15d8c1c960f43a060739acf0dc1f"
},
{
"kind": "uv:wheels-v6",
"entry": "12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64",
"content_digest": "657f02aee576df196a22c130a65a3dd0f7438a8b859c2221448bea2dac6d7112"
},
{
"kind": "uv:wheels-v6",
"entry": "12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64",
"content_digest": "02620d3560d2e91e0920c1045ed814b02a4d15d8c1c960f43a060739acf0dc1f"
}
]
},
{
"name": "truststore",
"version": "0.10.5",
"cache_hits": [
{
"kind": "uv:wheels-v7",
"entry": "0.10.5-py3-none-any",
"content_digest": "54c5d4c83a1183954a6ca33169f8881c576160c722e93064ece271776ee2eac4"
},
{
"kind": "uv:wheels-v6",
"entry": "0.10.5-py3-none-any",
"content_digest": "54c5d4c83a1183954a6ca33169f8881c576160c722e93064ece271776ee2eac4"
}
]
}
],
"build_backend": {
"hatchling_cached_or_local": true,
"hatchling_version": "1.32.4",
"hatchling_cache_evidence": [
"uv wheels-v6: ~/.cache/uv/wheels-v6/pypi/hatchling/1.32.4-py3-none-any",
"uv wheels-v7: ~/.cache/uv/wheels-v7/pypi/hatchling/1.32.4-py3-none-any",
"uv archive-v0 unpacked: hatchling-1.32.4.dist-info (wheels-v6 + wheels-v7 linked copies)"
],
"offline_wheel_built": {
"built_at": "2026-10-27T04:10:00Z",
"command": "cd <extracted mem0-2c81af3 tarball> && uv build --wheel --offline --python 3.11 -o <out>",
"tool": "uv 0.13.5, uv-managed CPython 3.11.16, build backend hatchling from the offline cache only",
"source_tarball_sha256": "9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23",
"source_commit": "2c81af3bbe27d9bccb09c772d583df9e9be72ee5",
"wheel": "mem0ai-2.2.1-py3-none-any.whl",
"wheel_sha256": "698284b6083ab8b7761454d65876a6d1bb983d278ad21a7bee38d760cb20e9db",
"wheel_bytes": 352586,
"wheel_files": 153,
"installed_anywhere": false,
"network_used": false,
"note": "HEL-879 勘误:v1 清单一面写 hatchling_cached_or_local=true 一面又称本机缺 hatchling,并把 hatchling 列入待下载——总管只读核查指出 uv v6/v7 均有 1.32.4 缓存。已核实并直接完成可离线构建:固定源码 tarball(sha 与 lock 一致)经缓存 hatchling 离线构建出上面这只 wheel,未安装、未联网、未混 3.10 site-packages。待下载清单据此只余 41 项运行闭包(不含 hatchling)。"
}
},
"fastembed_models": {
"cache_dir": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-data/fastembed-cache",
"exists": true,
"models": {
"models--Qdrant--bge-small-zh-v1.5": {
"files": 13,
"bytes": 190444780
},
"models--Qdrant--bm25": {
"files": 39,
"bytes": 56247
}
},
"default_cache_dir": "/home/xiaomo/.cache/fastembed",
"default_cache_exists": false,
"release_note": "真实 mem0 模式把 fastembed cache_dir 指向发布实例数据目录内的该缓存拷贝;本次零下载、不推理"
},
"downloads_this_card": 0,
"note": "产物清单以 release_code_commit 为准;不要用旧父提交(04eff61/152479b/c121c42)说明冒充该 head。缓存命中为解包目录时记录内容摘要(目录无单一 sha256),安装时以 --offline 命中为准。"
}
@@ -0,0 +1,86 @@
{
"started": "2026-10-26T19:35:15Z",
"venv_python": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-879-3c721d56b938/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-29494715/hel882-release-jk7k2a25/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-879-3c721d56b938/workdir/CyberGril/.venv311/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-26T19:35:18Z"
}
@@ -0,0 +1,67 @@
{
"ok": true,
"code_commit": "7c73d392b95f3d55091a066db0ffe9bc6c475e0d",
"tool_version": 3,
"checks": [
{
"name": "naive_online_copy_loses_wal_reproduced",
"ok": true,
"detail": "error=no such table: companion"
},
{
"name": "busy_checkpoint_state_reproduced",
"ok": true,
"detail": "checkpoint=(1, 9, 8) naive_snapshot_events=error=DatabaseError:database disk image is malformed"
},
{
"name": "fix1_busy_checkpoint_fails_no_archive",
"ok": true,
"detail": "error=yes leftovers=0"
},
{
"name": "fix1b_row_mismatch_fails_no_archive",
"ok": true,
"detail": "error=源/快照行数不一致,不打包:/tmp/multica-task-3939683741/hel879-snapshot-s artifacts=[]"
},
{
"name": "fix_verify_rejects_rows_match_false_legacy_package",
"ok": true,
"detail": "ok=False checks=[{'db': 'databases/companion.db', 'restored': '/tmp/multica-task-3939683741/hel879-snapshot-selftest-a4xt_3p_/verify-restore-legacy-pkg.tar-20261027-042723/by-source/tmp__multica-task-3939683741__hel879-snapshot-selftest-a4xt_3p___instance__companion.db', 'sha256_ok': True, 'rows_ok': False, 'counts': {'companion': 1, 'chat_message': 1, 'events': 1}, 'source_counts': {'companion': 1, 'chat_message': 1, 'events': 1}, 'snapshot_counts': {'companion': 1, 'chat_message': 1, 'events': 0}, 'rows_match_recorded': False, 'rejected_legacy_package': True}]"
},
{
"name": "fix2_online_mode_refuses_dir_with_sqlite",
"ok": true,
"detail": "在线模式只允许显式单库 backup API(--db);--file /tmp/multica-task-3939683741/hel879-snapshot-selftest-a4xt_3p_/instance/history_root"
},
{
"name": "fix2_stop_mode_embeds_sqlite_via_consistency_path",
"ok": true,
"detail": "embedded=['/tmp/multica-task-3939683741/hel879-snapshot-selftest-a4xt_3p_/instance/history_root/mem0_public_history.db']"
},
{
"name": "fix3_verify_maps_source_paths_and_passes",
"ok": true,
"detail": "ok=True commit=7c73d392b95f3d55091a066db0ffe9bc6c475e0d plan_entries=4"
},
{
"name": "backup_api_snapshot_complete",
"ok": true,
"detail": "{\"companion\": 1, \"chat_message\": 1, \"events\": 1}"
},
{
"name": "fix_rows_match_true_cannot_bypass_source_counts",
"ok": true,
"detail": "ok=False counts={'companion': 1, 'chat_message': 1, 'events': 1} source={'companion': 1, 'chat_message': 1, 'events': 2}"
},
{
"name": "fix_same_basename_no_overwrite_and_duplicate_refused",
"ok": true,
"detail": "snap_names=['ledger.db', 'ledger__0e75f128.db'] verify_ok=True restored_rows=['first', 'second'] dup_refused=yes"
},
{
"name": "restore_drill_paths_persist",
"ok": true,
"detail": "restore_root=/tmp/multica-task-3939683741/hel879-snapshot-selftest-a4xt_3p_/same-restore"
}
]
}
+886
View File
@@ -0,0 +1,886 @@
{
"head": "7c73d392b95f3d55091a066db0ffe9bc6c475e0d",
"scope": "只读审计:3.10 固定栈安装元数据按 3.11 Linux markers+extras 求闭包,对照产品 3.11 venv 与本机 uv/pip 缓存;这是依赖闭包清单,不是已完成的 cp311 解析或下载结果",
"read_only": true,
"zero_downloads_this_card": true,
"closure_total": 55,
"missing_count": 41,
"missing_pure_python": [
"backoff",
"coloredlogs",
"distro",
"fastembed",
"filelock",
"flatbuffers",
"fsspec",
"h2",
"hpack",
"httpcore2",
"httpx2",
"huggingface-hub",
"humanfriendly",
"hyperframe",
"loguru",
"mem0ai",
"mpmath",
"openai",
"portalocker",
"posthog",
"pytz",
"qdrant-client",
"requests",
"sniffio",
"sympy",
"tqdm",
"truststore",
"urllib3"
],
"missing_native": [
"charset-normalizer",
"greenlet",
"grpcio",
"hf-xet",
"jiter",
"mmh3",
"numpy",
"onnxruntime",
"pillow",
"protobuf",
"py-rust-stemmers",
"sqlalchemy",
"tokenizers"
],
"cache_reusable_now": [
"greenlet",
"pillow",
"truststore"
],
"top_level": {
"mem0ai": {
"name": "mem0ai",
"version": "2.2.1",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": "{\"dir_info\": {}, \"url\": \"file:///home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-src\"}",
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "fixed-source"
},
"qdrant-client": {
"name": "qdrant-client",
"version": "1.20.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
"fastembed": {
"name": "fastembed",
"version": "0.9.1",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
}
},
"missing_detail": [
{
"name": "backoff",
"version": "2.2.1",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "charset-normalizer",
"version": "3.5.2",
"wheel_tags": [
"cp310-cp310-manylinux_2_17_x86_64",
"cp310-cp310-manylinux2014_x86_64",
"cp310-cp310-manylinux_2_28_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "coloredlogs",
"version": "15.0.1",
"wheel_tags": [
"py2-none-any",
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "distro",
"version": "1.9.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "fastembed",
"version": "0.9.1",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "filelock",
"version": "4.1.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "flatbuffers",
"version": "25.12.19",
"wheel_tags": [
"py2-none-any",
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "fsspec",
"version": "2026.9.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "greenlet",
"version": "3.5.6",
"wheel_tags": [
"cp310-cp310-manylinux_2_24_x86_64",
"cp310-cp310-manylinux_2_28_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [
{
"kind": "uv:wheels-v7",
"path": "/home/xiaomo/.cache/uv/wheels-v7/pypi/greenlet/3.5.6-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64",
"name": "3.5.6-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64",
"sha256": null
},
{
"kind": "uv:wheels-v6",
"path": "/home/xiaomo/.cache/uv/wheels-v6/pypi/greenlet/3.5.6-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64",
"name": "3.5.6-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64",
"sha256": null
}
],
"cache_hits_usable_for_311": [
"3.5.6-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64"
],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "cache-reusable"
},
{
"name": "grpcio",
"version": "1.84.1",
"wheel_tags": [
"cp310-cp310-manylinux_2_17_x86_64",
"cp310-cp310-manylinux2014_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "h2",
"version": "4.4.1",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "hf-xet",
"version": "1.7.0",
"wheel_tags": [
"cp38-abi3-manylinux_2_17_x86_64",
"cp38-abi3-manylinux2014_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "hpack",
"version": "4.2.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "httpcore2",
"version": "2.14.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "httpx2",
"version": "2.14.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "huggingface-hub",
"version": "1.33.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "humanfriendly",
"version": "10.0",
"wheel_tags": [
"py2-none-any",
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "hyperframe",
"version": "6.1.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "jiter",
"version": "0.18.0",
"wheel_tags": [
"cp310-cp310-manylinux_2_17_x86_64",
"cp310-cp310-manylinux2014_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "loguru",
"version": "0.7.3",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "mem0ai",
"version": "2.2.1",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": "{\"dir_info\": {}, \"url\": \"file:///home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-src\"}",
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "fixed-source"
},
{
"name": "mmh3",
"version": "5.3.1",
"wheel_tags": [
"cp310-cp310-manylinux_2_5_x86_64",
"cp310-cp310-manylinux1_x86_64",
"cp310-cp310-manylinux_2_28_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "mpmath",
"version": "1.3.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "numpy",
"version": "2.2.6",
"wheel_tags": [
"cp310-cp310-manylinux_2_17_x86_64",
"cp310-cp310-manylinux2014_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "onnxruntime",
"version": "1.23.2",
"wheel_tags": [
"cp310-cp310-manylinux_2_27_x86_64",
"cp310-cp310-manylinux_2_28_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "openai",
"version": "3.37.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "pillow",
"version": "12.3.0",
"wheel_tags": [
"cp310-cp310-manylinux_2_27_x86_64",
"cp310-cp310-manylinux_2_28_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [
{
"kind": "uv:wheels-v7",
"path": "/home/xiaomo/.cache/uv/wheels-v7/pypi/pillow/12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64",
"name": "12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64",
"sha256": null
},
{
"kind": "uv:wheels-v6",
"path": "/home/xiaomo/.cache/uv/wheels-v6/pypi/pillow/12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64",
"name": "12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64",
"sha256": null
},
{
"kind": "uv:wheels-v6",
"path": "/home/xiaomo/.cache/uv/wheels-v6/pypi/pillow/12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64",
"name": "12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64",
"sha256": null
}
],
"cache_hits_usable_for_311": [
"12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64"
],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "cache-reusable"
},
{
"name": "portalocker",
"version": "4.4.1",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "posthog",
"version": "7.75.11",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "protobuf",
"version": "6.33.6",
"wheel_tags": [
"cp39-abi3-manylinux2014_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "py-rust-stemmers",
"version": "0.1.8",
"wheel_tags": [
"cp310-cp310-manylinux_2_28_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "pytz",
"version": "2026.5",
"wheel_tags": [
"py2-none-any",
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "qdrant-client",
"version": "1.20.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "requests",
"version": "2.34.2",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "sniffio",
"version": "1.3.1",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "sqlalchemy",
"version": "2.0.55",
"wheel_tags": [
"cp310-cp310-manylinux_2_17_x86_64",
"cp310-cp310-manylinux2014_x86_64",
"cp310-cp310-manylinux_2_28_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "sympy",
"version": "1.14.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "tokenizers",
"version": "0.23.3",
"wheel_tags": [
"cp310-abi3-manylinux_2_17_x86_64",
"cp310-abi3-manylinux2014_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "tqdm",
"version": "4.70.1",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "truststore",
"version": "0.10.5",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [
{
"kind": "uv:wheels-v7",
"path": "/home/xiaomo/.cache/uv/wheels-v7/pypi/truststore/0.10.5-py3-none-any",
"name": "0.10.5-py3-none-any",
"sha256": null
},
{
"kind": "uv:wheels-v6",
"path": "/home/xiaomo/.cache/uv/wheels-v6/pypi/truststore/0.10.5-py3-none-any",
"name": "0.10.5-py3-none-any",
"sha256": null
}
],
"cache_hits_usable_for_311": [
"0.10.5-py3-none-any",
"0.10.5-py3-none-any"
],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "cache-reusable"
},
{
"name": "urllib3",
"version": "2.8.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
}
],
"mem0_identity": {
"direct_url_of_installed": "{\"dir_info\": {}, \"url\": \"file:///home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-src\"}",
"fixed_source_dir": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-src",
"fixed_source_dir_exists": true,
"pinned_tarball": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-data/mem0-2c81af3.tar.gz",
"pinned_tarball_sha256_measured": "9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23",
"pinned_tarball_sha256_lock": "9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23",
"tarball_matches_lock": true,
"index_wheel_2_2_1_proves_same_code": false,
"note": "索引下载的 mem0ai==2.2.1 wheel 不自动等于固定源码;发布必须用本 tarball/源码目录离线构建安装",
"built_wheel_from_hel857_install": {
"filename": "mem0ai-2.2.1-py3-none-any.whl",
"size": 352586,
"sha256": "698284b6083ab8b7761454d65876a6d1bb983d278ad21a7bee38d760cb20e9db",
"ephemeral_wheel_cache": "/tmp/multica-task-1816511421 (不再存在)",
"persisted_anywhere": false
},
"offline_build_backend": {
"hatchling_cached_or_local": true,
"note": "离线构建 mem0 wheel 需要 hatchling 及其依赖可离线获得;未命中则发布卡需一并拉取"
}
},
"fastembed_models": {
"cache_dir": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-data/fastembed-cache",
"exists": true,
"models": {
"models--Qdrant--bge-small-zh-v1.5": {
"files": 13,
"bytes": 190444780
},
"models--Qdrant--bm25": {
"files": 39,
"bytes": 56247
}
},
"default_cache_dir": "/home/xiaomo/.cache/fastembed",
"default_cache_exists": false,
"release_note": "真实 mem0 模式把 fastembed cache_dir 指向发布实例数据目录内的该缓存拷贝;本次零下载、不推理"
},
"pip_2622_event": {
"claim_under_review": "HEL-895 seq48–51:uv pip install --offline pip 失败后去掉 --offline 安装 pip 26.2.2 成功;前卡日志仅存尾两行",
"installed_into": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-895-0261e950b9c2/workdir/CyberGril/.venv311/lib/python3.11/site-packages/pip-26.2.2.dist-info",
"installer": "uv",
"uv_cache_entry_created": "2026-10-27T02:10:04.960958",
"uv_http_cache_created": "2026-10-27T02:10:04.972959",
"recorded_source_url": "https://files.pythonhosted.org/packages/3f/06/3e4420302363fbb71be3c7f35ce042f54bfca330e3e66b3a2e3c4cca579f/pip-26.2.2-py3-none-any.whl",
"recorded_sha256": "fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70",
"other_new_cache_entries_in_window": [],
"verdict": "勘误成立:uv 缓存中 pip-26.2.2 wheel 条目创建于 2026-10-27 02:10:04 +0800(HEL-895 运行窗口内),来源 files.pythonhosted.org,且是该窗口唯一新增缓存条目——该步发生了网络获取,前卡『零下载』对此步不成立;requirements.lock 装机同期未产生任何新缓存条目(全部命中既有缓存)。字节级『完整下载 vs 条件请求』无法由缓存取证区分,按保守口径记为下载。"
}
}
@@ -0,0 +1,16 @@
# HEL-879 rework: real-SDK check regenerated from THIS head's voice sources
# head: 78a5cfd8d75e3cfd00c60dc5c3592eb08cc757a1
# run: 2026-10-26T20:16:05Z, .12 /opt/voice-agent/agent/venv/bin/python (livekit-agents 1.8.8), stdin-only, zero writes zero network, no service touched
# generator: scripts/gen_hel877_sdk_check.py (10 checks; #9/#10 new: worker callback path on real SpeechHandle/SpeechCreatedEvent; assistant_interim seam through real LLMStream)
HEL-877/879 real-SDK check on livekit-agents 1.8.8: ALL PASS
OK CyberGrillLLM/ChatApiLLMStream subclass real livekit-agents 1.8.8 LLM/LLMStream
OK latest_user_text reads a real ChatContext (items/role/text_content)
OK fake-SSE turn through real LLMStream emits ChatChunks, final text and receipt
OK chat.error phi is spoken verbatim via ChatChunk and carries no receipt
OK mint_token claims byte-parity with livekit.api.AccessToken (video/iss/sub/keys)
OK mint_server_token matches SDK no-grant auth header claims
OK worker.py SDK surface (SpeechHandle, UserInputTranscribedEvent.is_final) present
OK barge-in task cancel closes the in-flight HTTP stream immediately
OK worker callback path on real SpeechHandle/SpeechCreatedEvent: no-generation handle discards, next turn keeps its own stream (explicit binding)
OK assistant_interim seam: SSE deltas through real LLMStream feed her incremental subtitle payloads (replace semantics, reset works)
+9
View File
@@ -0,0 +1,9 @@
# HEL-896 · 固定 SDK 离线余缺审计摘要(只读)
- 闭包 55 项,产品 3.11 venv 缺 41 项:纯 Python 28 项、原生扩展 13 项(charset-normalizer, greenlet, grpcio, hf-xet, jiter, mmh3, numpy, onnxruntime, pillow, protobuf, py-rust-stemmers, sqlalchemy, tokenizers)。
- 三个顶层包 WHEEL 均 py3-none-any(纯 Python,可跨 3.10/3.11)。
- mem0ai 固定源码身份:tarball SHA256 一致(9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23);索引同名 wheel 不作数;hel857 构建过的 wheel(698284b6083a…)在临时缓存、未持久。
- 当前缓存可直接复用的缺项 wheel:['greenlet', 'pillow', 'truststore']。
- 其余缺项(纯 Python wheel 与 cp311 原生 wheel)由发布卡按 deploy/hel874-release.md §4 获授权后下载并 sha256 留证;缺项不阻塞其余发布工作。
- fastembed 模型缓存实测:{"models--Qdrant--bge-small-zh-v1.5": {"files": 13, "bytes": 190444780}, "models--Qdrant--bm25": {"files": 39, "bytes": 56247}}(hel857-data/fastembed-cache,随发布实例落位,零下载)。
- pip 26.2.2 事件:勘误成立:uv 缓存中 pip-26.2.2 wheel 条目创建于 2026-10-27 02:10:04 +0800(HEL-895 运行窗口内),来源 files.pythonhosted.org,且是该窗口唯一新增缓存条目——该步发生了网络获取,前卡『零下载』对此步不成立;requirements.lock 装机同期未产生任何新缓存条目(全部命中既有缓存)。字节级『完整下载 vs 条件请求』无法由缓存取证区分,按保守口径记为下载。
@@ -0,0 +1,16 @@
/opt/voice-agent/agent/venv/lib/python3.12/site-packages/jwt/api_jwt.py:149: InsecureKeyLengthWarning: The HMAC key is 6 bytes long, which is below the minimum recommended length of 32 bytes for SHA256. See RFC 7518 Section 3.2.
return self._jws.encode(
/opt/voice-agent/agent/venv/lib/python3.12/site-packages/jwt/api_jwt.py:370: InsecureKeyLengthWarning: The HMAC key is 6 bytes long, which is below the minimum recommended length of 32 bytes for SHA256. See RFC 7518 Section 3.2.
decoded = self.decode_complete(
HEL-877/879 real-SDK check on livekit-agents 1.8.8: ALL PASS
OK CyberGrillLLM/ChatApiLLMStream subclass real livekit-agents 1.8.8 LLM/LLMStream
OK latest_user_text reads a real ChatContext (items/role/text_content)
OK fake-SSE turn through real LLMStream emits ChatChunks, final text and receipt
OK chat.error phi is spoken verbatim via ChatChunk and carries no receipt
OK mint_token claims byte-parity with livekit.api.AccessToken (video/iss/sub/keys)
OK mint_server_token matches SDK no-grant auth header claims
OK worker.py SDK surface (SpeechHandle, UserInputTranscribedEvent.is_final) present
OK barge-in task cancel closes the in-flight HTTP stream immediately
OK installed-SDK source verified: speech tasks set agents_speech_handle around the pipeline; speech_created precedes the task; llm_node calls llm.chat inside it
OK ACTUAL worker module loaded; its registered callbacks driven on a real AgentSession: both-queued delayed generations bind exactly (manager interleaving), say never steals a stream, unattributed fails closed, interrupt cancels without final/receipt
OK assistant_interim seam: real LLMStream deltas feed per-speech_id v2 payloads (replace semantics); unattributed streams feed nothing
@@ -0,0 +1,15 @@
HEL-877/879 real-SDK check on livekit-agents 1.8.8: ALL PASS
OK CyberGrillLLM/ChatApiLLMStream subclass real livekit-agents 1.8.8 LLM/LLMStream
OK latest_user_text reads a real ChatContext (items/role/text_content)
OK fake-SSE turn through real LLMStream emits ChatChunks, final text and receipt
OK chat.error phi is spoken verbatim via ChatChunk and carries no receipt
OK mint_token claims byte-parity with livekit.api.AccessToken (video/iss/sub/keys)
OK mint_server_token matches SDK no-grant auth header claims
OK worker.py SDK surface (SpeechHandle, UserInputTranscribedEvent.is_final) present
OK barge-in task cancel closes the in-flight HTTP stream immediately
OK installed-SDK source verified: speech tasks set agents_speech_handle around the pipeline; speech_created precedes the task; llm_node calls llm.chat inside it
OK ACTUAL worker module loaded; its registered callbacks driven on a real AgentSession: both-queued delayed generations bind exactly (manager interleaving), say never steals a stream, unattributed fails closed, interrupt cancels without final/receipt
OK assistant_interim seam: real LLMStream deltas feed per-speech_id v2 payloads (replace semantics); unattributed streams feed nothing
OK speak_proactive_row publishes proactive_start while the real SpeechHandle is still playing (sync-say shape), end/cancel on the handle's done callback
OK interrupted proactive row emits proactive_cancel(reason=interrupted), never proactive_end
OK incomplete/no-receipt attributed stream: actual worker callbacks emit turn_start -> turn_cancel only, no assistant_final, no ack
@@ -0,0 +1,9 @@
{"v": 2, "seq": 1, "kind": "turn_start", "role": "assistant", "text": "", "speech_id": "speech_cut"}
{"v": 2, "seq": 2, "kind": "assistant_interim", "role": "assistant", "text": "我正想跟你说,今天下午", "speech_id": "speech_cut"}
{"v": 2, "seq": 3, "kind": "turn_cancel", "role": "assistant", "text": "", "speech_id": "speech_cut", "reason": "interrupted"}
{"v": 2, "seq": 4, "kind": "interim", "role": "user", "text": "等一下"}
{"v": 2, "seq": 5, "kind": "user_final", "role": "user", "text": "等一下,先听我说。"}
{"v": 2, "seq": 6, "kind": "turn_start", "role": "assistant", "text": "", "speech_id": "speech_next"}
{"v": 2, "seq": 7, "kind": "assistant_interim", "role": "assistant", "text": "好,你先说。", "speech_id": "speech_next"}
{"v": 2, "seq": 8, "kind": "assistant_final", "role": "assistant", "text": "好,你先说。", "speech_id": "speech_next", "turn_id": "0e2f-demo-next"}
{"v": 2, "seq": 9, "kind": "turn_end", "role": "assistant", "text": "", "speech_id": "speech_next", "turn_id": "0e2f-demo-next"}
@@ -0,0 +1,7 @@
{"v": 2, "seq": 1, "kind": "interim", "role": "user", "text": "你在听的"}
{"v": 2, "seq": 2, "kind": "user_final", "role": "user", "text": "你在听吗?"}
{"v": 2, "seq": 3, "kind": "turn_start", "role": "assistant", "text": "", "speech_id": "speech_normal"}
{"v": 2, "seq": 4, "kind": "assistant_interim", "role": "assistant", "text": "在听呢,", "speech_id": "speech_normal"}
{"v": 2, "seq": 5, "kind": "assistant_interim", "role": "assistant", "text": "在听呢,在听呢,你刚才说到一半的那件事我还在想。", "speech_id": "speech_normal"}
{"v": 2, "seq": 6, "kind": "assistant_final", "role": "assistant", "text": "在听呢,你刚才说到一半的那件事我还在想。", "speech_id": "speech_normal", "turn_id": "0e2f-demo-turn"}
{"v": 2, "seq": 7, "kind": "turn_end", "role": "assistant", "text": "", "speech_id": "speech_normal", "turn_id": "0e2f-demo-turn"}
@@ -0,0 +1,4 @@
{"v": 2, "seq": 1, "kind": "proactive_start", "role": "assistant", "text": "我看到一句很适合现在的话,先念给你听。", "speech_id": "speech_pro1", "message_id": 17}
{"v": 2, "seq": 2, "kind": "proactive_end", "role": "assistant", "text": "", "speech_id": "speech_pro1", "message_id": 17}
{"v": 2, "seq": 3, "kind": "proactive_start", "role": "assistant", "text": "还有一件事想告诉你——", "speech_id": "speech_pro2", "message_id": 18}
{"v": 2, "seq": 4, "kind": "proactive_cancel", "role": "assistant", "text": "", "speech_id": "speech_pro2", "message_id": 18, "reason": "interrupted"}
@@ -0,0 +1 @@
{"remote_delete_count_after_ttl": 1, "expired_but_registered": false, "cleanup_owner_present": false}
@@ -0,0 +1 @@
{"remote_delete_count_after_ttl": 0, "expired_but_registered": true, "cleanup_owner_present": false}
@@ -0,0 +1 @@
{"proactive_before_playout_finishes": {"handle_done": false, "events": ["proactive_start"]}, "proactive_after_playout_finishes": ["proactive_start", "proactive_end"], "proactive_payload_fields": {"v": 2, "kind": "proactive_start", "role": "assistant", "text": "offline sample", "speech_id": "speech_f28fdeb15e01", "message_id": 7}, "no_receipt_incomplete_actual_worker_events": ["turn_start", "turn_cancel"], "no_receipt_incomplete_confirmed": [], "complete_with_receipt_actual_worker_events": ["turn_start", "assistant_final", "turn_end"], "complete_with_receipt_confirmed": ["ok-turn"]}
@@ -0,0 +1 @@
{"proactive_before_playout_finishes": {"handle_done": false, "events": []}, "proactive_after_playout_finishes": ["proactive_start", "proactive_end"], "proactive_payload_fields": {"v": 2, "kind": "proactive_start", "role": "assistant", "text": "offline sample", "speech_id": "speech_2880d36ea013", "message_id": 7}, "no_receipt_incomplete_actual_worker_events": ["turn_start", "assistant_final", "turn_end"], "no_receipt_incomplete_confirmed": [], "complete_with_receipt_actual_worker_events": ["turn_start", "assistant_final", "turn_end"], "complete_with_receipt_confirmed": ["ok-turn"]}
+37
View File
@@ -0,0 +1,37 @@
# HEL-879 契约核准后 3 处实现时机收口(2026-10-27)
核对对象:`hel-879-assembly @ 3228b1e0d7f822b7030f945e285eac74126771aa`(总管《HEL-879-契约核准与接续.md》)。
本轮只修 3 处实现与已定契约 v2 不一致的问题:不改 `v` 信封字段、不改事件种类、不动 `web/src`、不重做已过绑定/备份/预算。
## 修前复现(全部先复现失败,与总管证据一致)
| # | 发现 | 修前证据 |
|---|------|----------|
| 1 | `open_session` 只在 `await create_dispatch` 前 kick;建房真实让出事件循环后 cleaner 空转退出,注册后无人再观察截止时间 | `delayed-dispatch-before.json`:`{"remote_delete_count_after_ttl": 0, "expired_but_registered": true, "cleanup_owner_present": false}`(与总管 delayed-dispatch.json 逐字段一致) |
| 2 | `speak_proactive_row` 旧写法 `handle = await session.say(...)`;真实 SDK `say` 为同步函数、`await handle`=等整段播完 → 播完才发 proactive_start | `sdk-probe-before.json`:`proactive_before_playout_finishes.events=[]`,播完后才 `["proactive_start","proactive_end"]` |
| 3 | `_after_playout` 先按 `stream.text` 发 final/end 再查回执;注入 text 非空、completed=False、receipt=None 的确切归属流 → `turn_start→assistant_final→turn_end`,与 v2「无完整同轮回执只 cancel」相反 | 同上:`no_receipt_incomplete_actual_worker_events=["turn_start","assistant_final","turn_end"]`(实际 worker `bind_voice_session` 回调路径,真实 AgentSession/SpeechHandle) |
新增专项测试在**未修复代码上先跑失败**(repro-first):
`tests/test_voice_rework_hel879.py::test_open_session_with_real_dispatch_yield_re_kicks_cleanup`、
`::test_second_registration_wakes_a_sleeping_cleaner_for_earlier_deadline` → 修前 2 failed / 修后 2 passed。
## 修复(精确小修)
1. `src/cybergril/voice/session.py` `open_session`:注册成功并 `_persist_state()` 之后**重新 `_kick_cleanup()`**——建房 await 期间旧 cleaner 可能已因「无可观察对象」退出,新截止时间需要新的所有者;自动截止、共享上限、重启补偿、有界放弃全部保留。测试用**真正让出事件循环**的建房替身(`asyncio.sleep(0.02)`),不再只用立即返回的 MockTransport 替身。
2. `src/cybergril/voice/worker.py` `speak_proactive_row`:先取 handle(`iscoroutine` 只解包 await 型替身——真实 SpeechHandle 自身可 await,不能误等)、**立即**发布 `proactive_start`、注册该 handle 的 `proactive_end/proactive_cancel`,再按需等待这一条播完(逐条顺序念保留)。
3. `src/cybergril/voice/worker.py` `_after_playout`:`assistant_final` 前置必要门——流 `completed` 且 `should_ack`(合法同轮 receipt、非旧 epoch)才 `finish`+`confirm`;未完整/无回执/旧 epoch 一律只 `turn_cancel`(reason=incomplete/no-receipt),**绝不先发 final 再决定不确认**;φ/打断/失败/无归属 fail-closed 原样保留,完整+合法回执路径的 final 与 ack 均保留。
## 修后验证
- 本地:`delayed-dispatch-after.json`:`{"remote_delete_count_after_ttl": 1, "expired_but_registered": false, "cleanup_owner_present": false}`(cleaner 在全部结清后按设计自行退出,无悬空协程)。
- `.12` 真 SDK(stdin 嵌入当前源码,零写入零网络,`/opt/voice-agent/agent/venv` livekit-agents 1.8.8)`sdk-probe-after.json`:
- `proactive_before_playout_finishes={"handle_done": false, "events": ["proactive_start"]}`(播放中即可见 start);
- 播完 `["proactive_start","proactive_end"]`,字段 `v/kind/role/text/speech_id/message_id` 与 v2 完全一致;
- `no_receipt_incomplete=["turn_start","turn_cancel"]`、`confirmed=[]`;
- 完整+合法回执路径 `["turn_start","assistant_final","turn_end"]` + ack 保留。
- 真SDK回归并入常驻检查 `scripts/gen_hel877_sdk_check.py`(**14/14 ALL PASS**,`../sdk-check-hel879-timing.log`):新增「主动播报时机」「主动播报打断只有 cancel」「无回执不 final」三负例,均驱动实际 worker 模块注册的真实回调。
- 产品套件:pytest **352/352**(3 次连跑);ruff(src/tests/tools) **0 错**;`verify_hel877_voice_chain.py` **8/8**;需求检查 **4/4**(origin/main 与 c121c42 各带/不带 handoff-HEL-879.json)。字幕样例 `subtitle-samples/` 与 v2 契约文档无需再生成(字段与生命周期未变)。
## 边界
零真实 LLM/嵌入/ASR/TTS/麦克风;.12 仅 SSH stdin 执行只读检查(零写入零网络,stderr 仅既有 jwt 弱密钥告警);零下载零安装;未部署、未合并;PR#11 与 `hel-870-assembly` 未动;权威账/冻结/共享服务/队员配置未碰;真实 cap 仍 0。R5 真机、完整 CG-011、R6/R7 继续承接,无需求变更(v1.11/60 项)。
@@ -0,0 +1,85 @@
{
"sdk": {
"livekit_agents": "1.8.8",
"livekit_api": "1.2.1"
},
"source": "actual fc905fe+fb7ea84 worker driven on .12 real SDK (livekit-agents 1.8.8, livekit-api 1.2.1) via SSH stdin, zero network/writes",
"scenarios": {
"normal": {
"kinds": [
"interim",
"user_final",
"turn_start",
"assistant_interim",
"assistant_interim",
"assistant_interim",
"assistant_final",
"turn_end"
],
"frames": 8,
"acks": [
"382c41d9-0d72-433e-b370-f838fa5d0bd0"
],
"turn_id": "382c41d9-0d72-433e-b370-f838fa5d0bd0"
},
"interrupt": {
"kinds": [
"user_final",
"turn_start",
"assistant_interim",
"assistant_interim",
"turn_cancel",
"user_final",
"turn_start",
"assistant_interim",
"assistant_interim",
"assistant_final",
"turn_end"
],
"frames": 11,
"acks": [
"edbcb837-f19f-47e7-8d5c-057010da43eb"
],
"turn_ids": {
"cut": "09d05c0f-3058-4ff8-a56e-4e94bd17d7b7",
"next": "edbcb837-f19f-47e7-8d5c-057010da43eb"
}
},
"phi": {
"kinds": [
"user_final",
"turn_start",
"assistant_interim",
"assistant_interim",
"assistant_interim",
"error",
"turn_cancel"
],
"frames": 7,
"acks": [],
"turn_id": "31e70e52-1471-4e50-ac49-cce89979a9bf"
},
"no_receipt": {
"kinds": [
"user_final",
"turn_start",
"assistant_interim",
"assistant_interim",
"turn_cancel"
],
"frames": 5,
"acks": [],
"turn_id": "36d160e3-e365-46ff-ad57-c2878c00720f"
},
"proactive": {
"kinds": [
"proactive_start",
"proactive_end",
"proactive_start",
"proactive_cancel"
],
"frames": 4,
"start_while_playing": true
}
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
需求结构检查通过:60项;来源和覆盖完整。仍须人工核对授权语义与实际体验。
@@ -0,0 +1 @@
需求结构检查通过:60项;来源和覆盖完整。仍须人工核对授权语义与实际体验。
@@ -0,0 +1 @@
需求结构检查通过:60项;来源和覆盖完整。仍须人工核对授权语义与实际体验。
@@ -0,0 +1 @@
需求结构检查通过:60项;来源和覆盖完整。仍须人工核对授权语义与实际体验。
@@ -0,0 +1,97 @@
# R5 测试包 v2 · .12 只读实测证据(2026-10-27)
> 性质:本轮材料返修的事实底稿。全部为 SSH 只读命令与输出摘录(零写入、零服务变更、
> 不发任何模型/嵌入/ASR/TTS 请求);涉及凭据的值一律以 `<REDACTED>` 脱敏,不落仓库。
> 命令在 .12(root@192.168.200.12)执行;时间 2026-10-27(UTC 时段见 issue)。
## E1 · 端口监听(`ss -tlnp` 摘录)
监听中(与 R5 相关/受保护):
| 端口 | 进程 | 说明 |
| --- | --- | --- |
| 7880、7881 | livekit-server (pid 219106, docker `voice-livekit`, host 网络) | 7880=HTTP/WS 信令;7881=WebRTC TCP 回落口 |
| 10095 | docker-proxy(funasr-runtime-sdk-online-cpu-0.1.13) | 流式 ASR |
| 8090 | python (pid 2136834, `/opt/voice-agent/agent/venv/bin/python server.py`, cwd `/opt/voice-agent/web`) | 语音演示页(https-only,见 E3) |
| 8081 | python (pid 451883, `venv/bin/python agent.py start`, cwd `/opt/voice-agent/agent`) | 通用 cascade worker |
| 8300/8310/8320/8340/8350 | 各既有 CyberGril 实例 | 受保护,不触碰 |
**空闲(无监听)**:8360、8361、8363、8082、9880(TTS 停机中)。
## E2 · LiveKit docker 无 TLS 配置(`docker inspect` + `docker-compose.yml`)
- `voice-livekit`:`command: --bind 0.0.0.0`,`network_mode: host`,无任何证书挂载
(Mounts=[]),环境仅 `LIVEKIT_KEYS`(值脱敏)。
- 探测:`curl http://127.0.0.1:7880` → **200**(纯 HTTP 存活);
`curl -k https://127.0.0.1:7881/` 与 `curl http://127.0.0.1:7881/` → 均失败
(7881 不是 HTTP(S) 端点,是 WebRTC TCP 回落口,不能当 WSS 信令用)。
## E3 · 8090 演示页现状
- `https://127.0.0.1:8090/` → 200;`http://127.0.0.1:8090/` → 失败(https-only)。
- 证书:`CN = minicpmo45-dev`,**无 subjectAltName 扩展**,notAfter 2027-02-11。
- `server.py:66`:`LIVEKIT_URL = "ws://192.168.200.12:7880"` —— 页面 `/api/token`
把该 ws:// 地址发给浏览器 `room.connect(url, token)`。https 页面连 ws:// 属
**混合内容**,库存浏览器默认拦截;**没有任何证据**表明该页在标准浏览器走通过
媒体链路。旧材料「自签一次点击同时覆盖 LiveKit WSS 7880」不成立,已删除。
- 面板功能(index.html/server.py 实读):音色列表 `GET /api/voices`、切换
`POST /api/voice`(写共享 `voice_state.json`)、克隆上传 `POST /api/voices/upload`
(写共享 `voices.json` + TTS 热加载)、删除。**没有既有音色的试听播放功能**
(`vPreview` 仅用于克隆上传的自选文件)。
## E4 · 通用 worker 依赖 7880 保持明文(改 TLS 的破坏面)
`/opt/voice-agent/agent/.env`:`LIVEKIT_URL=ws://127.0.0.1:7880`(另有
LIVEKIT_API_KEY/SECRET,值脱敏)。**7880 一旦改 TLS-only 即断通用 worker 与
8090 演示,禁止作为本卡路线。**
## E5 · .12 上没有已装 mem0 的产品 venv(「零真缺」不能笼统写)
- 8310(`/opt/cybergril-api/current/.venv`,r2-20261022)与 8350
(`/opt/cybergril-hel835/current/.venv`)抽查:`python -c "import mem0"` 均
`ModuleNotFoundError`;两者均为 **Python 3.12.3**(系统 python 建 venv)。
- 8320/8340 同构部署(各自 `current/.venv`),未逐一导入验证。
- 结论:所抽查的两处不能提供已装 mem0 闭包,不能据抽查推断 .12 所有实例均已
验证不存在。完整统一体验依赖分类以本仓库
`docs/rework/hel879/release-artifacts.json`(总管已核准)为准。
## E6 · worker venv 与 TTS 底座(语音专项链已备)
- `/opt/voice-agent/agent/venv`:`import livekit, livekit.agents` 成功,
livekit-agents **1.8.8**(venv 为 python3.12,worker 不依赖产品 mem0 闭包,
经 HTTP 调统一 API)。
- CosyVoice:`/opt/voice-agent/cosyvoice/server.py` 实读——路由 `/health`、`/tts`
存在;`uvicorn.run(host="0.0.0.0", port=9880)`;当前 9880 无监听(停机待授权
重启);权重在位(`/opt/models/CosyVoice3-0.5B-2512`,约 9.1GB)。
## E7 · 音色真实清单(voices.json 实读)
- 11 个 ID:`bailu, bajie, default, female_soft, haimianbaobao, jiaojiao,
male_warm, nezha, rain, sunwukong, xiaoyun`;每项含 `prompt_wav` 指向
`voice_samples/<id>.wav`(zero-shot 参考音频,即该音色的音色来源录音,
bailu.wav 1.77MB 等,目录实存)。
- 共享演示当前 `voice_state.json` = `{"voice": "rain"}` —— 是通用演示状态,
**不是本用户的选择**,不作为本用户音色依据。
## E8 · GPU / 内存 / 磁盘(`nvidia-smi` / `free` / `df`)
- RTX 5090D:总量 32607 MiB,已用 1558 MiB,**空闲 30520 MiB**(ComfyUI 仍处
已授权暂停;GPU 进程仅 gnome-remote-desktop 996MiB)→ CosyVoice 常驻约
5.6GB 无障碍。
- 内存 94GB(可用 83GB);`/` 916GB 用 16%,余 735GB。
## E9 · /opt 无冲突目录
`/opt` 现有 `cybergril-api / cybergril-data / cybergril-hel822 / cybergril-hel829 /
cybergril-hel835 / cybergril-ops / cybergril-s1 / voice-agent / models` 等;
**无 `voice-agent-cybergril`、无 `cybergril-voice-certs`**(新建不冲突)。
## E10 · 预算硬闸语义(本仓库代码,非 .12)
`src/cybergril/think/budget.py` `acquire()`:真实 provider 出口**发送前**原子
预占(类别日顶 `daily_requests` + 一次性总量 `total_requests` + `daily_tokens` +
`total_tokens` 同步检查,任一到顶抛 `BudgetStop`,持久化实现落在单个
`BEGIN IMMEDIATE` 事务)。**失败/重试/打断/后台调用同样计数**——计数粒度是
provider 调用,不是成功聊天行数。封账 `budget_sealed` 拒绝一切 acquire。
语音轮经统一 `/api/chat` 走同一 dialogue 闸;`POST /api/voice/session` 建房
**当前不在 budget 闸内**(如实列为操作侧上限+工程后续小项)。
@@ -0,0 +1,215 @@
# HEL-879 · R5 真机测试包 v2(供主卡拍板的集中材料,未执行)
> 性质:**准备材料**(v2 材料返修版,按总管 2026-10-27 四项返修清单重写)。零真实
> 模型/嵌入/ASR/TTS/麦克风请求、零下载/安装/部署/合并、真实 cap=0。.12 全部事实为
> 2026-10-27 只读实测,底稿与命令摘录见同目录 `r5-facts-20261027.md`(脱敏)。
> 事实三级标注:**已证**(实测/代码实读)|**拟议**(材料备齐待批,未执行)|
> **待真机**(须获批实测后才能写通过)。
>
> v2 勘误(相对 v1):①删「统一实例依赖零真缺/零下载」笼统结论,改两轨拆分(§1);
> ②删「自签一次点击同时覆盖 LiveKit WSS 7880」——7880 现为纯 ws 且被共享服务依赖,
> WSS 改由独立 8363 桥承担(§3);③预算 30 轮表改逐类计数位置+硬闸/操作停机拆分
> (§6);④工程项(端口/目录/健康口)由工程核定(§10),不再交用户选择;D1 改实际
> 音色清单+零合成试听方案(§4)。
## 1. 依赖两轨(不能混称「零真缺」)
### 1a. R5 语音专项(语音链本身)——**已证**零真缺、零下载
| 部件 | 状态 | 依据 |
| --- | --- | --- |
| LiveKit SFU | ✅ 运行中 .12:7880/7881 | 已证 7880 HTTP 200;**纯 ws 无 TLS**(E2) |
| FunASR 流式 ASR | ✅ 运行中 .12:10095 | 已证监听(E1) |
| worker 运行时 | ✅ 在位 `/opt/voice-agent/agent/venv` | 已证 import livekit-agents 1.8.8(py3.12;worker 经 HTTP 调统一 API,不依赖 mem0 闭包,E6) |
| CosyVoice3 TTS | ⛔ 停机待重启授权 | 已证权重在位(约 9.1GB)、9880 无监听、`/health` `/tts` 路由存在(E6);重启=运维动作非下载 |
| GPU/内存/磁盘 | ✅ 充足 | 已证空闲 30520MiB / 83GB / 735GB(E8) |
| 统一 API 实例(8360) | ⛔ 待部署 | 部署物=本候选 head + 已过发布包 §2 全离线基础安装(requirements.lock 装机全部命中既有缓存,已证,见 sdk-audit) |
**语义边界(必须如实)**:本轨在 `semantic=stub` 下验收**语音链**——同一份档案数据、
同一进程、同一把轮次锁,但**嵌入 0、语义记忆未激活**。本轨通过 ≠ 「同档案全记忆
验收」;完整统一体验是 1b,两者分开批、分开验,互不冒充。
### 1b. 完整统一体验(真实 mem0 记忆)——41 项闭包,**真缺待授权**(已证分类)
- **已证范围**:.12 的 8310/8350 两个既有产品实例 venv 抽查均
`ModuleNotFoundError`,且均为系统 3.12.3;8320/8340 未逐一验证,E5。
不能据两处抽查声称所有实例均已验证,也没有可据此采用的已装 mem0 闭包——
「零真缺」对完整体验**不成立**。
- 分类(`docs/rework/hel879/release-artifacts.json`,总管已核准口径):
- **已离线成品**:mem0ai 固定源码轮 `mem0ai-2.2.1-py3-none-any.whl`
(sha256 `698284b6…20e9db`,352,586 B / 153 文件,源 tarball 与
`deploy/mem0-stack.lock` 哈希一致;未安装任何位置)。
- **缓存可复用**:greenlet 3.5.6(cp311)、pillow 12.3.0(cp311)、
truststore 0.10.5(py3-none-any)——本机 uv wheels-v6/v7 实测命中。
- **待准备 37 项**(26 纯 Python + 11 原生;原 28/13 分别扣除
mem0ai、truststore 两项纯 Python 和 greenlet、pillow 两项原生):按当前闭包
扣除已成品/可复用项的数量,尚不等于目标平台已解析完成的下载结果。需下载授权后按
发布包 §4 第 3 步拉取,逐项 sha256 留证;**体积未下载不猜**,下载窗口逐项
记录。hatchling 已离线解决(1.32.4 缓存),不再申请。
- **fastembed 模型缓存**:已证在位 bge-small-zh-v1.5 190,444,780 B +
bm25 56,247 B(artifacts 实测数;hel895 文档「约 92MB」与此不符,以
artifacts 为准——已列勘误待修)。
- 逐项版本/哈希:以 `release-artifacts.json` 与
`docs/rework/hel879/sdk-audit/sdk-audit.json` 为准(不在本文件复制防漂移)。
- 安装位置:统一实例自足 `.venv311`(uv 管 CPython 3.11.16,发布包 §2/§4 唯一
入口);不混 .12 系统 3.12、不碰既有实例 venv。
- 边界重申:不以关闭 mem0、改 stub、少测记忆默认缩减用户完整体验要求。
## 2. 资源现状(已证,2026-10-27)
GPU RTX 5090D 空闲 30520MiB(ComfyUI 仍处已授权暂停);内存 94GB/可用 83GB;
盘 916GB/余 735GB。端口实测:8360/8361/8363/8082 **空闲**;受保护监听
8300/8310/8320/8340/8350/8090/8081/7880/7881/10095 一律不动;9880 停机待授权。
## 3. 可信 HTTPS + WSS(D3)——已证 / 拟议 / 待真机
### 3.1 已证事实(为什么 v1 的说法必须撤回)
1. LiveKit 7880 = **纯 HTTP/WS**(docker 无证书挂载无 TLS 配置,E2);7881 监听
但**不是 HTTP(S) 端点**(http/https 探测均失败),是 WebRTC TCP 回落口,
**不能当 WSS 用**。
2. 通用 worker 与 8090 演示都依赖 `ws://127.0.0.1:7880`/`ws://…:7880`(E4)——
**7880 改 TLS 会破坏共享服务,禁止**。
3. 8090 演示页 https-only(证书 `CN=minicpmo45-dev`、**无 SAN**、至 2027-02-11);
它给浏览器发的就是 `ws://…:7880`(E3)——https 页连 ws 属**混合内容**,无证据
表明曾在标准浏览器走通。旧句「自签一次点击同时覆盖页面 HTTPS 与 WSS 7880」
**不成立,已删除**。
4. `config/voice.yaml` 是部署本地文件(模板 `config/voice.example.yaml`),仓库内
无该文件——v1 写法勘误。
### 3.2 拟议路线(材料已备齐,未执行;缺的就是这个 WSS 桥,如实列出)
- **新自签证书一张**:`openssl req -x509 … subjectAltName=IP:192.168.200.12`,
存 `/opt/cybergril-voice-certs/`(600),同证书服务 8361 与 8363 两个端口。
- **8361**:统一实例 HTTPS 入口(同进程双入口,runbook-hel877-voice §4.4;
本地 18360/18361 已冒烟,.12 未执行——拟议)。
- **8363(新增)**:TLS→明文 TCP 桥 `deploy/voice/wss_bridge.example.py`
(asyncio+ssl 标准库零新依赖;本地冒烟 TLS1.3+字节穿透通过,见
`wss-bridge-smoke.log`):`wss://192.168.200.12:8363` → `127.0.0.1:7880`。
`voice.yaml` 的 `livekit_url` 改 `wss://192.168.200.12:8363`;
`livekit_internal` 保持 `http://127.0.0.1:7880`。不动 LiveKit、通用 worker、
8090(独立进程,回滚=停进程)。
- WebRTC 媒体面(UDP/7881 TCP)不经桥,由 WebRTC 自带 DTLS/SRTP 加密、无需
证书例外——协议性质说明,实际连通**待真机**。
### 3.3 每设备信任步骤(拟议;**分端口各一次**,不共享例外)
1. 打开 `https://192.168.200.12:8361` → 高级→继续(页面例外)。
2. 打开 `https://192.168.200.12:8363` → 高级→继续(WSS 信令例外;该地址 GET
返回 404/400 属预期,只看证书警告页点继续)。
3. 允许麦克风。
- 两个实际端点分别打开并验证;不推断一个端点的例外必然覆盖另一个,也不把
浏览器的例外缓存范围写成已实测事实。**不用** `--ignore-certificate-errors`、
不装根证书、不改系统信任库(未授权,也不作为验收路径)。
- **待真机**:iOS Safari「访问此网站」是否放行 wss、安卓 Chrome 部分版本无
「继续」按钮的已知限制——验收时如实记录;桌面 Chrome/Firefox 为保底组合。
### 3.4 备选路线(不采用,写明理由)
- LiveKit 原生 TLS(7880 直接上证书):破坏通用 worker/8090(E4),**禁止**。
- 系统 nginx 反代:.12 无运行中 nginx(E1/E2),安装/改系统配置超出零安装边界。
- 忽略证书/不安全浏览器开关:明令禁止,不作为验收。
## 4. D1 音色(实际清单 + 零合成试听方案;不替用户定)
- **已证**(voices.json 实读,E7):音色共 **11 个 ID**——`bailu、bajie、default、
female_soft、haimianbaobao、jiaojiao、male_warm、nezha、rain、sunwukong、
xiaoyun`;每支 = zero-shot 参考音频克隆,参考文件
`/opt/voice-agent/cosyvoice/voice_samples/<id>.wav` 实存(即该音色来源说话人
的真实录音)。v1「6 支女声」及各主观描述系笔误+猜测,已删;不猜名字定效果,
不引用通用演示状态(当前 `rain`)或其他测试/旧角色资料作为本用户选择。
- **已证**:8090 面板**没有既有音色试听播放**(只有列表/切换/克隆上传/删除),
且切换/上传写**共享** `voices.json`/`voice_state.json`、影响通用演示——v1
「试听入口=8090 面板」勘误删除,未经授权不用它给本用户做试听。
- **拟议试听方案(零合成、零 GPU、零下载)**:部署时把 11 段参考 wav 以只读静态
目录提供(随 8361 静态路径或离线打包随材料交付),白栖知听参考音频初选;
音色效果以参考音频为准 + 获批测试窗口内真实合成复核(计入 §6 TTS 计数)。
- 可选:克隆(录 5–20s 中文参考)需写共享 `voices.json`(新增条目为增量,但会触
共享 TTS 热加载)→ **需单独授权**,默认不启。
- worker 缺省回落 `default`(代码已证);专属 worker 用独立
`voice_state_cybergril.json`,不写通用演示状态(runbook §4.1)。
## 5. 部署顺序(获批后执行;含回滚)
1. 统一实例(8360 + 8361 同进程双入口 + **8363 WSS 桥**,隔离数据目录与
`/opt/voice-agent-cybergril/`、`/opt/cybergril-voice-certs/`;不动
8300/8310/8320/8340/8350/8090/8081/7880/10095)。
2. TTS 9880 重启:先核实 9880 无监听再按 cosyvoice 运维节启动;历史 `fuser -k`
禁止照抄(当前已证停机,无需杀任何进程)。
3. 专属 worker(`/opt/voice-agent-cybergril/`,agent venv 启动,健康口 8082,
`CYBERGRIL_API_BASE=http://127.0.0.1:8360`)。
4. `config/voice.yaml`(从 `config/voice.example.yaml` 复制):键
`livekit_url: wss://192.168.200.12:8363`、`livekit_internal:
http://127.0.0.1:7880`、api_key/api_secret 按 .12 现值只写服务器本地文件。
- 回滚:停 worker → 停 8363 桥 → 停 8360/8361 → TTS 回停机或原状(全独立进程);
Git 回退=本分支回退到 fc905fe 重新部署;冻结账与既有实例零接触。
## 6. 有限预算与停止条件(逐类;「代码硬闸」与「操作停机」分开写明)
**计数语义(已证,E10)**:budget 账 `acquire()` 在 provider 调用**发送前**原子
预占——失败/重试/打断/后台调用同样计数;**LLM 调用数 ≠ 成功聊天行数**。ASR 流、
TTS 分段、φ 播报、打断重合成、主动 say、闲置通话都**不等于** LLM 轮数,另列。
| 类别 | 建议上限(供拍板,不自动生效) | 计数位置 | 闸性质 | 异常停法 |
| --- | --- | --- | --- | --- |
| LLM 真实调用(dialogue 类,含语音轮与失败重试) | ≤40 次请求 / ≤500k tokens | 实例 budget 账 `request_usage`/`token_usage` 逐笔 | **代码硬闸已实现**:类别日顶+一次性总量+日 token 池发送前拒绝;封账库拒绝一切 | 到线自动停;异常扣费风险=立即停窗口(下 1–3 项全停) |
| 嵌入 | **0**(R5 专项 stub) | semantic 账(`embed_ops` 未启用) | **代码硬闸已实现**(semantic cap=0 fail-closed);1b 另批 | — |
| ASR(FunASR 流) | 测试窗口 ≤2h 且通话 ≤3 次 | worker 日志 job/stream 数(本地 CPU 服务,零外部计费) | **无代码闸(如实)**→操作停机 | 窗口到点/会话数到线/异常即停 worker |
| TTS(CosyVoice 合成段,含 φ/打断重合成/主动 say/分段) | 窗口内 ≤200 合成段 | `tts.log` 合成行(本地 GPU,零外部计费) | **无代码闸(如实)**→操作停机 | 同上;GPU 显存异常(>8GB 或 OOM)即停 |
| 语音会话(建房) | ≤3 次完整通话 | `POST /api/voice/session` 服务端日志 | **无代码闸(如实;列工程后续小项:建房数纳入 config 上限)** | 第 3 次结束或验收完成即停 |
| 主动开口 | 默认关(`CYBERGRIL_PROACTIVE_VOICE` 不设) | — | 已实现默认关;如开只念已落库行、不新增 LLM 调用(已证代码路径) | — |
- **上限不自动生效**:所有 cap 配置当前仍 0,批准后由总管写入实例配置;权威账/
冻结账不写、封账库永不重开;真实 cap 本轮=0。
- 逐笔输出:budget 表逐行 + worker/tts 日志,窗口结束导出逐笔清单交主卡。
- 本表数字为建议值,最终由白栖知定;**不以 30 轮聊天行数顶替任何账**。
## 7. 验收清单(R5 真机,integration-plan 原四条 + 补充)
1. 连续交谈 ≥3 轮(同一她、同档案、记忆延续,音色一致;R5 专项内「记忆」=
档案与对话历史,语义记忆属 1b 不冒充)。
2. 开口打断约 1 秒停声(VAD→TTS 截断;字幕同步消失)。
3. 挂断立即停麦(track.stop + 房间删除;麦克风指示灯灭)。
4. 无旧残音/残影(打断与掉线后旧帧不回显;已有普通离线用例通过,长会话防护等
HEL-898 三项返修仍待交回统一复核,随后真机复验)。
5. 网络失败/重开:诚实提示、重开仍是同一位她。
6. **WSS 桥 8363 + 页面 8361 分端口信任**(§3.3),真机记录各浏览器行为;
桌面 Chrome/Firefox 保底,移动端如实记录不硬闯。
7. 字幕全生命周期(后台实际 v2 帧及前端状态单测已有证据;HEL-898 短回复显示、
agent 离场与长会话防护返修尚未交回,不称界面层离线全通过;之后真机抽查
增量→final→落库一致)。
8. API 重启释放(房间与 worker job 真实回收)一次受控验证。
9. 口型**不在本包**(不伪装接通)。
## 8. 真正待白栖知拍板的项(工程项已移出,见 §10)
1. D1 音色(§4 参考试听后选,或另行授权克隆)。
2. 有限真实用量与新增费用上限确认/调整(§6 各值;R5 专项嵌入 0)。
3. 测试窗口与共享 TTS 重启授权(占 GPU ~5.6GB;ComfyUI 保持暂停到测试后与否)。
4. 1b 完整统一体验的当前 37 项待准备依赖下载授权(目标 cp311 解析后核定实际
产物清单,可与 R5 专项分开批;已有成品/缓存不重复下载)。
## 9. 本包不做
不合并 PR#11/任何 PR;不替换 8300/8310 正式入口;不改队员运行时/模型/认证;
不动冻结账与既有五实例;CG-011 完整会话中主动表达仍承接(默认关的「开场念一条
旧留言」不冒充完整项);R6/R7 继续承接。
## 10. 工程已定项(工程核冲突后定案,不交用户选)
| 项 | 取值 | 冲突核对(2026-10-27 实测) |
| --- | --- | --- |
| 统一实例端口 | HTTP 8360 + HTTPS 8361 | 空闲(E1);与 8300–8350/8090/8081 不冲突 |
| WSS 桥端口 | 8363 | 空闲(E1;本轮新增,v1 未列) |
| worker 健康端口 | 8082 | 空闲(E1;8081 为通用 worker,差一位不误用) |
| worker 目录 | `/opt/voice-agent-cybergril/` | 不存在、新建无冲突(E9) |
| 证书目录 | `/opt/cybergril-voice-certs/` | 不存在、新建无冲突(E9) |
## 附 · 材料与证据指针
- .12 只读实测底稿:`docs/rework/hel879/unified-review/r5-facts-20261027.md`(脱敏)
- WSS 桥:`deploy/voice/wss_bridge.example.py` + `wss-bridge-smoke.log`
- 依赖分类:`docs/rework/hel879/release-artifacts.json`、`sdk-audit/sdk-audit.json`
- 预算闸:`src/cybergril/think/budget.py`(acquire 语义)、`docs/budget-package-hel895.md`
- 部署草案:`docs/runbook-hel877-voice.md` §4、`deploy/hel895-release-candidate.md` §2/§4
@@ -0,0 +1,42 @@
# HEL-879 统一整合审核 · 焦点2:seq 命名空间与 job/房间/控制器代次(2026-10-27)
问题(原作者 HEL-898 提出):`SubtitleBus` 的 `seq` 按 worker job 从 1 计(`src/cybergril/voice/worker.py` `entrypoint` 内建 bus),而前端 `VoiceController` 按**控制器会话**(一个 `Live`,web/src/voice/session.ts `live.seq`)记高水位。若同一房间在控制器会话存活期间换了新 job(worker 重连/新派发),新 job 的 seq 归零,前端要么误丢新帧、要么误收旧帧。本文件用 .12 真实 SDK 源码(livekit-agents 1.8.8 / livekit-api 1.2.1,`/opt/voice-agent/agent/venv/lib/python3.12/site-packages/`,SSH 只读取证,零写入零网络)与仓库代码逐条核清。
## 1. 一个 job 只跑一次 entrypoint(seq 计数器只生一次)
真实源码 `livekit/agents/ipc/job_proc_lazy_main.py`(默认进程执行器路径):
- L241–246:进程内已有运行中 job 时再收到 `StartJobRequest` 只告警跳过(`"trying to start a new job while one is already running"` → `continue`)——**一个 job 进程最多承载一个 job**。
- L318–323:`_start_job` 为该 job 建 `JobContext` 并启动**唯一** `job_task`。
- `_run_job_task`(L327 起)里 `self._job_entrypoint_fnc(job_ctx)` 只被创建为一个 task(`job_user_entrypoint`);job 在以下任一时刻结束:entrypoint 返回、`_shutdown_fut` 置位(room disconnected / shutdown 请求 / job 崩溃),随后 `_shutdown_job` → `_on_cleanup`,进程随 `_exit_proc_flag` 退出。
结论:worker.py 的 `entrypoint`(含 `SubtitleBus` 实例)在**一个 job 内恰好执行一次**,job 结束即进程结束,不存在「同 job 二次 entrypoint 导致 seq 归零」。
## 2. 断线重连不重跑 entrypoint(seq 不归零)
真实源码 `livekit/rtc/room.py`:
- L85–86 / L1130–1133:瞬断走 `reconnecting` / `reconnected` 房间事件——这是 `rtc.Room` 内部的信号/媒体重连,job 任务不重建,entrypoint 不重跑。
- 只有房间真正关闭才发 `disconnected`;job_proc_lazy_main L288–293 将其置入 `_shutdown_fut`(reason=`room disconnected`)→ job 结束。此时房间已不存在,前端收到 `dropped`(livekit.ts L75),整个 `Live` 连同 `live.seq` 一起终结。
结论:**同一 job 内的 worker 重连,`SubtitleBus` 实例保留,seq 连续递增**;房间没了 = job 没了 = 前端这次通话也没了,不存在跨 job 的旧高水位。
## 3. 本产品没有「同房间第二个 job」的路径
- 后端唯一派发点:`src/cybergril/voice/session.py:396` `open_session` 内 `livekit_wire.create_dispatch(...)`,房间名 `cg-voice-` + 10 位随机(L394),每次 POST /api/voice/session 都是**新房间新派发**;`create_dispatch` 在 src/ 下仅此一处(grep 证据在库)。没有任何对既有房间补发/重发 job 的代码路径。
- 前端 join 的就是该房间(grant.room),一个控制器会话 ↔ 一个房间 ↔ 一个显式派发 ↔ 一个 job ↔ 一个 `SubtitleBus`,五者一一对应。
- 服务器侧 job 重投(resend)只发生在 job 从未启动时(无可供 worker 接单/启动超时):那时第一个 entrypoint 从未运行、从未发过任何帧,前端 `live.seq` 仍为 0,重投 job 从 1 计与前端水位天然一致。SDK 源码中不存在「已运行的 job 迁移到另一进程」的机制(job 进程即 job 本体,崩溃即终止;livekit-agents 侧无 resend 代码,grep `resend` 仅命中无关模块)。
## 4. 真实残余缺口(非 seq,但同场景):worker job 中途死亡 = 前端僵尸「listening」
job 死亡(worker 进程崩溃/被运维重启)时:agent participant 离开房间,但**房间仍在**(用户还在),前端收不到 `dropped`(它只挂 `Disconnected`,web/src/voice/livekit.ts L69–75 只处理 `ParticipantConnected`/`DataReceived`/`Disconnected`/`AudioPlaybackStatusChanged`),页面停在最后相位(多为 listening),再无任何帧。`RoomEvents` 无 agentLeft。这不破坏 seq 或旧帧防护(没有第二命名空间),但用户会面对一个没有她的「通话中」。有限修复建议(归原作者,前端):`livekit.ts` 增加 `ParticipantDisconnected`(agent 身份)→ `RoomEvents.agentLeft` → `VoiceController` 以诚实话术收场(与 dropped 同路径、不删她的话轮)。后端无需改动。
## 5. 可执行复核方式
- 证据 A(本轮):`scripts/gen_hel879_unified_frames.py` 在 .12 真 SDK 上驱动实际 worker 回调生成全部 v2 帧(见 frames/),各场景 seq 严格递增、每 speech 恰一次 `turn_start`。
- 证据 B(源码引用,可重复):本文第 1/2/3 节的 file:line 均可在 .12 venv 直接核对;`.12` 只读 SSH stdin。
- 证据 C(前端侧):`web/tests/hel879_frames.test.ts` 用实际 worker 帧验证 replay/迟到/乱序帧全部被 seq+speech 双门拦下;「新通话 = 新房间 = 新序列」在 drop+重开用例中验证(旧房间帧不再影响新 `Live`)。
## 6. 结论
**现网代码内,seq 命名空间 = job = 房间 = 控制器会话,一一对应;不存在「同房间新 job 序号归零」的产品路径或 SDK 机制。** 前端以控制器会话记高水位是安全的,且对任何假设的第二命名空间表现为 fail-closed(旧水位丢弃全部低 seq 新帧,不会把旧帧当新帧收)。唯一真实缺口是 §4 的 agent 离场僵尸界面(🟡 建议项,归原作者)。
@@ -0,0 +1,44 @@
# HEL-879 统一整合审核记录(2026-10-27)
整合对象:`hel-879-assembly` @ fc905fe(后台时机收口)+ `origin/hel-898-preserved` @ fb7ea84(总管逐字节保全的 HEL-898 前端接线,作者代码 46ac0a7)。本审核不开新串、不重审已过内核(预算/备份/恢复/绑定/三处时机/总管 898 六项检查均沿用其通过记录)。
## 统一 head
`hel-879-assembly` 快进至 **fb7ea84 + 本轮审核提交**(审核物:帧生成脚本、fixtures、前端用例、浏览器回放 harness、SDK 取证、R5 测试包、handoff 追加)。与 fc905fe 的差异 = HEL-898 的 8 文件(6 web/tests+docs/handoff-898+2 web/src:ChatPanel.tsx/session.ts,总管已核逐字节=作者附件)+ 本轮审核新增文件;`web/src` 除作者两文件外零改动,`src/` 零改动,PR#11 与 hel-870-assembly 未动。
## 焦点1 · 实际 worker 帧 → 当前前端(功能)
**帧来源**:`scripts/gen_hel879_unified_frames.py` 在 .12 真实 SDK(livekit-agents 1.8.8 / livekit-api 1.2.1,SSH 只读 stdin,零写入零网络)驱动**实际 worker 接线**(`make_voice_llm` 归属绑定 + `bind_voice_session` 真回调 + 真 SpeechHandle 生命周期 + `speak_proactive_row`)生成五套 v2 帧(frames/ + web/tests/fixtures/hel879/)。唯一替身=HTTP 客户端(离线注入 /api/chat SSE)与播放时钟(`_mark_done`/`interrupt` 定时器)。
场景与结果(vitest `web/tests/hel879_frames.test.ts` 9/9,实际帧逐帧喂 `VoiceController`):
| 场景 | 帧序列(实测) | 前端行为(断言) |
| --- | --- | --- |
| normal | interim→user_final→turn_start→3×interim→**final→end**(receipt ack ✓) | 增量替换生长;final=存稿原文;落库回读接替:`voice-<speech>` 键→存档行 id(同 turn),mergeTurns 恰加一次 |
| interrupt | cut 轮 **无 final 只有 cancel(interrupted)**;下一轮干净 final+ack | 被切行立即清;迟到帧(新 seq、死 speech)不回显;下一轮独立落库一次 |
| φ | interim(φ)→error(φ 原句)→cancel(error_phi),无 ack | spoken 槽可闻不入档;her 即清 |
| no_receipt | **cancel(no-receipt),无 final**(fc905fe 门实测生效) | 无任何「她的回复」残留 |
| proactive | start **播放中**(sidecar `start_while_playing=true`)→end;打断变体→cancel(interrupted) | 她的位置整句显示、绑定存档行 id;打断即清;页面零写请求 |
打断/文字接管/掉线重开(vitest 全过):user_final 立即关她当前 speech;interim 不抢清(由服务端 cancel 定夺);`hush()` 文字接管即清行+静音;`dropped` 整通话收场清残影;重开=新房新序列,旧房间帧无效。舞台身份与键接替(voiceLine/mergeTurns)用实际帧 turn_id 验证。
**界面层**:本机缺浏览器 GUI 系统库(libatk/libgbm 等十项,chromium/chrome-headless-shell 均无法启动),零安装授权下**未独立复跑浏览器**。`web/tests/hel879_unified_shots.mjs`(自持 mock+CDP,回放实际 worker 帧、双主题双尺寸)已入库,供有 Edge/Chromium 环境一键回放。界面证据仍=作者 61/61 假 LiveKit 浏览器与两主题截图(总管保全,未复跑,不称独立通过)。**录屏:本轮无新连续帧/视频产出,如实列未交。**
## 焦点2 · seq 命名空间与 job/房间/控制器代次
结论(详见 `seq-job-analysis.md`,全部 file:line 指向 .12 真 SDK 源码):**job=房间=派发=控制器会话一一对应,无「同房间新 job 序号归零」路径**。一 job 恰一次 entrypoint(job_proc_lazy_main L241-246/L318-323);RTC 瞬断走 reconnecting/reconnected 不重跑(rtc/room.py L85-86/L1130-1133),房间真断=job 结束=前端 Live 同终;后端唯一派发点 session.py:396 每次新房(10 位随机名);服务器重投仅限从未启动的 job(从未发帧,水位天然一致)。若假设性第二命名空间出现,前端表现为 fail-closed(丢帧而非收旧帧)。**残余缺口(🟡 归原作者前端)**:worker job 中途死亡(进程崩溃/重启)时 agent participant 离场但房间仍在,livekit.ts 无 agentLeft → 页面僵尸「listening」。有限修复:`ParticipantDisconnected`(agent 身份)→ 诚实话术收场;后端无需改。
## 焦点3 · closed 64 上限与长会话/清理边界
- **DOCUMENTED GAP(🟡 归原作者,非现网可触发)**:64+ 后续关闭把退休 speech 挤出窗口后,**重复高 seq turn_start 可重进**(vitest 用例以对抗帧实证:重进→重放终稿可再次立为她的行)。现 worker 无法产生该帧(SDK speech_id 每话唯一、`interim.begin` 每流一次、无重发路径),触发需第二/失常来源——与焦点2结论一致。防御性修复建议:`closed` 改无界 `Set<string>`(每通话 speech 数有 TTL 上界,内存可忽略)或对 turn_start 增加按 job 内序号窗的约束。
- 新用户 interim→cancel/终稿清理边界(vitest 全过):interim 只更新 heard 不清她的行(打断由服务端定夺);user_final 立即关闭当前 speech 并清行;cancel/final/turn_end 的即时清理由实际帧验证。
- 舞台回读/身份与键接替:实际帧 turn_id 驱动(焦点1 表),非单测数字。
## 回归与需求检查
- pytest **352/352**(py3.11 venv);vitest **108/108**(作者 99+本轮 9);typecheck/build 通过;lint 0 错 1 既有 fullButton 警告;ruff(src/tests/tools) 0 错。
- 需求检查 4/4:origin/main 与 fc905fe 各带/不带更新后的 handoff-HEL-879.json(本目录 requirements-*.log)。v1.11/60 项无需求变更;总管 898 四项检查沿用。
## 边界
零真实 LLM/嵌入/ASR/TTS/麦克风、零下载/安装/部署/合并;.12 仅 SSH 只读取证(含一次 nvidia-smi/端口探活供 R5 包);真实 cap=0;不选音色、不改绑定、不碰旧服务/档案/冻结账。R5 真机包材料(`r5-test-package.md`)仅备齐待批,未执行。完整 CG-011、R6/R7 继续承接。
@@ -0,0 +1,21 @@
# wss_bridge.example.py 本地冒烟(2026-10-27,零外网、回环 only)
- 一次性本地自签证书(CN=local-bridge-test,2 天有效期,仅本测试)。
- dummy 上游 127.0.0.1:18880(读 5 字节回 echo);桥 127.0.0.1:18363 → 18880。
- 结果:TLSv1.3 握手成功;`sendall(b"hello")` 经桥穿透后收到 `b"echo:hello"`。
- 结论:TLS 终结 + 字节透明转发路径可用;.12 实部署与 LiveKit 真实信令仍属
拟议/待真机(本冒烟不证明后者)。
```
$ python3 deploy/voice/wss_bridge.example.py --listen 127.0.0.1:18363 \
--upstream 127.0.0.1:18880 --cert cert.pem --key key.pem &
wss-bridge(PROPOSAL) tls ('127.0.0.1', 18363) -> ('127.0.0.1', 18880)
$ python3 - <<'PY'
ctx = ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
s = ctx.wrap_socket(socket.create_connection(("127.0.0.1", 18363), timeout=5))
print("TLS:", s.version()); s.sendall(b"hello"); print("payload:", s.recv(64))
PY
TLS: TLSv1.3
payload: b'echo:hello'
```
@@ -0,0 +1,87 @@
# HEL-879 · 语音接缝接口与验收(交原视觉作者接线;总工不改已认可 web/src 视觉)
> 背景:总管独立复核指出「CG-011 语音主动开口 no-op、她的增量字幕缺失」是
> **实现缺口**,不能归为「等音色/预算就能自动完成」。本文件给出后台已落地的
> 最小接缝、前端需要补的**精确接口**与**验收标准**。代码基点:分支
> `hel-879-assembly`(见交付评论精确 head);后台实现
> `src/cybergril/voice/seam.py` + `worker.py` 接线。
>
> **2026-10-27 收口更新**:字幕消息已升级为**版本化契约 v2**(信封
> `v/seq`、确切轮次 `speech_id`、显式起止/取消事件、迟到/乱序保护、主动
> 播报同源 `message_id`)。完整可执行契约与录制样例见
> `docs/voice-subtitle-contract-v2.md` 与
> `docs/rework/hel879/subtitle-samples/*.jsonl`——**接线卡(HEL-898)以
> 该契约为准**,本文 A 节的 v1 语义作为兼容背景保留。
## A. 她的增量回复字幕(assistant_interim)
### 现状与缺口
- 用户侧 ASR 字幕已齐:`interim`(ASR 草稿)与 `user_final`(纠错终稿)在
`web/src/voice/session.ts` 已渲染(现有 `interim` 分支当「听到用户在说」处理,
**不能复用来渲染她的回复**——那是另一个语义)。
- 缺的是**她**的回复在 TTS 首包前/播放中的增量文字。后台现已补齐:worker 把
/api/chat SSE 的每个 delta 累积后发布到同一 `cg.subtitle` 通道。
### 后台已交付(无需前端改动即生效的数据)
- 数据通道:topic `cg.subtitle`(不变)。
- 新 kind:`assistant_interim`,`role: "assistant"`,`text` 为**截至当前的完整
回复文本**(替换语义,同用户 interim;发布时机=SSE 分片到达,先于/伴随 TTS)。
- 既有 kind 不变:`assistant_final`(已落库的完整回复,播放完成且未打断后发)、
`error`(φ 原句,未落库)、`interim`/`user_final`(用户侧)。
- fail-closed 语义不变:打断/失败/无回执的轮**只有 interim,不会有 final**——
前端必须把无 final 收尾的 interim 按「这轮没有成为她的正式回复」处理。
### 前端需补(原视觉作者;具体视觉由其定)
1. `web/src/voice/session.ts` 字幕分支:新增 `assistant_interim` 处理(当前
`if (s.kind === 'interim')` 只覆盖用户草稿)。
2. 渲染位置/样式:与 `assistant_final` 同一气泡位(建议替换式过渡),与用户
`heard` 视觉可区分;**不进入正式聊天记录**(只有 final 落库)。
3. 收尾规则:`assistant_final` 到达 → interim 消失、final 生效;`error` 到达 →
interim 消失、按现有 error 呈现;被打断(无任何收尾)→ 下一次 interim 或
新轮开始时清掉;挂断 → 清掉。
### 验收(离线可测,无需真实通话)
- [ ] 用假 LiveKit/假音频脚本回放 `cg.subtitle` 序列
`interim(user) → user_final → assistant_interim("她在") →
assistant_interim("她在打字中") → assistant_final("她在打字中。")`,
页面按序呈现且 interim 被 final 原子替换。
- [ ] 打断负例:`assistant_interim` 后直接新的 `interim(user)`(无 final/error),
她的 interim 残影不越过新用户轮。
- [ ] φ 负例:`assistant_interim` → `error`,error 文案呈现且不落正式记录。
- [ ] 全程零新增权限申请、零非回环出站(沿用 HEL-896 浏览器检查口径)。
## B. CG-011 语音主动开口(最小接缝,默认关闭)
### 后台已交付
- `worker.py _maybe_open_proactive`(真实代码路径,`CYBERGRIL_PROACTIVE_VOICE=1`
才启用;**默认关**,等接线卡与呈现批准):
- 数据来源:与文字收件箱同一 `GET /api/messages`(`ChatApiClient.recent_messages`),
只选**服务端已落库**(`proactive=1`、`role=companion`、`status=sent`、非空、
本房间未念过)的行,最新优先,每会话 ≤1 条(`PROACTIVE_PER_SESSION`)。
- 播报:`session.say(text, allow_interruptions=True)`——可被用户打断;
无新模型调用、无第二人格、**无第二条回执通道**(文字回执规则独立且已存在)。
- 失败 fail-closed:接缝任何异常只记日志跳过,不影响会话开启。
- 选择逻辑独立在 `seam.py pick_proactive/pending_proactive`(离线可测,
`tests/test_voice_rework_hel879.py` 三例覆盖)。
### 前端/接线卡需定(原视觉作者 + 总管,经用户批准后启用)
1. 入口呈现:她主动开口时页面如何呈现(字幕走 `assistant_final` 同位?
是否复用留言卡「回她」入口语义?未读标记如何联动——**worker 不代标已读**,
阅读态仍归页面)。
2. 节奏:会话开启后何时念(现在=开启即查一次、每会话最多一条);是否需要在
会话中轮询新 proactive(后续卡,不默记完成)。
3. 打断呈现:用户开口打断她的主动播报时,字幕按打断负例处理(无 final 收尾)。
### 验收(启用后)
- [ ] 预置一条落库 proactive 行 → 开语音会话 → 她原文念出该行(与文字收件箱
同一行、一字不差),字幕按定稿呈现。
- [ ] 播报中用户开口 → 立即被打断、无残音;该行不被标记已读(除非用户在页面
读它)、无回执伪造。
- [ ] 无 proactive 行 → 会话正常开启,静默。
- [ ] API 不可达 → 接缝跳过,语音会话不受影响。
## 边界
- 本文件只交接口与验收;**总工未改任何已认可 web/src 视觉**(本卡 web/src 仅
勘正 session.ts 一处无证据注释,无逻辑/样式改动,diff 见交付评论)。
- R5 真机验收(≥3 轮、1 秒打断、停麦无残音、可信 HTTPS/WSS、D1 音色、有限
预算)仍待主卡集中批准,接缝通过≠R5 完成。
@@ -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
}
+39
View File
@@ -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
}
+162
View File
@@ -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"
}
+28
View File
@@ -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))
+134
View File
@@ -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())
+43
View File
@@ -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
}
+172
View File
@@ -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"
}
+45
View File
@@ -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"
}
+60
View File
@@ -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"
}
+40
View File
@@ -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
}
}
+59
View File
@@ -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"
}

Some files were not shown because too many files have changed in this diff Show More