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:
{
"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.masdbCreated: 2026-06-08 10:05 by ACME\m.adm Source backend: Sqlite Rows in backup: 13 identities · 42 packages · 18 distributions · 247 audit entriesRestore 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
.masdbitself 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\.
Related #
650-Audit-Chain— how the chain works, why the round-trip is approximate640-Migration-Wizard— the file-level SQLite quick-snapshot../800-Enterprise/820-GDPR-Export— focused per-actor extraction (different scope, different file shape)