feat(sync): tombstone, pull dall'export, ritocchi flush e fallback su 404

Prepara il client al gateway 2.12.0 (export + soft delete), mantenendo la
compatibilità con la 2.11.0 in produzione fino al redeploy.

- tombstone: colonna `deleted_at` (+ migrazione), monotona nel merge, esclusa
  dalle ricerche locali di default; `--deleted` in CLI e /qmem:local find;
  conteggio nel report/status; marker 🗑 nei risultati di qmem_get
- `pull` usa `include_deleted=true` e mappa l'intero payload dell'export
  (incluso deleted_at), così il mirror impara le cancellazioni
- rate limit: pace del flush 600 ms (100 req/min < 120/min del gateway) e
  messaggio dedicato su 429 (prima 300-400 ms → possibile 429 con code grandi)
- `qmem_get`: fallback sull'indice locale anche sul 404 (un id in coda non è
  ancora sul gateway) con etichetta "non presente sul gateway"
- test: 27 controlli (nuovi: pull con tombstone, esclusione/visibilità
  tombstone, ricerca del record esportato)

Verificato con la suite locale completa: 27/27.
This commit is contained in:
Matteo Benedetto
2026-09-13 17:53:59 +02:00
parent d509778448
commit d1985514e1
6 changed files with 92 additions and 24 deletions
+3 -1
View File
@@ -64,6 +64,7 @@ if (cmd === "status") {
if (r.queueLastError) console.log(` ultimo errore : ${r.queueLastError.slice(0, 120)}`);
}
if (r.pendingInIndex) console.log(` in indice : ${r.pendingInIndex} record marcati ⏳ (creati offline, non ancora sul gateway)`);
if (r.deleted) console.log(` tombstone : ${r.deleted} record cancellati sul gateway (soft delete)`);
console.log(` top progetti : ${r.topProjects.map((p) => `${p.project_id ?? "(null)"}=${p.n}`).join(", ") || "-"}`);
if (r.duplicates.length) {
console.log(` possibili duplicati (testo identico): ${r.duplicates.length} gruppi — es. ${r.duplicates[0].ids.map((i) => i.slice(0, 8)).join(", ")}`);
@@ -86,7 +87,7 @@ if (cmd === "status") {
} else if (cmd === "find") {
const query = argv[1] && !argv[1].startsWith("--") ? argv[1] : "";
if (!query) {
console.error('Uso: find "query" [--kind K] [--project P] [--top N] [--all] [--exact] [--json]');
console.error('Uso: find "query" [--kind K] [--project P] [--top N] [--all] [--deleted] [--exact] [--json]');
process.exit(2);
}
const hits = await localSearch(
@@ -99,6 +100,7 @@ if (cmd === "status") {
topic: typeof opt("topic", null) === "string" ? opt("topic", null) : undefined,
include_superseded: has("all"),
include_private: has("private"),
include_deleted: has("deleted"),
exact: has("exact"),
top_k: Number(opt("top", 5)) || 5,
},