Software Memory Database · Chapter 3 of 3

Portable Backup and Restore

The portable backup is a single .masdb file that contains every row in the Memory Database. The file is human-readable JSON, schema-versioned, and operator-readable without any MAS-specific tooling.

8 min read
Applies to v2.0+
Last updated 2026-07-19
PUBLISHED

The portable backup is a single .masdb file that contains every row in the Memory Database. The file is human-readable JSON, schema-versioned, and operator-readable without any MAS-specific tooling.

This chapter covers the file format, the restore strategies, the audit-chain semantics across a restore boundary, and what the bundle deliberately does not include.


When to use it — vs the SQLite quick-snapshot #

MAS 2.0 stores its Memory Database in a local SQLite (WAL) file. Two backup mechanisms sit side by side in Settings → Database:

Scenario Portable backup (.masdb) SQLite quick-snapshot
Local pre-flight snapshot before a destructive operation ✅ ✅ (faster, byte-identical — preferred for same-day rollback)
Cross-machine restore (move history to a new workstation) ✅ —
Disaster-recovery backup archived off-machine ✅ —
Send the database to support for forensics ✅ —
Audit-team archive (human-readable JSON) ✅ —
Fastest same-machine same-day rollback — ✅

The SQLite quick-snapshot lives one card up in Settings → Database under Database backup. It copies the .db file after a WAL checkpoint and is byte-identical to the source — fastest restore, but tied to the exact schema version of the current install.

The portable backup fills the gaps the quick-snapshot leaves: cross-machine, human-readable, and archivable in a format that any JSON-aware tool can read months later without MAS installed.


File format #

The .masdb file is JSON with this shape:

jsonc
{
  "schemaVersion": 1,
  "header": {
    "createdAtUtc":               "2026-06-08T10:05:00+00:00",
    "createdBy":                  "DESKTOP-AB12C\\m.adm",
    "sourceDialect":              "Sqlite",
    "sourceFingerprint":          "726474ad…",
    "createdByMasVersion":        "2.0.0.0",
    "counts": {
      "softwareIdentityCount":    13,
      "packageRecordCount":       42,
      "distributionRecordCount":  18,
      "auditEntryCount":          247
    }
  },
  "softwareIdentities":  [ /* every row */ ],
  "packageRecords":      [ /* every row */ ],
  "distributionRecords": [ /* every row */ ],
  "auditEntries":        [ /* every row, including PrevHash + RowHash bytes
                              as Base64 strings */ ]
}

Schema-version 1 is the v1.0 baseline and carries into v2.0 unchanged. Future schema bumps add new optional sections (PackageRecord ↔ DistributionRecord cross-reference, Cmdlet-Library sync state, …) without breaking v1 readers.

The file is UTF-8, indented for human inspection, with camelCase property names. Average size: roughly 1.5 KB per package record plus 0.8 KB per audit entry.


How to take a backup #

Settings → Database → Portable backup → Create portable backup…. The picker suggests a timestamped filename in the form MAS-Backup-YYYY-MM-DD-HHMM.masdb. Pick a destination outside the MAS data directory.

Each backup gets a fresh filename — consecutive backups never collide. A DatabaseBackedUp entry is appended to the audit log on success with the destination path and row counts in the payload, so the trail shows when backups were taken even if the backup files themselves are later moved.

The backup completes in roughly 100 ms per 1 000 audit rows on a local SQLite database. For databases approaching ten thousand rows in any table, expect single-digit-second backup times.


How to restore #

Settings → Database → Portable backup → Restore from .masdb…. The file picker opens to .masdb files. After selection, MAS reads the header and shows a preview dialog:

Restore from portable backup?

Backup file: MAS-Backup-2026-06-08-1005.masdb Created: 2026-06-08 10:05 by ACME\m.adm Source backend: Sqlite Rows in backup: 13 identities · 42 packages · 18 distributions · 247 audit entries

Restore strategy ○ Abort if target has any rows (safest — recommended for fresh-machine restore) ● Replace all — DELETE target rows then load backup (destructive, irreversible) ☐ Yes, I understand this will overwrite the current database.

The Primary button stays disabled until either Abort-mode is selected or the destructive-confirm checkbox is ticked.

The full restore runs inside a single transaction. Any failure rolls the target back to its pre-restore state. A DatabaseRestored entry is appended to the audit log on success, recording the source path, the strategy used, the source backend's dialect, and the row counts.

Restore strategy reference #

Strategy Behaviour When to pick
AbortIfNotEmpty (default) Refuses if any of the four tables already has data. Restores into a truly empty database. Fresh-machine restore, disaster recovery
ReplaceAll DELETEs every row from every table, then INSERTs the backup verbatim. Single transaction — failure rolls back. Rolling back to a known-good snapshot, recovering from accidental destructive operations
SkipExisting Reserved for a later release —
OverwriteExisting Reserved for a later release —

