Skip to content
←Return to Projects
Case Study

KIWI

Autonomous local AI agent and community of voices living in The Grove with private memory.

The Problem

Autonomous AI agents often suffer from leaky memory isolation, unpredictable side-effects on host systems, and lack of human-in-the-loop governance for high-stakes file operations and external device actions.

My Role

Architected and implemented the cognitive engine ("The Grove"), fail-closed host action safety policies, immutable exact-action plan ledgers, multi-device agent companion protocol, and unified multi-channel gateways.

Highlights

  • Community of six voices living in The Grove with local memory running on the user's machine
  • Local cognitive core ("The Grove") separating owner memory from credentialed client sessions
  • Fail-closed security policy requiring immutable exact plans with rollback for all destructive actions
  • DPAPI-secured Windows companion agent with local execution receipts and anti-re-execution guarantees
  • Multi-modal interaction surface: Next.js Web UI, Voice HUD (:4321), and Telegram bot gateway with inline approvals
  • Deterministic audit ledger tracking every state mutation, file movement, and memory association

Stack

TypeScriptNext.js 15Node.jsSQLiteDrizzle ORMDPAPITelegram Bot APIDocker

Constraints

  • Host safety is non-negotiable: no autonomous file deletion or batch reorganization without human-confirmed exact plans.
  • Offline-first engine: core cognitive loops and memory databases must run purely locally without cloud dependencies.
  • Multi-tenant memory isolation: administrative sessions and API token holders must never access the owner private mind context.

System Architecture

Interaction Channels
Next.js 15 Web App (:3333)
Voice HUD (:4321)
Telegram Bot
Embed Bridge (:3234)
Gateway & Protocol
Private Grove API (:8431)
Device Control API
DPAPI Token Auth
Cognitive Engine ("The Grove")
Mind Core
Behavior & Persona
Memory Tiering (STM/MTM/LTM)
Skill Registry
Execution & Safety
Host Action Policy
Exact-Action Ledger
Backup & Rollback Ledger
Distributed Node Runtime
Windows Companion Daemon
Local File Executor
SQLite / Drizzle State

Key Trade-offs

The decisions worth defending — what I chose, what I turned down, and why.

File management execution model

Chose

Immutable two-phase exact-action plans with hash verification and dry-run preview

Rejected

Direct agent tool execution with broad shell permissions

Agents making speculative shell or file modifications risk catastrophic data loss; two-phase plans guarantee human oversight and rollback capability.

Engine-to-web communication architecture

Chose

Dedicated loopback HTTP/SSE gateway with bearer token authentication

Rejected

Monolithic single-process Next.js application

Decoupling the AI engine process from the Next.js frontend ensures background monitoring, telegram polling, and long-running skills survive web redeployments.

Device pairing security model

Chose

Windows DPAPI machine-bound tokens with polling architecture

Rejected

Inbound SSH or remote socket servers on client machines

Inbound listening ports expose clients to local network scans; outbound HTTPS polling behind DPAPI keeps remote companion devices secure.

What I'd Do Differently

An honest retrospective — the stuff I'd change with more time, more users, or a second pass.

  1. 1Implement automated regression tests for memory graph decay sweeps earlier in the development lifecycle.
  2. 2Consolidate SQLite schema migrations between better-sqlite3 and pg-schema under a single unified migration runner.
  3. 3Add fine-grained telemetry dashboards to observe cognitive latency across multi-turn reasoning chains.

Interactive Autonomous Agent Shell

Test live KIWI autonomous agent supervisor routines (agent status, grove ping, mcp tools, help) in the emulated shell below.

icelegends-interactive-shell ~ [kiwi]
History: ↑ / ↓
kiwi@the-grove:~/voices$

Technical Deep-Dive

Architecture, specifications, and implementation details.

Audit P0 — limitele conturilor și execuției

