Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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: |
Expand Down Expand Up @@ -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' }}
Expand Down Expand Up @@ -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' }}
Expand Down
39 changes: 10 additions & 29 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 .
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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'
Expand Down
16 changes: 13 additions & 3 deletions .github/workflows/deploy-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
65 changes: 64 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion deploy/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion deploy/engine.overlay.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion deploy/vogt.compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/CUSTOMISATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
4 changes: 2 additions & 2 deletions docs/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |

Expand All @@ -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:<digest>
Expand Down
11 changes: 10 additions & 1 deletion engine/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
2 changes: 1 addition & 1 deletion mobile/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vogt-mobile",
"version": "0.2.2",
"version": "0.3.0",
"private": true,
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
50 changes: 50 additions & 0 deletions scripts/write_firebase_config.sh
Original file line number Diff line number Diff line change
@@ -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"
2 changes: 1 addition & 1 deletion src/vogt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

from __future__ import annotations

__version__ = "0.2.2"
__version__ = "0.3.0"

__all__ = ["__version__"]
3 changes: 3 additions & 0 deletions tests/test_demo_delivery.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading