# SQLite Database

Madagascar Local Authority folosește SQLite ca store runtime pentru registry și Work Orders.

Locația implicită în checkout:

```text
var/host-manager.sqlite
```

Locația runtime pe jumper:

```text
/usr/local/xdev-host-manager/var/host-manager.sqlite
```

Path-ul poate fi schimbat cu:

```text
HOST_MANAGER_DB=/path/to/host-manager.sqlite
```

## Rol

SQLite este sursa de adevăr runtime. Fișierele din `config/` nu mai sunt store-ul live:

- `config/hosts.yaml` seed-uiește documentul `hosts_yaml` dacă baza este nouă.
- `config/work-orders.yaml` seed-uiește documentul `work_orders_yaml` dacă baza este nouă.
- `config/local-hosts.tsv` este manifest generat explicit din registry-ul SQLite.
- `/download/hosts.yaml`, `/download/local-hosts.tsv` și `/download/monitoring.json` sunt randate din SQLite.

Aplicația nu face commit/push automat după modificări. Exportul și arhivarea rămân pași operaționali separați.

## Schema curentă

Schema este creată automat de `scripts/host_manager.pl` la prima citire/scriere a store-ului.

```sql
CREATE TABLE IF NOT EXISTS documents (
    name TEXT PRIMARY KEY,
    content TEXT NOT NULL,
    updated_at TEXT NOT NULL
);
```

### Catalog

| Obiect | Tip | Rol |
|--------|-----|-----|
| `documents` | table | Document-store runtime pentru registry și Work Orders |
| `sqlite_autoindex_documents_1` | index intern SQLite | Indexul automat creat pentru `PRIMARY KEY (name)` |

Nu există alte tabele, view-uri, trigger-e sau indexuri definite de aplicație în schema curentă.

### Tabel: `documents`

| Coloană | Tip declarat | Null | Default | Cheie | Descriere |
|---------|--------------|------|---------|-------|-----------|
| `name` | `TEXT` | `NOT NULL` implicit prin PK | none | `PRIMARY KEY` | Identificatorul documentului operațional. Valorile cunoscute sunt `hosts_yaml` și `work_orders_yaml`. |
| `content` | `TEXT` | `NOT NULL` | none | none | Payload-ul documentului, serializat ca YAML strict compatibil cu parserul aplicației. |
| `updated_at` | `TEXT` | `NOT NULL` | none | none | Timestamp ISO UTC setat de aplicație la insert/update. |

### Chei

| Tabel | Cheie | Coloane | Observații |
|-------|------|---------|------------|
| `documents` | Primary key | `name` | Garantează un singur rând pentru fiecare document operațional. |

SQLite implementează cheia primară textuală prin indexul intern `sqlite_autoindex_documents_1`.

### Indexuri

| Index | Tabel | Coloane | Unic | Creat de | Rol |
|-------|-------|---------|------|----------|-----|
| `sqlite_autoindex_documents_1` | `documents` | `name` | da | SQLite | Lookup rapid pentru `SELECT ... WHERE name = ?` și enforcement pentru primary key. |

Nu există indexuri explicite definite de aplicație. Nu există index pe `updated_at`, pentru că aplicația nu face query-uri de tip listare istorică sau filtrare temporală.

### Relații

Nu există foreign keys între tabele, pentru că schema curentă are o singură tabelă.

Relațiile de business sunt în interiorul YAML-ului din `content`, nu modelate relational. De exemplu:

- hosturile, numele, rolurile și sursele sunt structuri YAML în documentul `hosts_yaml`
- checklist-ul și acțiunile unui Work Order sunt structuri YAML în documentul `work_orders_yaml`
- acțiunile Work Order referă `host_id` textual în YAML, nu printr-un `FOREIGN KEY`

`PRAGMA foreign_keys = ON` este activat ca pregătire pentru o schemă viitoare cu tabele relaționale, dar în schema curentă nu are efect practic.

### Constrângeri

| Tabel | Constrângere | Efect |
|-------|--------------|-------|
| `documents` | `PRIMARY KEY (name)` | Nu permite două documente cu același `name`. |
| `documents` | `content TEXT NOT NULL` | Nu permite documente fără payload. |
| `documents` | `updated_at TEXT NOT NULL` | Nu permite rânduri fără timestamp de modificare. |

Nu există `CHECK` constraints pentru valorile permise în `name`, formatul YAML sau formatul timestamp-ului. Aceste validări sunt făcute în codul Perl prin parserul strict YAML și prin fluxurile API.

### Operațiuni SQL folosite de aplicație

Citire document:

```sql
SELECT content FROM documents WHERE name = ?;
```

Insert/update document:

```sql
INSERT INTO documents (name, content, updated_at)
VALUES (?, ?, ?)
ON CONFLICT(name) DO UPDATE
SET content = excluded.content,
    updated_at = excluded.updated_at;
```

Creare schemă:

```sql
CREATE TABLE IF NOT EXISTS documents (
    name TEXT PRIMARY KEY,
    content TEXT NOT NULL,
    updated_at TEXT NOT NULL
);
```

Setări aplicate la conectare:

```sql
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;
```

### Versiune schemă