Revizia 17: auditul închide itemii P0 rămași deschiși mai jos, cap-coadă. Gaura închisă: automatizarea file-organizer nu putea muta fișiere, dar nici fluxul sigur de înlocuire nu exista; acum executantul real e skill-ul builtin file_organizer (kind file_organizer în ledger) — clasificare partajată în src/skills/fileOrganize.ts (computeOrganizeMoves: categorii din EXTENSION_MAP, arbore de destinație coerent, ținte anti-coliziune (2) verificate și pe disc, opțional archiveAfterDays → _archive, fișierele neclasificate/directoare numărate și lăsate în loc; întoarce { moves:[{from,to,size,archived}], skipped }), dry-run pregătește un plan exact cu copii de recuperare, execuția DOAR cu exactPlan:{id,kind:'file_organizer'} valid + aprobare umană (allow la pregătire, confirm la execuție — aceeași formă de politică ca revizia 16; fără referință → deny). Automatizarea mind file-organizer (v1.1.0) e preview-only — raportează ce s-ar muta și unde, nu atinge discul; singura cale de mutare rămâne planul exact aprobat în chat. dev_task e fail-closed implicit: src/config/config.ts pune security.skillPolicy.dev_task: 'deny' (înainte confirm, o capcană latentă), iar dev_status/dev_cancel refuză când dev e oprit — DevManager.start n-are apelant activ în producție, deci niciun job de codare de fundal nu poate porni (înlocuitorul sigur: sarcini-dispozitiv P1 / codare interactivă P2). Drift-ul config/ținte: configPath() în src/config/config.ts onorează acum KELVIN_CONFIG_PATH (implicit neschimbat; loadConfig/configExists/saveConfig toate îl folosesc — testele rulează pe un fișier aruncabil, nu pe kiwi.config.json-ul real cu chei cifrate), iar POST /api/documents/root în src/grove-api.ts sincronizează rezultatul saveConfig înapoi în kiwi.mind.config.skills și kiwi.skillContext.documentsDir — rădăcina vie a documentelor nu mai diverge de obiectul de config până la restart (înainte, auto-summarizer-ul și indexatorul puteau vedea rădăcina nouă cât timp config-ul runtime avea încă pe cea veche). Gaura de continuitate a memoriei închisă: GET /api/kiwi/knows în web/src/app/api/kiwi/knows/route.ts permitea oricărui utilizator credențiat să facă proxy pe /api/knows al proprietarului din motor (fapte/natură/peeves/fire); acum e engine-backed DOAR pentru sesiunea browser admin fără antet authorization, altfel răspunde embedded {source:'embedded', nature: getNatureObservations(userId), facts:[], ...} per utilizator (DELETE/PATCH erau deja admin-gated). Intrarea limitată: ruta document chat stream citește prin readChatBody (500 KB corp / 100k caractere mesaj), iar motorul răspunde 413 pe ambele /api/documents/chat și /api/documents/chat/stream înainte de a contacta LLM-ul. Audit atașamente (producție): scripts/audit-attachment-links.mjs e preflight read-only pe data/kelvin.db (raportează încă tableExists:false, migrationRequired:true — tabelul message_files apare doar la pornirea web-ului); noul scripts/repair-attachment-links.mjs auditează și, la --fix, repară într-o singură tranzacție — dedupe perechile (message_id,file_id) păstrând MIN(rowid), șterge asocierile orfane (mesaj/fișier lipsă) și pe cele cu proprietar greșit; nu atinge niciodată stocarea fișierelor. /api/chat/files respinge 413 din Content-Length înainte de buffering-ul multipart (plafonul planului + 1 MB antet). Teste noi: motor 4 (flux exact file_organizer prepare/deny/execuție în tests/exact-plan.test.ts), motor 2 (tests/dev.test.ts: config implicit neagă dev_task; dev_status/dev_cancel fail-closed), motor 2 (tests/documents-root-scope.test.ts), motor 1 (tests/documents-chat-bound.test.ts); web 4 actualizate în tests/mind-route-isolation.test.ts (+ acoperire ruta knows) + 5 noi în web/tests/document-chat-body-bound.test.ts + 1 nou în web/tests/chat-file-lifecycle.test.ts (413 Content-Length). Scriptul de reparare și --fix au fost exersate pe un DB-fixture (5 asocieri → 2, duplicate/orfane/mismatch eliminate). Verificări: 132 teste motor (131 trecute + 1 skip semantic opțional); 228 mind/web în 34 suite; ambele typecheck-uri trecute. Fără lansare în producție.

