Skip to content

feat(manifest): add version and compatibility checks to dream update#350

Open
buddy0323 wants to merge 4 commits intoLight-Heart-Labs:mainfrom
buddy0323:feat/release-manifest-versioned
Open

feat(manifest): add version and compatibility checks to dream update#350
buddy0323 wants to merge 4 commits intoLight-Heart-Labs:mainfrom
buddy0323:feat/release-manifest-versioned

Conversation

@buddy0323
Copy link
Contributor

Summary

  • Add dream_version, min_compatible_dream_version, and schema_version to manifest.json
  • Add _check_version_compat() to dream-cli — hard-blocks updates below min_compatible_dream_version, warns and requires confirmation when jumping more than 1 major version
  • Persist DREAM_VERSION in .env at install time so dream update always has a baseline to compare against
  • Add --force flag to dream update to bypass version-compat prompts

What changed and why

manifest.json

Field Value Purpose
schema_version 2 Integer version of the manifest structure itself — increment when fields are added/removed
dream_version "2.1.0" Canonical machine-readable release version read by dream-cli
min_compatible_dream_version "1.0.0" Oldest installed version that can safely update directly to dream_version. If an installation is below this, dream update hard-blocks unless --force is used.

Also bumped release.version2.1.0, updated date, added intel GPU backend entry, and added docker-compose.arc.yml to contracts.compose.canonical.

dream-cli — new helpers

_semver_major() — extracts the major version number from a semver string.

_manifest_field() — reads a named field from manifest.json using jq with a python3 fallback, so it works on systems without jq.

_check_version_compat(force) — full version compatibility check:

  1. Reads installed version from DREAM_VERSION in .env (falls back to manifest.json, then hardcoded $VERSION)
  2. Reads target version from manifest.json on disk
  3. Reads min_compatible_dream_version from manifest.json
  4. Hard block (exit unless --force): installed major < min_compatible_dream_version major — prints a red banner with upgrade path instructions
  5. Warn + confirm (skip with --force): target major − installed major > 1 — prints a yellow warning with incremental upgrade recommendation

dream-clicmd_update() changes

  • Parses --force / -f flag before any other logic
  • Calls _check_version_compat before any docker compose pull — exits cleanly on user cancel or hard block
  • Logs Updating: vX.Y.Z → vA.B.C when versions differ
  • Calls _env_set DREAM_VERSION <target> after a successful update so future runs know what was last installed
  • update|u) dispatcher now passes "$@" so --force reaches the function

installers/phases/06-directories.sh

Added DREAM_VERSION=${VERSION:-2.1.0} to the .env template so every fresh install writes the installed version — giving dream update a baseline on the very first update.

Behaviour summary

Scenario Behaviour
Same version Silent pass, no output
Minor/patch bump (e.g. 2.0 → 2.1) Silent pass
1 major jump (e.g. 1.x → 2.x) Silent pass
2+ major jump (e.g. 1.x → 3.x) Yellow warning + confirmation prompt; --force skips prompt
Below min_compatible_dream_version Red hard-block + upgrade path hint; --force overrides with warning
dream update --force Skips all confirmation prompts, proceeds with warning logged

Test plan

  • Fresh install: confirm .env contains DREAM_VERSION=2.1.0 after Phase 06 completes
  • dream update with same version installed: no version output, proceeds silently
  • Manually set DREAM_VERSION=1.9.0 in .env, run dream update with a v2.x manifest: no warning (1 major jump is allowed)
  • Manually set DREAM_VERSION=1.0.0 in .env, run dream update with a v3.x manifest: yellow warning with confirmation prompt
  • Answer N at the prompt: confirm update exits cleanly with no containers touched
  • Answer Y at the prompt: confirm docker compose pull runs and DREAM_VERSION is updated in .env
  • dream update --force with a 2+ major jump: confirm no prompt, warning logged, update proceeds
  • Manually set DREAM_VERSION=0.9.0 in .env and set min_compatible_dream_version to "1.0.0": confirm red hard-block with no containers touched
  • Same scenario with --force: confirm warning is logged and update proceeds
  • System without jq: confirm python3 fallback correctly reads dream_version from manifest

