Până acum, Voost Control nu putea răspunde: un task avea o singură notă mutabilă, fără autor, fără istoric. PR #1289 o înlocuiește cu Task Activity — comentarii care nu se mai pot suprascrie. Am auditat schimbarea cu 16 agenți + un review independent Codex. Asta a ieșit.
48fișiere atinse
+1.475linii adăugate
0probleme P1
2P2 confirmate
16+1agenți de audit + Codex
Verdict: merge
Feature-ul merită producția — rezolvă o pierdere reală de informație, fără consumatori inventați. Dar cere 2 fixuri înainte de merge, ambele în același loc: podul de compatibilitate din migrare.
ACTUL 1
Nota care se ștergea singură
Câmpul reviewNote era folosit de tine și de mașini în același timp. Dialogul de Review îți punea în față toată nota și salva ce lăsai tu — peste tot ce scriseseră agenții.
Demo — suprascrierea (comportamentul de pe main)
Follow-up ofertă — lead Necesit (instalații electrice)
[agent-work-evidence] pachet #a41f: 3 apeluri, transcript promovat, outcome=partial[reconciliation] Task înlocuit de follow-up-ul din 8 iul
Client 360Review PolicyDigest reconciliere
Contextul mașinilor a dispărut. Dialogul salvează câmpul întreg — evidence-ul agentului și markerul de reconciliere s-au dus, iar cei trei consumatori de mai sus citesc acum o notă goală de context. Fără autor, fără timestamp, fără urmă.
Asta nu e ipoteză de laborator: pe main, dialogul chiar preîncarcă tot reviewNote și salvează înlocuirea integrală, pe același câmp în care Agent Work Evidence și markerii [reconciliation] își țin datele. Un singur edit de-al tău putea orbi trei sisteme de siguranță.
ACTUL 2
Ce a construit Codex în loc
O tabelă nouă, append-only: comentarii care se pot doar adăuga, niciodată edita sau șterge. „Activity” e doar o proiecție de citire — comentariile + schimbările de status, în ordine cronologică. Nu apare o a doua autoritate pentru status.
Anatomia unui Task Comment — apasă pe câmpuri
task_comments
bodycreated_bycreated_atsourcetask_id
Un comentariu de-al tău bifează și reviewedAt pe task — semnalul „owner-ul a văzut”, păstrat.
Același scenariu, cu Task Activity
autor indisponibil[legacy] nota istorică, migrată cu timestamp-ul ei real
Nimic nu s-a pierdut. Fiecare rând are autor și oră; nota ta stă lângă evidence-ul mașinii, nu peste el. Aceeași istorie o văd UI-ul, voost tasks show, contextul agenților și timeline-ul.
Compatibilitatea nu a fost ruptă: cei 9 scriitori vechi de note (reconciliere, delivery guard, import Necesit, e-Factura Conta, work reporting…) scriu acum în ambele locuri, în aceeași tranzacție, până când consumatorii lor structurați migrează și ei. Aici începe însă partea cu probleme.
ACTUL 3
Auditul: 8 lentile, apoi avocatul diavolului
Opt agenți au auditat câte o dimensiune. Fiecare acuzație serioasă (P1/P2) a primit apoi un verificator advers cu o singură misiune: s-o dărâme citind codul. Ce n-a supraviețuit, a fost respins.
Scor pe dimensiuni (1–10)
valoare produs
9
API
8
UI operator
8
integrări
8
teste
8
docs & skills
8
migrare DB
7
simplitate
6
Simplitatea e nota mică: ~100 din 129 de linii de migrare există doar pentru fereastra de rollback. Restul PR-ului e disciplinat.
Acuzațiile mari — apasă să vezi dacă au supraviețuit verificării
P2Cast-ul de xid din trigger crapă după primul wraparound — orice commit pe review_note ar eșua
CONFIRMATVerificatorul n-a putut respinge — și a găsit el însuși un caz în care cele două diverg azi (whitespace). Codex a lovit independent exact același punct. Demo-ul e în Actul 4.
P2„Podul de rollback din migrare e ceremonie — topologia ta (1 prod + 2 tenanți) nu-l justifică”
migration.sql (trigger-ul întreg)
RESPINSPremisele acuzației erau greșite: fără pod, o notă scrisă în fereastra de deploy ar lipsi din Activity, iar un re-backfill naiv ar duplica rânduri. Podul e conservator, dar funcțional și ieftin la rulare. Rămâne valabilă doar partea de „nu are dată de scoatere”.
P2„Invariantul append-only nu are test negativ și nici gardă în DB”
src/app/api/tasks/[id]/comments/route.ts
RESPINSFaptele sunt adevărate, dar nu există nicio cale reală de scriere care să editeze sau să șteargă: ruta e doar POST, iar codul cheamă exclusiv create. Acuzația cerea PR-ului să inventeze o convenție de testare pe care repo-ul n-o are nicăieri.
P2 → P3„Backfill-ul de migrare rulează complet netestat — transformă datele de producție o singură dată, la deploy”
migration.sql:111
CONFIRMAT, retrogradatGolul de acoperire e real (runner-ul clonează DB-uri goale), dar riscul e mic: review_note rămâne intact, iar rândurile migrate sunt marcate cu source și regenerabile. Debt, nu pericol.
P2 → P3„Rutele de reconcile resping acum actori pe care ieri îi acceptau, iar API.md nu spune”
docs/API.md:146
CONFIRMAT, retrogradatÎnăsprirea e intenționată (atribuire onestă) și documentată în blocul de convenții + ADR; apelanții fără actor primesc default-uri și nu simt nimic. Rămâne un nit de completat în docs, nu un contract rupt.
ACTUL 4
Cele două probleme reale — declanșează-le
Ambele stau în podul de compatibilitate: un trigger în Postgres care prinde notele scrise de un runtime vechi (fereastra de deploy sau un rollback) și le copiază în Activity. Ideea e bună. Două detalii nu sunt.
P2 nr. 1 — bomba cu ceas din cast · apasă butonul
Trigger-ul se dedublează întrebând: „comentariul ăsta a fost scris în tranzacția curentă?”. Pentru asta compară id-ul de tranzacție — printr-un cast care merge azi și crapă mâine:
xmin = (pg_current_xact_id()::text)::xid
epoch xid (azi: 0)0
tranzacții consumate5.233.093
valoarea castată5.233.093 — încape în 32 de biți ✓
commit pe review_noteOK
ERROR: value "4294967296" is out of range for type xid — verificat pe PostgreSQL 16 local. După primul epoch, castul aruncă eroare la fiecare commit care atinge un review_note, pe fiecare bază tenant care poartă migrarea — la ani după ce toată lumea a uitat că trigger-ul există. Fixul e o linie (compară pe 64 de biți), dar trebuie să intre înainte ca podul să fie uitat.
P2 nr. 2 — aceeași regulă, scrisă în două limbi · declanșează divergența
Ca să nu creeze duplicate, trigger-ul compară byte cu byte corpul comentariului scris de aplicație cu bucata de notă pe care o calculează el singur, cu aceeași euristică rescrisă în plpgsql. Egalitatea ține azi — dar n-o păzește niciun test. Un singur spațiu o rupe:
aplicația scrie comentariul
[reconciliation] Task înlocuit de #482
=
trigger-ul calculează din notă
[reconciliation] Task înlocuit de #482
neo[reconciliation] Task înlocuit de #482
Duplicat anonim în Activity. Cu spațiul la final, nota veche (trimuită de aplicație) nu mai e prefix al celei noi, așa că trigger-ul cade pe ramura „notă întreagă” — alt corp, egalitatea pică, și inserează el însuși un al doilea comentariu, cu autor indisponibil. Codex și verificatorul Fable au găsit independent exact acest caz. Fixul minim: un test care pinuiește egalitatea + contractul scris la call site-uri; fixul curat: o singură implementare a regulii, nu două.
Egalitate perfectă → zero duplicate. Așa arată toate cele 9 call site-uri azi. Problema nu e că e stricat — e că nimic nu-l ține așa.
P3-uri reale, niciunul blocant — plus ce a adus review-ul independent Codex (gpt-5.6-sol).
P3 — de pus pe listă, nu în calea merge-ului · apasă pentru detalii
P3Contextul tău de operator nu vede comentariile noi
src/lib/context/context-read-model.ts:414 — fișier neatins de PR
VERIFICAT DIRECTBucket-ul „task-uri cu note” filtrează pe reviewNote not null. Un task adnotat doar prin noul comment box nu apare acolo. E debt de tranziție asumat, dar merită în issue-ul de follow-up.
P3Empty state-ul Activity e un placeholder cu bordură punctată, nu tăcere
Ambele marcate „legacy” și dual-write-ul le ține funcționale, deci impact mic — dar canonul spune acum voost tasks comment, iar referințele n-au aflat.
P3Limita de statusChanges din contextul agenților a scăzut tacit din 20 în 12
entity-context-resolvers.ts:638 — verificat direct pe diff
Probabil intenționat (aliniere la limita generală), dar e o schimbare de comportament nedocumentată pentru agenți.
Codex (gpt-5.6-sol), review independent — 154k tokeni de explorare
Zero P1. Șase P2 candidate, din care după verificare:
1 CONFIRMĂexact edge-ul de whitespace din Actul 4 — găsit independent, fără să vadă raportul Fable
Convergența a două modele diferite pe același bug e cel mai puternic semnal din tot auditul că problema e reală.
3 RESPINSEpreexistente pe main, nu regresii ale PR-ului
Race-ul PATCH/DELETE (cod neschimbat — iar PR-ul chiar îl repară pe calea de comments, cu FOR UPDATE), limita globală de body de 1 GB (config pentru upload-uri de call artifacts, comună tuturor rutelor) și regex-ul lowercase de actori (schema din issue #771, doar refolosită).
2 MINOREedge-uri de rollback multi-append + NUL byte → 500 în loc de 400
Primul cere un runtime vechi care face două append-uri în aceeași tranzacție (podul reține doar ultimul suffix); al doilea e un nit de validare. Ambele P3 în practică.
ACTUL 6
Verdictul, pe criteriile tale
Barele tale: „cleanest code, minimum complexity, never build ahead of the first real consumer” + cele trei coloane din VISION. Aplicate:
Trece
Consumator real?
Toți există azi: pagina de Task (pagina ta cea mai folosită), voost tasks comment pentru agenți, cei 5 scriitori de note din producție. Replies/edit/paginare — explicit amânate în ADR-0091.
Trece
Funcționalitate reală?
Da: elimină o pierdere de date demonstrată (edit-ul tău peste evidence-ul mașinii) și dă atribuire nefalsificabilă. Trust + Visibility + Context, la propriu.
Cu rezervă
Complexitate minimă?
Suprafața de produs — da (UI-ul chiar a scăzut cu 109 linii). Migrarea — nu: podul de rollback e conservator pentru 1 prod + 2 tenanți și n-are dată de scoatere. Funcțional, dar peste nevoie.
Next steps — în ordine
1Fix de o linie pe cast-ul xid în trigger (comparație pe 64 de biți) — cerut pe PR #1289 înainte de merge.blochează merge
2Pinuiește invariantul dual-write: un test care rupe build-ul dacă corpul comentariului diverge de incrementul de review_note (cazul cu whitespace devine test), + contractul scris la call site-uri.blochează merge
3Issue de follow-up: data de scoatere a trigger-ului (legată de „nu mai suportăm rollback pre-Activity”) + migrarea context-read-model de pe reviewNote pe comments.înainte de deploy
4Merge + deploy, apoi fast lane pentru gustul UI: empty state tăcut, copy-ul „Created to”.după 1–3
Concluzia
PR-ul răspunde la „cine a scris asta?”. Mai rămâne de răspuns la „cine scoate trigger-ul?”.