Engineering notes

ENGINEERING NOTE 10

Draft, Publish, Deploy, Back Up, Roll Back

Transactional content administration and versioned pve21 releases reduce partial-state failures, while explicit recovery boundaries keep operational confidence grounded.

  • draft / published / archivedRelease states — Admin publication validates a draft before atomically replacing the active curriculum; bootstrap follows a separate seed path.
  • 2Runtime services — A hardened standalone web container depends on a healthy PostgreSQL container.
  • PostgreSQL customBackup format — Each completed dump is list-validated before its partial suffix is atomically removed.

SECTION 01

Make editorial publication a database state transition

Draft creation, question editing, validation, publication, and audit recording use locks and transactions so readers never observe half a release.

Creating a draft takes a table lock, copies the active release's modules and lessons, increments every question revision, rewrites template revision references, and records an audit event inside one transaction. Editors can change only a draft. The update path locks the release row, applies structured options, key, explanation, and difficulty, then writes an audit event before commit. A failure rolls back both the edit and its claimed evidence.

Publishing locks release transitions, row-locks the selected draft, executes the full database validator on that same transaction client, archives the former published release, stamps and publishes the draft, and records validation counts. If validation finds a broken graph, incomplete item, wrong inventory, or malformed simulation template, the endpoint returns an error and no status changes commit. Curriculum readers continue resolving the prior published release throughout.

Curriculum release state machineA published release can be cloned into a draft; only a valid draft may become published, and the prior published release becomes archived atomically.The active release is cloned to an editable draft. Failed validation leaves it a draft. Successful validation publishes it and archives the former active release in the same transaction. Connections: Published to Draft clone (transactional clone); Draft clone to Validate (request publication); Validate to Draft clone (invalid: no commit); Validate to New published (valid: publish); Published to Archived previous (same commit).
  1. Published

    Current catalogue for pages and new sessions.

  2. Draft clone

    Editable question revisions and matching template references.

  3. Validate

    Graph, inventory, source, item, and template checks.

  4. New published

    Stamped active release after one commit.

  5. Archived previous

    Former release retained but no longer selected.

SECTION 02

Ship versioned, health-checked containers to pve21

Deployment copies the current workspace with selected exclusions, builds a versioned image, backs up the database, publishes seeded content explicitly, verifies application version, and only then advances the current symlink.

The deploy script validates its release identifier and absolute remote root, refuses to overwrite an existing release directory, and excludes environment variants, dependencies, build output, backups, and workspace metadata from transfer. It archives the current workspace rather than enforcing a clean Git tree. On pve21 it maintains versioned release directories plus a shared private environment file. Before replacing an existing version it takes a logical backup, then builds the web image and starts PostgreSQL to health.

Bootstrap applies migrations and seeds the new content with publication explicitly enabled. Compose then starts the web service and waits for health; an HTTP check must report the expected application version before the current symlink moves. The runtime uses an unprivileged UID, a read-only root filesystem, bounded temporary filesystems, dropped Linux capabilities, no-new-privileges, resource and PID limits, log rotation, and health checks. These are defense-in-depth controls, not a replacement for the trusted-LAN boundary described earlier.

Deployment checkpoints — The sequence limits how far a failed release can progress before it becomes current.
CheckpointEvidenceIf it fails
TransferNew validated release directoryDo not overwrite prior files
BackupList-readable custom-format dumpAbort before publication
DatabaseCompose health check succeedsKeep or restore prior service
ApplicationHealth JSON reports expected versionAttempt automatic previous-version restore
Activationcurrent symlink targets releasePrior symlink remains or is restored

SECTION 03

Validate backup completion and make restore destructive on purpose

Backup writes privately to a partial file, validates its archive table of contents, and renames only after success; restore requires explicit confirmation and one transaction.

The backup script sets a restrictive umask, writes pg_dump custom format to a timestamped partial path, and installs a trap that removes an incomplete file. It pipes the partial archive through pg_restore --list before an atomic rename removes the suffix. A systemd timer invokes the script on the pve21 release path. This verifies archive readability, though it is not the same as restoring into a clean database and running application checks.

Restore is intentionally harder to trigger. It requires an existing file plus either an interactive RESTORE token or an explicit noninteractive confirmation variable after the operator verifies the target. The script stops web traffic, runs pg_restore with clean, if-exists, no-owner, single-transaction, and exit-on-error options, then starts web again. A trap attempts to restart web if restoration exits early. The repository does not define retention, encryption, off-host replication, or routine restore drills; operations should not imply those controls exist.

Backup completion protocol — Consumers see the final filename only after the custom-format archive can be listed successfully.shell
pg_dump -Fc > "$backup_file.partial"
pg_restore --list < "$backup_file.partial" > /dev/null
mv -- "$backup_file.partial" "$backup_file"

SECTION 04

Document what rollback does not reverse

Versioned images and symlinks make application rollback fast, but schema migration and data recovery have distinct procedures and risk.

An explicit rollback validates the target release directory and image. When the current release and its backup script are available, rollback first takes a fresh logical backup. It then updates the shared application version, starts the target release's database and web services, verifies health and the running image, and moves the current symlink. If that operation fails, a trap attempts to restart the formerly active version. The manual path changes application code and image; it does not select a historical curriculum or restore a database dump.

Failed deployment has a slightly broader compensating path: it attempts to restart the previous image and reselect the previously published curriculum ID. It still does not reverse database migrations or restore the pre-deployment dump. Migrations may be forward compatible, but the script does not prove or enforce that property. When a schema or data change requires reversal, operators need a separately reviewed migration or the explicit restore workflow, with the outage and data-loss window understood before acting.

  • Application rollback: switch to an existing versioned image and release directory.
  • Failed-deploy compensation: also attempt to reselect the prior curriculum release.
  • Neither path reverses applied database migrations.
  • Neither path automatically restores a logical backup.
  • Retention and off-host backup policy are not implemented in this repository.

REFERENCES

Sources and further reading

  1. TransactionsPostgreSQL Global Development Group

    Database reference for all-or-nothing release changes, session creation, grading, and auditable state transitions.

  2. pg_dumpPostgreSQL Global Development Group

    Database reference for the custom-format logical backups produced before deployments and by the scheduled backup unit.

  3. pg_restorePostgreSQL Global Development Group

    Database reference for listing backup contents and performing explicit, single-transaction restores.

  4. Deploying a Next.js applicationNext.js

    Framework deployment reference for the standalone Node.js output used by the container image.