Nu există încă tabelă `schema_migrations` sau `schema_version`. Schema este implicit versiunea 1 și este definită de blocul `CREATE TABLE IF NOT EXISTS documents` din `scripts/host_manager.pl`.

În schema curentă există o singură tabelă generică, `documents`. Conținutul operațional rămâne serializat în formatul YAML strict deja folosit de aplicație. Alegerea este deliberată pentru migrarea minimă: sursa de adevăr se mută din working tree în SQLite fără să schimbăm încă parserul, UI-ul sau formatul exporturilor.

## Documente

### `hosts_yaml`

Conține registry-ul de hosturi în formatul `config/hosts.yaml`.

Este citit de:

- `GET /api/hosts`
- `GET /download/hosts.yaml`
- `GET /download/local-hosts.tsv`
- `GET /download/monitoring.json`
- `POST /api/render/local-hosts-tsv`

Este scris de:

- `POST /api/hosts/upsert`
- `POST /api/hosts/delete`
- `POST /api/work-orders/confirm`, când o acțiune modifică registry-ul

Aplicația normalizează la salvare:

```yaml
policy:
  storage_authority: "sqlite"
  runtime_database: "<HOST_MANAGER_DB>"
```

### `work_orders_yaml`

Conține Work Orders în formatul `config/work-orders.yaml`.

Este citit de:

- `GET /api/work-orders`
- `POST /api/work-orders/checklist`
- `POST /api/work-orders/confirm`

Este scris de:

- `POST /api/work-orders/checklist`
- `POST /api/work-orders/confirm`

## Seed inițial

La prima accesare a unui document:

1. Aplicația caută rândul în `documents`.
2. Dacă rândul există, conținutul din SQLite câștigă.
3. Dacă rândul lipsește, aplicația citește fișierul seed din `config/`.
4. Dacă fișierul seed lipsește, aplicația creează un document gol valid.
5. Documentul este inserat în SQLite și de atunci SQLite devine autoritatea.

Important: după seed, modificările ulterioare ale fișierelor `config/hosts.yaml` sau `config/work-orders.yaml` nu schimbă automat baza existentă.

## Fișiere SQLite

Cu WAL activ, runtime-ul poate avea trei fișiere:

```text
var/host-manager.sqlite
var/host-manager.sqlite-wal
var/host-manager.sqlite-shm
```

Toate trei aparțin store-ului runtime. Nu se comit în git și nu se înlocuiesc prin deploy de cod.

## Inspecție

Pe jumper:

```bash
cd /usr/local/xdev-host-manager
sqlite3 var/host-manager.sqlite '.schema'
sqlite3 var/host-manager.sqlite '.tables'
sqlite3 var/host-manager.sqlite 'pragma table_info(documents);'
sqlite3 var/host-manager.sqlite 'pragma index_list(documents);'
sqlite3 var/host-manager.sqlite 'pragma index_info(sqlite_autoindex_documents_1);'
sqlite3 var/host-manager.sqlite 'pragma foreign_key_list(documents);'
sqlite3 var/host-manager.sqlite 'select name, updated_at, length(content) from documents order by name;'
sqlite3 var/host-manager.sqlite 'select content from documents where name = "hosts_yaml";'
sqlite3 var/host-manager.sqlite 'select content from documents where name = "work_orders_yaml";'
```

Pentru export YAML curent:

```bash
curl -fsS -b cookies.txt https://hosts.madagascar.xdev.ro/download/hosts.yaml
```

Endpoint-urile de download sunt OTP-protected; exemplul presupune o sesiune validă.

## Backup

Backup recomandat, cu serviciul oprit sau prin API-ul SQLite:

```bash
cd /usr/local/xdev-host-manager
sqlite3 var/host-manager.sqlite ".backup 'backups/host-manager/host-manager.sqlite.$(date +%Y%m%d_%H%M%S).bak'"
```

Pentru o copie brută, include și fișierele WAL/SHM sau oprește serviciul înainte:

```bash
sudo systemctl stop host-manager
sudo cp -a var/host-manager.sqlite* backups/host-manager/
sudo systemctl start host-manager
```

## Restore

Restore-ul înlocuiește sursa de adevăr runtime și trebuie făcut explicit:

```bash
sudo systemctl stop host-manager
sudo cp backups/host-manager/host-manager.sqlite.YYYYMMDD_HHMMSS.bak var/host-manager.sqlite
sudo chown host-manager:host-manager var/host-manager.sqlite
sudo systemctl start host-manager
curl -fsS http://127.0.0.1:8088/healthz >/dev/null
```

Dacă se restaurează o copie brută, tratează `host-manager.sqlite`, `host-manager.sqlite-wal` și `host-manager.sqlite-shm` ca set coerent.

## Evoluție probabilă

Schema generică `documents` este potrivită pentru tranziția curentă. Dacă aplicația are nevoie de query-uri structurale, validări SQL sau merge/audit mai granular, următorul pas ar trebui să fie o migrare versionată către tabele dedicate, de exemplu:

- `hosts`
- `host_names`
- `host_roles`
- `host_sources`
- `work_orders`
- `work_order_checklist`
- `work_order_actions`

Până atunci, contractul stabil este numele documentelor din `documents` și formatele YAML randate de aplicație.