Revizia 16: auditul continuă cu planurile exacte de acțiune pentru mutările/ștergerile largi. Gaura închisă în revizia 16: documents_organize non-dryRun și disk_doctor reap erau refuzate (motiv exact-plan), dar nu exista încă mecanismul; acum există src/exactions/ledger.ts — plan imutabil (id, kind, operațiuni cu căi absolute + size; sha256 pentru fișiere move ≤16 MB), prepareExactPlan (validează sursă există/țintă liberă/size>0, copii de recuperare pentru move în data/exactions/<id>/moves/N.bin sub KELVIN_DATA_DIR, ledger data/exactions/ledger.json cu status:'prepared'), validateExactPlan (imuabilitate canonizată {kind, ops} + stare pe disc re-verificată la fiecare execuție), executeExactPlan (move=rename; delete=rename în deletes/N, fără rm; status:'partial' la prima eroare; 'done' + executedAt la succes), rollbackExactPlan (restaurează, idempotent; planurile rolledback nu se re-încarcă). Politica (src/security/host-action-policy.ts): exactPlanRefOf acceptă doar exactPlan:{id,kind}; fără ref → deny (neschimbat), cu ref valid → confirm (aprobare umană obligatorie prin ConversationManager, nu auto-executare); pregătirea rămâne allow (dryRun:true / action:plan_reap). documents_organize pregătește planul pe dryRun:true (clasificare LLM, ținte anti-coliziune, copii de recuperare) și execută doar cu plan valid; disk_doctor plan_reap pregătește, reap+exactPlan mută în backup (ștergerea destructivă veche eliminată). dev_task și file-organizer rămân refuzate (P1). Teste noi: 11 în tests/exact-plan.test.ts (move+delete prepare/validate/execute/rollback, coliziune, parțial, flux documents_organize, flux disk_doctor, politică deny→confirm, aprobare reală prin manager, shape ref, block la nivel registru). Verificări: 124 teste motor (123 trecute + 1 skip semantic opțional; +11); 222 mind/web în 33 suite; ambele typecheck-uri trecute. Fără lansare în producție.

Revizia 15: pluginurile de skills intră sub politică fail-closed pe capabilități declarate. Gaura închisă în revizia 14: skill-urile din plugins/ (încărcate de src/skills/plugins.ts) nu aveau metadata de efecte — hostSkillPolicy le trata ca necunoscute cu allow implicit, iar apelurile directe la registry.run (document skills în src/grove-api.ts:941, diagnostic CLI în src/cli/kiwi.ts:302) executau fără politica completă. Corecții: Skill.capabilities (read/write/exec/network/system/media, src/skills/types.ts); hostSkillPolicy primește DeclaredSkillInfo și declaredSkillPolicy (src/security/host-action-policy.ts) — plugin fără declarație sau cu efect mutațional → confirm, numai read → allow, iar un allow configurat vechi nu slăbește neverificatul (fail-closed); SkillRegistry.skillDeclaration expune metadata și este alimentată în toate căile: Agent.declaredSkill (JSON/stream/offline, src/agent/agent.ts), ConversationManager (src/conversation/manager.ts), Telegram direct (src/telegram/run.ts); backstop în registry.run refuză plugin-ul fără capabilități înainte de run() (rutele directe rămân sigure); src/skills/plugins.ts avertizează la încărcare; plugins/example-dice.mjs declară ['read']. Builtin-ii și skill-urile înregistrate fără sourceFile nu sunt pluginuri → allow păstrat (teste host-action-boundary/web-conversation-scope neafectate). Teste noi: 8 în tests/plugin-capabilities.test.ts (fail-closed cu allow vechi, deny configurat, tool scope, skillDeclaration builtin vs plugin, backstop fără execuția run(), builtin run direct care trece). Verificări: 113 teste motor (112 trecute + 1 skip semantic opțional; +8); 222 mind/web în 33 suite; ambele typecheck-uri trecute. Fără lansare în producție.

Revizia 14: auditul continuă cu automatizarea mind și rădăcina documentelor. Branch-ul de automatizare din mind.hear (src/mind/kelvin.ts) executa skill-urile automation cu TOATE permisiunile și fără politică host-action — unic apelant producție: fallback-ul brain-down din src/grove-api.ts:122 (ruta scoped 503 cât brain-ul e jos; fereastră de racord ≤120s). Corecții: hostSkillPolicy clasifică automatizările (app-launcher/note-taker → confirm; file-organizer non-dryRun → deny cu motivul exact-plan; read-only web-search/system-monitor/file-finder/daily-briefing/timer/reminder rămân allow) și mind.hear aplică politica înainte de execuție — confirm → blocat ca fir intern (automationResult:'blocked', memorat ca atare), deny → refuz cu motivul; acordă numai permisiunile declarate ale skill-ului (privilegiu minim); app-launcher refuză metacharacterii de shell în appName/args (isSafeLaunchToken) înainte de execSync. POST /api/documents/root folosește acum resolveDocumentsRoot (src/documents/request-scope.ts): crearea de foldere noi e permisă doar în interiorul PROJECT_ROOT, selectarea unui folder existent rămâne permisă oriunde (deploy curent C:\Users\alexm\Documents\KiwiDocs, neatins). Pluginurile rămân accesibile numai prin registru sub aceeași politică, dar fără metadata de capabilități (migrare P0 deschisă). Teste noi: motor 4 (tests/mind-automation-policy.test.ts ×3: clasificare, motiv exact-plan, refuz metacharactere + dry-run; tests/document-scope.test.ts +1: rădăcină documente). Verificări: 105 teste motor, 104 trecute + 1 skip semantic opțional; 222 mind/web în 33 suite; ambele typecheck-uri trecute. Fără lansare în producție.

Revizia 13: recuperarea după cădere pentru chatul web al proprietarului. La o întrerupere a browserului admin mid-turn, motorul finalizează și persistează runda în firul scoped, dar ruta SSE a web-ului se oprește la stopped() și nu salvează răspunsul assistant. Motorul expune GET /api/chat/scoped/thread în src/grove-api.ts (read-only; validează identitatea owner-web:v1:<sha256> cu isWebConversation și întoarce { ok, conversationId, turns: [{ role, content }] }) prin ConversationManager.getTurns(chatId) din src/conversation/manager.ts (copie a firului; fir inexistent → []; citirea nu mută store-ul). Web-ul o consumă la GET /api/chat/history?channel=web&sessionId=... pentru un cont admin prin reconcileOwnerChat din web/src/lib/chat-reconcile.ts (bridge ownerChatTurns în web/src/lib/owner-chat.ts): colapsează firele în runde { user, assistant }, iar dacă ultimul mesaj web este user fără assistant după el și ultima rundă engine completă are același conținut user, adaugă răspunsul lipsă (appendMessage('assistant', ...)) — numai coadă, max 1 append, idempotent. Runda engine cu user solitar la final = generare în curs, nu se reconciliază. Best-effort: eroare motor ignorată/logată, istoricul se servește oricum; multi-gap respins deliberat (append la final ar amesteca ordinea). Teste noi: motor 3 (getTurns copie fără mutare; endpoint thread validare identitate + răspuns corect; fir gol/inexistent), web 6 (reconcile missing reply adăugat o dată; ultim deja răspuns → 0; divergență conținut → 0; user solitar la final → 0; fir gol/istoric gol → 0; ruta history admin vs non-admin, ne-web, motor offline nu blochează). Verificări: 101 teste motor trecute, unul semantic opțional sărit; 222 teste mind/web în 33 suite; ambele typecheck-uri trecute. Fără lansare în producție.

Revizia 12: webhook-ul Telegram și cota mesajelor nu mai pot fi deranjate de re-livrare sau concurență. web/src/app/api/telegram/webhook/route.ts captează update_id, îl înregistrează o singură dată în telegram_webhook_updates (cheie (chat_id, update_id), INSERT OR IGNORE, răspuns 200 fără procesare la duplicat, curățare a rândurilor mai vechi de 48h) și consumă cota prin consumeMessageIfUnder (un singur UPDATE ... WHERE messages_used < limit, changes === 1), plasat după filtrarea mesajelor numai-fișier și înainte de appendMessage — mesajele fără text/fișiere nu consumă cota. web/src/lib/db/index.ts, sqlite-schema.ts și pg-schema.ts conțin tabelul (paritate). src/telegram/run.ts persistează offset-ul update_id în data/telegram-owner-offset.json (în KELVIN_DATA_DIR în teste) și reia de acolo la restart. Teste noi: web 4 (web/tests/telegram-webhook-dedup.test.ts: duplicat procesat o singură dată, consum atomic la plafon pentru cereri concurente, respingere peste cotă, mesaj numai-fișier fără consum), motor 1 (offset persista, reluare la restart). Verificări: 97 teste motor trecute, unul semantic opțional sărit; 210 teste mind/web în 31 suite; ambele typecheck-uri trecute. Fără lansare în producție.