…ma_version and version-compat check in dream update
@buddy0323 buddy0323 changed the title feat(manifest): add dream_version, min_compatible_dream_version, schema_version and version-compat check in dream update feat(manifest): add version and compatibility checks to dream update Mar 17, 2026
@buddy0323 buddy0323 force-pushed the feat/release-manifest-versioned branch from 2ca719b to a01e40f Compare March 17, 2026 18:29
Copy link
Collaborator

@Lightheartdevs Lightheartdevs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: feat(manifest): add version and compatibility checks to dream update

Good concept — version gating before updates is the right safety mechanism. Several issues to resolve.

Blocking

  1. Semver comparison is major-only. _semver_major() extracts just the first component, so min_compatible_dream_version: "1.5.0" vs installed "1.2.0" passes because both are major 1. The check is advertised as semver-aware but only compares major versions. dream-update.sh already has a full semver_compare() — reuse it or implement proper 3-component comparison.

  2. Pre-update backup removed with no replacement. The old cmd_update() created a backup via dream-backup.sh before pulling. This PR deletes that block entirely. Meanwhile PR #358 adds rollback snapshots to dream-update.sh's update path. After both merge, dream update (CLI) has zero safety net while the standalone script has a robust one. Don't remove backup without replacing it — coordinate with #358 to integrate snapshots into the CLI path too.

  3. 2>/dev/null in _manifest_field(). Both the jq and python3 calls suppress errors. If manifest.json is malformed, the function silently returns empty and the version check is skipped (return 0). Per CLAUDE.md: never 2>/dev/null. At minimum log warnings on parse failures.

Should-fix

  1. Unrelated changes bundled. New workflow API endpoints (/api/workflows/categories, /api/workflows/n8n/status, POST /api/workflows/{id}/disable), action validation refactor in updates.py, and logging import in agent_monitor.py should be separate PRs.

  2. Negative major_jump (downgrade) silently skips warning. The regex [[ "$major_jump" =~ ^[0-9]+$ ]] won't match negative numbers. Either warn or block on downgrades explicitly.

  3. Shell injection risk in Python fallback. open('$file') and d.get('$field') use string interpolation — paths with quotes or special chars will break or inject. Use sys.argv instead.

Version source conflict with PR #349

This PR reads version from DREAM_VERSION in .env + manifest.json. PR #349 reads/writes .version file exclusively. After both merge, three different version sources with no single source of truth. Coordinate before merging either.

@buddy0323
Copy link
Contributor Author

All review items addressed:

Blocking — Semver comparison is major-only
Added _semver_lt(): full 3-component comparator (strips v prefix, pads missing components, iterates major/minor/patch). The min_compat hard-block now calls _semver_lt "$_COMPAT_INSTALLED_VER" "$min_compat_ver" instead of (( cur_major < min_major )), so 1.2.0 is correctly blocked by min_compatible_dream_version: "1.5.0". _semver_major is kept only for counting the jump size, with a comment clarifying it is not used for ordering.

Blocking — Pre-update backup removed
cmd_update now creates a named snapshot (pre-update-<timestamp>) before any images are pulled. Delegates to dream-update.sh backup (writes to data/backups/, captures extension configs) when present; falls back to dream-backup.sh; warns but continues if neither is found. Coordinates with #358.

Blocking — 2>/dev/null in _manifest_field
Both jq and python3 paths check the exit code with if ! and call warn … >&2 on failure. Malformed manifest.json is now reported instead of silently skipping all checks. Python uses sys.argv[1]/sys.argv[2] — no shell injection via file path or field name. The duplicated inline Python for min_compat_ver in _check_version_compat is replaced by a call to _manifest_field, removing both injection risks at once.

Should-fix — Negative major_jump silently skips warning
Added explicit downgrade block before the min_compat check: if major_jump < 0, prints a warning, suggests dream rollback, and — without --force — requires confirmation.

Should-fix — Shell injection in Python fallback
All Python now uses sys.argv for file and field arguments. No shell-interpolated strings remain in any Python code path.

Should-fix — Version source conflict with PR #349
Installed version is resolved with documented precedence: DREAM_VERSION in .env.version file (PR #349 convention) → manifest.json. Comment in _check_version_compat states the policy so both PRs can merge without a silent conflict.

Note on unrelated changes
The workflow API endpoints, updates.py action validation, and agent_monitor.py logging fix were already extracted into their own PR prior to this revision.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants