diff --git a/backend/data/release_digests.json b/backend/data/release_digests.json
index 7ea6956..4bd3d16 100644
--- a/backend/data/release_digests.json
+++ b/backend/data/release_digests.json
@@ -41,5 +41,10 @@
"ShadowBroker_v0.9.8.zip": "183bb5cd62b9b9349d95df5ef7696cb6ca810ab4b991fa9dab6f898af4c7a175",
"ShadowBroker_0.9.8_x64-setup.exe": "94a0309862e9c81c92cdcbfea8eec9dbb97eef19ded82b26217b397defbc810c",
"ShadowBroker_0.9.8_x64_en-US.msi": "fe22f9d51e4360d74c18a7250c2fbb9ed4fa4c7a884b3ac0d04a21115466386b"
+ },
+ "v0.9.81": {
+ "ShadowBroker_v0.9.81.zip": "42f8a51f9a5690d1e7349d90d8ecf2d163c9061d6cf90c69ee03647a785437ff",
+ "ShadowBroker_0.9.81_x64-setup.exe": "eca884b9d37eeccd0f11c91dcc6f6ae1b3609d9dee72bd73c37c9a427babfef2",
+ "ShadowBroker_0.9.81_x64_en-US.msi": "a45b177c26c95d2b28d71592d7147e88ff4e104865f214fde11249d311ec9e25"
}
}
diff --git a/backend/main.py b/backend/main.py
index 4ad8542..5ee1c0e 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -14,7 +14,7 @@ from dataclasses import dataclass, field
from typing import Any
from json import JSONDecodeError
-APP_VERSION = "0.9.8"
+APP_VERSION = "0.9.81"
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
diff --git a/backend/pyproject.toml b/backend/pyproject.toml
index 6b734b9..7fd1dd3 100644
--- a/backend/pyproject.toml
+++ b/backend/pyproject.toml
@@ -7,7 +7,7 @@ py-modules = []
[project]
name = "backend"
-version = "0.9.8"
+version = "0.9.81"
requires-python = ">=3.10"
dependencies = [
"apscheduler==3.10.3",
@@ -43,7 +43,7 @@ dev = ["pytest>=8.3.4", "pytest-asyncio==0.25.0", "ruff>=0.9.0", "black>=24.0.0"
[tool.ruff.lint]
# The current backend carries historical style debt in large legacy modules.
-# Keep CI focused on actionable correctness checks for the v0.9.8 release.
+# Keep CI focused on actionable correctness checks for the v0.9.81 release.
ignore = ["E401", "E402", "E701", "E731", "E741", "F401", "F402", "F541", "F811", "F841"]
[tool.black]
diff --git a/backend/routers/ai_intel.py b/backend/routers/ai_intel.py
index 320fb5a..2f9ce2a 100644
--- a/backend/routers/ai_intel.py
+++ b/backend/routers/ai_intel.py
@@ -1590,7 +1590,7 @@ async def agent_tool_manifest(request: Request):
return {
"ok": True,
- "version": "0.9.8",
+ "version": "0.9.81",
"access_tier": access_tier,
"available_commands": available_commands,
"transport": {
@@ -2226,7 +2226,7 @@ async def api_capabilities(request: Request):
access_tier = str(get_settings().OPENCLAW_ACCESS_TIER or "restricted").strip().lower()
return {
"ok": True,
- "version": "0.9.8",
+ "version": "0.9.81",
"auth": {
"method": "HMAC-SHA256",
"headers": ["X-SB-Timestamp", "X-SB-Nonce", "X-SB-Signature"],
diff --git a/backend/routers/health.py b/backend/routers/health.py
index 2a20e56..d11c983 100644
--- a/backend/routers/health.py
+++ b/backend/routers/health.py
@@ -8,7 +8,7 @@ from services.data_fetcher import get_latest_data
from services.schemas import HealthResponse
import os
-APP_VERSION = os.environ.get("_HEALTH_APP_VERSION", "0.9.8")
+APP_VERSION = os.environ.get("_HEALTH_APP_VERSION", "0.9.81")
router = APIRouter()
diff --git a/backend/tests/test_per_operator_outbound_attribution.py b/backend/tests/test_per_operator_outbound_attribution.py
index 813ccb2..90cd4d5 100644
--- a/backend/tests/test_per_operator_outbound_attribution.py
+++ b/backend/tests/test_per_operator_outbound_attribution.py
@@ -240,6 +240,8 @@ class TestNoMonsterUserAgentRemains:
"ShadowBroker/0.9.79 Finnhub connector",
"ShadowBroker/0.9.8 local Shodan connector",
"ShadowBroker/0.9.8 Finnhub connector",
+ "ShadowBroker/0.9.81 local Shodan connector",
+ "ShadowBroker/0.9.81 Finnhub connector",
"Mozilla/5.0 (compatible; ShadowBroker CCTV proxy)",
)
diff --git a/desktop-shell/package-lock.json b/desktop-shell/package-lock.json
index d465ca7..db194c5 100644
--- a/desktop-shell/package-lock.json
+++ b/desktop-shell/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@shadowbroker/desktop-shell",
- "version": "0.9.8",
+ "version": "0.9.81",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@shadowbroker/desktop-shell",
- "version": "0.9.8",
+ "version": "0.9.81",
"devDependencies": {
"typescript": "^5.6.0"
}
diff --git a/desktop-shell/package.json b/desktop-shell/package.json
index 4c80ce7..519e426 100644
--- a/desktop-shell/package.json
+++ b/desktop-shell/package.json
@@ -1,6 +1,6 @@
{
"name": "@shadowbroker/desktop-shell",
- "version": "0.9.8",
+ "version": "0.9.81",
"private": true,
"description": "ShadowBroker desktop shell packaging, runtime bridge, and release tooling",
"scripts": {
diff --git a/desktop-shell/tauri-skeleton/src-tauri/Cargo.lock b/desktop-shell/tauri-skeleton/src-tauri/Cargo.lock
index 00985fd..d37be8c 100644
--- a/desktop-shell/tauri-skeleton/src-tauri/Cargo.lock
+++ b/desktop-shell/tauri-skeleton/src-tauri/Cargo.lock
@@ -4201,7 +4201,7 @@ dependencies = [
[[package]]
name = "shadowbroker-tauri-shell"
-version = "0.9.8"
+version = "0.9.81"
dependencies = [
"axum",
"base64 0.22.1",
diff --git a/desktop-shell/tauri-skeleton/src-tauri/Cargo.toml b/desktop-shell/tauri-skeleton/src-tauri/Cargo.toml
index c42ecb6..dc18274 100644
--- a/desktop-shell/tauri-skeleton/src-tauri/Cargo.toml
+++ b/desktop-shell/tauri-skeleton/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "shadowbroker-tauri-shell"
-version = "0.9.8"
+version = "0.9.81"
edition = "2021"
[build-dependencies]
diff --git a/desktop-shell/tauri-skeleton/src-tauri/tauri.conf.json b/desktop-shell/tauri-skeleton/src-tauri/tauri.conf.json
index f7c3211..b2f0af8 100644
--- a/desktop-shell/tauri-skeleton/src-tauri/tauri.conf.json
+++ b/desktop-shell/tauri-skeleton/src-tauri/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "ShadowBroker",
- "version": "0.9.8",
+ "version": "0.9.81",
"identifier": "com.shadowbroker.desktop",
"build": {
"frontendDist": "../../../frontend/out",
@@ -38,7 +38,7 @@
},
"plugins": {
"updater": {
- "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEUxODExMjQ4MkJBMThFNTgKUldSWWpxRXJTQktCNFF3ZXNQbndUK0pVWUEwNDNuajcrUGI3ZEI4TWtDUDlQdHhudmlHUkNjQUUK",
+ "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDVEMTFERDdCNjhBRTk3MDcKUldRSGw2NW9lOTBSWGRjS1ZobFN5TkZsd3NkZ2g2L09WZzU4aytTR2FtN3ZtR0ZKejlNNldTbFUK",
"endpoints": [
"https://github.com/BigBodyCobain/Shadowbroker/releases/latest/download/latest.json"
],
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index ea77654..75d7048 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "frontend",
- "version": "0.9.8",
+ "version": "0.9.81",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "frontend",
- "version": "0.9.8",
+ "version": "0.9.81",
"dependencies": {
"@mapbox/point-geometry": "^1.1.0",
"@tauri-apps/plugin-process": "^2.3.1",
diff --git a/frontend/package.json b/frontend/package.json
index fb981ef..8f9fd15 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "frontend",
- "version": "0.9.8",
+ "version": "0.9.81",
"private": true,
"scripts": {
"dev": "node scripts/dev-all.cjs",
diff --git a/frontend/src/__tests__/desktop/updateRuntime.test.ts b/frontend/src/__tests__/desktop/updateRuntime.test.ts
index 9e04922..f0c5c05 100644
--- a/frontend/src/__tests__/desktop/updateRuntime.test.ts
+++ b/frontend/src/__tests__/desktop/updateRuntime.test.ts
@@ -9,12 +9,12 @@ import {
} from '@/lib/updateRuntime';
const RELEASE: GitHubLatestRelease = {
- html_url: 'https://github.com/BigBodyCobain/Shadowbroker/releases/tag/v0.9.8',
+ html_url: 'https://github.com/BigBodyCobain/Shadowbroker/releases/tag/v0.9.81',
assets: [
- { name: 'ShadowBroker_0.9.8_x64_en-US.msi', browser_download_url: 'https://example.test/windows.msi' },
- { name: 'ShadowBroker_0.9.8_x64-setup.exe', browser_download_url: 'https://example.test/windows-setup.exe' },
- { name: 'ShadowBroker_0.9.8_aarch64.dmg', browser_download_url: 'https://example.test/macos.dmg' },
- { name: 'ShadowBroker_0.9.8_amd64.AppImage', browser_download_url: 'https://example.test/linux.AppImage' },
+ { name: 'ShadowBroker_0.9.81_x64_en-US.msi', browser_download_url: 'https://example.test/windows.msi' },
+ { name: 'ShadowBroker_0.9.81_x64-setup.exe', browser_download_url: 'https://example.test/windows-setup.exe' },
+ { name: 'ShadowBroker_0.9.81_aarch64.dmg', browser_download_url: 'https://example.test/macos.dmg' },
+ { name: 'ShadowBroker_0.9.81_amd64.AppImage', browser_download_url: 'https://example.test/linux.AppImage' },
],
};
diff --git a/frontend/src/components/ChangelogModal.tsx b/frontend/src/components/ChangelogModal.tsx
index af96f76..cfb3d5f 100644
--- a/frontend/src/components/ChangelogModal.tsx
+++ b/frontend/src/components/ChangelogModal.tsx
@@ -20,22 +20,22 @@ import {
Heart,
} from 'lucide-react';
-const CURRENT_VERSION = '0.9.8';
+const CURRENT_VERSION = '0.9.81';
const STORAGE_KEY = `shadowbroker_changelog_v${CURRENT_VERSION}`;
-const RELEASE_TITLE = 'Cumulative Fuel/CO2, AIS Resilience, and Data-Layer Repair';
+const RELEASE_TITLE = 'Signed Auto-Update + Update Button Race Fix';
const HEADLINE_FEATURES = [
{
- icon: ,
+ icon: ,
accent: 'purple' as const,
- title: 'Cumulative Fuel & CO2 per Flight',
- subtitle: 'The aircraft tooltip now shows how much fuel each plane has actually burned in the air, not just the per-hour rate.',
+ title: 'Signed Auto-Update Going Forward (one manual hop)',
+ subtitle: 'After installing v0.9.81, the in-app Update button finally works end-to-end. This release establishes a fresh signing key — every release from here is a one-click upgrade.',
details: [
- 'New flight_observations module tracks first-seen-at per ICAO24 hex. Multiplies the model-based rate by elapsed observation time to produce running totals — FUEL BURNED (gal) and CO2 EMITTED (kg) — in the EMISSIONS ESTIMATE block.',
- '15-minute gap between sightings resets the session (treated as a new flight: landed and took off again, or transited a dead zone). The cumulative counter survives trail pruning so map-rendering lifecycle and emission tracking are independent.',
- '24-hour clamp defends against clock-skew bugs; per-icao prune every 5 minutes keeps memory bounded. The per-hour rate is still shown as smaller context underneath each cumulative figure.',
+ 'tauri.conf.json now carries a fresh minisign pubkey (the previous keypair was generated before v0.9.79 shipped but the matching private key was lost before any release was actually signed, so no release before v0.9.81 has working auto-update).',
+ 'The v0.9.81 release artifacts ship with a signed latest.json + .sig files so every install on v0.9.81 or later can verify and apply the next release automatically via the Tauri updater plugin.',
+ 'One-time cost: if you are upgrading from v0.9.79 or v0.9.8, the click-Update path falls back to a manual download because the new pubkey does not match the one baked into your install. Click the MANUAL DOWNLOAD button in the update dialog → grab the .msi from the release page → run it → from then on auto-update works in-app.',
],
- callToAction: 'OPEN A FLIGHT TOOLTIP → EMISSIONS ESTIMATE',
+ callToAction: 'CLICK UPDATE → DOWNLOAD MSI ONCE → AUTO-UPDATE FOREVER',
},
{
icon: ,
@@ -64,6 +64,11 @@ const HEADLINE_FEATURES = [
];
const NEW_FEATURES = [
+ {
+ icon: ,
+ title: 'Cumulative Fuel & CO2 per Flight',
+ desc: 'Aircraft tooltip now shows how much fuel each plane has actually burned in the air since first observation, not just the per-hour rate. 15-minute gap between sightings resets the session; 24-hour clamp protects against clock skew; per-icao prune every 5 minutes keeps memory bounded.',
+ },
{
icon: ,
title: 'Per-Flight Source Attribution',
@@ -82,6 +87,8 @@ const NEW_FEATURES = [
];
const BUG_FIXES = [
+ 'Update button no longer throws "admin_session_required" on desktop installs. The initial updateAction now syncs to Tauri detection at React-init time (window.__TAURI__ is injected before mount), so a click before the async runtime probe completes opens the GitHub release page in a browser instead of POSTing to /api/system/update.',
+ 'Desktop installer now bundles defusedxml + PySocks (declared in pyproject.toml but missing from the venv shipped with v0.9.79 and the initial v0.9.8 publish). Fixes the bundled-backend launch crash reported in #319 and #296 (managed_backend_exited_early:exit code: 103).',
'UAP layer no longer serves 3-year-old NUFORC sightings via the Hugging Face static-mirror fallback (60-day cutoff now applied to the fallback path too).',
'GPS jamming detection now counts nac_p == 0 (the actual GPS-lost signal) instead of filtering it out as an old-transponder artifact.',
'GPS jamming thresholds lowered (MIN_AIRCRAFT 5 → 3, MIN_RATIO 0.30 → 0.20) so sparser hotspots clear the bar without losing the 1-aircraft noise cushion.',
diff --git a/frontend/src/components/OnboardingModal.tsx b/frontend/src/components/OnboardingModal.tsx
index ac86362..7c3d49a 100644
--- a/frontend/src/components/OnboardingModal.tsx
+++ b/frontend/src/components/OnboardingModal.tsx
@@ -4,7 +4,7 @@ import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { X, ExternalLink, Key, Shield, Radar, Globe, Satellite, Ship, Radio, Bot, Copy, Check, Network } from 'lucide-react';
-const CURRENT_ONBOARDING_VERSION = '0.9.8-agentic-onboarding-1';
+const CURRENT_ONBOARDING_VERSION = '0.9.81-agentic-onboarding-1';
const STORAGE_KEY = `shadowbroker_onboarding_complete_v${CURRENT_ONBOARDING_VERSION}`;
const LEGACY_STORAGE_KEY = 'shadowbroker_onboarding_complete';
diff --git a/frontend/src/components/StartupWarmupModal.tsx b/frontend/src/components/StartupWarmupModal.tsx
index 99bf1d1..c41faf8 100644
--- a/frontend/src/components/StartupWarmupModal.tsx
+++ b/frontend/src/components/StartupWarmupModal.tsx
@@ -4,7 +4,7 @@ import React, { useEffect, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Database, Clock, X } from 'lucide-react';
-const CURRENT_VERSION = '0.9.8';
+const CURRENT_VERSION = '0.9.81';
const STORAGE_KEY = `shadowbroker_startup_warmup_notice_v${CURRENT_VERSION}`;
interface StartupWarmupModalProps {
diff --git a/frontend/src/components/TopRightControls.tsx b/frontend/src/components/TopRightControls.tsx
index 970eb31..62ab864 100644
--- a/frontend/src/components/TopRightControls.tsx
+++ b/frontend/src/components/TopRightControls.tsx
@@ -91,7 +91,19 @@ export default function TopRightControls({
const [manualUpdateUrl, setManualUpdateUrl] = useState(DEFAULT_RELEASES_URL);
const [releasePageUrl, setReleasePageUrl] = useState(DEFAULT_RELEASES_URL);
const [dockerCommands, setDockerCommands] = useState('');
- const [updateAction, setUpdateAction] = useState('auto_apply');
+ // Pre-detection initial value: the right action depends on the runtime.
+ // For desktop installs (Tauri webview), the default should be
+ // ``manual_download`` so that clicking Update before the async runtime
+ // probe completes opens the release page in a browser instead of POSTing
+ // to /api/system/update — which throws ``admin_session_required`` on
+ // fresh sessions and confused v0.9.79/v0.9.8 users with a cryptic error.
+ // ``window.__TAURI__`` is injected synchronously by Tauri before React
+ // mounts, so this check is safe to do at useState init time.
+ const initialUpdateAction: UpdateActionKind =
+ typeof window !== 'undefined' && (window as { __TAURI__?: unknown }).__TAURI__
+ ? 'manual_download'
+ : 'auto_apply';
+ const [updateAction, setUpdateAction] = useState(initialUpdateAction);
const [updateDetail, setUpdateDetail] = useState(AUTO_UPDATE_DETAIL);
const pollRef = useRef | null>(null);
const timeoutRef = useRef | null>(null);
diff --git a/helm/chart/Chart.yaml b/helm/chart/Chart.yaml
index e5a42ec..1bffe2e 100644
--- a/helm/chart/Chart.yaml
+++ b/helm/chart/Chart.yaml
@@ -1,8 +1,8 @@
---
apiVersion: v2
name: shadowbroker
-version: 0.9.8
-appVersion: "0.9.8"
+version: 0.9.81
+appVersion: "0.9.81"
description: simple shadowbroker installation
type: application
diff --git a/pyproject.toml b/pyproject.toml
index 73de004..125908d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "shadowbroker"
-version = "0.9.8"
+version = "0.9.81"
readme = "README.md"
requires-python = ">=3.10"
dependencies = []
diff --git a/uv.lock b/uv.lock
index 6af7c66..8332a06 100644
--- a/uv.lock
+++ b/uv.lock
@@ -74,7 +74,7 @@ wheels = [
[[package]]
name = "backend"
-version = "0.9.8"
+version = "0.9.81"
source = { editable = "backend" }
dependencies = [
{ name = "apscheduler" },
@@ -2231,7 +2231,7 @@ wheels = [
[[package]]
name = "shadowbroker"
-version = "0.9.8"
+version = "0.9.81"
source = { virtual = "." }
[package.metadata]