Bite 33.2 — Backup Creation, Retention, and Verification - RodrigoMattosoSilveira/EnterpriseRemoteSystems GitHub Wiki
Bite 33.2 — Backup Creation, Retention, and Verification
Manual Verification Checklist Before Promotion
Branch: 930-bite-332-backup-creation-retention-and-verification
SHA: 05e9eff3b56c5a1308c17c131e188633e4e0e3b6
1. Purpose
These manual tests verify the operator-visible behavior introduced by Bite 33.2:
- a live SQLite database can be snapshotted with SQLite's online
.backupoperation; - a backup is not accepted until an adjacent verified JSON manifest exists;
- an operator independently records the backup file SHA-256 immediately after creation;
- the manifest checksum must match that independently recorded SHA-256;
- the same recorded SHA-256 is re-checked later to prove the retained backup has not changed;
- the manifest contains recovery evidence but not application credentials/secrets;
- an accepted backup can be independently re-verified later through the supported Make target;
- verification rejects the wrong environment, a modified backup, and a missing manifest;
- retention keeps at least the configured minimum number of verified backups and all verified backups within the configured age window;
- retention fails closed and deletes nothing when any managed backup pair is corrupt;
- all testing is performed against the LOCAL database and disposable files under
/tmp/ers-332-manual.
Off-host replication is not part of Bite 33.2. It belongs to Bite 33.3.
2. Safety Rules
Follow these rules throughout this suite:
- Run every command from the ERS repository root unless a step explicitly says otherwise.
- Do not point any command in this suite at Test or Production.
- Do not use
/opt/EnterpriseRemoteSystems/testor/opt/EnterpriseRemoteSystems/production. - Do not edit or delete
backend/data/app.dbas part of these tests. - All backup artifacts created by this suite must stay under:
/tmp/ers-332-manual
- Tests that intentionally expect a command to fail explicitly say so. A non-zero exit status in those tests is the expected PASS behavior.
- Keep Terminal 1 running the LOCAL backend until Test 10 is complete.
3. Automated Prerequisite
The following are prerequisites for starting manual promotion verification:
make local-check
CI
Both must already be passing for this SHA.
4. Test 01 — Establish the Exact LOCAL Source Database
Identity
No ERS login is required.
Purpose
Pin the LOCAL backend to the exact SQLite database that will be snapshotted during this suite and verify that the source database is healthy before creating backup evidence.
Terminal 1 — Confirm Branch and SHA
Run:
git branch --show-current
git rev-parse HEAD
Expected:
930-bite-332-backup-creation-retention-and-verification
05e9eff3b56c5a1308c17c131e188633e4e0e3b6
If either value differs, STOP. Do not continue this manual suite against a different baseline.
Terminal 2 — Confirm Required Local Tools
Run:
command -v sqlite3
command -v python3
command -v jq
Expected:
- each command prints a path;
- none of the commands returns an empty result.
If jq is missing, install it before continuing because later verification steps use it to inspect the backup manifest explicitly.
Terminal 2 — Prepare the Disposable Manual-Test Directory
Run:
rm -rf /tmp/ers-332-manual
mkdir -p /tmp/ers-332-manual/good
Confirm:
ls -ld /tmp/ers-332-manual /tmp/ers-332-manual/good
Expected: both directories exist.
Terminal 1 — Start the LOCAL Backend Against the Normal LOCAL Database
If another make local-backend process for this repository is already running, stop that process first with Ctrl+C.
Then run:
APP_ENV=development \
ERS_DATABASE_PATH="$(pwd)/backend/data/app.db" \
make local-backend
Leave Terminal 1 running.
Terminal 2 — Confirm the Backend Is Running
Run:
curl -fsS http://localhost:8080/api/v1/healthz
Expected: the command succeeds without a curl error.
Immediately run:
echo $?
Expected:
0
Terminal 2 — Confirm Source Database Integrity
Run:
sqlite3 backend/data/app.db 'PRAGMA integrity_check;'
Expected:
ok
Then run:
sqlite3 backend/data/app.db 'PRAGMA foreign_key_check;'
Expected: no rows and no output.
Confirm the latest migration:
sqlite3 backend/data/app.db \
'SELECT filename FROM schema_migrations ORDER BY filename DESC LIMIT 1;'
Expected:
000077_journey_bonus_award_approval.up.sql
PASS Criteria
PASS only if:
- branch and SHA exactly match this suite;
- the backend is running against
backend/data/app.db; - health check succeeds;
PRAGMA integrity_checkreturnsok;PRAGMA foreign_key_checkreturns no rows;- latest migration is
000077_journey_bonus_award_approval.up.sql.
Promotion Blocker
Do not promote if the source LOCAL database is already corrupt, has foreign-key violations, or is not at the expected migration level.
5. Test 02 — Create a Live SQLite Backup and Verified Manifest
Identity
No ERS login is required.
Prerequisite
Terminal 1 from Test 01 must still be running the LOCAL backend against:
backend/data/app.db
Terminal 2 — Create a Transactionally Consistent SQLite Snapshot
Run exactly:
GOOD_DIR=/tmp/ers-332-manual/good
GOOD_BACKUP="$GOOD_DIR/app-$(date -u +%Y%m%dT%H%M%SZ).db"
sqlite3 backend/data/app.db ".backup '$GOOD_BACKUP'"
printf '%s\n' "$GOOD_BACKUP" > /tmp/ers-332-manual/GOOD_BACKUP_PATH
Confirm the backup exists and is non-empty:
ls -lh "$GOOD_BACKUP"
test -s "$GOOD_BACKUP" && echo 'PASS: backup file is non-empty'
Expected final line:
PASS: backup file is non-empty
Record an Independent SHA-256 for Later Verification
Run:
GOOD_BACKUP_SHA256="$(shasum -a 256 "$GOOD_BACKUP" | awk '{print $1}')"
printf '%s\n' "$GOOD_BACKUP_SHA256" \
> /tmp/ers-332-manual/GOOD_BACKUP_SHA256
printf 'Recorded backup SHA-256: %s\n' "$GOOD_BACKUP_SHA256"
Expected:
- one 64-character hexadecimal SHA-256 value is printed;
/tmp/ers-332-manual/GOOD_BACKUP_SHA256contains exactly that value.
Confirm:
cat /tmp/ers-332-manual/GOOD_BACKUP_SHA256
Record this value in your test notes:
Known-good backup SHA-256:
This checksum is the operator-observed reference value used in later tests.
Create the 33.2 Manifest
Run:
python3 scripts/ers-backup.py create-manifest \
--backup "$GOOD_BACKUP" \
--environment development \
--source-container local-manual-test \
--source-database-path "$(pwd)/backend/data/app.db"
Expected output includes all of the following:
Verified backup manifest written to
Backup SHA-256:
Latest schema migration: 000077_journey_bonus_award_approval.up.sql
Define the manifest path:
GOOD_MANIFEST="${GOOD_BACKUP}.manifest.json"
Confirm both files exist:
test -s "$GOOD_BACKUP" && echo 'PASS: backup exists'
test -s "$GOOD_MANIFEST" && echo 'PASS: manifest exists'
Expected:
PASS: backup exists
PASS: manifest exists
Verify the Snapshot Itself
Run:
sqlite3 "$GOOD_BACKUP" 'PRAGMA integrity_check;'
Expected:
ok
Then:
sqlite3 "$GOOD_BACKUP" 'PRAGMA foreign_key_check;'
Expected: no rows and no output.
PASS Criteria
PASS only if:
- SQLite
.backupsucceeds while the LOCAL backend remains running; - the backup file is non-empty;
- an independent SHA-256 is recorded in
/tmp/ers-332-manual/GOOD_BACKUP_SHA256; - the recorded SHA-256 is exactly 64 hexadecimal characters;
- manifest creation succeeds;
- the adjacent
.manifest.jsonfile exists; - backup integrity is
ok; - foreign-key check is empty;
- the manifest reports latest migration
000077_journey_bonus_award_approval.up.sql.
Promotion Blocker
Do not promote if a backup can be created but cannot be verified into a managed backup/manifest pair.
6. Test 03 — Inspect Manifest Evidence Explicitly
Identity
No ERS login is required.
Terminal 2 — Recover the Good Backup Paths
Run:
GOOD_BACKUP="$(cat /tmp/ers-332-manual/GOOD_BACKUP_PATH)"
GOOD_MANIFEST="${GOOD_BACKUP}.manifest.json"
Display the Manifest
Run:
jq . "$GOOD_MANIFEST"
Visually confirm the manifest is readable JSON.
Assert Required Manifest Fields
Run exactly:
jq -e \
--arg dbpath "$(pwd)/backend/data/app.db" \
--arg filename "$(basename "$GOOD_BACKUP")" '
.format_version == 1 and
.status == "verified" and
.environment == "development" and
.source.container == "local-manual-test" and
.source.database_path == $dbpath and
.backup.file == $filename and
.backup.size_bytes > 0 and
(.backup.sha256 | length) == 64 and
.verification.integrity_check == "ok" and
.verification.foreign_key_check == "ok" and
.verification.schema_migration_count > 0 and
.verification.latest_schema_migration == "000077_journey_bonus_award_approval.up.sql" and
(.verification.required_tables | index("schema_migrations") != null) and
(.verification.required_tables | index("tenants") != null) and
(.verification.required_tables | index("auth_user_accounts") != null) and
(.verification.required_tables | index("authz_actors") != null) and
(.verification.required_tables | index("collaborator_journeys") != null) and
(.verification.required_tables | index("ledger_entries") != null) and
(.verification.required_tables | index("work_periods") != null)
' "$GOOD_MANIFEST"
Expected:
true
Immediately run:
echo $?
Expected:
0
Compare the Manifest SHA-256 with the Independently Recorded SHA-256
Run:
RECORDED_SHA256="$(cat /tmp/ers-332-manual/GOOD_BACKUP_SHA256)"
MANIFEST_SHA256="$(jq -r '.backup.sha256' "$GOOD_MANIFEST")"
printf 'Recorded SHA-256: %s\n' "$RECORDED_SHA256"
printf 'Manifest SHA-256: %s\n' "$MANIFEST_SHA256"
test "$RECORDED_SHA256" = "$MANIFEST_SHA256" && \
echo 'PASS: manifest checksum matches independently recorded backup checksum'
Expected final line:
PASS: manifest checksum matches independently recorded backup checksum
If that PASS line is not printed, STOP. Do not continue the suite.
Confirm No Credential/Secret Fields Are Present
Run:
if grep -Eqi \
'password|jwt_secret|openai_api_key|ledger_correction_key|ledger_settlement_key' \
"$GOOD_MANIFEST"; then
echo 'FAIL: manifest contains a credential/secret field'
exit 1
else
echo 'PASS: manifest contains no credential/secret fields'
fi
Expected:
PASS: manifest contains no credential/secret fields
PASS Criteria
PASS only if:
- manifest is valid JSON;
statusisverified;- environment is
development; - source path is the exact LOCAL database used in Test 01;
- size and SHA evidence are present;
- the manifest SHA-256 exactly matches the independently recorded checksum from Test 02;
- integrity and foreign-key evidence are
ok; - migration evidence is present;
- required ERS table evidence is present;
- no password/secret fields are present.
Promotion Blocker
Do not promote if the manifest lacks recovery evidence or contains application credentials/secrets.
7. Test 04 — Independently Re-Verify the Retained Backup Through the Supported Make Target
Identity
No ERS login is required.
Terminal 2
Recover the known-good backup path and independently recorded checksum:
GOOD_BACKUP="$(cat /tmp/ers-332-manual/GOOD_BACKUP_PATH)"
RECORDED_SHA256="$(cat /tmp/ers-332-manual/GOOD_BACKUP_SHA256)"
Before invoking ERS verification, independently recompute the backup checksum:
CURRENT_SHA256="$(shasum -a 256 "$GOOD_BACKUP" | awk '{print $1}')"
printf 'Recorded SHA-256: %s\n' "$RECORDED_SHA256"
printf 'Current SHA-256: %s\n' "$CURRENT_SHA256"
test "$RECORDED_SHA256" = "$CURRENT_SHA256" && \
echo 'PASS: retained backup checksum is unchanged before independent verification'
Expected final line:
PASS: retained backup checksum is unchanged before independent verification
Now run the supported ERS verification target:
make server-backup-verify \
ENV=development \
BACKUP_FILE="$GOOD_BACKUP"
Expected output includes:
Backup verification passed:
Environment: development
SHA-256:
Latest schema migration: 000077_journey_bonus_award_approval.up.sql
Immediately run:
echo $?
Expected:
0
PASS Criteria
PASS only if:
- the backup SHA-256 recomputed immediately before verification exactly matches the checksum recorded in Test 02;
- the independently invoked Make target reopens and verifies the retained backup/manifest pair successfully.
Promotion Blocker
Do not promote if a newly accepted backup cannot later be re-verified independently.
8. Test 05 — Wrong Environment Is Rejected
Identity
No ERS login is required.
Purpose
Prove that a Development backup cannot be presented as Production recovery evidence.
Terminal 2
Run:
GOOD_BACKUP="$(cat /tmp/ers-332-manual/GOOD_BACKUP_PATH)"
make server-backup-verify \
ENV=production \
BACKUP_FILE="$GOOD_BACKUP"
STATUS=$?
printf 'verification exit status: %s\n' "$STATUS"
This command is expected to fail intentionally.
Expected output includes:
Backup verification error: backup environment mismatch: expected production, manifest declares development
Expected status is non-zero. With the current implementation, Make normally reports status 2.
Now prove the original backup is still valid:
make server-backup-verify \
ENV=development \
BACKUP_FILE="$GOOD_BACKUP"
Expected: verification passes.
PASS Criteria
PASS only if:
- Production verification rejects the Development backup;
- the error explicitly identifies the environment mismatch;
- the backup and manifest are not modified;
- Development verification still succeeds afterward.
Promotion Blocker
Do not promote if backup evidence from one environment can be accepted as another environment's backup.
9. Test 06 — Tampered Backup Is Rejected Without Damaging the Good Backup
Identity
No ERS login is required.
Terminal 2 — Create a Disposable Tampered Copy
Run:
GOOD_BACKUP="$(cat /tmp/ers-332-manual/GOOD_BACKUP_PATH)"
GOOD_MANIFEST="${GOOD_BACKUP}.manifest.json"
TAMPER_DIR=/tmp/ers-332-manual/tampered
rm -rf "$TAMPER_DIR"
mkdir -p "$TAMPER_DIR"
TAMPER_BACKUP="$TAMPER_DIR/$(basename "$GOOD_BACKUP")"
TAMPER_MANIFEST="${TAMPER_BACKUP}.manifest.json"
cp "$GOOD_BACKUP" "$TAMPER_BACKUP"
cp "$GOOD_MANIFEST" "$TAMPER_MANIFEST"
printf '\nTAMPER\n' >> "$TAMPER_BACKUP"
Attempt Verification
Run:
make server-backup-verify \
ENV=development \
BACKUP_FILE="$TAMPER_BACKUP"
STATUS=$?
printf 'tampered verification exit status: %s\n' "$STATUS"
This command is expected to fail intentionally.
Expected output includes either a backup size mismatch or SHA-256 mismatch. With this exact test, size mismatch should be detected first.
Expected status: non-zero.
Confirm the Original Good Backup Still Verifies
Run:
make server-backup-verify \
ENV=development \
BACKUP_FILE="$GOOD_BACKUP"
Expected: verification passes.
PASS Criteria
PASS only if:
- the tampered copy is rejected;
- the error identifies changed backup evidence;
- the original good backup is unaffected and still verifies.
Promotion Blocker
Do not promote if a modified backup can still pass verification.
10. Test 07 — Missing Manifest Is Rejected
Identity
No ERS login is required.
Terminal 2 — Create a Backup Copy Without Its Manifest
Run:
GOOD_BACKUP="$(cat /tmp/ers-332-manual/GOOD_BACKUP_PATH)"
MISSING_DIR=/tmp/ers-332-manual/missing-manifest
rm -rf "$MISSING_DIR"
mkdir -p "$MISSING_DIR"
MISSING_BACKUP="$MISSING_DIR/$(basename "$GOOD_BACKUP")"
cp "$GOOD_BACKUP" "$MISSING_BACKUP"
Confirm the database exists and the manifest does not:
test -s "$MISSING_BACKUP" && echo 'PASS: backup copy exists'
test ! -e "${MISSING_BACKUP}.manifest.json" && echo 'PASS: manifest is absent'
Expected:
PASS: backup copy exists
PASS: manifest is absent
Attempt Verification
Run:
make server-backup-verify \
ENV=development \
BACKUP_FILE="$MISSING_BACKUP"
STATUS=$?
printf 'missing-manifest verification exit status: %s\n' "$STATUS"
This command is expected to fail intentionally.
Expected output includes:
backup manifest does not exist
Expected status: non-zero.
PASS Criteria
PASS only if a .db file without its adjacent manifest is rejected as verified recovery evidence.
Promotion Blocker
Do not promote if file existence alone is accepted as a verified ERS backup.
11. Test 08 — Retention Keeps Minimum Count AND All Backups Within the Age Window
Identity
No ERS login is required.
Purpose
Prove this exact policy:
Keep at least the newest 2 verified backups and keep every verified backup created during the last 30 days.
For this test, five managed backups will be created:
old60 60 days old -> should be removed
old45 45 days old -> should be removed
recent10 10 days old -> should be kept because it is within 30 days
recent5 5 days old -> should be kept
recent1 1 day old -> should be kept
The minimum count is 2, but the final retained count must be 3 because the 10-day-old backup is also inside the 30-day window.
Terminal 2 — Prepare a Disposable Server Root
Run:
GOOD_BACKUP="$(cat /tmp/ers-332-manual/GOOD_BACKUP_PATH)"
RET_SERVER_ROOT=/tmp/ers-332-manual/retention-server
RET_DIR="$RET_SERVER_ROOT/development/backups"
rm -rf "$RET_SERVER_ROOT"
mkdir -p "$RET_DIR"
Create Five Verified Managed Backup Pairs
Copy and run this entire block exactly:
python3 - "$GOOD_BACKUP" "$RET_DIR" "$(pwd)" <<'PY'
from datetime import datetime, timedelta, timezone
from pathlib import Path
import shutil
import subprocess
import sys
source = Path(sys.argv[1])
out = Path(sys.argv[2])
repo = Path(sys.argv[3])
specs = [
("old60", 60),
("old45", 45),
("recent10", 10),
("recent5", 5),
("recent1", 1),
]
now = datetime.now(timezone.utc).replace(microsecond=0)
for label, days_ago in specs:
backup = out / f"app-{label}.db"
shutil.copy2(source, backup)
created_at = (now - timedelta(days=days_ago)).isoformat().replace("+00:00", "Z")
subprocess.check_call([
sys.executable,
str(repo / "scripts" / "ers-backup.py"),
"create-manifest",
"--backup", str(backup),
"--environment", "development",
"--source-container", "local-retention-test",
"--source-database-path", str(repo / "backend" / "data" / "app.db"),
"--created-at", created_at,
])
PY
Confirm exactly ten files exist: five databases plus five manifests.
find "$RET_DIR" -maxdepth 1 -type f -print | sort
find "$RET_DIR" -maxdepth 1 -type f | wc -l
Expected count:
10
Apply Retention Through the Supported Make Target
Run:
make server-backup-retention \
ENV=development \
SERVER_ROOT="$RET_SERVER_ROOT" \
SERVER_BACKUP_RETENTION_COUNT=2 \
SERVER_BACKUP_RETENTION_DAYS=30
Expected output includes removal of:
app-old60.db
app-old45.db
Expected summary is equivalent to:
Retention: kept 3 verified backup(s); removed 2; minimum-count=2; max-age-days=30.
Verify Exact Retention Result
Run:
test ! -e "$RET_DIR/app-old60.db" && \
test ! -e "$RET_DIR/app-old60.db.manifest.json" && \
test ! -e "$RET_DIR/app-old45.db" && \
test ! -e "$RET_DIR/app-old45.db.manifest.json" && \
echo 'PASS: backups outside both retention protections were removed'
Expected:
PASS: backups outside both retention protections were removed
Then run:
test -s "$RET_DIR/app-recent10.db" && \
test -s "$RET_DIR/app-recent10.db.manifest.json" && \
test -s "$RET_DIR/app-recent5.db" && \
test -s "$RET_DIR/app-recent5.db.manifest.json" && \
test -s "$RET_DIR/app-recent1.db" && \
test -s "$RET_DIR/app-recent1.db.manifest.json" && \
echo 'PASS: every backup inside the 30-day window was retained'
Expected:
PASS: every backup inside the 30-day window was retained
Finally:
find "$RET_DIR" -maxdepth 1 -type f | wc -l
Expected:
6
That means exactly three managed backup pairs remain.
PASS Criteria
PASS only if:
old60andold45pairs are removed;recent10,recent5, andrecent1pairs remain;- exactly three verified backup pairs remain;
- retention therefore implements minimum count OR age window, not merely “keep the newest 2.”
Promotion Blocker
Do not promote if retention removes a verified backup that is still inside the configured age window or violates the minimum-count rule.
12. Test 09 — Retention Fails Closed and Deletes Nothing When Any Managed Pair Is Corrupt
Identity
No ERS login is required.
Purpose
Prove that retention verifies all managed pairs before deleting anything.
Three old backups will be created. Retention would normally delete two of them under count=1 and days=1. One pair will then be corrupted. The correct result is:
verification failure
+ zero deletions
Terminal 2 — Prepare the Fail-Closed Directory
Run:
GOOD_BACKUP="$(cat /tmp/ers-332-manual/GOOD_BACKUP_PATH)"
FAIL_SERVER_ROOT=/tmp/ers-332-manual/failclosed-server
FAIL_DIR="$FAIL_SERVER_ROOT/development/backups"
rm -rf "$FAIL_SERVER_ROOT"
mkdir -p "$FAIL_DIR"
Create Three Verified Old Backup Pairs
Run this block exactly:
python3 - "$GOOD_BACKUP" "$FAIL_DIR" "$(pwd)" <<'PY'
from datetime import datetime, timedelta, timezone
from pathlib import Path
import shutil
import subprocess
import sys
source = Path(sys.argv[1])
out = Path(sys.argv[2])
repo = Path(sys.argv[3])
specs = [
("old90", 90),
("old80", 80),
("old70", 70),
]
now = datetime.now(timezone.utc).replace(microsecond=0)
for label, days_ago in specs:
backup = out / f"app-{label}.db"
shutil.copy2(source, backup)
created_at = (now - timedelta(days=days_ago)).isoformat().replace("+00:00", "Z")
subprocess.check_call([
sys.executable,
str(repo / "scripts" / "ers-backup.py"),
"create-manifest",
"--backup", str(backup),
"--environment", "development",
"--source-container", "local-failclosed-test",
"--source-database-path", str(repo / "backend" / "data" / "app.db"),
"--created-at", created_at,
])
PY
Confirm six files exist:
find "$FAIL_DIR" -maxdepth 1 -type f | wc -l
Expected:
6
Corrupt One Managed Backup After Its Manifest Has Been Created
Run:
printf '\nCORRUPTED AFTER VERIFICATION\n' >> "$FAIL_DIR/app-old80.db"
Record the complete file list before retention:
find "$FAIL_DIR" -maxdepth 1 -type f -exec basename {} \; | sort \
> /tmp/ers-332-manual/failclosed-before.txt
cat /tmp/ers-332-manual/failclosed-before.txt
Expected: six filenames are listed.
Run Retention
Run:
make server-backup-retention \
ENV=development \
SERVER_ROOT="$FAIL_SERVER_ROOT" \
SERVER_BACKUP_RETENTION_COUNT=1 \
SERVER_BACKUP_RETENTION_DAYS=1
STATUS=$?
printf 'fail-closed retention exit status: %s\n' "$STATUS"
This command is expected to fail intentionally.
Expected output includes a backup verification error for app-old80.db, normally a size mismatch.
Expected status: non-zero.
Prove That Nothing Was Deleted
Run:
find "$FAIL_DIR" -maxdepth 1 -type f -exec basename {} \; | sort \
> /tmp/ers-332-manual/failclosed-after.txt
diff -u \
/tmp/ers-332-manual/failclosed-before.txt \
/tmp/ers-332-manual/failclosed-after.txt
Expected: diff prints no output.
Immediately run:
echo $?
Expected:
0
Also verify the file count is still six:
find "$FAIL_DIR" -maxdepth 1 -type f | wc -l
Expected:
6
PASS Criteria
PASS only if:
- retention detects the corrupted managed pair;
- retention exits non-zero;
- no managed
.dbfile is deleted; - no managed manifest is deleted;
- the before/after filename lists are identical.
Promotion Blocker
Do not promote if retention deletes any valid historical backup before discovering that another managed pair is corrupt.
13. Test 10 — Retention Cannot Be Configured to Keep Zero Backups
Identity
No ERS login is required.
Terminal 2
Use the already-created retention server root:
RET_SERVER_ROOT=/tmp/ers-332-manual/retention-server
Run:
make server-backup-retention \
ENV=development \
SERVER_ROOT="$RET_SERVER_ROOT" \
SERVER_BACKUP_RETENTION_COUNT=0 \
SERVER_BACKUP_RETENTION_DAYS=30
STATUS=$?
printf 'zero-count retention exit status: %s\n' "$STATUS"
This command is expected to fail intentionally.
Expected output includes:
retention count must be an integer >= 1
Expected status: non-zero.
Now test zero days:
make server-backup-retention \
ENV=development \
SERVER_ROOT="$RET_SERVER_ROOT" \
SERVER_BACKUP_RETENTION_COUNT=2 \
SERVER_BACKUP_RETENTION_DAYS=0
STATUS=$?
printf 'zero-days retention exit status: %s\n' "$STATUS"
This command is expected to fail intentionally.
Expected output includes:
retention days must be an integer >= 1
Expected status: non-zero.
PASS Criteria
PASS only if both zero-valued retention settings are rejected.
Promotion Blocker
Do not promote if automatic retention can be configured with a minimum count of zero or an age window of zero days.
14. Test 11 — Final Good Backup Still Verifies After All Negative Tests
Identity
No ERS login is required.
Terminal 2
Recover the known-good backup and original checksum:
GOOD_BACKUP="$(cat /tmp/ers-332-manual/GOOD_BACKUP_PATH)"
RECORDED_SHA256="$(cat /tmp/ers-332-manual/GOOD_BACKUP_SHA256)"
Recompute the checksum after all negative tests:
FINAL_SHA256="$(shasum -a 256 "$GOOD_BACKUP" | awk '{print $1}')"
printf 'Original recorded SHA-256: %s\n' "$RECORDED_SHA256"
printf 'Final SHA-256: %s\n' "$FINAL_SHA256"
test "$RECORDED_SHA256" = "$FINAL_SHA256" && \
echo 'PASS: known-good backup checksum is unchanged after all negative tests'
Expected final line:
PASS: known-good backup checksum is unchanged after all negative tests
Then perform the final supported verification:
make server-backup-verify \
ENV=development \
BACKUP_FILE="$GOOD_BACKUP"
Expected output includes:
Backup verification passed:
Environment: development
Latest schema migration: 000077_journey_bonus_award_approval.up.sql
Immediately run:
echo $?
Expected:
0
PASS Criteria
PASS only if:
- the final SHA-256 exactly matches the checksum recorded immediately after backup creation in Test 02;
- the original accepted backup still passes
make server-backup-verify; - wrong-environment, tamper, missing-manifest, and retention failure testing did not modify the known-good backup.
Promotion Blocker
Do not promote if negative verification/retention operations modify a previously accepted good backup.
15. Test 12 — Re-Run the Bite 33.2 Automated Gate After Manual Verification
Identity
No ERS login is required.
Terminal 2
Run:
make backup-creation-retention-verification-check
Expected final result:
Bite 33.2 backup creation, retention, and verification verified.
Then run:
make local-check
Expected: make local-check passes completely.
CI for this exact SHA must also pass before promotion.
PASS Criteria
PASS only if:
make backup-creation-retention-verification-checkpasses;make local-checkpasses;- CI passes.
Promotion Blocker
Any local or CI regression is a promotion blocker.
16. Cleanup
After every manual test has passed, stop the LOCAL backend in Terminal 1 with:
Ctrl+C
Then remove only the disposable manual-test directory:
rm -rf /tmp/ers-332-manual
Do not delete:
backend/data/app.db
Confirm the normal LOCAL database still exists:
ls -lh backend/data/app.db
17. Promotion Gate
Bite 33.2 is ready for promotion only when every item below is true:
- Branch is
930-bite-332-backup-creation-retention-and-verification. - SHA is
05e9eff3b56c5a1308c17c131e188633e4e0e3b6. - LOCAL source database passes SQLite integrity and foreign-key checks.
- A live SQLite
.backupsnapshot can be created while the LOCAL backend remains running. - The backup SHA-256 is independently recorded immediately after creation.
- A verified adjacent JSON manifest is created for the backup.
- Manifest records environment, source path, size, SHA-256, integrity, migration, required-table, and record-count evidence.
- The manifest SHA-256 exactly matches the independently recorded backup checksum.
- Manifest does not contain credential/secret fields.
-
make server-backup-verify ENV=development BACKUP_FILE=<good-backup>succeeds. - The same Development backup is rejected when verified as Production.
- A tampered backup is rejected.
- A backup without its manifest is rejected.
- Retention removes only backups outside both the minimum-count and age-window protections.
- Retention keeps all verified backups still inside the configured age window.
- Retention fails closed when any managed backup pair is corrupt.
- Fail-closed retention deletes nothing.
- Retention count
0is rejected. - Retention days
0is rejected. - The original known-good backup SHA-256 is unchanged after all negative tests.
- The original known-good backup still verifies after all negative tests.
-
make backup-creation-retention-verification-checkpasses. -
make local-checkpasses. - CI passes for this exact SHA.
18. Promotion-Blocking Defects
Do not promote Bite 33.2 if any of the following occurs:
- SQLite backup cannot be created consistently while the source database is live.
- A backup is accepted without a valid adjacent manifest.
- No independent SHA-256 is recorded for the known-good backup at creation time.
- The manifest SHA-256 does not match the independently recorded checksum.
- Manifest verification does not validate size/SHA-256.
- Manifest omits migration or required-table evidence.
- Manifest contains application passwords, JWT secrets, OpenAI keys, ledger keys, or similar credentials.
- A Development backup verifies successfully as Production.
- A modified backup verifies successfully.
- A backup with a missing manifest verifies successfully.
- Retention removes a backup that is still within the configured age window.
- Retention keeps fewer than the configured minimum count when more valid backups exist.
- Retention begins deleting backups before discovering another managed pair is corrupt.
- Corrupt managed backup evidence is silently ignored by retention.
- Retention permits a minimum count of zero.
- The known-good backup SHA-256 changes after any negative verification/retention test.
- Negative verification/retention tests damage the known-good backup.
make backup-creation-retention-verification-checkfails.make local-checkfails.- CI fails.