Skip to content

feat: provide a verified portable backup and restore journey for local memory #731

Open

Description

@cdeust

Problem and evidence

Cortex stores database data, profile/configuration JSON, wiki pages and raw artifacts separately. checkpoint saves working state. The examined public tool/doctor/README entries and scripts contain targeted migration/backup procedures but no demonstrated complete portable product restore journey. Hindsight has admin backup/restore and bank export/import contracts.

This is a bounded public-surface finding; manual SQLite backup or pg_dump remain possible.

Acceptance evidence

  • Define included/excluded owned resources and a versioned manifest with digests.
  • Produce a consistent backup under concurrent writes, preserving source links and version chains.
  • Restore into a fresh isolated location and verify integrity plus known-memory/source queries.
  • Reject incomplete/incompatible archives with an actionable result while preserving the original target.
  • Document tested recovery and upgrade rollback on supported platforms.

Scope and related work

#662 covers PostgreSQL-to-SQLite migration. A complete backup/restore also needs to work without changing backend.

Raised by the global Hindsight/Cortex audit. The target is a measurable local-first experience with the same supported semantics in Claude and Codex. Acceptance criteria describe a target, not current conformance. Static findings refer to the pinned source; probes identify their boundary.

Primary references

mcp_server/infrastructure/config.py:20; PRIVACY.md:24; scripts/reclassify_team_scope_db.py:12; hindsight-api-slim/hindsight_api/admin/cli.py:336

