From cc36279891136a051f59c21badc521fa8fdc1d0f Mon Sep 17 00:00:00 2001 From: thedancingdeveloper <306930456+thedancingdeveloper@users.noreply.github.com> Date: Fri, 28 Aug 2026 02:04:53 +0000 Subject: [PATCH 1/4] ci(deploy-dev): default the live-smoke URL to a runner-reachable variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dev front door (vogt-dev.sprooty.com) resolves — even on public DNS — to a LAN-only IP the self-hosted tailnet runner cannot route to, and MagicDNS did not resolve on the deploy runner, so the required public_url input had to be hand-set to the dev stack's raw tailnet IP for the live smoke to reach it. Make public_url an optional override and default the smoke base from the VOGT_DEV_SMOKE_URL repository variable (operator configuration, like VOGT_KOMODO_URL), failing fast with a clear message if neither is set. The variable is set to the runner-reachable dev address; no estate IP is hardcoded in the workflow. Closes #452 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01GRBLneTGgBt4YkFacu9HD7 --- .github/workflows/deploy-dev.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index b6a81b34..73879fc1 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -17,8 +17,12 @@ on: required: true type: string public_url: - description: Dev front-door URL used for live smoke checks - required: true + description: >- + Optional override for the live-smoke base URL. Leave blank to use the + VOGT_DEV_SMOKE_URL repository variable, which must name an address the + self-hosted runner can reach (the dev front door resolves to a + LAN-only IP, so a tailnet address is required here). + required: false type: string permissions: @@ -115,11 +119,17 @@ jobs: python3 scripts/deploy_dev.py - name: run the live dev smoke contract + # The dev front door resolves to a LAN-only IP the tailnet runner cannot + # route to, so the smoke base must be a runner-reachable address. It + # comes from the VOGT_DEV_SMOKE_URL repository variable (operator + # configuration, like VOGT_KOMODO_URL), overridable per-dispatch via the + # public_url input. env: - VOGT_SMOKE_BASE: ${{ inputs.public_url }} + VOGT_SMOKE_BASE: ${{ inputs.public_url || vars.VOGT_DEV_SMOKE_URL }} SOURCE_SHA: ${{ inputs.source_sha }} run: | set -euo pipefail + : "${VOGT_SMOKE_BASE:?Set the VOGT_DEV_SMOKE_URL repository variable or pass public_url}" smoke_token="$(cat "$RUNNER_TEMP/vogt-dev-smoke-token")" test -n "$smoke_token" trap 'rm -f -- "$RUNNER_TEMP/vogt-dev-smoke-token"' EXIT From 9c30b522b303afd300f0c227f0bb478b3d3778eb Mon Sep 17 00:00:00 2001 From: thedancingdeveloper <306930456+thedancingdeveloper@users.noreply.github.com> Date: Fri, 28 Aug 2026 02:09:04 +0000 Subject: [PATCH 2/4] ci: write the dev Firebase config from a GitHub secret, not Infisical MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The android job installed the Infisical CLI and fetched the dev Firebase config (VOGT_FIREBASE_DEV_JSON) from a private-LAN broker on every non-PR build — the same estate coupling removed from the dev deploy. It also required the infisical binary on the runner and reachability to the broker. Read the dev Firebase config from a plain GitHub Actions secret (secrets.VOGT_FIREBASE_DEV_JSON) written by a new broker-free helper (scripts/write_firebase_config.sh) that writes without echoing and verifies the dev Android client is present. Remove the Infisical CLI install and fetch from the android job. PR builds keep using the sanitized example placeholder. The release (prod) APK build is intentionally out of scope and still fetches its prod Firebase project via Infisical; scripts/fetch_infisical_secret.sh is retained for it. Tests split accordingly. Closes #453 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01GRBLneTGgBt4YkFacu9HD7 --- .github/workflows/ci.yml | 37 ++++++----------------- scripts/write_firebase_config.sh | 50 ++++++++++++++++++++++++++++++ tests/test_mobile_identity.py | 52 ++++++++++++++++++++++---------- 3 files changed, 95 insertions(+), 44 deletions(-) create mode 100755 scripts/write_firebase_config.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e90824b3..e3d432d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -541,8 +541,8 @@ jobs: needs: changes if: needs.changes.outputs.mobile == 'true' runs-on: [self-hosted] - # Pull requests, including fork pull requests, must not receive the - # Infisical machine identity. They build against the sanitized example. + # Pull requests, including fork pull requests, must not receive the dev + # Firebase secret. They build against the sanitized example. steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 @@ -571,36 +571,17 @@ jobs: working-directory: mobile run: pnpm audit - - name: install Infisical CLI - if: github.event_name != 'pull_request' - run: | - set -euo pipefail - if command -v infisical >/dev/null 2>&1; then - exit 0 - fi - install -m 0755 -d /usr/share/keyrings - curl -1sLf https://artifacts-cli.infisical.com/infisical.gpg \ - | gpg --dearmor -o /usr/share/keyrings/infisical-archive-keyring.gpg - chmod 0644 /usr/share/keyrings/infisical-archive-keyring.gpg - printf '%s\n' \ - 'deb [arch=amd64 signed-by=/usr/share/keyrings/infisical-archive-keyring.gpg] https://artifacts-cli.infisical.com/deb stable main' \ - > /etc/apt/sources.list.d/infisical.list - apt-get update - apt-get install -y --no-install-recommends infisical - rm -rf /var/lib/apt/lists/* - - - name: fetch the dev Firebase config + - name: write the dev Firebase config if: github.event_name != 'pull_request' + # The dev Firebase config is a plain GitHub Actions secret — no secret + # broker and no CLI, so this runs on any self-hosted runner and any + # fork can supply its own. The helper writes it without echoing and + # verifies it carries the dev Android client. env: - INFISICAL_API_URL: ${{ vars.INFISICAL_API_URL }} - INFISICAL_CLIENT_ID: ${{ secrets.INFISICAL_CLIENT_ID }} - INFISICAL_CLIENT_SECRET: ${{ secrets.INFISICAL_CLIENT_SECRET }} - INFISICAL_PROJECT_ID: ${{ vars.INFISICAL_PROJECT_ID }} - INFISICAL_ENV: prod - VOGT_FIREBASE_SECRET_NAME: VOGT_FIREBASE_DEV_JSON + VOGT_FIREBASE_JSON: ${{ secrets.VOGT_FIREBASE_DEV_JSON }} VOGT_FIREBASE_OUTPUT: mobile/android/app/google-services.json VOGT_ANDROID_EXPECTED_PACKAGE: com.sprooty.vogt.dev - run: bash scripts/fetch_infisical_secret.sh + run: bash scripts/write_firebase_config.sh - name: verify the dev Firebase config is available if: github.event_name == 'pull_request' diff --git a/scripts/write_firebase_config.sh b/scripts/write_firebase_config.sh new file mode 100755 index 00000000..447b4bfc --- /dev/null +++ b/scripts/write_firebase_config.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Write one Firebase config, provided as an environment value, into a private, +# short-lived build file and validate the Android package it is meant to build. +# +# Generic and broker-free: the workflow passes the config through a plain +# GitHub Actions secret (VOGT_FIREBASE_JSON) and names the package it expects. +# The value is never echoed, uploaded as a standalone artifact, or committed. + +set -euo pipefail + +: "${VOGT_FIREBASE_JSON:?VOGT_FIREBASE_JSON is required}" +: "${VOGT_FIREBASE_OUTPUT:?VOGT_FIREBASE_OUTPUT is required}" +: "${VOGT_ANDROID_EXPECTED_PACKAGE:?VOGT_ANDROID_EXPECTED_PACKAGE is required}" + +output_dir="$(dirname -- "$VOGT_FIREBASE_OUTPUT")" +mkdir -p -- "$output_dir" +umask 077 + +temp_output="${VOGT_FIREBASE_OUTPUT}.tmp.$$" +trap 'rm -f -- "$temp_output"' EXIT + +# The value arrives through the environment, so it is written without ever +# being echoed to the log. +printf '%s' "$VOGT_FIREBASE_JSON" >"$temp_output" + +python3 - "$temp_output" "$VOGT_ANDROID_EXPECTED_PACKAGE" <<'PY' +from __future__ import annotations + +import json +import sys +from pathlib import Path + +path = Path(sys.argv[1]) +expected_package = sys.argv[2] +try: + document = json.loads(path.read_text(encoding="utf-8")) +except (OSError, json.JSONDecodeError) as exc: + raise SystemExit(f"Firebase config is not valid JSON: {exc}") from exc + +packages = { + client.get("client_info", {}).get("android_client_info", {}).get("package_name") + for client in document.get("client", []) +} +if expected_package not in packages: + raise SystemExit( + f"Firebase config has no Android client for {expected_package}" + ) +PY + +mv -- "$temp_output" "$VOGT_FIREBASE_OUTPUT" diff --git a/tests/test_mobile_identity.py b/tests/test_mobile_identity.py index 277c0d34..fc8d42f6 100644 --- a/tests/test_mobile_identity.py +++ b/tests/test_mobile_identity.py @@ -54,6 +54,7 @@ CI_WORKFLOW = WORKFLOWS / "ci.yml" RELEASE_WORKFLOW = WORKFLOWS / "release.yml" FIREBASE_FETCH = REPO_ROOT / "scripts" / "fetch_infisical_secret.sh" +FIREBASE_WRITE = REPO_ROOT / "scripts" / "write_firebase_config.sh" #: The variable both build files read. Named once here so a rename shows up as #: one failure rather than as a silent divergence. @@ -163,39 +164,58 @@ def test_the_dev_stream_builds_under_its_own_id() -> None: ) -def test_android_workflows_fetch_the_matching_infisical_firebase_config() -> None: - """The APK build must use the Firebase project for the id it assembles. +def test_ci_android_writes_firebase_from_a_github_secret() -> None: + """CI's dev APK build reads the dev Firebase config from a plain secret. - The JSON files are intentionally not GitHub secrets or repository files: - Infisical is the source of truth and the runner removes the material after - Gradle has consumed it. Keeping these assertions here prevents a release - job from silently reverting to a stale local copy or the dev project. + #453: the dev path is deliberately broker-free — the config is a GitHub + Actions secret (``VOGT_FIREBASE_DEV_JSON``) written by a generic helper, so + the job runs on any self-hosted runner and a fork can supply its own. These + assertions prevent a silent regression back to the Infisical CLI for the + dev build, and keep the built package matched to the dev id. """ ci = CI_WORKFLOW.read_text(encoding="utf-8") - release = RELEASE_WORKFLOW.read_text(encoding="utf-8") - assert "VOGT_FIREBASE_SECRET_NAME: VOGT_FIREBASE_DEV_JSON" in ci + assert "secrets.VOGT_FIREBASE_DEV_JSON" in ci assert "VOGT_ANDROID_EXPECTED_PACKAGE: com.sprooty.vogt.dev" in ci + assert "scripts/write_firebase_config.sh" in ci + assert ci.count("remove Firebase config") == 1 + # No secret broker for the dev build. + assert "INFISICAL" not in ci + assert "infisical" not in ci + assert "scripts/fetch_infisical_secret.sh" not in ci + + +def test_release_android_fetches_the_matching_infisical_firebase_config() -> None: + """The release APK build still fetches its prod Firebase project via Infisical. + + The prod path is out of scope for #453 and unchanged: Infisical remains the + source of truth and the runner removes the material after Gradle consumes + it. Keeping these assertions prevents the release job from silently + reverting to a stale local copy or the dev project. + """ + release = RELEASE_WORKFLOW.read_text(encoding="utf-8") + assert "VOGT_FIREBASE_SECRET_NAME: VOGT_FIREBASE_PROD_JSON" in release assert "VOGT_ANDROID_EXPECTED_PACKAGE: com.sprooty.vogt" in release - assert "scripts/fetch_infisical_secret.sh" in ci assert "scripts/fetch_infisical_secret.sh" in release - assert "vars.INFISICAL_API_URL" in ci - assert "vars.INFISICAL_PROJECT_ID" in ci - assert "secrets.INFISICAL_CLIENT_ID" in ci - assert "secrets.INFISICAL_CLIENT_SECRET" in ci - assert "install Infisical CLI" in ci assert "vars.INFISICAL_API_URL" in release assert "vars.INFISICAL_PROJECT_ID" in release assert "secrets.INFISICAL_CLIENT_ID" in release assert "secrets.INFISICAL_CLIENT_SECRET" in release assert "install Infisical CLI" in release - assert ci.count("remove Firebase config") == 1 assert release.count("remove Firebase config") == 1 - assert "secrets.VOGT_FIREBASE_DEV_JSON" not in ci assert "secrets.VOGT_FIREBASE_PROD_JSON" not in release +def test_firebase_writer_never_prints_the_secret_and_checks_package() -> None: + script = FIREBASE_WRITE.read_text(encoding="utf-8") + # The value arrives through the environment and is written without echo. + assert 'printf \'%s\' "$VOGT_FIREBASE_JSON" >"$temp_output"' in script + assert "json.loads" in script + assert "VOGT_ANDROID_EXPECTED_PACKAGE" in script + assert 'mv -- "$temp_output" "$VOGT_FIREBASE_OUTPUT"' in script + + def test_firebase_fetcher_never_prints_the_secret_and_checks_package() -> None: script = FIREBASE_FETCH.read_text(encoding="utf-8") assert '--plain --silent >"$temp_output"' in script From 84f59b5dc4d993cf9fe40dda4e8cce58e49175da Mon Sep 17 00:00:00 2001 From: thedancingdeveloper <306930456+thedancingdeveloper@users.noreply.github.com> Date: Fri, 28 Aug 2026 02:11:46 +0000 Subject: [PATCH 3/4] fix(demo): drop the bundled npm from the demo image to clear Trivy findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The static demo image failed the now-fatal Trivy gate with CRITICAL:1 and HIGH:10 — all in the npm CLI the node base image bundles (its own transitive deps: tar CVE-2026-59873, brace-expansion, pacote, sigstore, picomatch, ip-address, ...). The demo runtime only ever runs `node demo-server.mjs`, and that file imports node builtins exclusively, so npm is dead weight. Remove npm/npx (and npm's node_modules) in the demo-runtime stage before dropping to the node user. Verified locally against node:22-bookworm: the flagged packages are present before and gone after, while node and its builtins keep working. Add a regression assertion. Closes #454 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01GRBLneTGgBt4YkFacu9HD7 --- engine/Dockerfile | 11 ++++++++++- tests/test_demo_delivery.py | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/engine/Dockerfile b/engine/Dockerfile index 940a90d1..4672222d 100644 --- a/engine/Dockerfile +++ b/engine/Dockerfile @@ -163,7 +163,16 @@ COPY --from=demo-web /app/web/dist /app/dist COPY engine/deploy/demo-server.mjs /app/demo-server.mjs # A restrictive checkout umask may make COPY preserve mode 0600. Normalize # the one executable input before dropping to the unprivileged node user. -RUN chmod 0444 /app/demo-server.mjs +# +# The demo server imports only `node:` builtins (see demo-server.mjs), so the +# npm CLI the base image bundles is dead weight — and its own bundled +# dependencies (tar, brace-expansion, pacote, sigstore, …) are what the fatal +# Trivy gate flags as CRITICAL/HIGH. Removing npm/npx clears every one of those +# findings without touching anything the demo runtime uses. +RUN chmod 0444 /app/demo-server.mjs \ + && rm -rf /usr/local/lib/node_modules/npm \ + /usr/local/bin/npm \ + /usr/local/bin/npx USER node EXPOSE 8910 ENV DEMO_BIND=0.0.0.0 \ diff --git a/tests/test_demo_delivery.py b/tests/test_demo_delivery.py index eb68882c..a391be9e 100644 --- a/tests/test_demo_delivery.py +++ b/tests/test_demo_delivery.py @@ -70,6 +70,9 @@ def test_demo_image_branches_from_the_normal_web_build() -> None: demo = text.split("FROM web-build AS demo-web", 1)[1].split("# ─── Stage 2:", 1)[0] assert "FROM ${NODE_IMAGE} AS demo-runtime" in demo assert "chmod 0444 /app/demo-server.mjs" in demo + # The demo server uses only node builtins, so the bundled npm CLI is removed + # — its transitive deps are what the fatal Trivy gate flags (#454). + assert "rm -rf /usr/local/lib/node_modules/npm" in demo assert "COPY --from=server-build" not in demo assert "COPY --from=core" not in demo assert "vogt-engine" not in demo From 2908bcb09401b3ff90d65870902a8c535b2463cd Mon Sep 17 00:00:00 2001 From: thedancingdeveloper <306930456+thedancingdeveloper@users.noreply.github.com> Date: Fri, 28 Aug 2026 10:45:34 +0000 Subject: [PATCH 4/4] chore(release): bump to 0.3.0 and write the 0.3.0 changelog Bumps the canonical product version across every user-facing manifest and workflow injection (pyproject, __init__, web/mobile package.json, build.yml, ci.yml gate, product-version test) and records the 0.3.0 changelog. No operation renamed/removed and no schema migration (declared 0015 / observed 0004), so this is a non-breaking minor per opensource.md. check_product_version.py 0.3.0 passes. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01GRBLneTGgBt4YkFacu9HD7 --- .github/workflows/build.yml | 8 ++--- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 65 ++++++++++++++++++++++++++++++++++- deploy/.env.example | 2 +- deploy/engine.overlay.yml | 2 +- deploy/vogt.compose.yml | 2 +- docs/CUSTOMISATION.md | 2 +- docs/DEPLOYMENT.md | 4 +-- mobile/package.json | 2 +- pyproject.toml | 2 +- src/vogt/__init__.py | 2 +- tests/test_product_version.py | 4 +-- uv.lock | 2 +- web/package.json | 2 +- 14 files changed, 82 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5f0fe181..7e0f6c8d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -362,7 +362,7 @@ jobs: build-args: | VOGT_SOURCE_REF=dev VOGT_SOURCE_SHA=${{ github.sha }} - VOGT_PRODUCT_VERSION=0.2.2 + VOGT_PRODUCT_VERSION=0.3.0 cache-from: | type=registry,ref=${{ env.CACHE_IMAGE }}:stack-dev type=registry,ref=${{ env.CACHE_IMAGE }}:demo-dev @@ -460,7 +460,7 @@ jobs: build-args: | VOGT_SOURCE_REF=dev VOGT_SOURCE_SHA=${{ github.sha }} - VOGT_PRODUCT_VERSION=0.2.2 + VOGT_PRODUCT_VERSION=0.3.0 provenance: true sbom: true cache-from: | @@ -626,7 +626,7 @@ jobs: CORE_IMAGE=${{ env.IMAGE }}@${{ needs.image.outputs.digest }} VOGT_SOURCE_REF=${{ github.ref_name }} VOGT_SOURCE_SHA=${{ github.sha }} - VOGT_PRODUCT_VERSION=0.2.2 + VOGT_PRODUCT_VERSION=0.3.0 INSTALL_AI_CLIENTS=${{ github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/prod' }} INSTALL_CADASTRE_MCP=${{ github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/prod' }} INSTALL_THECLAWBAY=${{ github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/prod' }} @@ -691,7 +691,7 @@ jobs: CORE_IMAGE=${{ env.IMAGE }}@${{ needs.image.outputs.digest }} VOGT_SOURCE_REF=${{ github.ref_name }} VOGT_SOURCE_SHA=${{ github.sha }} - VOGT_PRODUCT_VERSION=0.2.2 + VOGT_PRODUCT_VERSION=0.3.0 INSTALL_AI_CLIENTS=${{ github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/prod' }} INSTALL_CADASTRE_MCP=${{ github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/prod' }} INSTALL_THECLAWBAY=${{ github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/prod' }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3d432d7..d008c74f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -219,7 +219,7 @@ jobs: - run: uv sync --locked - name: canonical product version is aligned - run: uv run --no-sync python scripts/check_product_version.py 0.2.2 + run: uv run --no-sync python scripts/check_product_version.py 0.3.0 - name: ruff (lint) run: uv run --no-sync ruff check . diff --git a/CHANGELOG.md b/CHANGELOG.md index ad197ed2..400117ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,10 +14,73 @@ git log rather than being reconstructed here. Nothing yet. +## [0.3.0] - 2026-08-28 + +The first release since the merged core+engine stack reached production. No +operation was renamed or removed; no schema migration is required (the declared +schema stays at 0015, the observed schema at 0004). Pre-1.0, this remains a +minor bump per the compatibility policy in [`opensource.md`](opensource.md). + +### Added + +- **Voice assistant, first-party.** A sidecar foundation plus opt-in + subprocess speech backends and in-process speech inference, moving voice + from an unproven adoption toward a validated path. +- **Fabro workflow provider** in the engine, with checkpoint-timeline + collection and workflow gate/steering bridging for agent sessions. +- **Conditional HTTP reads.** Stable read endpoints now emit validators + (ETag / `If-None-Match`-style) so clients can revalidate cheaply instead of + refetching. + +### Changed + +- **Performance — engine.** HTTP responses are compressed; the authentication + check is split from operational status so health/status probes are cheaper; + inactive terminals are parked and silent sockets detected. +- **Performance — web/PWA.** Terminal replay work is bounded and Monaco + languages load on demand; foreground reconciliation and FileTree wake + refreshes are coordinated through a shared wake coordinator; a typed TTL/SWR + cache backs stable reads; taxonomy reads and assistant hydration are shared + and deferred across surfaces; shell "place" metrics are aggregated; a + repeatable large-estate rendering profile was added. +- **Documentation** reconciled across engine workflow-provider status, the + voice POC/delivery gates, and public image examples now matching the current + release. + +### Fixed + +- **Sessions/terminals.** PTY output is drained before a run concludes; + session reconciliation stays cancellable; parked terminals reactivate when + ready; foreground API reads are bounded and cancellable. +- **Caching correctness.** Conditional stable-read validators are honored, + metadata ordering is preserved through the cache, taxonomy cache request + order is preserved, and shared taxonomy reads stay authoritative. +- **Workflows.** Duplicate workflow gate answers are rejected; workflow event + subscriptions retry once; checkpoints are preserved during poll fallback. +- **Mobile.** Native FCM listeners are awaited before registration. +- **Web.** File-tree conflict resolution is retained; demo assistant approvals + hydrate correctly. + +### Security + +- Tracked Firebase credentials are guarded, and open-source-readiness code gaps + were closed ahead of publishing the container publicly. +- The tailscale auth key is kept out of the container environment. + +### Internal + +- Release/CI: self-contained GitHub Release job; canonical GHCR retention + policy consumed; CodeQL (JS/TS) and RustSec audit made runnable on the + self-hosted runners; estate deployment receipts correlated; dev live-smoke + window and startup tolerances tuned; `pnpm-workspace.yaml` copied into the + web build stage; Komodo credentials read from GitHub secrets (Infisical + dropped for the dev deploy). + ## [0.2.0] - 2026-08-14 Baseline entry for this changelog. See the git log and release notes for the full history up to this tag. -[Unreleased]: https://github.com/TheDancingDeveloper-org/vogt/compare/v0.2.0...HEAD +[Unreleased]: https://github.com/TheDancingDeveloper-org/vogt/compare/v0.3.0...HEAD +[0.3.0]: https://github.com/TheDancingDeveloper-org/vogt/compare/v0.2.0...v0.3.0 [0.2.0]: https://github.com/TheDancingDeveloper-org/vogt/releases/tag/v0.2.0 diff --git a/deploy/.env.example b/deploy/.env.example index d53efd9c..bc709ec6 100644 --- a/deploy/.env.example +++ b/deploy/.env.example @@ -19,7 +19,7 @@ VOGT_BIND_IP=127.0.0.1 # The image to run. Pin a digest in anything you would call a deployment. # Ignored when you add deploy/vogt.build.yml, which builds from the checkout. -# VOGT_IMAGE=ghcr.io/thedancingdeveloper-org/vogt:0.2.2 +# VOGT_IMAGE=ghcr.io/thedancingdeveloper-org/vogt:0.3.0 # The uid the container runs as. The gid is always 0 — that is what makes the # data directory writable at any uid. Set this to whoever owns the files this diff --git a/deploy/engine.overlay.yml b/deploy/engine.overlay.yml index 2702c2f5..8d6ecca9 100644 --- a/deploy/engine.overlay.yml +++ b/deploy/engine.overlay.yml @@ -59,7 +59,7 @@ services: # container carries both halves. Defaults to the same public image the # base would run, so the embedded core matches it; pin a digest for a # real deployment. The Dockerfile already carries a public default too. - CORE_IMAGE: "${VOGT_IMAGE:-ghcr.io/thedancingdeveloper-org/vogt:0.2.2}" + CORE_IMAGE: "${VOGT_IMAGE:-ghcr.io/thedancingdeveloper-org/vogt:0.3.0}" # Optional image-managed agent CLIs. When enabled, versions come from # engine/agent-versions.env unless an operator deliberately overrides # one with a reproducible build arg. diff --git a/deploy/vogt.compose.yml b/deploy/vogt.compose.yml index c0832c96..0d834a69 100644 --- a/deploy/vogt.compose.yml +++ b/deploy/vogt.compose.yml @@ -25,7 +25,7 @@ services: # digest — publishing an image and moving a deployment are separate acts # (NFR-D10), and a digest is the only form of "which image is this" a # rebuild cannot silently change. - image: ${VOGT_IMAGE:-ghcr.io/thedancingdeveloper-org/vogt:0.2.2} + image: ${VOGT_IMAGE:-ghcr.io/thedancingdeveloper-org/vogt:0.3.0} restart: unless-stopped # The image runs as any uid so long as the gid is 0: `/var/lib/vogt` is diff --git a/docs/CUSTOMISATION.md b/docs/CUSTOMISATION.md index d67d99a1..98e4cfd9 100644 --- a/docs/CUSTOMISATION.md +++ b/docs/CUSTOMISATION.md @@ -359,7 +359,7 @@ The published image is built to be a base: to it. ```dockerfile -FROM ghcr.io/thedancingdeveloper-org/vogt:0.2.2 +FROM ghcr.io/thedancingdeveloper-org/vogt:0.3.0 USER root RUN apt-get update \ && apt-get install -y --no-install-recommends ripgrep \ diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 6e0514e2..e0b74ec2 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -91,7 +91,7 @@ What the base does, and why it does it that way: | `VOGT_PUBLIC_URL` | yes | — | The URL clients use to reach this instance. | | `VOGT_PORT` | no | `8080` | Host port the container's 8000 is published on. | | `VOGT_BIND_IP` | no | `127.0.0.1` | Host interface the port is published on. | -| `VOGT_IMAGE` | no | `ghcr.io/thedancingdeveloper-org/vogt:0.2.2` | The image to run. | +| `VOGT_IMAGE` | no | `ghcr.io/thedancingdeveloper-org/vogt:0.3.0` | The image to run. | | `VOGT_UID` | no | `1000` | The uid the container runs as (gid is always 0). | | `VOGT_LOG_LEVEL` | no | `info` | Verbosity of Vogt's own logger. | @@ -118,7 +118,7 @@ cosign verify \ ``` ```console -docker buildx imagetools inspect ghcr.io/thedancingdeveloper-org/vogt:0.2.2 \ +docker buildx imagetools inspect ghcr.io/thedancingdeveloper-org/vogt:0.3.0 \ | grep -m1 Digest # then, in deploy/.env: VOGT_IMAGE=ghcr.io/thedancingdeveloper-org/vogt@sha256: diff --git a/mobile/package.json b/mobile/package.json index f921f24f..7273f734 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -1,6 +1,6 @@ { "name": "vogt-mobile", - "version": "0.2.2", + "version": "0.3.0", "private": true, "type": "module", "scripts": { diff --git a/pyproject.toml b/pyproject.toml index c96397a9..8e7379a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "vogt" -version = "0.2.2" +version = "0.3.0" description = "A product development environment for the AI era" readme = "README.md" requires-python = ">=3.11" diff --git a/src/vogt/__init__.py b/src/vogt/__init__.py index e50992bb..0d089ceb 100644 --- a/src/vogt/__init__.py +++ b/src/vogt/__init__.py @@ -2,6 +2,6 @@ from __future__ import annotations -__version__ = "0.2.2" +__version__ = "0.3.0" __all__ = ["__version__"] diff --git a/tests/test_product_version.py b/tests/test_product_version.py index 9042fc1b..fc6c523c 100644 --- a/tests/test_product_version.py +++ b/tests/test_product_version.py @@ -16,7 +16,7 @@ def test_product_version_contract_is_in_sync() -> None: result = subprocess.run( - [sys.executable, "scripts/check_product_version.py", "0.2.2"], + [sys.executable, "scripts/check_product_version.py", "0.3.0"], cwd=ROOT, capture_output=True, text=True, @@ -28,6 +28,6 @@ def test_product_version_contract_is_in_sync() -> None: def test_android_version_name_is_derived_from_the_canonical_mobile_manifest() -> None: package = (ROOT / "mobile/package.json").read_text(encoding="utf-8") gradle = (ROOT / "mobile/android/app/build.gradle").read_text(encoding="utf-8") - assert '"version": "0.2.2"' in package + assert '"version": "0.3.0"' in package assert "new JsonSlurper().parse(file('../../package.json'))" in gradle assert "versionName androidVersionName" in gradle diff --git a/uv.lock b/uv.lock index a976b77b..9887c61d 100644 --- a/uv.lock +++ b/uv.lock @@ -995,7 +995,7 @@ wheels = [ [[package]] name = "vogt" -version = "0.2.2" +version = "0.3.0" source = { editable = "." } dependencies = [ { name = "cryptography" }, diff --git a/web/package.json b/web/package.json index 669f9895..61ccd2f8 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "vogt-web", - "version": "0.2.2", + "version": "0.3.0", "private": true, "type": "module", "scripts": {