GDocs — Platform Design
GDocs (Registrul de Documente / gStack) — platformă centralizată de stocare și partajare a documentelor generate în procesul de prestare a serviciilor publice din Republica Moldova. Reduce documentele pe suport de hârtie, standardizează partajarea rezultatelor serviciilor publice, oferă o arhivă electronică și un API de schimb automat de documente pentru celelalte sisteme gStack.
Acest document este artefactul de design (sursa de adevăr pentru arhitectură).
Modelul de entități trăiește în ../gdocs.jdl; DDL-ul derivat în
db_schema.md; brief-ul de domeniu în
draft/small_documentation.md.
Stiva urmează șablonul gStack (ca interdictii / gnotify): JHipster monolith
(Spring Boot 3 + Angular), PostgreSQL (prod) / H2 (dev), Elasticsearch
(căutare full-text), object-store MinIO/S3 (fișiere binare), RabbitMQ
(cozi de procesare), Keycloak SSO. Pachet md.gov.gdocs, port 8095.
i18n RO (nativ) + EN.
1. System context
Section titled “1. System context”flowchart TB subgraph Public["Utilizatori"] anon["Cetățean ne-autentificat<br/>(căutare publică L1)"] client["Client autentificat<br/>(PF / PJ / ONG / OGOV)"] operator["Operator / Admin<br/>(user_read … admin_securizat)"] end
subgraph GDocs["GDocs (gStack)"] spa["Angular SPA<br/>(3 blocuri: Documente / Servicii / Administrare)"] api["Spring Boot API<br/>REST + /api/v1 (mașină-mașină)"] worker["Workeri asincroni<br/>(ingestie, OCR/transcriere, comprimare)"] db[("PostgreSQL")] es[("Elasticsearch<br/>full-text + fațete")] store[("Object store<br/>MinIO / S3")] mq[["RabbitMQ"]] end
subgraph Ext["Ecosistem"] kc["Keycloak SSO<br/>(realm gdocs)"] msign["MSign<br/>(semnare — demo)"] others["Alte sisteme gStack<br/>(interdictii, gnotify, …)"] end
anon -->|HTTPS| spa client -->|HTTPS| spa operator -->|HTTPS| spa spa --> api others -->|API + token| api api --> db api --> es api --> store api --> mq mq --> worker worker --> store worker --> es worker --> db api -->|OIDC| kc spa -->|OIDC login| kc api -->|semnare| msignTrei planuri de utilizare (brief §Architectura):
- Public — cetățeni ne-autentificați: doar căutarea documentelor publice (L1) — legislație, acte aprobate — după cuvinte-cheie / cod / autoritate emitentă, cu filtrări (an, emitent) și sortări (data apariției).
- Client — PF/PJ/ONG/OGOV autentificați (Keycloak): documentele proprii, partajări, cereri de acces, transcrieri, șabloane.
- Administrare — operatori; blocul de Administrare (Utilizatori / Roluri / Configurare / Jurnale / Loguri) este vizibil doar rolurilor de admin.
2. Component / architecture view
Section titled “2. Component / architecture view”flowchart LR subgraph FE["Frontend (Angular)"] landing["Landing 3-blocuri"] docs["Documente (mele / secrete / arhivă / upload)"] tpl["Template-uri"] serv["Servicii (transcriere / cereri / trash)"] admin["Administrare"] pub["Căutare publică"] end
subgraph BE["Backend (Spring Boot, md.gov.gdocs)"] rest["web.rest.*Resource (DTO)"] v1["web.rest.v1.* (API mașină-mașină)"] svc["service.*Service (+impl)"] qsvc["service.*QueryService<br/>(filtre + gardă nivel acces)"] acl["security.acl.*<br/>(L1/L2/L3 + roluri)"] stori["service.storage.StorageService<br/>(MinIO/S3)"] conv["service.conversie.*<br/>(OCR / transcriere)"] arch["service.arhiva.*<br/>(comprimare)"] audit["audit.AppendOnlyAuditGuard"] repo["repository.* (JPA)"] srepo["repository.search.* (ES)"] end
FE -->|HTTPS + JWT| rest rest --> svc svc --> qsvc svc --> acl svc --> stori svc --> repo svc --> srepo conv -.->|@RabbitListener| svc arch -.->|@RabbitListener| svc audit -.-> repo v1 --> svcStratificare standard JHipster — entitățile nu părăsesc stratul de service (controllerele fac schimb de DTO). Reguli specifice GDocs:
- Gardă de nivel de acces — orice listare/căutare trece prin
*QueryServicesecurity.aclcare filtrează dupăNivelAccesși rolul curent. L3 nu este returnat niciodată unui operator fără nivel 3 (nici în liste, nici în ES, nici prin API).
- Fișierele binare trăiesc în object-store; DB ține doar
storageUri+ metadate. Descărcarea trece prin backend (verifică ACL, apoi stream / URL pre-semnat). - Doar
DocumentșiTemplatesunt oglindite în Elasticsearch (textIndexat= text extras prin OCR/conversie → căutare full-text + fațete). ES este cablat manual (claserepository.search.*/service/search/*cu tipuri ES complet-calificate), NU prin JDL — pentru a păstra numele de entitateDocumentfără coliziunea cu adnotarea@Documentdin Spring Data Elasticsearch (convenția interdictii #4). La fel ca RabbitMQ, e o dependență adăugată în afara modelului generat. - Procesarea grea e asincronă (RabbitMQ): ingestie (extragere text, hash), transcriere/OCR, comprimare la arhivare. Workerii sunt separați de request.
EvenimentDocumenteste append-only (audit / Jurnale), impus deAppendOnlyAuditGuard(listener Hibernate care veto-ează UPDATE/DELETE).
3. Entity-relationship (ER) diagram
Section titled “3. Entity-relationship (ER) diagram”erDiagram CLIENT ||--o{ DOCUMENT : "proprietar" EMITENT ||--o{ DOCUMENT : "emite" APP_USER ||--o{ DOCUMENT : "încarcă" TEMPLATE ||--o{ DOCUMENT : "generat din" EMITENT ||--o{ TEMPLATE : "publică" CLIENT ||--o{ APP_USER : "reprezentat de" ROL ||--o{ APP_USER : "are rol" DOCUMENT ||--o{ PARTAJARE_DOCUMENT : "partajat prin" CLIENT ||--o{ PARTAJARE_DOCUMENT : "beneficiar" DOCUMENT ||--o{ CERERE_ACCES : "vizat de" CLIENT ||--o{ CERERE_ACCES : "solicitant" DOCUMENT ||--o{ CONVERSIE : "sursă" APP_USER ||--o{ CONVERSIE : "inițiază" DOCUMENT ||--o{ SEMNATURA_DOCUMENT : "semnat prin" DOCUMENT ||--o{ EVENIMENT_DOCUMENT : "audit append-only"
CLIENT { bigint id PK varchar tip "PF/PJ/ONG/OGOV" varchar identificator "IDNP/IDNO (unic pe tip)" varchar nume } DOCUMENT { bigint id PK varchar cod "unic, auto" varchar denumire varchar format "enum FormatDocument" varchar storage_uri "cheie MinIO/S3" varchar tip_securitate "STANDARD/HASH/PAROLA/CRIPTAT" varchar nivel_acces "PUBLIC/PERSONAL/SECRET" varchar statut "ACTIV/ARHIVAT/STERS" date data_expirarii text text_indexat "→ Elasticsearch" text etichete "jsonb" } ROL { bigint id PK varchar cod "unic" boolean poate_scrie varchar nivel_maxim "NivelAcces" } PARTAJARE_DOCUMENT { bigint id PK varchar drept "CITIRE/SCRIERE/CITIRE_SCRIERE" instant data_expirare } CERERE_ACCES { bigint id PK varchar drept_cerut varchar statut "PENDING/APROBATA/RESPINSA" } CONVERSIE { bigint id PK varchar tip "pdf↔word / photo→…" varchar statut }Entitățile complete și câmpurile sunt în gdocs.jdl; DDL-ul în db_schema.md.
4. Modelul de control al accesului
Section titled “4. Modelul de control al accesului”Două axe ortogonale — nivelul documentului și rolul operatorului — plus partajări punctuale.
4.1 Niveluri de acces (pe document)
Section titled “4.1 Niveluri de acces (pe document)”| Nivel | Cod | Cine poate vedea |
|---|---|---|
| L1 | PUBLIC | Oricine, inclusiv ne-autentificați (căutarea publică). |
| L2 | PERSONAL | Proprietarul (Client) + operatorii responsabili (admin), în limita nivelMaxim al rolului. |
| L3 | SECRET | „Secret de stat”: DOAR clientul-proprietar. Niciun operator obișnuit; doar admin_securizat căruia i s-a acordat explicit nivel 3. |
4.2 Roluri (tabel Rol, administrabil)
Section titled “4.2 Roluri (tabel Rol, administrabil)”| Rol | poateScrie | nivelMaxim | Descriere (brief) |
|---|---|---|---|
user_read | ✗ | PERSONAL | Citește documente L1/L2 în funcție de nivelul de acces. |
user_write | ✓ | PERSONAL | Citește și modifică documente L1/L2. |
admin | ✓ | PERSONAL | Vede și modifică documentele (dar NU L3). |
admin_securizat | ✓ | SECRET | Vede/modifică și L3 dacă i se acordă nivel 3. |
Citirea este implicită pentru oricine poate accesa fișierul; poateScrie
guvernează modificarea. Rolurile sunt un tabel (nu enum) ca un admin să le
poată edita din blocul de Administrare fără re-deploy.
4.3 Decizia de acces (pseudo-cod, stratul de service)
Section titled “4.3 Decizia de acces (pseudo-cod, stratul de service)”grant(doc, user, drepturi) verifică un PartajareDocument activ pe doc al
cărui beneficiar este fie user.client, fie user (operator) — vezi §5.3.
poateCiti(user, doc): if doc.nivelAcces == PUBLIC: return true if doc.proprietar == user.client: return true # proprietarul mereu if doc.nivelAcces == SECRET: # L3 — cel mai strict return user.hasAuthority(ROLE_SECRET) # rol cu nivelMaxim=SECRET and grant(doc, user, {CITIRE, CITIRE_SCRIERE}) # grant EXPLICIT pe ACEST doc if doc.nivelAcces == PERSONAL: # L2 return user.rol in (admin, admin_securizat) or grant(doc, user, {CITIRE, CITIRE_SCRIERE}) return false
poateScrie(user, doc): return poateCiti(user, doc) and (user.rol.poateScrie or grant(doc, user, {SCRIERE, CITIRE_SCRIERE}))L3 („secret de stat”) nu are cale „blanket”: nici măcar admin_securizat nu
vede un L3 fără un PartajareDocument țintit pe acel document exact — și fiecare
astfel de acces scrie EvenimentDocument (ACCES_ACORDAT / VIZUALIZAT). Dacă
poateCiti/poateScrie = false, solicitantul (Client sau operator) poate depune o
CerereAcces (§6.3), care la aprobare creează PartajareDocument.
5. Lifecycle — document state diagram
Section titled “5. Lifecycle — document state diagram”stateDiagram-v2 [*] --> ACTIV: upload / generare din template ACTIV --> ACTIV: vizualizare / modificare / partajare / semnare ACTIV --> ARHIVAT: expirare (dataExpirarii) sau înlocuire → comprimare ARHIVAT --> ACTIV: restaurare din arhivă ACTIV --> STERS: ștergere (trash bin) ARHIVAT --> STERS: ștergere din arhivă STERS --> ACTIV: restaurare (< 30 zile) STERS --> [*]: purjare automată (după 30 zile)- Arhivare — documentele expirate sau înlocuite trec în
ARHIVATși se comprimă (worker RabbitMQ); rămân căutabile de operatori. - Trash bin — ștergerea marchează
STERS+stersLa; un job programat purjează definitiv după 30 de zile; până atunci se pot restaura. - Fiecare tranziție scrie o intrare în
EvenimentDocument(audit).
6. Fluxuri cheie
Section titled “6. Fluxuri cheie”6.1 Upload / ingestie document
Section titled “6.1 Upload / ingestie document”sequenceDiagram actor U as Client/Operator participant SPA as Angular SPA participant API as DocumentResource participant S as StorageService (MinIO) participant MQ as RabbitMQ participant W as Worker (ingestie/OCR) participant ES as Elasticsearch
U->>SPA: alege fișier + nivel acces + securitate SPA->>API: POST /api/documents (metadate) API->>S: PUT obiect (binar) API->>API: generează cod, hash SHA-256, statut=ACTIV API->>MQ: publică job „ingest {docId}" API-->>SPA: 201 (document ACTIV) W->>MQ: consumă job W->>S: citește binar → extrage text (OCR dacă imagine) W->>API: salvează textIndexat + etichete W->>ES: indexează Document W->>API: EvenimentDocument CREAT6.2 Căutare (publică și internă)
Section titled “6.2 Căutare (publică și internă)”sequenceDiagram actor U as Utilizator participant SPA as Angular SPA participant Q as DocumentQueryService + ACL participant ES as Elasticsearch
U->>SPA: termen + filtre (an, emitent, cod) SPA->>Q: GET /api/documents?query=… (sau /api/public/search) Q->>Q: aplică gardă nivel acces (public → doar L1) Q->>ES: interogare full-text + fațete ES-->>Q: rezultate (doar cele permise) Q-->>SPA: listă + fațete (emitent / an / format)6.3 Cerere de acces → partajare
Section titled “6.3 Cerere de acces → partajare”sequenceDiagram actor C as Client participant SPA as Angular SPA participant API as CerereAccesResource actor O as Operator (admin) participant P as PartajareService
C->>SPA: „cere acces" (drept + motivare) SPA->>API: POST /api/cerere-acces (statut=PENDING) API-->>O: apare în „Servicii → Cereri" O->>API: aprobă API->>P: creează PartajareDocument (drept) API->>API: EvenimentDocument ACCES_ACORDAT API-->>C: notificare (acces acordat)6.4 Transcriere / OCR (bloc Servicii)
Section titled “6.4 Transcriere / OCR (bloc Servicii)”sequenceDiagram actor U as Client participant API as ConversieResource participant MQ as RabbitMQ participant W as Worker conversie (OCR/format) participant S as StorageService
U->>API: POST /api/conversii (tip: PHOTO_TO_WORD, sursă) API->>MQ: job conversie (statut=PENDING) API-->>U: 202 (în procesare) W->>MQ: consumă → PROCESSING W->>S: citește sursa → OCR + reformatare (ML) W->>S: scrie rezultatul W->>API: statut=DONE + rezultatUri + rezultatMeta6.5 Login (Keycloak OIDC + JWT)
Section titled “6.5 Login (Keycloak OIDC + JWT)”Aceeași rețetă dual-auth ca interdictii/gnotify: chain OIDC (@Order(1)) pentru
UI + jhi_user/JWT ca fallback; realm gdocs pe sso.gstack.esempla.systems,
client gdocs-web. Identitatea Keycloak se corelează cu AppUser (rol +
client).
7. Suprafața API
Section titled “7. Suprafața API”| Grup | Exemple | Auth |
|---|---|---|
| CRUD entități | /api/documents, /api/templates, /api/clients, /api/rols, /api/emitents, /api/cerere-acces, /api/conversii, /api/partajare-documents | JWT (rol) |
| Fișiere | POST /api/documents (multipart), GET /api/documents/{id}/download (ACL + stream / URL pre-semnat) | JWT + ACL |
| Căutare | GET /api/documents?query=…&emitent=…&an=… (fațete) | JWT + gardă nivel |
| Public | GET /api/public/search (doar L1, fără auth) | — |
| Mașină-mașină | /api/v1/** (alte sisteme gStack: emit/consumă documente) | token de serviciu Keycloak (OAuth2 client-credentials) |
| Admin | /api/admin/* (Configurare, Jurnale, purjare trash, re-index) | rol admin |
Autentificare /api/v1 (decizie): fiecare sistem consumator are un client
Keycloak în realm-ul gdocs și obține un token prin client-credentials;
apelează /api/v1 cu Authorization: Bearer …. gdocs îl validează ca
resource-server OIDC; scope-urile se mapează pe niveluri de acces
(ex. docs:read:public → doar L1; docs:write → încărcare). NU mTLS (diferă de
gnotify) — rămâne consistent cu SSO-ul deja folosit de UI.
sequenceDiagram participant S as Sistem gStack (ex. interdictii) participant KC as Keycloak (realm gdocs) participant API as GDocs /api/v1
S->>KC: POST /token (client_credentials, client=interdictii-svc) KC-->>S: access_token (scopes: docs:read:public …) S->>API: GET /api/v1/documents?… (Bearer) API->>API: validează token + mapează scope → nivel acces API-->>S: documente permise (gardă L1/L2/L3)Erorile CRUD/admin păstrează JHipster problem+json; /api/v1 poate folosi un
format simplu {"message":…} (ca gnotify), scopat pe pachetul v1.
8. UI / Design
Section titled “8. UI / Design”- GDS / gstyle este sursa de adevăr vizuală (reutilizat pe tot gStack), DAR
accentul este cherry red, nu albastru (mockup: accent
rose). Definit ca temă GDS (token--gds-accent), la fel ca celelalte accente. - Landing în 3 blocuri mari (ca în mockups): Documente, Servicii, Administrare — carduri navigabile către secțiuni.
- Meniu lateral (sidenav GDS) în 3 grupuri:
- Documente — Documentele mele · Template-uri · Documente secrete (L3) · Arhivă · Upload
- Servicii — Transcriere · Cereri · Trash bin
- Administrare (doar admini) — Utilizatori · Roluri · Configurare · Jurnale · Loguri
- Mockup-urile de referință:
assets/mockups/*.html(versiunilered= accentul cherry).
9. Cerințe non-funcționale & scalare
Section titled “9. Cerințe non-funcționale & scalare”Din brief §nonfunctional:
- 5 milioane de utilizatori înregistrați; 100 000 utilizatori concurenți.
- Replici DB master-slave cu failover (promovare slave dacă master cade).
- Load balancer + mai mulți workeri (procesare asincronă) + auto-scale (noduri noi la vârf de trafic).
- Object-store scalabil (MinIO/S3) pentru fișiere; DB ține doar metadate.
- Elasticsearch pentru căutarea full-text la scară (independent de DB).
- RabbitMQ decuplează ingestia/OCR/comprimarea de calea de request.
flowchart LR lb["Load balancer"] --> be1["API #1"] lb --> be2["API #2"] lb --> beN["API #N"] be1 & be2 & beN --> pgm[("PG master")] pgm -. replicare .-> pgs1[("PG slave")] pgm -. replicare .-> pgs2[("PG slave")] be1 & be2 & beN --> es[("ES cluster")] be1 & be2 & beN --> mq[["RabbitMQ"]] mq --> w1["Worker OCR"] mq --> w2["Worker arhivă"] be1 & be2 & beN --> store[("MinIO/S3")]10. Decizii
Section titled “10. Decizii”Vezi și checklist-ul din db_schema.md §8.
Închise (2026-07-16):
- ✅ Nivel 3 („secret de stat”) —
admin_securizatdeschide un L3 doar cu authorityROLE_SECRETȘI unPartajareDocumentexplicit pe acel document (beneficiarOperator). Fără cale „blanket”; fiecare acces jurnalizat. Vezi §4.3. - ✅
/api/v1(mașină-mașină) — token de serviciu Keycloak (OAuth2 client-credentials, scope→nivel de acces). NU mTLS. Vezi §7. - ✅ JSONB — amânat:
@Lobtext pentru demo;jsonb= pas de hardening (db_schema.md §7). - ✅ Etichete/keywords —
jsonbarray peDocument; fațete/grupare în Elasticsearch. Fără tabelEtichetaM2M. - ✅ Semnătura MSign — entitate
SemnaturaDocument(multi-semnatar).
Închise (2026-07-16, runda 2) — detalii în §11:
- ✅ Identificatori —
Clientunic pe(tip, identificator)(changelog).codgenerat server-side:DOC-<an>-<secvență 6 cifre>(Document),TPL-<an>-<secvență>(Template). - ✅
ROLE_SECRET— derivată la login dinRol.nivelMaxim = SECRET(authority-mapper Spring);Rolrămâne unica sursă de adevăr, fără mapare separată în Keycloak. - ✅ Purjare trash + comprimare arhivă — hibrid:
@Scheduled(detecție zilnică) + workeri RabbitMQ (comprimare/purjare grea). - ✅ Object-store — MinIO (dev) → S3-compatibil (prod); descărcare prin backend pentru L2/L3 (ACL), URL pre-semnate doar pentru L1.
- ✅ Ștergere/retenție — soft-delete
statut=STERS; la +30 zile se șterge binarul și se goleștestorageUri, dar rândul rămâne tombstone cu jurnalulEvenimentDocumentpăstrat permanent (niciodată hard-delete).
Toate deciziile sunt închise — design gata de generare.
11. Storage, operațiuni programate & retenție
Section titled “11. Storage, operațiuni programate & retenție”Detalierea deciziilor închise în §10 (runda 2).
11.1 Identificatori & coduri
Section titled “11.1 Identificatori & coduri”Client— unicitate pe(tip, identificator)(un IDNP/IDNO poate apărea o singură dată per tip). Constrângere prin changelog Liquibase dedicat (JDL nu suportă unique compus).codgenerat de server la creare, needitabil din formular:Document→DOC-<an>-<secvență 6 cifre>(ex.DOC-2026-000481).Template→TPL-<an>-<secvență>.- Secvența dintr-o secvență DB per an (sau tabel de contoare); coliziuni evitate
la nivel de DB (
unique).
11.2 ROLE_SECRET (derivare)
Section titled “11.2 ROLE_SECRET (derivare)”Rol (tabelul administrabil) rămâne unica sursă de adevăr. La autentificare,
un GrantedAuthoritiesMapper adaugă authority-ul Spring ROLE_SECRET dacă
AppUser.rol.nivelMaxim = SECRET (rolul admin_securizat). Nu se configurează o
authority separată în Keycloak — evită dublarea sursei de adevăr și menține
regulile de acces editabile din blocul „Administrare → Roluri”. ROLE_SECRET
este condiția (a) din decizia de acces L3 (§4.3); grantul explicit e condiția (b).
11.3 Object-store & descărcare
Section titled “11.3 Object-store & descărcare”flowchart LR subgraph Acces l12["Document L2 / L3"] l1["Document L1 (public)"] end l12 -->|"GET /documents/{id}/download"| be["Backend (verifică ACL)"] be -->|"stream octeți"| user1["Client / Operator"] l1 -->|"URL pre-semnat (TTL scurt)"| store[("MinIO / S3")] store --> user2["Oricine"]- Dev/demo: MinIO (compatibil S3). Prod: stocare S3-compatibilă.
- L2 / L3 — descărcare exclusiv prin backend: se verifică ACL-ul, se scrie
EvenimentDocument DESCARCAT, apoi se face stream. Niciun URL direct (ca să nu scape conținut prin linkuri partajabile). - L1 public — se pot emite URL-uri pre-semnate cu TTL scurt (descarcă presiunea de pe backend pentru documente publice la scară).
- Fișierul binar NU trece niciodată prin DB;
Document.storageUri= cheia obiectului.
11.4 Operațiuni programate (arhivă & trash)
Section titled “11.4 Operațiuni programate (arhivă & trash)”Model hibrid — detecția e ieftină și periodică (@Scheduled), munca grea
(comprimare / ștergere binar) e delegată workerilor RabbitMQ:
flowchart TB sched["@Scheduled (zilnic)"] -->|"data_expirarii < azi & statut=ACTIV"| q1["coadă: arhivează {docId}"] sched -->|"statut=STERS & sters_la < azi-30z"| q2["coadă: purjează {docId}"] q1 --> w1["Worker arhivă"] w1 -->|"comprimă binar în store"| store[("MinIO/S3")] w1 -->|"statut=ARHIVAT, arhivat_la=now"| db[("PostgreSQL")] w1 --> ev1["EvenimentDocument ARHIVAT"] q2 --> w2["Worker purjare"] w2 -->|"șterge binar + storageUri=null"| store w2 -->|"păstrează rândul (tombstone) + evenimente"| db w2 --> ev2["EvenimentDocument STERS (definitiv)"]- Arhivare — documentele expirate (sau înlocuite) →
ARHIVAT+ binar comprimat; rămân căutabile de operatori. - Trash (30 zile) — ștergerea marchează
STERS+sters_la; până la 30 zile se pot restaura (STERS → ACTIV). Sweep-ul zilnic enqueue-ază purjarea celor expirate.
11.5 Retenție & tombstone
Section titled “11.5 Retenție & tombstone”- Soft-delete peste tot (
statut = STERS) — nimic nu dispare instant. - Purjarea (după 30 zile) șterge doar binarul din object-store și golește
storage_uri; rânduldocumentrămâne ca tombstone (metadate + statut), iarEvenimentDocumentse păstrează permanent (forensic / Jurnale). - Motiv:
EvenimentDocument.document_ideste FK required + append-only — un hard-delete al documentului ar rupe/casca jurnalul de audit. Tombstone-ul păstrează integritatea auditului chiar și după ce conținutul a fost eliminat.