Activity

  1. cdeust commented on Nov 5, 2026

    @cdeust
    OwnerAuthor

    CI autopilot : tranche 1 livrée dans #892 (docs(audit): what a Cortex backup must contain, Refs #731).

    Elle couvre le premier critère (ressources incluses/exclues) : docs/audits/owned-resources-backup-2026-11-05.md classe chaque fichier et store qu'écrit Cortex (données utilisateur, dérivées, état de processus, hors Cortex), avec la ligne de source qui nomme son chemin, et donne l'ensemble à sauvegarder par backend. Constat : les graphes de code, le tampon de consolidation et l'installation du pipeline sont construits depuis le répertoire personnel et ne suivent pas CORTEX_CLAUDE_DIR.

    Tranches restantes :

    1. Manifeste versionné des entrées de classe A, avec un digest chacune.
    2. Instantané cohérent de chaque store sous écritures concurrentes.
    3. Restauration dans un CORTEX_CLAUDE_DIR neuf, puis vérification d'intégrité et requêtes mémoire/source connues.
    4. Refus d'une archive incomplète ou incompatible, cible intacte.
    5. Documentation testée de la récupération et du rollback de mise à jour par plateforme.

    Generated by Claude Code

  2. cdeust commented on Nov 10, 2026

    @cdeust
    OwnerAuthor

    CI autopilot : la tranche 2 est ouverte dans #960 (Refs #731, pas Closes).

    Elle couvre la moitié « manifeste versionné avec digests » du premier critère.

    • core/backup_manifest.py contient la table des ressources de classe A de l'audit, par backend, avec le format cortex-backup-manifest version 1.
    • infrastructure/backup_scan.py calcule le SHA-256 et la taille de chaque fichier. Il refuse les liens symboliques et les fichiers spéciaux.
    • handlers/backup_manifest.py fait la composition.
    • Une base requise absente est refusée, pas omise. C'est aussi le cas d'une base déplacée hors de la racine par CORTEX_MEMORY_DB_PATH.

    Vérifié localement :

    • 48 tests réussis.
    • Gate de mutation sur le diff : 0 survivant.
    • Gate de craftsmanship : OK.

    Non vérifié : la suite complète et Windows, faute de torch dans mon environnement. La CI fera foi.

    Tranches restantes :

    1. Instantané cohérent de chaque store sous écritures concurrentes : backup en ligne SQLite ; pour PostgreSQL géré, copie à l'arrêt ou dump logique. Le manifeste de feat(backup): a versioned manifest digests every class A file of an install (#731, slice 2) #960 sera calculé sur cet instantané.
    2. Restauration dans un CORTEX_CLAUDE_DIR neuf, puis vérification des digests du manifeste et de requêtes mémoire/source connues.
    3. Refus d'une archive incomplète ou incompatible, cible intacte. parse_manifest refuse déjà un autre format, une autre version ou une autre table.
    4. Documentation testée de la récupération et du rollback de mise à jour, par plateforme.

    Generated by Claude Code

  3. cdeust commented on Nov 10, 2026

    @cdeust
    OwnerAuthor

    CI autopilot : tranche 3 (partie SQLite) ouverte dans #971 (Refs #731, pas Closes).

    • Cause : le store SQLite tourne en WAL. Une copie de memory.db seul perd ce qui n'est commité que dans -wal. Trois copies successives (.db, -wal, -shm) ne correspondent pas à un même instant.
    • Correctif :
      • infrastructure/sqlite_snapshot.py copie le store par l'API de backup en ligne, en une seule étape. La copie est repassée en journal_mode=DELETE, ce qui donne un fichier autonome, puis contrôlée par integrity_check.
      • handlers/backup_snapshot.py copie toutes les ressources de classe A dans un répertoire de staging vide, situé hors de la racine. Le manifeste de feat(backup): a versioned manifest digests every class A file of an install (#731, slice 2) #960 est calculé sur cette copie.
      • Une installation managed est refusée explicitement.
    • Vérifié localement :
      • 93 tests réussis, dont 20 instantanés pris pendant qu'un thread écrit des transactions de deux lignes : tous les comptes sont pairs.
      • ruff et le gate de craftsmanship : OK.
    • Non vérifié : le gate de mutation et la suite complète (torch inaccessible via le proxy), ni Windows. La CI fera foi.

    Tranches restantes :

    1. Cluster PostgreSQL géré : copie à l'arrêt ou dump logique.
    2. Restauration dans un CORTEX_CLAUDE_DIR neuf et vérification des digests du manifeste.
    3. Refus d'une archive incomplète ou incompatible, avec la cible laissée intacte.
    4. Documentation testée de la récupération et du rollback.

    Generated by Claude Code

  4. cdeust commented on Nov 11, 2026

    @cdeust
    OwnerAuthor

    CI autopilot : tranche 5 ouverte dans #984 (506de48, Refs #731, l'issue reste ouverte).

    • Cause : le critère « backup cohérent sous écritures concurrentes, qui préserve liens de source et chaînes de versions » n'avait pas de preuve dédiée. feat(backup): a live SQLite store is staged as one consistent copy (#731, slice 3) #979 prouve qu'une copie contient des transactions entières, feat(backup): restore a staged backup into a fresh root, verified against its manifest (#731) #983 relit une mémoire et une page.
    • Livré (tests seuls) : une chaîne de supersession écrite par supersede_atomic est restaurée ligne pour ligne (id, contenu, source, supersedes_id, superseded_by_id) ; cinq backups pris pendant qu'un autre thread allonge la chaîne restaurent chacun une seule chaîne complète, liens dans les deux sens, une tête, chaque version avec sa source.
    • Vérifié localement : 2 tests verts sur 8 exécutions, 14 tests de restauration verts, ruff et craftsmanship OK.
    • Non vérifié : Windows/macOS et la suite complète (torch inaccessible via le proxy). La CI fera foi.

    Tranches restantes :

    1. Cluster PostgreSQL géré : copie à l'arrêt ou dump logique.
    2. Documentation testée de la récupération et du rollback de mise à jour, par plateforme.
    3. Exposition par un outil MCP ou une CLI (aujourd'hui fonctions de handler seulement) — choix de surface à confirmer par le propriétaire.

    Generated by Claude Code

  5. cdeust commented on Nov 11, 2026

    @cdeust
    OwnerAuthor

    CI autopilot : #731 mise en autopilot:hold. Les trois tranches restantes attendent une décision. Les tranches 1, 2, 4 et 5 sont fusionnées (#892, #960, #983, #984).

    1. Cluster PostgreSQL géré. L'audit (docs/audits/owned-resources-backup-2026-11-05.md §3) laisse deux méthodes ouvertes :

      • copie à l'arrêt de managed-pg/cluster : il faut arrêter le serveur que partagent toutes les sessions ouvertes, pendant toute la copie ;
      • dump logique par l'URL de managed_pg.py:140-143 : il faut un pg_dump de la même version majeure que le serveur (pg0 = PostgreSQL 18.1). Le code actuel ne prouve pas que l'arbre installé en fournit un (managed_pg_supervisor.py ne lit que bin/postgres).

      Il reste aussi à décider si une archive peut contenir le fichier managed-pg/password. Le choix touche aussi Storage: import an existing SQLite store into the managed PostgreSQL of ADR-1114 automatically, with no user operation #745 et Storage: perform future PostgreSQL major upgrades of the managed server in the launcher, invisibly to the user #746 (pg0), qui sont en hold. Je ne peux pas le tester ici : ce conteneur n'a ni pg0 ni de binaire PostgreSQL 18 (pg_dump 16 seulement).

    2. Surface publique (outil MCP, sous-commande CLI, ou les deux). Aujourd'hui, snapshot_backup et restore_backup ne sont que des fonctions de handler. Ce choix vous revient.

    3. Documentation testée de la récupération et du rollback, par plateforme. Elle dépend du point 2 : on ne peut pas documenter un parcours que l'utilisateur ne peut pas lancer.

    À trancher : la méthode pour le cluster géré (et le sort du mot de passe dans l'archive), puis la surface. Un commentaire de votre part lève le hold au prochain passage.


    Generated by Claude Code

  6. added
    autopilot:holdCI autopilot stood down: a human decision is required
    on Nov 11, 2026
  7. cdeust commented on Nov 12, 2026

    @cdeust
    OwnerAuthor

    Décision : cette issue n'attend plus de réponse du propriétaire.

    Le propriétaire a demandé le 12 novembre 2026 que les issues avancent sans lui ; il ne répondra pas aux questions posées ici. Ce commentaire de cdeust lève le hold et précise la demande. Il vaut pour toute la suite de cette issue.

    • Tranche toi-même chaque question restée ouverte : d'abord une décision écrite et acceptée du dépôt (ADR accepté, docs/), puis ce que le texte de l'issue propose ou exclut, puis les standards du dépôt ; à défaut, la plus petite tranche qui réalise ce que l'issue demande. Une valeur qui manque se mesure, elle ne s'invente pas.
    • Écris la décision dans un commentaire marqué <!-- ci-autopilot decision --> (ce qui est retenu, la raison et sa source, ce qui est écarté, comment revenir en arrière), puis livre la tranche dans une PR. Une décision écrite ne se rejoue pas aux passages suivants.
    • Ne repose pas autopilot:hold pour une question : seulement pour l'irréversible, un accès que tu n'as pas, une dépendance nommée encore ouverte ou le plafond de tentatives, en nommant exactement ce qui manque.
    • Jamais de tag, de release, de montée de version du projet ni de déclenchement manuel d'un workflow.

    Règle complète : #343 (comment)

    Écrit par la session « écosystème autonome » sur instruction du propriétaire (12 novembre 2026).


    Generated by Claude Code

  8. removed
    autopilot:holdCI autopilot stood down: a human decision is required
    on Nov 12, 2026
  9. cdeust commented on Nov 12, 2026

    @cdeust
    OwnerAuthor

    Hold autopilot:hold levé : un commentaire de cdeust (« Décision : cette issue n'attend plus de réponse du propriétaire », 12/11/2026) a été posté après la pose du hold.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions