Skip to content

v0.20.1 Deployment Discipline Plan ​

Purpose ​

v0.20.1 is a structural hardening release. It exists to prevent the production drift discovered during the CT 115 reconciliation window from recurring.

This is not a product-feature release. It is a deployment-discipline release.

Background ​

During the CT 115 reconciliation window, production was found to have drifted across three layers:

text
Disk:       v0.20.0
Container:  stale image
Database:   stale migration ledger / missing governance tables

The system has since been reconciled:

text
Disk:       v0.20.0
Container:  v0.20.0
Database:   migration 036

The remaining risk is recurrence: future rebuilds or restarts must not rely on manual migration application, manual Redis configuration, or manually remembered version checks.

Release split ​

v0.20.1 is split into two scopes.

v0.20.1-A — Structural invariants ​

These changes materially alter the system’s failure modes and should ship first.

  1. Add a fail-closed Docker entrypoint that runs database migrations before app startup.
  2. Persist Redis memory ceiling and noeviction policy in Docker Compose.
  3. Expose version, git SHA, and migration status in health/readiness responses.
  4. Update deployment documentation with the operational lessons from the reconciliation window.

v0.20.1-B — Version centralization ​

This is quality-of-life and observability hardening. It should follow v0.20.1-A unless implementation is trivial.

  1. Introduce a single build-info source.
  2. Remove hardcoded version strings from app/dashboard surfaces.
  3. Ensure fresh clones, tests, local dev, Docker builds, and dashboard bundles all have a safe fallback.

v0.20.1-A Scope ​

1. Fail-closed migration entrypoint ​

Decision ​

The app container must run migrations before starting the application process.

If migrations fail, the container must exit non-zero. The system must fail closed rather than running newer code against an uncertain schema.

Rationale ​

The migration runner is already idempotent and transactional. A failed migration indicates the database is not safe for the code being started.

Silent continuation would recreate the exact drift class this release is meant to prevent.

Expected implementation ​

Add a Docker entrypoint script similar to:

bash
#!/usr/bin/env sh
set -e

echo "[entrypoint] running database migrations"
node scripts/migrate.js

echo "[entrypoint] starting GitWire app"
exec node packages/web/src/index.js

The final app start command should match the current production image start command.

Acceptance criteria ​

  • App startup runs node scripts/migrate.js.
  • If migration fails, the container exits non-zero.
  • If all migrations are already applied, startup proceeds normally.
  • Logs clearly show migration status before worker startup.
  • Rebuilding/recreating the app container does not require manual migration commands.

2. Durable Redis memory ceiling ​

Decision ​

Redis must retain append-only persistence and add a durable memory ceiling through the Compose command.

Use noeviction, not allkeys-lru.

Required Compose command ​

yaml
redis:
  image: redis:7-alpine
  command:
    - redis-server
    - --appendonly
    - "yes"
    - --maxmemory
    - 256mb
    - --maxmemory-policy
    - noeviction

Rationale ​

BullMQ depends on Redis keys for queue state. Evicting keys with allkeys-lru can silently delete waiting, delayed, or active job state. noeviction is the correct failure mode because Redis write failures are loud, recoverable, and observable.

Acceptance criteria ​

After container recreation:

bash
redis-cli CONFIG GET maxmemory
redis-cli CONFIG GET maxmemory-policy

Expected:

text
maxmemory = 268435456
maxmemory-policy = noeviction

3. Health/readiness deployment visibility ​

Goal ​

A remote health/readiness check should make deployment drift visible.

Suggested fields ​

Health or readiness should include:

json
{
  "status": "ok",
  "service": "gitwire",
  "version": "0.20.1",
  "git_sha": "abc1234",
  "db_migrations_applied": 36,
  "db_migrations_available": 36,
  "db_migration_status": "current"
}

The migration count should be reported as applied vs. available, not as a hardcoded target. The system computes available by counting migration files in the image and applied by counting schema_migrations rows. db_migration_status is "current" when applied == available, and "behind" (with readiness degraded) otherwise. This way the check stays correct after migration 037, 038, and beyond — no release ever needs to update a baked-in number.

Acceptance criteria ​

  • Health/readiness exposes app version.
  • Health/readiness exposes git SHA when available.
  • Health/readiness exposes applied migration count AND available migration count (applied vs available, not a hardcoded target).
  • If applied < available, readiness should fail or clearly report degraded state.
  • This check can be used after every deploy to confirm disk/container/database alignment.

4. Deployment documentation updates ​

Update the deployment runbook and related installation docs with:

  1. The canonical migration path:

    bash
    docker exec gitwire-gitwire-app-1 node scripts/migrate.js
  2. The rule that Postgres docker-entrypoint-initdb.d runs only on first database creation.

  3. The Redis caveat:

    • CONFIG SET is process-memory only when Redis runs without a config file.
    • Redis maxmemory must be persisted through Compose command flags.
  4. The fail-closed entrypoint behavior:

    • migrations run before app startup,
    • migration failure prevents app startup.
  5. CRLF normalization during emergency scp hotfixes:

    bash
    sed -i 's/\r$//' <file>

Rationale ​

CT 115 is a Linux deployment. Some emergency edits originate from Windows working copies. CRLF drift can create noisy full-file diffs and obscure real changes during incident response.


v0.20.1-B Scope ​

Version centralization ​

Problem ​

Version strings have drifted across root package, workspace packages, dashboard footer, and running container reports.

Preferred approach ​

Use a committed fallback build-info module that can be overwritten during build.

This avoids a generated-file chicken-and-egg problem where fresh clones, tests, or local dev fail because the generated module does not exist.

Commit a fallback module with safe defaults:

js
export const BUILD_INFO = {
  name: "gitwire",
  version: "0.0.0-dev",
  gitSha: null,
  builtAt: null
};

Then have a generation script overwrite it during Docker/build:

text
scripts/generate-build-info.js

The script should read:

  • root package.json for version,
  • git SHA when available,
  • build timestamp.

It should write build-info modules for both backend and dashboard use.

Why committed fallback is required ​

If buildInfo.js is generated but gitignored, a fresh clone can fail before generation:

text
ERR_MODULE_NOT_FOUND

A committed fallback guarantees imports never fail. Build-time generation then improves the values without making the module’s existence conditional.

Acceptance criteria ​

  • Fresh clone works before running generation.
  • Tests work before running generation.
  • Docker build overwrites fallback with real version/SHA.
  • Dashboard footer does not hardcode version.
  • Backend health reports the same version as the dashboard.
  • Version source is root package version unless explicitly overridden at build time.

Out of scope for v0.20.1 ​

The following are intentionally deferred:

  • production validator image,
  • backend isolation evidence generation,
  • post-apply proof chain,
  • LLM provider abstraction,
  • code graph indexing,
  • operator audit bundle UI.

These belong to the product roadmap after deployment discipline is stable.


Recommended execution order ​

  1. Implement v0.20.1-A.

  2. Rebuild CT 115 once.

  3. Verify:

    • migrations report all applied,
    • Redis maxmemory persists after recreation,
    • health/readiness reports version/SHA/migration count,
    • logs are clean,
    • all containers healthy.
  4. Tag and release v0.20.1.

  5. Implement v0.20.1-B as follow-up if not included.

  6. Resume product roadmap.


Success definition ​

v0.20.1 is successful when this statement is true:

text
A fresh container rebuild can safely start GitWire against the current production database without manual migration commands, without losing Redis memory limits, and with externally visible version/schema status.

Released under the MIT License.