Revizia 11: rutele de chat impun acum limite de corp și de mesaj înaintea oricărui apel către brain sau motor. Web-ul citește corpul prin web/src/lib/chat-body.ts cu un cap de 500.000 de octeți (citire în flux, fără buffer nedefinit), validează JSON-ul (400 pentru corp lipsă/invalid/mesaj gol) și respinge mesajele peste 100.000 de caractere cu 413 pe ambele rute (/api/chat JSON și /api/chat/stream SSE); fileIds rămâne brut, iar resolveChatAttachments respinge valorile malformate. Motorul (/api/chat/scoped în src/grove-api.ts) respinge mesajele non-string sau peste 100.000 de caractere cu 413, fără a contacta managerul. Teste noi: motor 1 (corpuri peste limită respinse fără a contacta managerul), web 5 (web/tests/chat-body-bound.test.ts). Verificări: 97 teste motor trecute, unul semantic opțional sărit; 205 teste mind/web în 30 suite; ambele typecheck-uri trecute. Fără lansare în producție.

Status: în desfășurare, plan revizia 17, 2026-09-09. Modificări locale; fără deploy. Audit parțial, nu certificare a întregului proiect. Revizia 17: file_organizer își închide fluxul exact (kind file_organizer în ledger; skill builtin allow la pregătire / confirm la execuție; automatizarea mind e preview-only, nu mută), dev_task e fail-closed implicit (dev_task:'deny' în config; dev_status/dev_cancel refuză fără mod dev), drift-ul config/ținte e închis (configPath() respectă KELVIN_CONFIG_PATH; POST /api/documents/root sincronizează kiwi.mind.config.skills), memoria proprietarului e izolată (GET /api/kiwi/knows engine-backed doar pentru browser admin; alte conturi primesc payload embedded per-user), rutele de chat documente sunt limitate (web readChatBody + motor 413 pe ambele rute), iar auditul atașamentelor e complet cu --fix tranzacțional (scripts/repair-attachment-links.mjs; data/kelvin.db produs încă nu are message_files până la restart — preflight, fără scrieri). Revizia 16: documents_organize și disk_doctor reap trec pe planuri exacte persistente cu backup/rollback (src/exactions/ledger.ts) și execuție numai cu exactPlan:{id,kind} valid + aprobare umană; dev_task rămâne oprit. Revizia 15: pluginurile de skills trec pe capabilități declarate fail-closed (Skill.capabilities, declaredSkillPolicy, registry.run refuză undeclaratul înainte de execuție) — gaura de la allow implicit închisă. Revizia 14: automatizarea mind (mind.hear) trece prin politica host-action cu privilegiu minim și app-launcher fără injecție shell; POST /api/documents/root restricționează crearea de foldere la proiect. Revizia 13: recuperarea după cădere — GET /api/chat/scoped/thread (read-only) + reconcileOwnerChat la GET /api/chat/history?channel=web&sessionId=... pentru admin adaugă răspunsul assistant pierdut la întrerupere (doar coadă, idempotent, best-effort). Revizia 12: deduplicarea update_id Telegram și consumul atomic al cotei. Revizia 11: limite de corp/mesaj pe rutele de chat. Revizia 10: ConversationManager serializează livrările per fir (runSerialized), motorul expune DELETE /api/chat/scoped (validează identitatea owner-web:v1:<sha256> și așteaptă runda în curs), iar web-ul, la ștergerea unei sesiuni browser de admin, cere motorului să uite firul scoped corespunzător, fără să se blocheze la eroare; POST /api/documents/organize refuză orice non-dryRun. Verificare revizia 17: 132 teste motor (131 trecute + 1 skip semantic opțional); 228 mind/web în 34 suite; ambele typecheck-uri trecute.

Revizia 8: continuitatea transportului chat trimite chatul JSON și SSE al sesiunii browser admin către același endpoint al proprietarului. Eroarea motorului este explicită și nu schimbă mintea cu cea embedded; browserul nu repetă automat prin JSON cererea eșuată sau incompletă. SSE refuză Authorization, inclusiv împreună cu un cookie admin. Răspunsul proprietarului este livrat integral, fără streaming incremental al modelului. Verificare curentă: 192 teste mind/web în 28 suite, typecheck web, build de producție izolat și probă chat în Edge trecute. Persistența personală, fallback-ul intern al motorului și delimitarea conversațiilor rămân deschise. Fără deploy de producție.