The two merge modes (Skip / Overwrite) need careful hash-chain integrity design before they are safe to ship. The v2.0 release deliberately restricts the supported set to the two clean-cut paths.


Typical workflows #

Local pre-flight snapshot #

Before running a destructive operation — Clear History, mass-delete, or a Storage-section maintenance action — take a portable backup as the safety net.

Step Action
1 Settings → Database → Portable backup → Create portable backup…
2 Pick MAS-Backup-2026-06-10-1430.masdb on the Desktop or a USB stick
3 Run the destructive operation that needed the safety net
4 If something goes wrong: Settings → Database → Portable backup → Restore from .masdb…, pick the file from step 2, select Replace all strategy, tick the destructive-confirm checkbox, confirm

Same-machine rollback works equally well via the SQLite quick-snapshot. Pick the portable backup if the goal is archival (the .masdb file is JSON, the operator can inspect it months later without MAS installed). Pick the SQLite quick-snapshot if the goal is the fastest possible same-day rollback.

Cross-machine restore #

Moving MAS history to a new workstation — laptop refresh, VM provisioning, or a fresh install after a rebuild.

Step Action
1 On the current install: Settings → Database → Portable backup → Create portable backup…, save to a network share or USB stick
2 Install MAS 2.0 on the new machine
3 On first launch the Memory Database is empty. Settings → Database → Portable backup → Restore from .masdb…, pick the file from step 1
4 The default AbortIfNotEmpty strategy is correct because the target database is fresh
5 Verify hash-chain integrity on the target (see Audit chain after restore below)

The pre-transfer database file stays on disk untouched — keep it as an emergency rollback artifact for the first weeks.

Support handoff #

When investigating a build or lookup problem with support, a portable backup captures the exact Memory-DB state without shipping the raw SQLite file (which may hold WAL data, machine-specific timestamps, or paths that are awkward to share).

Step Action
1 Settings → Database → Portable backup → Create portable backup…
2 Attach the .masdb to the support ticket or share via secure transfer
3 Support reproduces locally by restoring into a scratch MAS install

Because the file is JSON, support can inspect specific rows without running MAS at all — useful for narrowing down a suspected bad row before spinning up a repro environment.


Audit chain after restore #

The audit chain bytes survive the round-trip but the chain is not guaranteed byte-perfect after a restore.

The reason: DateTimeOffset values go through JSON serialisation and SQLite TEXT storage. Both normalise sub-microsecond precision (SQLite stores ISO-8601 strings with up to seven fractional digits; JSON round-trips through DateTimeOffset.Parse which truncates to .NET's 100-nanosecond tick granularity). The persistent ActedAt representation that the verifier reads after restore can differ from the source machine's representation by a few bytes per row.

In practice:

What Result after restore
All audit rows present? ✅ Yes — every row, including PrevHash + RowHash bytes
Chain self-consistency on the target? ✅ Yes — every PrevHash still equals the previous RowHash
Stored RowHash matches re-computed RowHash? ⚠️ Possibly not — the recompute uses the round-tripped ActedAt string, which may differ
Verify-chain button result on the target? ⚠️ May report broken if the ActedAt round-trip shifted bytes

To re-establish a self-consistent verifier-passing chain on the target, run Settings → Database → Storage health → Verify chain integrity after restore. If the verifier reports a break, the chain is not corrupted in a tampering sense — the bytes are simply on a slightly different ISO-8601 trajectory. A rebuild pass (internally exposed as BackfillHashChainAsync) re-computes a consistent chain from the restored rows. The current release does not surface that pass as a one-click action; it is on the follow-up list.

For compliance contexts where the post-restore chain must verify, two mitigations apply:

  • Treat the .masdb itself as the immutable evidence (it's a human-readable file; archive it write-once alongside any other compliance artifacts).
  • Run Verify chain integrity on the source immediately before taking the backup; record the result. The source chain's pre-backup state is the canonical verification anchor.

Tracked as N6.followup. The next release will either preserve the exact ActedAt bytes through the round-trip or surface a one-click chain rebuild on the restore page.


What the backup does NOT include #

  • Settings. Per-user configuration (%LOCALAPPDATA%\MAS\settings.user.json) is not part of the bundle. Settings travel separately — copy the file manually.
  • Source installers. The Memory Database stores metadata only, never the installer binaries.
  • Build outputs. Generated PSADT folders live under the workspace folder, not in the database. Back those up at the file-system level.
  • Workspace state. Recent-projects list, sidecars, queue state are separate JSON files under %LOCALAPPDATA%\MAS\.

Up next · Enterprise & Compliance
GDPR Subject Access Request Export
Article 15 of the GDPR (and § 15 BDSG, the German implementation) gives every natural person whose personal data is processed the right to receive a copy of that data on request. For a packaging tool,…
→