/** * portable-bundle.ts — export side of the lossless machine-migration bundle * (manifest v2). * * Replaces the v1 pack path for `cleo backup export`. v1 snapshotted a fixed * list of pre-E6 filenames (`tasks.db`, `brain.db`, `nexus.db`, …); after the * E6 cutover (ADR-068) the live stores are the consolidated `cleo.db` files, * so v1 exported nothing of value and still reported success. v2: * * - snapshots the live primary store of every in-scope root with * `VACUUM INTO` (read-only, consistent under WAL) and fails when a primary * store is missing or unreadable; * - captures the rest of the root byte-for-byte, except an explicit denylist * whose sizes are reported, and SQLite files, which are also snapshotted; * - includes secrets only in encrypted bundles, recording what was omitted * and what the user must redo (each credential with its re-entry command); * - never carries the machine-key: encrypted bundles instead carry the * credential VALUES sealed under the passphrase (`secrets/
.sealed`, * T12326), which the importing device re-encrypts under its own key; * - records per-table row counts so import can prove the restore lossless. * * Archive layout: * manifest.json FIRST entry * checksums.sha256 GNU sha256sum format over every other file * global/home/ content * global/config/ content * projects/-/cleo/ a project's .cleo/ content * secrets/
.sealed passphrase-sealed credentials (encrypted only) * * @task T12318 * @epic T12317 * @module store/portable-bundle */ import type { PortableBundleManifest, PortableBundleScope, PortableDatabaseEntry, PortableExportResult, PortableImportResult, PortableSectionBase, PortableSkippedProject, PortableUnmigratedLegacyReport } from '@cleocode/contracts'; import { type SectionRules, type SnapshotRowFilter } from './portable-bundle-scan.js'; import { type SealedCredentialCell } from './vault-manifest.js'; /** Symbolic error codes raised by the portable bundle export/import. */ export type PortableBundleErrorCode = 'E_PRIMARY_STORE_MISSING' | 'E_PRIMARY_STORE_UNREADABLE' | 'E_NO_PROJECT' | 'E_REGISTRY_UNREADABLE' | 'E_PASSPHRASE_REQUIRED' | 'E_BUNDLE_DECRYPT' | 'E_BUNDLE_FORMAT' | 'E_BUNDLE_INTEGRITY' | 'E_DATA_EXISTS' | 'E_TARGET_AMBIGUOUS' | 'E_RESTORE_MISMATCH' | 'E_REDACTION_FAILED' | 'E_PROJECT_STORE_UNAVAILABLE'; /** * Numeric exit codes for {@link PortableBundleErrorCode}. Decrypt / format / * integrity / data-exists keep the v1 bundle codes (ADR-038 §4.3) so scripts * written against v1 keep working; the rest reuse the shared `ExitCode` enum. */ export declare const PORTABLE_BUNDLE_EXIT_CODES: Readonly>; /** * Error raised by the portable bundle export/import. Carries a symbolic code * and an exit code so the CLI can emit a LAFS error envelope. */ export declare class PortableBundleError extends Error { readonly code: PortableBundleErrorCode; readonly details?: PortableImportResult | undefined; /** Numeric process exit code. */ readonly exitCode: number; /** * @param code - Symbolic error code. * @param message - Human-readable description. * @param details - Structured context (e.g. the full import report on a mismatch). */ constructor(code: PortableBundleErrorCode, message: string, details?: PortableImportResult | undefined); } /** Input for {@link exportPortableBundle}. */ export interface ExportPortableBundleInput { /** * The bundle carries each project store's row identity to wherever it is * imported, so every project store it bundles (one, or each registered * project of a `machine` export) is marked shared first * (`row_identity_synced`, T13250) and the bundle carries the marker too. Default `true`; `false` * only for a local safety bundle that never leaves this machine (the vault's * pre-restore export). */ sharesIdentity?: boolean; /** Export scope. */ scope: PortableBundleScope; /** Project root (required for `project` / `all`). */ projectRoot?: string; /** Output bundle path. */ outputPath: string; /** Advisory label recorded in the manifest. */ label: string; /** Encrypt the bundle (and include secrets). */ encrypt?: boolean; /** Passphrase, required when `encrypt` is true. */ passphrase?: string; /** Global CLEO home (defaults to `getCleoHome()`). */ cleoHome?: string; /** Config home (defaults to `getCleoConfigDir()`). */ configHome?: string; /** Machine scope: predicate for temp/fixture paths (defaults to {@link isTempProjectPath}). */ isTempPath?: (absPath: string) => boolean; /** * Extra exclusions for the global home, on top of {@link GLOBAL_HOME_RULES} * (relative paths mapped to the reason recorded in the manifest). The cloud * vault uses this to leave machine-local state out (T12336). */ globalHomeExclusions?: { dirs?: Readonly>; files?: Readonly>; }; /** Stage the config home with the global home (default `true`). */ includeConfigHome?: boolean; /** * Columns cleared in every primary store snapshot, on top of an unencrypted * bundle's credential columns (table to column names). The cloud vault * passes the classification registry's `strip` columns, such as a git * remote URL, which can embed a token (T13007). Rows are kept. */ stripColumns?: Readonly>; /** * Tables emptied in every primary store snapshot. The cloud vault passes this * device's local-only change journal (`_sync_capture`, `_sync_undo`, * `_sync_frame`, `_sync_quarantine`), whose images hold values the snapshot strips; a restore * takes those tables from the live store (T13042). */ clearTables?: readonly string[]; /** * Paths of the exported project's section (relative to its `.cleo/`) that * its git checkout tracks; their file entries are marked `gitTracked`. The * cloud vault leaves tracked files to git (T13019). Project scope only. */ gitTrackedFiles?: ReadonlySet; /** * Seal the primary store snapshot's credential cells before they are * cleared; the sealed cells are written to the snapshot's * sealed-credentials table (`vault-manifest.ts`). The cloud vault seals under the * account key, so a joining device gets usable credentials (T13530). */ sealCredentials?: (snapshotPath: string) => Promise; /** * Read the primary store snapshot as taken, before anything in it is sealed, * stripped or cleared. The cloud vault checks a journal checkpoint's * snapshot holds exactly the sealed and pushed state (T12999). */ inspectSnapshot?: (snapshotPath: string) => Promise; /** * Rows kept in tables of every primary store snapshot, after the inspection; * the rest are deleted. The cloud vault keeps only the unresolved inbox rows * a journal checkpoint must carry, never the whole receive history (T12999). */ keepRows?: Readonly>; } /** A registered project row read from the global registry snapshot. */ interface RegistryRow { projectId: string; path: string; } /** * True when a path is a temp or test-fixture directory that must never be * exported as a project (the registry accumulates these from test runs). * * @param absPath - Resolved project path. * @returns Whether the path is temp/fixture material. */ export declare function isTempProjectPath(absPath: string): boolean; /** * Choose which registered projects `machine` scope exports. * * @param rows - Registry rows. * @param cleoHome - Global home (a project whose `.cleo/` IS the home is skipped). * @param isTempPath - Temp/fixture predicate. * @returns Included project roots and skipped rows with reasons. */ export declare function selectMachineProjects(rows: readonly RegistryRow[], cleoHome: string, isTempPath?: (absPath: string) => boolean): { included: RegistryRow[]; skipped: PortableSkippedProject[]; }; /** * Compare legacy per-domain tables with their consolidated counterparts. * * @param databases - Snapshotted databases of one root. * @returns Report; `detected` when any legacy table holds more rows than the primary. */ export declare function detectUnmigratedLegacy(databases: readonly PortableDatabaseEntry[]): PortableUnmigratedLegacyReport; /** * Compute the manifest self-hash (SHA-256 of the compact JSON with * `integrity.manifestHash` set to ""). * * @param manifest - Manifest to hash. * @returns Lowercase hex digest. */ export declare function computeManifestHash(manifest: PortableBundleManifest): string; /** * Export a portable (manifest v2) bundle. * * Reads only: every database is snapshotted over a read-only connection and * nothing is written outside the output path's directory (staging lives * beside the output and is removed on success or failure). * * @param input - Export options. * @returns Export summary. * @throws {PortableBundleError} When a primary store is missing/unreadable, the * registry cannot be read (machine scope), or encryption lacks a passphrase. * * @example * ```ts * const r = await exportPortableBundle({ scope: 'project', projectRoot: '/p', outputPath: '/b/p.cleobundle.tar.gz', label: 'p' }); * ``` */ export declare function exportPortableBundle(input: ExportPortableBundleInput): Promise; /** * The walk rules of the global home, with extra exclusions on top of * {@link GLOBAL_HOME_RULES} (the export's `globalHomeExclusions`). * * @param extra - Extra excluded directories and files, or none. * @returns The rules a global-home walk uses. */ export declare function globalHomeRules(extra?: ExportPortableBundleInput['globalHomeExclusions']): SectionRules; /** * Every section of a manifest, in archive order. * * @param manifest - Portable manifest. * @returns Sections (global home, global config, projects). */ export declare function allSections(manifest: PortableBundleManifest): PortableSectionBase[]; export {}; //# sourceMappingURL=portable-bundle.d.ts.map