Revizia 7: autorizarea acțiunilor pe gazdă aplică lista permisă în JSON/stream/offline, compune restricțiile configurate și ale apelantului, păstrează politica inițială a aprobării și împiedică textul rutinelor interne să autorizeze modificări. Mutațiile cunoscute cer confirmare; previzualizările sunt complete sau refuzate. Telegram direct reverifică politica. Pornirea dev autonomă, mutările documentelor în lot și curățarea distructivă a discului sunt oprite până la planuri exacte. Verificare în revizia 7: 90 teste motor trecute, unul semantic opțional sărit; 180 teste mind/web în 27 suite trecute. P0 rămâne deschis pentru continuitatea memoriei, automatizări/pluginuri și migrarea țintelor exacte. Fără lansare în producție.

#Corecții și dovezi din revizia 6

  • Credencialele Authorization invalide, goale sau revocate nu mai moștenesc identitatea browserului prin currentUserIdFrom; descărcarea ISO refuză aceeași rezervă. Rutele browser pentru atașamente refuză antetele Authorization.
  • Ciclul atașamentelor: încărcarea/listarea cer conversație proprie; SQLite rezervă cota înaintea scrierilor asincrone; numele generate înlocuiesc căile primite; ștergerea verifică rădăcina administrată și păstrează metadatele la erori. Atașamentele expirate nu intră în prompturi noi.
  • Curățarea Telegram ștergea toate fișierele comune de upload mai vechi de 24 de ore, inclusiv fișiere web neexpirate. Acum elimină numai fișiere/înregistrări administrate explicit expirate, păstrând înregistrările vechi fără expirare și fișierele necunoscute. Uploadurile Telegram noi expiră după 30 de zile. Nu s-a rulat curățarea producției și nu s-au modificat bibliotecile PDF.
  • Verificare web curentă: 177 teste în 27 suite trecute, typecheck web și compilare companion trecute. Build-ul de producție izolat și proba browser/HTTP au trecut cu conturi sintetice, editare temporară reală, refuzul contului străin, rezultat păstrat după restartul serverului de probă și revocare. Aprobarea locală este simulată; este un PC, nu acceptare pe două PC-uri. Reperul anterior al motorului rămâne 80 trecute și un test semantic opțional sărit.
  • Inițializarea prematură a clientului email a fost mutată la trimitere, permițând build fără credențiale de furnizor. Nu s-au trimis emailuri reale. Migrarea producției, mutațiile gazdei și continuitatea memoriei rămân deschise.

#Corecții și dovezi din revizia 5

  • /api/kiwi/status și /api/kiwi/memory aveau aceeași scăpare spre motorul proprietarului ca chatul JSON. Conturile obișnuite și cererile cu credențiale folosesc acum starea embedded proprie; accesul browser admin este păstrat. Patru teste verifică două identități, lipsa autentificării și combinarea credențial/admin.
  • Telegram web acceptă numai solicitantul/chatul privat corespunzător, consumă atomic codurile neexpirate și nu regenerează coduri expirate la mesaje externe. Căutarea legăturii refuză proprietari ambigui. Sesiunile vechi sunt filtrate după proprietar; ID-urile noi includ hash-ul contului și chatului. Șase teste SQLite/handler verifică proprietatea, expirarea și reutilizarea.
  • Confirmarea conversațională leagă identitatea originală a canalului, copiază argumentele, expiră după cinci minute și reverifică politica de refuz. Textul ambiguu nu autorizează execuția. Telegram direct folosește telegram:<solicitant> pentru această limită, chiar dacă istoricul configurat comun rămâne comun.
  • Auto-resolve verifică expirarea înaintea deciziilor și execuției în lot. Modelul poate folosi numai unelte de diagnostic prin src/security/diagnostic-policy.ts; nu mai primește autorizare nelimitată. Repararea exactă rămâne de integrat în sarcinile dispozitivelor.
  • Gateway-ul documentelor executa nume arbitrare de skills și schimba directorul comun al motorului. Acum validează lista de unelte pentru documente, refuză suprascrierea proprietarului și mutările reale de organizare, folosind context clonat per cerere. Chatul documentelor nu păstrează stare între cereri și folosește numai documentele curente, fără istoric, unelte, status sau fallback din mintea proprietarului. Trei teste motor și unul de handler web verifică limitele; furnizorul live/endpoint-urile motorului rămân de validat.
  • node scripts/audit-attachment-links.mjs a citit data/kelvin.db fără scrieri și a raportat tableExists: false, migrationRequired: true. Nu s-au reparat asocieri de producție. O bază configurată la altă cale trebuie verificată separat.
  • Proprietatea stării și limitele de continuitate sunt documentate. Pilotul P1 are teste SQLite/fișiere temporare reale și instalare de pachet izolată; este oprit și nu a trecut acceptarea pe două PC-uri.
  • Ultima rulare completă înaintea regresiei pentru Origin prin tunel: 158 teste mind/web în 23 suite; 79 motor trecute, unul semantic opțional sărit. Ambele typecheck-uri și compilarea companion-ului trec. Pilotul/checkpoint-ul consemnează verificările ulterioare. Fără build web, deploy sau restart în această etapă.

#Defect confirmat și corecție

Ruta web fără streaming, web/src/app/api/chat/route.ts, trimitea mesajul și notele personalităților fiecărui utilizator autentificat către motorul gazdă, fără identificator de utilizator. Endpoint-ul din src/grove-api.ts procesează prin kiwi.manager.reply('web', ...), folosind mintea comună a proprietarului.

Ruta permite acum motorul gazdă numai pentru sesiunea browser de admin fără antet Authorization. Conturile obișnuite și cererile cu credențiale folosesc chat(message, userId, briefs). Credencialele nu moștenesc accesul la motor al unei alte sesiuni browser de admin. Istoricul, atașamentele și facturarea sunt păstrate; revizia 8 înlocuiește fallback-ul web al proprietarului cu eroare explicită și folosește motorul inclusiv pentru SSE.

Este restabilită limita din ADR-0006. Corecția chatului singură nu implementează rutarea dispozitivelor, nu dovedește izolarea brain-ului embedded și nu unește memoriile proprietarului din streaming și ruta obișnuită. Pilotul P1 separat nu rezolvă aceste lipsuri de memorie. Niciuna dintre schimbări nu a fost publicată prin deploy.

#Proprietatea atașamentelor și pornirea unei baze curate

POST /api/chat și POST /api/chat/stream folosesc acum web/src/lib/chat-attachments.ts înainte de crearea istoricului sau consumarea cotei. Câmpul opțional fileIds trebuie să fie un array de ID-uri string nevide; fiecare ID unic trebuie să aparțină utilizatorului autentificat. Un ID străin, lipsă sau malformat respinge întreaga cerere cu HTTP 400 și Invalid or unavailable attachments. ID-urile lipsă și cele străine primesc același răspuns. Duplicatele sunt asociate o singură dată. Fișierele proprii în curs de extragere rămân atașate; numai textul extras disponibil intră în prompt.

Anterior, selecția conținutului filtra proprietarul, dar asocierile mesajelor foloseau ID-urile brute cerute. Ambele folosesc acum lista verificată. Inițializarea din web/src/lib/db/index.ts creează și tabela lipsă message_files, cu chei străine cascade și indexurile existente în Drizzle, inclusiv unicitatea (message_id, file_id). Fluxul este verificat în baze SQLite temporare curate. Asocierile existente din producție nu au fost auditate sau reparate; verificăm duplicatele și referințele orfane înainte de lansare. Nu este migrare PostgreSQL sau audit complet al rutelor de încărcare/descărcare.

#Canalul Telegram direct al proprietarului

src/telegram/run.ts acceptă acum numai ID-uri Telegram pozitive, autorizate explicit, în conversația privată a respectivului utilizator. Lista goală refuză accesul și nu distribuie rezultate; grupurile nu pot emite comenzi pentru gazdă. Mesajele neautorizate nu mai schimbă destinatarul de rezervă al notificărilor dev; și destinatarii dev expliciți trebuie să fie autorizați.

src/telegram/approvals.ts leagă butoanele acțiunilor și auto-resolve de utilizator, conversația privată și mesajul livrat. Aprobările expiră după cinci minute, sunt consumate înainte de execuție și dispar la restart. Alegerile invalide, tokenurile necunoscute, alt solicitant/mesaj și reutilizarea sunt respinse. O propunere distribuită este consumată o singură dată între destinatarii autorizați; curățarea folosește fiecare conversație și mesaj original. /open cere acum confirmare înainte de lansarea programului.

Acesta rămâne un canal de administrare al proprietarului, cu istoric comun, nu acces per cont la dispozitive. Persoanele autorizate administrează aceeași gazdă. Auto-resolve este limitat la diagnostic. Din revizia 16, documents_organize și disk_doctor reap rulează pe planuri exacte aprobate cu backup/rollback; dev_task (pornirile OpenCode din aplicație) și file-organizer rămân blocate până la planuri exacte; acțiunile generice cunoscute au confirmare obligatorie. Legarea țintelor/configurației rezolvate și căile de automatizări/pluginuri rămase necesită migrare. Telegram administrat de web este o cale separată per cont, corectată conform reviziei 5 de mai sus.

#Corecțiile mediului de teste

Vitest rezolvă server-only către un modul gol exclusiv pentru teste, conform comportamentului intrării server; importurile din producție sunt păstrate. Conexiunile SQLite de inițializare din teste se închid înainte de ștergerea temporarelor. Fixture-ul instanțelor nu mai inserează coloana veche users.mission. Testele offline embedded-chat nu mai citesc web/.env și blochează fetch; modul cu furnizor real rămâne explicit prin RUN_LLM_E2E=1 și nu a fost rulat.

#Reper anterior de verificare — revizia 4

  • Typecheck web trecut înainte și după schimbare.
  • npm run test:mind -- --run web/tests/chat-routing-isolation.test.ts: înainte, 2 teste eșuate și 3 trecute; după, toate cele 5 trecute.
  • Testele apelează handler-ul real, cu autentificare, persistență și brain-uri simulate. Acoperă două identități obișnuite, cheie API împreună cu sesiune admin, accesul proprietarului, fallback-ul și refuzul fără autentificare. Nu folosesc DB live, furnizor LLM sau PC fizic.
  • Reper anterior: 11 suite trecute, 4 eșuate; 99 teste trecute, 20 sărite, cu server-only nerezolvat și EPERM Windows. După corectarea mediului au trecut 119 teste; după regresiile adăugate în acest pas, npm run test:mind trece toate cele 17 suite / 138 teste.
  • npm run test: 66 trecute, 1 test semantic opțional sărit, 0 eșuate. npm run typecheck și npm run typecheck:web trec.
  • web/tests/chat-attachments-isolation.test.ts: 14 teste ale handler-elor reale, cu SQLite temporar și doi proprietari; brain-urile, autentificarea și facturarea sunt simulate. Verifică respingerea fără istoric/consum, conținutul propriu și asocierile unice în ambele rute.
  • tests/telegram-approvals.test.ts: 6 teste pentru identitate, legarea mesajului, expirare, invalidare la restart, utilizare unică și destinatari multipli. tests/mind/unit/telegram-owner-channel.test.ts: 5 teste pe handler-ul real de polling, cu transport simulat și fără execuții pe gazdă: /open, refuz, callback-uri falsificate și reutilizare.
  • Verificările nu dovedesc comportamentul live Telegram/LLM, compatibilitatea migrării producției sau izolarea pe două dispozitive. Nu s-a făcut build, restart de servicii sau lansare.

#Ce rămâne în P0

  1. Delimitarea conversațiilor și fallback-ului intern al motorului, plus memoria embedded volatilă; auditarea celorlalte rute cu credențiale mixte și a limitelor conversațiilor.
  2. Migrarea acțiunilor largi/codării rămase oprite (dev_task, file-organizer) către planuri exacte aprobate — documents_organize și disk_doctor reap sunt migrate în revizia 16; auditarea automatizărilor/pluginurilor și schimbărilor țintelor/configurației din afara căilor conversație/Telegram corectate. Auto-resolve limitat la diagnostic rămâne o restricție, nu reparare completă.
  3. Verificarea căilor/asocierilor de producție și backupurilor înainte de lansare; limite multipart în flux, reconciliere după cădere și cote Telegram concurente. Corecțiile locale ale ciclului atașamentelor nu repară istoricul.
  4. Acceptare live cu două conturi/două PC-uri și review de lansare înainte de activarea pilotului P1 izolat. Testele automate cu dispozitive logice nu reprezintă acceptare pe dispozitive fizice.

Surse: plan aprobat, checkpoint, web/src/lib/access.ts, web/src/app/api/chat/stream/route.ts, src/telegram/run.ts, web/tests/chat-routing-isolation.test.ts.

~ End of Document ~