Skip to content

Commit 377fa00

Browse files
author
Shadowfetch
committed
Verify native Buzz inference and finish mission desktop review
1 parent 22b8421 commit 377fa00

11 files changed

Lines changed: 222 additions & 20 deletions

File tree

packages/shadowfetch-control-center/data/usr/share/shadowfetch/control-center/sfcc/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
("recover", "Recover", None),
4545
("local-ai", "Local AI", "Buzz & models"),
4646
("drivers", "Drivers", None),
47-
("software", "Software & Updates", "Updates & bundles"),
47+
("software", "Software", "Updates & bundles"),
4848
]
4949

5050
ALIASES = {

packages/shadowfetch-control-center/data/usr/share/shadowfetch/control-center/sfcc/missions_page.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -459,9 +459,9 @@ def _shown(self, requested, data, error):
459459
self.artifacts.clear()
460460
for artifact in data.get("artifacts", []):
461461
path = artifact.get("path", "") if isinstance(artifact, dict) else str(artifact)
462-
item = QListWidgetItem(path)
462+
item = QListWidgetItem(Path(path).name or path)
463463
item.setData(Qt.ItemDataRole.UserRole, path)
464-
item.setToolTip("Double-click to open this output file")
464+
item.setToolTip(f"{path}\nDouble-click to open this output file")
465465
self.artifacts.addItem(item)
466466
if not self.artifacts.count():
467467
item = QListWidgetItem("No output files recorded yet.")

packages/shadowfetch-control-center/tests/test_mission_ui_4_0.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,22 @@ def test_success_parses_json(self):
176176

177177

178178
class PageStateTests(unittest.TestCase):
179+
def test_results_show_filenames_and_open_the_full_recorded_path(self):
180+
with patch("sfcc.missions_page.MissionClient", FakeClient):
181+
page = MissionsPage(lambda _: None)
182+
page.selected_id = "m1"
183+
output = "/home/sfqa/Workspaces/long-project/mission-output/m1/01-studio-tone.wav"
184+
page._shown("m1", {"id": "m1", "state": "waiting-review", "artifacts": [output]}, None)
185+
item = page.artifacts.item(0)
186+
self.assertEqual("01-studio-tone.wav", item.text())
187+
self.assertIn(output, item.toolTip())
188+
with patch.object(page, "_open_path") as open_path:
189+
page._open_artifact(item)
190+
open_path.assert_called_once_with(output)
191+
page.timer.stop()
192+
page.deleteLater()
193+
APP.processEvents()
194+
179195
def test_review_controls_follow_state(self):
180196
with patch("sfcc.missions_page.MissionClient", FakeClient):
181197
page = MissionsPage(lambda _: None)

packages/shadowfetch-missions/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ It validates the native executable identity, same-user PID, process start time,
3232
and ownership of the advertised loopback listening socket through Linux `/proc`.
3333
It sends prompts directly to that native server's port and revalidates the process
3434
before reporting success. No model process is started or downloaded by this code.
35+
Buzz 0.5.17 embeds native Skippy inside its desktop executable. This route requires
36+
the exact root-owned `/usr/bin/buzz-desktop`, pinned installed package version,
37+
and executable contents matching its protected dpkg manifest; the receipt records
38+
the binary SHA-256. Copied executables with the same name do not qualify. Any
39+
distributed stage/topology deployment, or unavailable stage inventory, refuses
40+
offline selection; stage inventory is checked again after inference.
41+
Every direct native request also sets the vendor's `mesh_hooks: false` switch,
42+
disabling Skippy's automatic peer-consultation hooks even when the desktop has
43+
community peers connected.
44+
Pinned Skippy requests use `reasoning_effort: "none"` and
45+
`chat_template_kwargs: {"enable_thinking": false}` so bounded missions receive
46+
final output instead of exhausting their budget on hidden intermediate reasoning.
47+
The receipt records this generation mode.
3548

3649
With `--network none`, absence of a verified native model fails closed before any
3750
prompt is sent. With `--network allow`, native compute is still preferred, but a

packages/shadowfetch-missions/data/usr/lib/shadowfetch/missions/sf_local_compute.py

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@
88
"""
99
from __future__ import annotations
1010
import json
11+
import functools
12+
import hashlib
1113
import os
1214
from pathlib import Path
1315
import re
16+
import stat
17+
import subprocess
1418
import urllib.error
1519
import urllib.request
1620

@@ -19,6 +23,9 @@
1923
MAX_BODY = 2_000_000
2024
# These are native inference server identities, not an installation mechanism.
2125
NATIVE_EXECUTABLES = {"llama": {"llama-server"}, "skippy": {"skippy-server", "skippy"}}
26+
BUZZ_EXECUTABLE = Path("/usr/bin/buzz-desktop")
27+
BUZZ_MANIFEST = Path("/var/lib/dpkg/info/buzz.md5sums")
28+
BUZZ_VERSION = "0.5.17"
2229

2330
class ComputeError(ValueError):
2431
pass
@@ -45,6 +52,41 @@ def request(url, payload=None, timeout=3):
4552
def valid_name(value):
4653
return isinstance(value, str) and 0 < len(value.strip()) <= 256 and not any(ord(char) < 32 for char in value)
4754

55+
def protected_file(path):
56+
metadata = path.lstat()
57+
return stat.S_ISREG(metadata.st_mode) and metadata.st_uid == 0 and not metadata.st_mode & 0o022
58+
59+
@functools.lru_cache(maxsize=2)
60+
def file_hashes(path, identity):
61+
# Cache only within this controller process, keyed by inode/mtime/ctime/size.
62+
md5, sha = hashlib.md5(), hashlib.sha256()
63+
with Path(path).open("rb") as stream:
64+
while block := stream.read(1024 * 1024):
65+
md5.update(block)
66+
sha.update(block)
67+
return md5.hexdigest(), sha.hexdigest()
68+
69+
def buzz_binary_proof(binary):
70+
if binary != BUZZ_EXECUTABLE or not protected_file(binary) or not protected_file(BUZZ_MANIFEST):
71+
raise ComputeError("Embedded Buzz inference requires the protected system package executable")
72+
try:
73+
package = subprocess.run(["/usr/bin/dpkg-query", "-W", "-f=${Version} ${db:Status-Abbrev}", "buzz"], capture_output=True, text=True, timeout=3)
74+
except subprocess.SubprocessError as exc:
75+
raise ComputeError("Could not verify the embedded Buzz package: " + str(exc))
76+
if package.returncode or package.stdout.strip() != BUZZ_VERSION + " ii":
77+
raise ComputeError("Embedded native inference requires the verified Buzz " + BUZZ_VERSION + " package")
78+
entries = [line.split(None, 1) for line in BUZZ_MANIFEST.read_text().splitlines() if line.strip()]
79+
expected = [checksum for checksum, name in entries if name == "usr/bin/buzz-desktop"]
80+
if len(expected) != 1 or not re.fullmatch(r"[a-f0-9]{32}", expected[0]):
81+
raise ComputeError("Buzz executable is missing from its protected package manifest")
82+
before = binary.stat()
83+
identity = (before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns, before.st_ctime_ns)
84+
md5, sha = file_hashes(str(binary), identity)
85+
after = binary.stat()
86+
if identity != (after.st_dev, after.st_ino, after.st_size, after.st_mtime_ns, after.st_ctime_ns) or md5 != expected[0]:
87+
raise ComputeError("Buzz executable does not match the installed package manifest")
88+
return {"package": "buzz", "version": BUZZ_VERSION, "sha256": sha, "integrity": "root-owned pinned package and matching dpkg file manifest", "mode": "embedded-native-skippy"}
89+
4890
def process_proof(record, proc_root=Path("/proc")):
4991
if not isinstance(record, dict) or not valid_name(record.get("name")):
5092
raise ComputeError("Invalid native process record")
@@ -58,7 +100,10 @@ def process_proof(record, proc_root=Path("/proc")):
58100
if proc.stat().st_uid != os.getuid():
59101
raise ComputeError("Native model process belongs to a different user")
60102
binary = Path(os.readlink(proc / "exe"))
61-
if binary.name not in NATIVE_EXECUTABLES[backend]:
103+
package_proof = None
104+
if backend == "skippy" and binary == BUZZ_EXECUTABLE:
105+
package_proof = buzz_binary_proof(binary)
106+
elif binary.name not in NATIVE_EXECUTABLES[backend]:
62107
raise ComputeError("Model process executable does not match its native backend")
63108
# Field 22 is process start time. The command can contain spaces/parens.
64109
started = (proc / "stat").read_text().rsplit(")", 1)[1].split()[19]
@@ -80,13 +125,15 @@ def process_proof(record, proc_root=Path("/proc")):
80125
raise ComputeError("Native process does not own the advertised loopback listening port")
81126
except OSError as exc:
82127
raise ComputeError("Cannot prove native model process/socket ownership: " + str(exc))
83-
return {"name": record["name"].strip(), "pid": pid, "port": port, "backend": backend, "instance_id": record.get("instance_id"), "process_start": started, "executable": str(binary), "local_only_verified": True, "endpoint": f"http://127.0.0.1:{port}", "proof": "Buzz native process inventory + same-user executable + owned loopback socket"}
128+
return {"name": record["name"].strip(), "pid": pid, "port": port, "backend": backend, "instance_id": record.get("instance_id"), "process_start": started, "executable": str(binary), "package_identity": package_proof, "local_only_verified": True, "endpoint": f"http://127.0.0.1:{port}", "proof": "Buzz native process inventory + same-user executable + owned loopback socket"}
84129

85130
def local_models():
86131
try:
87132
records = request(MANAGEMENT + "/api/runtime/processes").get("processes")
88133
if not isinstance(records, list):
89134
return []
135+
if not no_distributed_stages():
136+
return []
90137
models = []
91138
for record in records[:64]:
92139
try:
@@ -98,6 +145,12 @@ def local_models():
98145
except (OSError, ValueError):
99146
return []
100147

148+
def no_distributed_stages():
149+
# Pinned MeshLLM exposes stage deployment separately from local processes.
150+
# Be conservative when any pipeline is present, even beside a local model.
151+
state = request(MANAGEMENT + "/api/runtime/stages")
152+
return state.get("stages") == [] and state.get("topologies") == []
153+
101154
def shared_models():
102155
try:
103156
records = request(ROUTER + "/v1/models").get("data")
@@ -123,11 +176,26 @@ def target(model, allow_network=False):
123176
def complete(payload, allow_network=False, timeout=180):
124177
selected = target(payload.get("model", ""), allow_network)
125178
payload = dict(payload, model=selected["name"])
179+
if selected["local_only_verified"]:
180+
# Native Skippy also supports automatic peer-consultation hooks. The
181+
# pinned vendor request switch must disable every hook for local work.
182+
payload["mesh_hooks"] = False
183+
selected["mesh_hooks_enabled"] = False
184+
if selected.get("backend") == "skippy":
185+
# Pinned Skippy hides unfinished reasoning; a short task can exhaust
186+
# its budget with no answer unless the supported template switch
187+
# explicitly requests direct output.
188+
payload["reasoning_effort"] = "none"
189+
payload["chat_template_kwargs"] = dict(payload.get("chat_template_kwargs") or {}, enable_thinking=False)
190+
selected["reasoning_mode"] = "disabled for bounded artifact generation"
126191
response = request(selected["endpoint"] + "/v1/chat/completions", payload, timeout=timeout)
127192
if selected["local_only_verified"]:
128193
# Detect a disappeared/replaced process before reporting a verified run.
129194
verified = process_proof(dict(selected, status="ready"))
130195
if verified["process_start"] != selected["process_start"]:
131196
raise ComputeError("Native model process changed while inference was running")
197+
if not no_distributed_stages():
198+
raise ComputeError("Distributed model stages appeared during native inference; locality cannot be verified")
199+
selected["distributed_stages"] = "No stage or topology deployments observed before and after inference"
132200
response["shadowfetch_compute"] = selected
133201
return response

packages/shadowfetch-missions/tests/test_local_compute.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import http.server
2+
import hashlib
23
import json
34
import os
45
from pathlib import Path
56
import sys
7+
import subprocess
68
import tempfile
79
import threading
810
import unittest
@@ -40,6 +42,39 @@ def test_mismatched_executable_is_refused(self):
4042
(self.proc / 'exe').symlink_to('/usr/bin/python3')
4143
with self.assertRaisesRegex(compute.ComputeError,'executable'):
4244
compute.process_proof(self.record, self.proc.parent)
45+
def test_embedded_buzz_still_requires_owned_native_socket(self):
46+
(self.proc / 'exe').unlink()
47+
(self.proc / 'exe').symlink_to('/usr/bin/buzz-desktop')
48+
record = dict(self.record, backend='skippy')
49+
with patch.object(compute, 'buzz_binary_proof', return_value={'package':'buzz','version':'0.5.17'}) as identity:
50+
proof = compute.process_proof(record, self.proc.parent)
51+
self.assertEqual(proof['package_identity']['package'], 'buzz')
52+
identity.assert_called_once_with(Path('/usr/bin/buzz-desktop'))
53+
(self.proc / 'fd/5').unlink()
54+
with self.assertRaisesRegex(compute.ComputeError, 'does not own'):
55+
compute.process_proof(record, self.proc.parent)
56+
(self.proc / 'exe').unlink()
57+
(self.proc / 'exe').symlink_to('/home/person/buzz-desktop')
58+
with self.assertRaisesRegex(compute.ComputeError, 'executable'):
59+
compute.process_proof(record, self.proc.parent)
60+
def test_embedded_binary_requires_pinned_package_and_content_integrity(self):
61+
binary = Path(self.temp.name) / 'buzz-desktop'
62+
binary.write_bytes(b'official package fixture')
63+
manifest = Path(self.temp.name) / 'buzz.md5sums'
64+
manifest.write_text(hashlib.md5(binary.read_bytes()).hexdigest() + ' usr/bin/buzz-desktop\n')
65+
query = subprocess.CompletedProcess([], 0, stdout='0.5.17 ii ', stderr='')
66+
with patch.object(compute, 'BUZZ_EXECUTABLE', binary), patch.object(compute, 'BUZZ_MANIFEST', manifest), patch.object(compute, 'protected_file', return_value=True), patch.object(compute.subprocess, 'run', return_value=query):
67+
self.assertEqual(compute.buzz_binary_proof(binary)['sha256'], hashlib.sha256(binary.read_bytes()).hexdigest())
68+
query.stdout = '0.5.18 ii '
69+
with self.assertRaisesRegex(compute.ComputeError, 'verified Buzz'):
70+
compute.buzz_binary_proof(binary)
71+
query.stdout = '0.5.17 ii '
72+
binary.write_bytes(b'modified executable')
73+
with self.assertRaisesRegex(compute.ComputeError, 'does not match'):
74+
compute.buzz_binary_proof(binary)
75+
with patch.object(compute, 'BUZZ_EXECUTABLE', binary), patch.object(compute, 'protected_file', return_value=False):
76+
with self.assertRaisesRegex(compute.ComputeError, 'protected'):
77+
compute.buzz_binary_proof(binary)
4378
def test_no_native_model_fails_closed_before_shared_inference(self):
4479
with patch.object(compute,'local_models',return_value=[]), patch.object(compute,'shared_models',return_value=[{'name':'remote'}]) as shared:
4580
with self.assertRaisesRegex(compute.ComputeError,'Offline missions never'):
@@ -48,10 +83,33 @@ def test_no_native_model_fails_closed_before_shared_inference(self):
4883
target = compute.target('remote',True)
4984
self.assertFalse(target['local_only_verified'])
5085
self.assertEqual(target['endpoint'],compute.ROUTER)
86+
def test_native_discovery_refuses_distributed_or_unknown_stage_state(self):
87+
for stages in ({'stages':[{'stage_id':'remote'}],'topologies':[]}, {'stages':[], 'topologies':[{'run_id':'split'}]}, {}):
88+
def reply(url):
89+
return {'processes':[self.record]} if url.endswith('/processes') else stages
90+
with self.subTest(stages=stages), patch.object(compute, 'request', side_effect=reply), patch.object(compute, 'process_proof', side_effect=AssertionError('must refuse before native selection')):
91+
self.assertEqual(compute.local_models(), [])
5192
def test_native_route_wins_over_mesh_when_both_allowed(self):
5293
native = compute.process_proof(self.record, self.proc.parent)
5394
with patch.object(compute,'local_models',return_value=[native]), patch.object(compute,'shared_models',side_effect=AssertionError('do not route mesh')):
5495
self.assertEqual(compute.target('model',True),native)
96+
def test_native_requests_disable_all_peer_consultation_hooks(self):
97+
native = compute.process_proof(self.record, self.proc.parent)
98+
payload = {'model':'model','mesh_hooks':True,'messages':[{'role':'user','content':'private text'}]}
99+
with patch.object(compute, 'target', return_value=native), patch.object(compute, 'request', return_value={'choices':[{'message':{'content':'done'}}]}) as call, patch.object(compute, 'process_proof', return_value=native), patch.object(compute, 'no_distributed_stages', return_value=True):
100+
result = compute.complete(payload)
101+
self.assertIs(call.call_args.args[1]['mesh_hooks'], False)
102+
self.assertIs(payload['mesh_hooks'], True)
103+
self.assertIs(result['shadowfetch_compute']['mesh_hooks_enabled'], False)
104+
def test_skippy_requests_produce_direct_output_with_bounded_token_budget(self):
105+
native = dict(compute.process_proof(self.record, self.proc.parent), backend='skippy')
106+
payload = {'model':'model','reasoning_effort':'high','chat_template_kwargs':{'enable_thinking':True},'messages':[{'role':'user','content':'READY'}]}
107+
with patch.object(compute, 'target', return_value=native), patch.object(compute, 'request', return_value={'choices':[{'message':{'content':'READY'}}]}) as call, patch.object(compute, 'process_proof', return_value=native), patch.object(compute, 'no_distributed_stages', return_value=True):
108+
result = compute.complete(payload)
109+
self.assertEqual(call.call_args.args[1]['reasoning_effort'], 'none')
110+
self.assertIs(call.call_args.args[1]['chat_template_kwargs']['enable_thinking'], False)
111+
self.assertIs(payload['chat_template_kwargs']['enable_thinking'], True)
112+
self.assertIn('bounded', result['shadowfetch_compute']['reasoning_mode'])
55113
def test_only_literal_loopback_urls_accepted(self):
56114
for url in ['https://example.com/v1/models','http://localhost:3131/api/status','http://127.0.0.1:3131@evil.test/api/status','file:///etc/passwd']:
57115
with self.subTest(url=url), self.assertRaises(compute.ComputeError):

packages/shadowfetch-welcome/src/shadowfetch-welcome

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1935,7 +1935,7 @@ class WelcomePage(QWidget):
19351935
points = QGridLayout()
19361936
for i, (title, detail) in enumerate((
19371937
("01 Choose your scope", "One project, an explicit connection and the provider you choose."),
1938-
("02 Watch work progress", "Persistent code, private report and media missions with activity receipts."),
1938+
("02 Watch work progress", "Persistent code, source report and media missions with activity receipts."),
19391939
("03 Review the evidence", "Inspect files and changes. Accept results or restore local mission changes."),
19401940
)):
19411941
card = Card()

qa/4.0.0/acceptance.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,14 @@
7070
"required": true,
7171
"status": "pending",
7272
"title": "Existing 3.5 installed system upgrades with preserved user data and working recovery",
73-
"evidence": []
73+
"evidence": [
74+
{
75+
"kind": "json",
76+
"path": "package/final-upgrade-recovery.json",
77+
"sha256": "77edf24a83b84c0f57ed189f37e5879f197006df8747961e3e1f4a0d332d75da"
78+
}
79+
],
80+
"notes": "Candidate 2 rehearsal passed: separate immutable 3.5 BIOS disk overlay, upgrade/reboot to 4.0, real Phoenix root rollback/reboot to 3.5, second upgrade/reboot to 4.0, preserved personal data and clean audits. Exact final package refresh and recovery verification remain required before final acceptance."
7481
},
7582
{
7683
"id": "MISSION-01",

0 commit comments

Comments
 (0)