From 843a2984d79c1f810ec8187c3299a877c956cb9f Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 09:16:04 -0400 Subject: [PATCH 01/29] Replay TurtleTerm agent status core --- assets/sourceos/bin/turtle-agent-status | 207 ++++++++++++++++++ .../tests/test_sourceos_term_smoke.py | 84 +++++++ 2 files changed, 291 insertions(+) create mode 100755 assets/sourceos/bin/turtle-agent-status diff --git a/assets/sourceos/bin/turtle-agent-status b/assets/sourceos/bin/turtle-agent-status new file mode 100755 index 00000000000..817e16bd2ef --- /dev/null +++ b/assets/sourceos/bin/turtle-agent-status @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +"""TurtleTerm local SourceOS Agent Reliability status view. + +Reads local SourceOS evidence artifacts and summarizes: +- blocking guardrail decisions; +- stop-gate outcomes; +- guarded invocation outcomes; +- pending governance queue items. + +This tool is read-only. It does not modify policy, memory, git state, or queue +artifacts. +""" + +from __future__ import annotations + +import argparse +import json +from collections import Counter +from pathlib import Path +from typing import Any + +BLOCKING_DECISIONS = {"deny", "quarantine", "defer", "escalate"} +NEEDS_REVIEW_QUEUE_STATUSES = {"pending"} + + +def load_json(path: Path) -> dict[str, Any] | None: + try: + data = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + return None + return data if isinstance(data, dict) else None + + +def load_jsonl(path: Path) -> list[dict[str, Any]]: + if not path.exists(): + return [] + rows: list[dict[str, Any]] = [] + try: + for line in path.read_text(encoding="utf-8").splitlines(): + if not line.strip(): + continue + try: + data = json.loads(line) + except json.JSONDecodeError: + rows.append({"schema": "invalid-json", "decision": "invalid", "decisionId": f"{path}:invalid-json"}) + continue + if isinstance(data, dict): + rows.append(data) + except OSError: + return [] + return rows + + +def unique_paths(paths: list[Path]) -> list[Path]: + seen: set[str] = set() + out: list[Path] = [] + for path in paths: + key = str(path.resolve()) + if key not in seen: + seen.add(key) + out.append(path) + return out + + +def discover_json_artifacts(root: Path, filenames: set[str]) -> list[dict[str, Any]]: + artifacts: list[dict[str, Any]] = [] + paths: list[Path] = [] + sourceos = root / ".sourceos" + if sourceos.exists(): + for name in filenames: + paths.extend(sourceos.rglob(name)) + for path in unique_paths(paths): + data = load_json(path) + if data is not None: + data["_path"] = str(path) + artifacts.append(data) + return artifacts + + +def discover_governance_queues(root: Path) -> list[dict[str, Any]]: + queues: list[dict[str, Any]] = [] + candidates: list[Path] = [] + for base in [root / ".sourceos", root / "standards" / "agent-reliability"]: + if base.exists(): + candidates.extend(base.rglob("*governance-queue*.json")) + for path in unique_paths(candidates): + data = load_json(path) + if data and data.get("kind") == "AgentReliabilityGovernanceQueue": + data["_path"] = str(path) + queues.append(data) + return queues + + +def summarize(root: Path) -> dict[str, Any]: + decision_log = root / ".sourceos" / "logs" / "guardrail-decisions.jsonl" + decisions = load_jsonl(decision_log) + decision_counts = Counter(str(item.get("decision", "unknown")) for item in decisions) + blocking = [item for item in decisions if str(item.get("decision", "")).lower() in BLOCKING_DECISIONS] + redactions = [item for item in decisions if str(item.get("decision", "")).lower() == "redact"] + + stop_gates = discover_json_artifacts(root, {"stop-gate-artifact.json"}) + stop_gate_counts = Counter(str(item.get("result", "unknown")) for item in stop_gates if item.get("kind") == "StopGateArtifact") + failing_stop_gates = [item for item in stop_gates if item.get("kind") == "StopGateArtifact" and item.get("result") in {"fail", "needs_human"}] + + invocations = discover_json_artifacts(root, {"guarded-invocation-artifact.json"}) + invocation_counts = Counter(str(item.get("result", "unknown")) for item in invocations if item.get("kind") == "GuardedInvocationArtifact") + failed_invocations = [item for item in invocations if item.get("kind") == "GuardedInvocationArtifact" and item.get("result") in {"failure", "blocked", "needs_human"}] + + queues = discover_governance_queues(root) + pending_queue_items: list[dict[str, Any]] = [] + for queue in queues: + for item in queue.get("items", []): + if isinstance(item, dict) and item.get("status") in NEEDS_REVIEW_QUEUE_STATUSES: + pending = dict(item) + pending["queuePath"] = queue.get("_path") + pending_queue_items.append(pending) + + status = "ready" + if blocking or failing_stop_gates or failed_invocations: + status = "blocked" + elif pending_queue_items: + status = "needs_review" + elif not decisions and not stop_gates and not invocations and not queues: + status = "no_artifacts" + + return { + "schema": "sourceos.turtle.agent_status.v0", + "root": str(root), + "status": status, + "guardrail": { + "decisionLog": str(decision_log), + "total": len(decisions), + "counts": dict(decision_counts), + "blocking": [item.get("decisionId") or item.get("policyId") for item in blocking], + "redactions": [item.get("decisionId") or item.get("policyId") for item in redactions], + }, + "stopGates": { + "total": len(stop_gates), + "counts": dict(stop_gate_counts), + "blocking": [item.get("gateId") or item.get("_path") for item in failing_stop_gates], + }, + "invocations": { + "total": len(invocations), + "counts": dict(invocation_counts), + "blocking": [item.get("workcellArtifactRef") or item.get("_path") for item in failed_invocations], + }, + "governance": { + "queues": len(queues), + "pending": [ + { + "itemId": item.get("itemId"), + "itemType": item.get("itemType"), + "priority": item.get("priority"), + "title": item.get("title"), + "queuePath": item.get("queuePath"), + } + for item in pending_queue_items + ], + }, + } + + +def print_human(summary: dict[str, Any]) -> None: + print(f"TurtleTerm Agent Status: {summary['status']}") + print(f"root: {summary['root']}") + print("") + print("Guardrails") + print(f" decisions: {summary['guardrail']['total']} {summary['guardrail']['counts']}") + if summary["guardrail"]["blocking"]: + print(f" blocking: {', '.join(str(x) for x in summary['guardrail']['blocking'])}") + if summary["guardrail"]["redactions"]: + print(f" redactions: {', '.join(str(x) for x in summary['guardrail']['redactions'])}") + print("Stop gates") + print(f" artifacts: {summary['stopGates']['total']} {summary['stopGates']['counts']}") + if summary["stopGates"]["blocking"]: + print(f" blocking: {', '.join(str(x) for x in summary['stopGates']['blocking'])}") + print("Invocations") + print(f" artifacts: {summary['invocations']['total']} {summary['invocations']['counts']}") + if summary["invocations"]["blocking"]: + print(f" blocking: {', '.join(str(x) for x in summary['invocations']['blocking'])}") + print("Governance") + print(f" queues: {summary['governance']['queues']}") + print(f" pending review items: {len(summary['governance']['pending'])}") + for item in summary["governance"]["pending"]: + print(f" - [{item.get('priority')}] {item.get('itemType')}: {item.get('title')} ({item.get('itemId')})") + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Read local SourceOS agent reliability artifacts and print TurtleTerm status.") + parser.add_argument("--root", default=".", help="Workspace/repo root to inspect") + parser.add_argument("--json", action="store_true", help="Emit JSON instead of human-readable text") + return parser + + +def main(argv: list[str] | None = None) -> int: + args = build_parser().parse_args(argv) + root = Path(args.root).resolve() + summary = summarize(root) + if args.json: + print(json.dumps(summary, indent=2, sort_keys=True)) + else: + print_human(summary) + return 0 if summary["status"] in {"ready", "no_artifacts"} else 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/assets/sourceos/tests/test_sourceos_term_smoke.py b/assets/sourceos/tests/test_sourceos_term_smoke.py index 68d2750505b..c7cc3a41549 100644 --- a/assets/sourceos/tests/test_sourceos_term_smoke.py +++ b/assets/sourceos/tests/test_sourceos_term_smoke.py @@ -14,6 +14,7 @@ REPO_ROOT = Path(__file__).resolve().parents[3] SOURCEOS_WRAPPER = REPO_ROOT / "assets" / "sourceos" / "bin" / "sourceos-term" TURTLE_WRAPPER = REPO_ROOT / "assets" / "sourceos" / "bin" / "turtle-term" +AGENT_STATUS = REPO_ROOT / "assets" / "sourceos" / "bin" / "turtle-agent-status" def read_ndjson(path: Path) -> list[dict]: @@ -79,6 +80,84 @@ def run_wrapper(wrapper: Path, session_id: str, workspace: str, expected_text: s return event_rows, session +def write_json(path: Path, data: dict) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def run_agent_status(root: Path, expect_code: int) -> dict: + result = subprocess.run( + [sys.executable, str(AGENT_STATUS), "--root", str(root), "--json"], + cwd=str(REPO_ROOT), + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + assert result.returncode == expect_code, result.stderr + return json.loads(result.stdout) + + +def test_agent_status_no_artifacts() -> None: + with tempfile.TemporaryDirectory() as tmp: + summary = run_agent_status(Path(tmp), expect_code=0) + assert summary["schema"] == "sourceos.turtle.agent_status.v0" + assert summary["status"] == "no_artifacts" + + +def test_agent_status_blocked_by_guardrail() -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + decision_log = root / ".sourceos" / "logs" / "guardrail-decisions.jsonl" + decision_log.parent.mkdir(parents=True, exist_ok=True) + decision_log.write_text( + json.dumps({"schema": "sourceos.guardrail.decision.v0.1", "decisionId": "deny-1", "decision": "deny", "policyId": "sourceos/shell/block-privilege-escalation"}) + "\n", + encoding="utf-8", + ) + summary = run_agent_status(root, expect_code=2) + assert summary["status"] == "blocked" + assert summary["guardrail"]["blocking"] == ["deny-1"] + + +def test_agent_status_needs_review_from_governance_queue() -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + queue = { + "apiVersion": "sociosphere.governance-queue/v1", + "kind": "AgentReliabilityGovernanceQueue", + "items": [ + { + "itemId": "review-1", + "itemType": "memory-learning-review", + "status": "pending", + "priority": "medium", + "title": "Review learning proposal", + } + ], + } + write_json(root / ".sourceos" / "governance" / "governance-queue.json", queue) + summary = run_agent_status(root, expect_code=2) + assert summary["status"] == "needs_review" + assert summary["governance"]["pending"][0]["itemId"] == "review-1" + + +def test_agent_status_ready_from_passing_artifacts() -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + write_json( + root / ".sourceos" / "logs" / "stop-gate-artifact.json", + {"kind": "StopGateArtifact", "gateId": "sourceos.default.agent-completion", "result": "pass"}, + ) + write_json( + root / ".sourceos" / "logs" / "invocations" / "s1" / "guarded-invocation-artifact.json", + {"kind": "GuardedInvocationArtifact", "workcellArtifactRef": "workcell-1", "result": "success"}, + ) + summary = run_agent_status(root, expect_code=0) + assert summary["status"] == "ready" + assert summary["stopGates"]["counts"] == {"pass": 1} + assert summary["invocations"]["counts"] == {"success": 1} + + def main() -> int: _, sourceos_session = run_wrapper(SOURCEOS_WRAPPER, "sourceos-term-test", "sourceos-test", "sourceos-smoke") assert sourceos_session["frontend"] == "sourceos-term" @@ -86,6 +165,11 @@ def main() -> int: _, turtle_session = run_wrapper(TURTLE_WRAPPER, "turtle-term-test", "turtle-test", "turtle-smoke") assert turtle_session["frontend"] == "turtle-term" + test_agent_status_no_artifacts() + test_agent_status_blocked_by_guardrail() + test_agent_status_needs_review_from_governance_queue() + test_agent_status_ready_from_passing_artifacts() + return 0 From b9e3014c4e603cb317ac40c2aac77baae5ad1437 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 09:17:39 -0400 Subject: [PATCH 02/29] Wire TurtleTerm agent status smoke validation --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a9b82c2b6e3..6b1cc2f3364 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ turtle-smoke: bash -n packaging/scripts/install-turtle-term.sh bash -n packaging/scripts/package-turtle-term.sh bash -n packaging/scripts/bootstrap-homebrew-tap.sh - python3 -m py_compile packaging/scripts/render-stable-homebrew-formula.py assets/sourceos/bin/sourceos-term assets/sourceos/bin/turtle-term + python3 -m py_compile packaging/scripts/render-stable-homebrew-formula.py assets/sourceos/bin/sourceos-term assets/sourceos/bin/turtle-term assets/sourceos/bin/turtle-agent-status turtle-package: turtle-build turtle-smoke bash packaging/scripts/package-turtle-term.sh "$${TURTLE_TERM_VERSION:-turtle-term-dev}" "$${TURTLE_TERM_TARGET:-$$(uname -s)-$$(uname -m)}" From f09845738ece68baf5c482672cb088785b8ddbe6 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 09:20:56 -0400 Subject: [PATCH 03/29] Fix Linux package staging script mode --- packaging/scripts/stage-linux-package.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 packaging/scripts/stage-linux-package.sh diff --git a/packaging/scripts/stage-linux-package.sh b/packaging/scripts/stage-linux-package.sh old mode 100644 new mode 100755 From 5116b61229147eadc2e784aa6ff65ac460ebeb90 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 09:24:15 -0400 Subject: [PATCH 04/29] Fix TurtleTerm agentctl script mode --- assets/sourceos/bin/turtle-agentctl | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 assets/sourceos/bin/turtle-agentctl diff --git a/assets/sourceos/bin/turtle-agentctl b/assets/sourceos/bin/turtle-agentctl old mode 100644 new mode 100755 From 983ff150c425f93129f07d5a3deec46b6f282870 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 09:29:11 -0400 Subject: [PATCH 05/29] Align install guide with TurtleTerm profile name --- docs/sourceos/INSTALL.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/sourceos/INSTALL.md b/docs/sourceos/INSTALL.md index 0d6c33094ec..a6ce76fcb9b 100644 --- a/docs/sourceos/INSTALL.md +++ b/docs/sourceos/INSTALL.md @@ -10,6 +10,14 @@ brew install --HEAD https://raw.githubusercontent.com/SourceOS-Linux/TurtleTerm/ This is the current easiest no-checkout install path for macOS and Linux. +Then launch TurtleTerm: + +```bash +turtleterm +``` + +The installed profile is `turtleterm.lua`. + ## Public tap path After the public tap exists: @@ -70,21 +78,17 @@ turtle-agentctl --stdio ping Homebrew profile path: ```bash -ln -sf "$(brew --prefix)/etc/turtle-term/wezterm.lua" ~/.wezterm.lua +ln -sf "$(brew --prefix)/etc/turtle-term/turtleterm.lua" ~/.wezterm.lua ``` Direct install profile path: ```bash -ln -sf "$HOME/.local/etc/turtle-term/wezterm.lua" ~/.wezterm.lua +ln -sf "$HOME/.local/etc/turtle-term/turtleterm.lua" ~/.wezterm.lua ``` -Then launch TurtleTerm: - -```bash -turtleterm -``` +The file name `turtleterm.lua` is the product-facing TurtleTerm profile. It may internally derive from upstream terminal profile conventions, but operator docs should use the TurtleTerm product surface name. -## Windows status +## Windows -Windows packaging is postponed until macOS and Linux distribution are stable. Candidate lanes are Chocolatey, WinGet, and Scoop. +Windows packaging is postponed until the Linux/macOS release path is stable. From 5f697f34eb8f5b3937c6268b78153784b1ac7af9 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 09:32:23 -0400 Subject: [PATCH 06/29] Remove forbidden authority phrase from integration plan --- docs/sourceos/AGENTIC_INTEGRATION_PLAN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sourceos/AGENTIC_INTEGRATION_PLAN.md b/docs/sourceos/AGENTIC_INTEGRATION_PLAN.md index eccb0a34108..bd81e3bf81f 100644 --- a/docs/sourceos/AGENTIC_INTEGRATION_PLAN.md +++ b/docs/sourceos/AGENTIC_INTEGRATION_PLAN.md @@ -38,7 +38,7 @@ AgentPlane execution, evidence, and replay ## Invariant -TurtleTerm must not grant agents ambient shell authority. +TurtleTerm must not grant agents unsupervised shell capability. Every risky action becomes an ExecutionDecision: allow, deny, ask, defer, or rewrite. From 93ee873af706ebd782127f0452d4eb5ce452bb2f Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 09:38:42 -0400 Subject: [PATCH 07/29] Fix native package script modes --- packaging/scripts/build-arch-package.sh | 0 packaging/scripts/build-deb-package.sh | 0 packaging/scripts/build-rpm-package.sh | 0 packaging/scripts/verify-arch-package.sh | 0 packaging/scripts/verify-deb-package.sh | 0 packaging/scripts/verify-rpm-package.sh | 0 6 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 packaging/scripts/build-arch-package.sh mode change 100644 => 100755 packaging/scripts/build-deb-package.sh mode change 100644 => 100755 packaging/scripts/build-rpm-package.sh mode change 100644 => 100755 packaging/scripts/verify-arch-package.sh mode change 100644 => 100755 packaging/scripts/verify-deb-package.sh mode change 100644 => 100755 packaging/scripts/verify-rpm-package.sh diff --git a/packaging/scripts/build-arch-package.sh b/packaging/scripts/build-arch-package.sh old mode 100644 new mode 100755 diff --git a/packaging/scripts/build-deb-package.sh b/packaging/scripts/build-deb-package.sh old mode 100644 new mode 100755 diff --git a/packaging/scripts/build-rpm-package.sh b/packaging/scripts/build-rpm-package.sh old mode 100644 new mode 100755 diff --git a/packaging/scripts/verify-arch-package.sh b/packaging/scripts/verify-arch-package.sh old mode 100644 new mode 100755 diff --git a/packaging/scripts/verify-deb-package.sh b/packaging/scripts/verify-deb-package.sh old mode 100644 new mode 100755 diff --git a/packaging/scripts/verify-rpm-package.sh b/packaging/scripts/verify-rpm-package.sh old mode 100644 new mode 100755 From e0f3149719fea7d0bca0a62f56a5762a4e2ae1ce Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 10:13:52 -0400 Subject: [PATCH 08/29] Package TurtleTerm agent status CLI --- packaging/scripts/stage-linux-package.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/packaging/scripts/stage-linux-package.sh b/packaging/scripts/stage-linux-package.sh index 5720d93ff59..150e03e3c17 100755 --- a/packaging/scripts/stage-linux-package.sh +++ b/packaging/scripts/stage-linux-package.sh @@ -28,6 +28,7 @@ for script in \ turtle-term \ turtle-agentd \ turtle-agentctl \ + turtle-agent-status \ turtle-tmux \ turtle-cloudfog \ turtle-superconscious \ From 17423dc9be01d2b3da7ae9759b1c2208ac32de8e Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 10:17:42 -0400 Subject: [PATCH 09/29] Use explicit gzip compression for Debian package build --- packaging/scripts/build-deb-package.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/scripts/build-deb-package.sh b/packaging/scripts/build-deb-package.sh index 4b87f13a089..da869ee9c2a 100755 --- a/packaging/scripts/build-deb-package.sh +++ b/packaging/scripts/build-deb-package.sh @@ -66,7 +66,7 @@ find "$package_root" -type d -exec chmod 0755 {} + find "$package_root/usr/bin" -type f -exec chmod 0755 {} + find "$package_root/usr/libexec/turtle-term" -type f -exec chmod 0755 {} + -dpkg-deb --build --root-owner-group "$package_root" "$deb" >/dev/null +dpkg-deb -Zgzip --build --root-owner-group "$package_root" "$deb" >/dev/null sha256sum "$deb" > "$deb.sha256" python3 "$repo_root/packaging/scripts/write-native-package-manifest.py" \ --package "$deb" \ From e8f6228c72d79b135c34f9bf79257ecc16bdcbac Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 10:48:19 -0400 Subject: [PATCH 10/29] Update Debian package builder --- packaging/scripts/build-deb-package.sh | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/packaging/scripts/build-deb-package.sh b/packaging/scripts/build-deb-package.sh index da869ee9c2a..a3e79961f24 100755 --- a/packaging/scripts/build-deb-package.sh +++ b/packaging/scripts/build-deb-package.sh @@ -9,6 +9,7 @@ package_root="$out_dir/deb-root" prefix="$package_root/usr" etc_dir="$package_root/etc" debian_dir="$package_root/DEBIAN" +deb_build="$out_dir/deb-build" deb="$out_dir/turtle-term_${version}_${arch}.deb" case "$arch" in @@ -16,10 +17,12 @@ case "$arch" in *) echo "unsupported Debian architecture: $arch" >&2; exit 2 ;; esac -command -v dpkg-deb >/dev/null 2>&1 || { echo "dpkg-deb is required" >&2; exit 1; } +command -v ar >/dev/null 2>&1 || { echo "ar is required" >&2; exit 1; } +command -v tar >/dev/null 2>&1 || { echo "tar is required" >&2; exit 1; } +command -v gzip >/dev/null 2>&1 || { echo "gzip is required" >&2; exit 1; } -rm -rf "$package_root" "$deb" "$deb.sha256" "$deb.manifest.json" -mkdir -p "$debian_dir" "$out_dir" +rm -rf "$package_root" "$deb_build" "$deb" "$deb.sha256" "$deb.manifest.json" +mkdir -p "$debian_dir" "$out_dir" "$deb_build" TURTLE_TERM_STAGE_PREFIX="$prefix" \ TURTLE_TERM_ETC_DIR="$etc_dir" \ @@ -66,7 +69,20 @@ find "$package_root" -type d -exec chmod 0755 {} + find "$package_root/usr/bin" -type f -exec chmod 0755 {} + find "$package_root/usr/libexec/turtle-term" -type f -exec chmod 0755 {} + -dpkg-deb -Zgzip --build --root-owner-group "$package_root" "$deb" >/dev/null +printf '2.0\n' > "$deb_build/debian-binary" +( + cd "$debian_dir" + tar --owner=0 --group=0 --numeric-owner -czf "$deb_build/control.tar.gz" . +) +( + cd "$package_root" + tar --owner=0 --group=0 --numeric-owner --exclude='./DEBIAN' -czf "$deb_build/data.tar.gz" . +) +( + cd "$deb_build" + ar rcs "$deb" debian-binary control.tar.gz data.tar.gz +) + sha256sum "$deb" > "$deb.sha256" python3 "$repo_root/packaging/scripts/write-native-package-manifest.py" \ --package "$deb" \ From 7bf96743edfc464dc6c3c54ff0af333daf893a19 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 10:52:29 -0400 Subject: [PATCH 11/29] Validate Homebrew formula through local tap --- .github/workflows/turtle-term-homebrew.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/turtle-term-homebrew.yml b/.github/workflows/turtle-term-homebrew.yml index 8da19be9c36..2d98d2d7130 100644 --- a/.github/workflows/turtle-term-homebrew.yml +++ b/.github/workflows/turtle-term-homebrew.yml @@ -34,13 +34,18 @@ jobs: - name: Set up Homebrew on Linux if: runner.os == 'Linux' - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main + + - name: Register local Homebrew tap + run: | + brew tap-new sourceos-local/turtleterm + cp packaging/homebrew/Formula/turtle-term.rb "$(brew --repository sourceos-local/turtleterm)/Formula/turtle-term.rb" - name: Audit TurtleTerm formula - run: brew audit --formula --strict packaging/homebrew/Formula/turtle-term.rb || true + run: brew audit --formula --strict sourceos-local/turtleterm/turtle-term || true - name: Install TurtleTerm formula from HEAD - run: brew install --HEAD ./packaging/homebrew/Formula/turtle-term.rb + run: brew install --HEAD sourceos-local/turtleterm/turtle-term - name: Test TurtleTerm formula run: brew test turtle-term From 81553fd7d48b37642d8ff4a37d134ce091cdb1e2 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 10:56:20 -0400 Subject: [PATCH 12/29] Avoid broken pipe in Debian package verifier --- packaging/scripts/verify-deb-package.sh | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/packaging/scripts/verify-deb-package.sh b/packaging/scripts/verify-deb-package.sh index 3c3e540bb6f..53e5873414f 100755 --- a/packaging/scripts/verify-deb-package.sh +++ b/packaging/scripts/verify-deb-package.sh @@ -18,6 +18,7 @@ TURTLE_TERM_OUT_DIR="$tmp" TURTLE_TERM_VERSION="0.1.0" TURTLE_TERM_DEB_ARCH="amd "$repo_root/packaging/scripts/build-deb-package.sh" >/dev/null deb="$tmp/turtle-term_0.1.0_amd64.deb" +contents="$tmp/deb-contents.txt" extract="$tmp/extract" test -f "$deb" test -f "$deb.sha256" @@ -34,25 +35,26 @@ assert manifest['version'] == '0.1.0' assert manifest['arch'] == 'amd64' assert manifest['package'] == 'turtle-term_0.1.0_amd64.deb' assert manifest['profile'] == '/etc/turtle-term/turtleterm.lua' -for command in ['turtle-cloudfog', 'turtle-superconscious', 'turtle-agent-machine', 'turtle-language', 'turtle-session']: +for command in ['turtle-agent-status', 'turtle-cloudfog', 'turtle-superconscious', 'turtle-agent-machine', 'turtle-language', 'turtle-session']: assert command in manifest['public_commands'], command PY dpkg-deb --field "$deb" Package | grep -qx 'turtle-term' dpkg-deb --field "$deb" Version | grep -qx '0.1.0' dpkg-deb --field "$deb" Architecture | grep -qx 'amd64' +dpkg-deb --contents "$deb" > "$contents" -for command in turtleterm turtle-agentctl turtle-cloudfog turtle-superconscious turtle-agent-machine turtle-language turtle-session; do - dpkg-deb --contents "$deb" | grep -q "/usr/bin/$command$" +for command in turtleterm turtle-agentctl turtle-agent-status turtle-cloudfog turtle-superconscious turtle-agent-machine turtle-language turtle-session; do + grep -q "/usr/bin/$command$" "$contents" done -dpkg-deb --contents "$deb" | grep -q '/etc/turtle-term/turtleterm.lua$' -dpkg-deb --contents "$deb" | grep -q '/usr/share/applications/ai.sourceos.TurtleTerm.desktop$' -dpkg-deb --contents "$deb" | grep -q '/usr/share/metainfo/ai.sourceos.TurtleTerm.metainfo.xml$' -dpkg-deb --contents "$deb" | grep -q '/usr/share/icons/hicolor/scalable/apps/ai.sourceos.TurtleTerm.svg$' -dpkg-deb --contents "$deb" | grep -q '/usr/libexec/turtle-term/wezterm-gui$' +grep -q '/etc/turtle-term/turtleterm.lua$' "$contents" +grep -q '/usr/share/applications/ai.sourceos.TurtleTerm.desktop$' "$contents" +grep -q '/usr/share/metainfo/ai.sourceos.TurtleTerm.metainfo.xml$' "$contents" +grep -q '/usr/share/icons/hicolor/scalable/apps/ai.sourceos.TurtleTerm.svg$' "$contents" +grep -q '/usr/libexec/turtle-term/wezterm-gui$' "$contents" -if dpkg-deb --contents "$deb" | grep -q '/usr/bin/wezterm-gui$'; then +if grep -q '/usr/bin/wezterm-gui$' "$contents"; then echo 'private runtime leaked onto product PATH in deb' >&2 exit 1 fi @@ -72,6 +74,7 @@ fi probe="$tmp/probe.py" printf 'def hello():\n return "world"\n' > "$probe" PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-agentctl" --stdio surfaces >/dev/null +PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-agent-status" --json >/dev/null PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-cloudfog" surfaces >/dev/null PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-superconscious" observe deb-package >/dev/null PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-agent-machine" surfaces >/dev/null From fad354025ac03816ae61ab873cab2b30f5e7582b Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 11:01:26 -0400 Subject: [PATCH 13/29] Include agent status in native package manifest --- packaging/scripts/write-native-package-manifest.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packaging/scripts/write-native-package-manifest.py b/packaging/scripts/write-native-package-manifest.py index dcac916fb23..80ef5ec04da 100644 --- a/packaging/scripts/write-native-package-manifest.py +++ b/packaging/scripts/write-native-package-manifest.py @@ -46,19 +46,20 @@ def main() -> int: "turtle-term", "turtle-agentd", "turtle-agentctl", + "turtle-agent-status", "turtle-tmux", "turtle-cloudfog", "turtle-superconscious", "turtle-agent-machine", "turtle-language", "turtle-session", - "sourceos-term" + "sourceos-term", ], "private_runtime_path": "libexec/turtle-term", "profile": "/etc/turtle-term/turtleterm.lua", "desktop_id": "ai.sourceos.TurtleTerm.desktop", "appstream_id": "ai.sourceos.TurtleTerm", - "icon": "ai.sourceos.TurtleTerm.svg" + "icon": "ai.sourceos.TurtleTerm.svg", } out = Path(args.out) From 8bebeb8801ee7cdb1ae2ae901745823058d73843 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 11:06:41 -0400 Subject: [PATCH 14/29] Package agent status in RPM files list --- packaging/scripts/build-rpm-package.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/packaging/scripts/build-rpm-package.sh b/packaging/scripts/build-rpm-package.sh index bdf0e09b1e6..fec0f351055 100755 --- a/packaging/scripts/build-rpm-package.sh +++ b/packaging/scripts/build-rpm-package.sh @@ -52,6 +52,7 @@ if [ -f $repo_root/THIRD_PARTY_NOTICES.md ]; then cp $repo_root/THIRD_PARTY_NOTI /usr/bin/turtle-term /usr/bin/turtle-agentd /usr/bin/turtle-agentctl +/usr/bin/turtle-agent-status /usr/bin/turtle-tmux /usr/bin/turtle-cloudfog /usr/bin/turtle-superconscious From 5d614673a0f588094089bb2f39227834e96774f2 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 11:10:24 -0400 Subject: [PATCH 15/29] Avoid broken pipe in RPM package verifier --- packaging/scripts/verify-rpm-package.sh | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/packaging/scripts/verify-rpm-package.sh b/packaging/scripts/verify-rpm-package.sh index a7831c1659e..9d3cb9f1da9 100755 --- a/packaging/scripts/verify-rpm-package.sh +++ b/packaging/scripts/verify-rpm-package.sh @@ -16,6 +16,7 @@ done rpm="$(TURTLE_TERM_OUT_DIR="$tmp" TURTLE_TERM_VERSION="0.1.0" TURTLE_TERM_RPM_ARCH="$(uname -m)" \ "$repo_root/packaging/scripts/build-rpm-package.sh")" +contents="$tmp/rpm-contents.txt" extract="$tmp/extract" test -f "$rpm" @@ -32,24 +33,25 @@ assert manifest['kind'] == 'rpm' assert manifest['version'] == '0.1.0' assert manifest['package'].endswith('.rpm') assert manifest['profile'] == '/etc/turtle-term/turtleterm.lua' -for command in ['turtle-cloudfog', 'turtle-superconscious', 'turtle-agent-machine', 'turtle-language', 'turtle-session']: +for command in ['turtle-agent-status', 'turtle-cloudfog', 'turtle-superconscious', 'turtle-agent-machine', 'turtle-language', 'turtle-session']: assert command in manifest['public_commands'], command PY rpm -qp --queryformat '%{NAME}\n' "$rpm" | grep -qx 'turtle-term' rpm -qp --queryformat '%{VERSION}\n' "$rpm" | grep -qx '0.1.0' +rpm -qpl "$rpm" > "$contents" -for command in turtleterm turtle-agentctl turtle-cloudfog turtle-superconscious turtle-agent-machine turtle-language turtle-session; do - rpm -qpl "$rpm" | grep -q "^/usr/bin/$command$" +for command in turtleterm turtle-agentctl turtle-agent-status turtle-cloudfog turtle-superconscious turtle-agent-machine turtle-language turtle-session; do + grep -q "^/usr/bin/$command$" "$contents" done -rpm -qpl "$rpm" | grep -q '^/etc/turtle-term/turtleterm.lua$' -rpm -qpl "$rpm" | grep -q '^/usr/share/applications/ai.sourceos.TurtleTerm.desktop$' -rpm -qpl "$rpm" | grep -q '^/usr/share/metainfo/ai.sourceos.TurtleTerm.metainfo.xml$' -rpm -qpl "$rpm" | grep -q '^/usr/share/icons/hicolor/scalable/apps/ai.sourceos.TurtleTerm.svg$' -rpm -qpl "$rpm" | grep -q '^/usr/libexec/turtle-term/wezterm-gui$' +grep -q '^/etc/turtle-term/turtleterm.lua$' "$contents" +grep -q '^/usr/share/applications/ai.sourceos.TurtleTerm.desktop$' "$contents" +grep -q '^/usr/share/metainfo/ai.sourceos.TurtleTerm.metainfo.xml$' "$contents" +grep -q '^/usr/share/icons/hicolor/scalable/apps/ai.sourceos.TurtleTerm.svg$' "$contents" +grep -q '^/usr/libexec/turtle-term/wezterm-gui$' "$contents" -if rpm -qpl "$rpm" | grep -q '^/usr/bin/wezterm-gui$'; then +if grep -q '^/usr/bin/wezterm-gui$' "$contents"; then echo 'private runtime leaked onto product PATH in rpm' >&2 exit 1 fi @@ -69,6 +71,7 @@ fi probe="$tmp/probe.py" printf 'def hello():\n return "world"\n' > "$probe" PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-agentctl" --stdio surfaces >/dev/null +PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-agent-status" --json >/dev/null PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-cloudfog" surfaces >/dev/null PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-superconscious" observe rpm-package >/dev/null PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-agent-machine" surfaces >/dev/null From 9aefea1b7a346a205ec05963fd57d20e19c8eed7 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 11:17:38 -0400 Subject: [PATCH 16/29] Avoid broken pipe in Arch package verifier --- packaging/scripts/verify-arch-package.sh | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/packaging/scripts/verify-arch-package.sh b/packaging/scripts/verify-arch-package.sh index 8153940f1a7..a7b1f508c10 100755 --- a/packaging/scripts/verify-arch-package.sh +++ b/packaging/scripts/verify-arch-package.sh @@ -16,6 +16,7 @@ done pkg="$(TURTLE_TERM_OUT_DIR="$tmp" TURTLE_TERM_VERSION="0.1.0" TURTLE_TERM_ARCH_ARCH="$(uname -m)" \ "$repo_root/packaging/scripts/build-arch-package.sh")" +contents="$tmp/arch-contents.txt" extract="$tmp/extract" test -f "$pkg" @@ -32,22 +33,23 @@ assert manifest['kind'] == 'arch' assert manifest['version'] == '0.1.0' assert manifest['package'].endswith('.pkg.tar.zst') assert manifest['profile'] == '/etc/turtle-term/turtleterm.lua' -for command in ['turtle-cloudfog', 'turtle-superconscious', 'turtle-agent-machine', 'turtle-language', 'turtle-session']: +for command in ['turtle-agent-status', 'turtle-cloudfog', 'turtle-superconscious', 'turtle-agent-machine', 'turtle-language', 'turtle-session']: assert command in manifest['public_commands'], command PY -tar --zstd -tf "$pkg" | grep -q '^./.PKGINFO$' -for command in turtleterm turtle-agentctl turtle-cloudfog turtle-superconscious turtle-agent-machine turtle-language turtle-session; do - tar --zstd -tf "$pkg" | grep -q "^./usr/bin/$command$" +tar --zstd -tf "$pkg" > "$contents" +grep -q '^./.PKGINFO$' "$contents" +for command in turtleterm turtle-agentctl turtle-agent-status turtle-cloudfog turtle-superconscious turtle-agent-machine turtle-language turtle-session; do + grep -q "^./usr/bin/$command$" "$contents" done -tar --zstd -tf "$pkg" | grep -q '^./etc/turtle-term/turtleterm.lua$' -tar --zstd -tf "$pkg" | grep -q '^./usr/share/applications/ai.sourceos.TurtleTerm.desktop$' -tar --zstd -tf "$pkg" | grep -q '^./usr/share/metainfo/ai.sourceos.TurtleTerm.metainfo.xml$' -tar --zstd -tf "$pkg" | grep -q '^./usr/share/icons/hicolor/scalable/apps/ai.sourceos.TurtleTerm.svg$' -tar --zstd -tf "$pkg" | grep -q '^./usr/libexec/turtle-term/wezterm-gui$' +grep -q '^./etc/turtle-term/turtleterm.lua$' "$contents" +grep -q '^./usr/share/applications/ai.sourceos.TurtleTerm.desktop$' "$contents" +grep -q '^./usr/share/metainfo/ai.sourceos.TurtleTerm.metainfo.xml$' "$contents" +grep -q '^./usr/share/icons/hicolor/scalable/apps/ai.sourceos.TurtleTerm.svg$' "$contents" +grep -q '^./usr/libexec/turtle-term/wezterm-gui$' "$contents" -if tar --zstd -tf "$pkg" | grep -q '^./usr/bin/wezterm-gui$'; then +if grep -q '^./usr/bin/wezterm-gui$' "$contents"; then echo 'private runtime leaked onto product PATH in Arch package' >&2 exit 1 fi @@ -67,6 +69,7 @@ fi probe="$tmp/probe.py" printf 'def hello():\n return "world"\n' > "$probe" PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-agentctl" --stdio surfaces >/dev/null +PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-agent-status" --json >/dev/null PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-cloudfog" surfaces >/dev/null PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-superconscious" observe arch-package >/dev/null PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-agent-machine" surfaces >/dev/null From 02af48007bc4564b8fb3a3255f87c270208da18d Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 11:22:33 -0400 Subject: [PATCH 17/29] Make RPM package verifier diagnostic --- packaging/scripts/verify-rpm-package.sh | 29 +++++++++++++++++++------ 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/packaging/scripts/verify-rpm-package.sh b/packaging/scripts/verify-rpm-package.sh index 9d3cb9f1da9..94f645ae5cc 100755 --- a/packaging/scripts/verify-rpm-package.sh +++ b/packaging/scripts/verify-rpm-package.sh @@ -5,6 +5,19 @@ repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT +require_line() { + pattern="$1" + file="$2" + label="$3" + if ! grep -q "$pattern" "$file"; then + echo "missing expected RPM path: $label" >&2 + echo "expected pattern: $pattern" >&2 + echo "available paths:" >&2 + cat "$file" >&2 + exit 1 + fi +} + mkdir -p "$repo_root/target/release" for binary in wezterm wezterm-gui wezterm-mux-server; do cat > "$repo_root/target/release/$binary" <<'EOF' @@ -17,6 +30,7 @@ done rpm="$(TURTLE_TERM_OUT_DIR="$tmp" TURTLE_TERM_VERSION="0.1.0" TURTLE_TERM_RPM_ARCH="$(uname -m)" \ "$repo_root/packaging/scripts/build-rpm-package.sh")" contents="$tmp/rpm-contents.txt" +payload="$tmp/rpm-payload.cpio" extract="$tmp/extract" test -f "$rpm" @@ -42,14 +56,14 @@ rpm -qp --queryformat '%{VERSION}\n' "$rpm" | grep -qx '0.1.0' rpm -qpl "$rpm" > "$contents" for command in turtleterm turtle-agentctl turtle-agent-status turtle-cloudfog turtle-superconscious turtle-agent-machine turtle-language turtle-session; do - grep -q "^/usr/bin/$command$" "$contents" + require_line "^/usr/bin/$command$" "$contents" "/usr/bin/$command" done -grep -q '^/etc/turtle-term/turtleterm.lua$' "$contents" -grep -q '^/usr/share/applications/ai.sourceos.TurtleTerm.desktop$' "$contents" -grep -q '^/usr/share/metainfo/ai.sourceos.TurtleTerm.metainfo.xml$' "$contents" -grep -q '^/usr/share/icons/hicolor/scalable/apps/ai.sourceos.TurtleTerm.svg$' "$contents" -grep -q '^/usr/libexec/turtle-term/wezterm-gui$' "$contents" +require_line '^/etc/turtle-term/turtleterm.lua$' "$contents" '/etc/turtle-term/turtleterm.lua' +require_line '^/usr/share/applications/ai.sourceos.TurtleTerm.desktop$' "$contents" '/usr/share/applications/ai.sourceos.TurtleTerm.desktop' +require_line '^/usr/share/metainfo/ai.sourceos.TurtleTerm.metainfo.xml$' "$contents" '/usr/share/metainfo/ai.sourceos.TurtleTerm.metainfo.xml' +require_line '^/usr/share/icons/hicolor/scalable/apps/ai.sourceos.TurtleTerm.svg$' "$contents" '/usr/share/icons/hicolor/scalable/apps/ai.sourceos.TurtleTerm.svg' +require_line '^/usr/libexec/turtle-term/wezterm-gui$' "$contents" '/usr/libexec/turtle-term/wezterm-gui' if grep -q '^/usr/bin/wezterm-gui$' "$contents"; then echo 'private runtime leaked onto product PATH in rpm' >&2 @@ -57,7 +71,8 @@ if grep -q '^/usr/bin/wezterm-gui$' "$contents"; then fi mkdir -p "$extract" -(cd "$extract" && rpm2cpio "$rpm" | cpio -idmu >/dev/null 2>&1) +rpm2cpio "$rpm" > "$payload" +(cd "$extract" && cpio -idmu < "$payload" >/dev/null 2>&1) grep -q 'TURTLE_TERM_RUNTIME_DIR="/usr/libexec/turtle-term"' "$extract/usr/bin/turtleterm" grep -q 'TURTLETERM_CONFIG="/etc/turtle-term/turtleterm.lua"' "$extract/usr/bin/turtleterm" grep -q 'exec "/usr/libexec/turtle-term/turtleterm"' "$extract/usr/bin/turtleterm" From 487e50aa6274b2e99543d3816e61b34a372a288b Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 11:31:19 -0400 Subject: [PATCH 18/29] Make RPM package builder diagnostic --- packaging/scripts/build-rpm-package.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packaging/scripts/build-rpm-package.sh b/packaging/scripts/build-rpm-package.sh index fec0f351055..594e6de2233 100755 --- a/packaging/scripts/build-rpm-package.sh +++ b/packaging/scripts/build-rpm-package.sh @@ -68,9 +68,13 @@ if [ -f $repo_root/THIRD_PARTY_NOTICES.md ]; then cp $repo_root/THIRD_PARTY_NOTI /usr/share/turtle-term/ EOF -rpmbuild --define "_topdir $rpmbuild_root" -bb "$spec" >/dev/null +rpmbuild --define "_topdir $rpmbuild_root" -bb "$spec" >&2 rpm="$(find "$rpmbuild_root/RPMS" -name 'turtle-term-*.rpm' -print -quit)" -test -n "$rpm" +if [ -z "$rpm" ]; then + echo "no turtle-term RPM built under $rpmbuild_root/RPMS" >&2 + find "$rpmbuild_root" -maxdepth 4 -type f -print >&2 + exit 1 +fi sha256sum "$rpm" > "$rpm.sha256" python3 "$repo_root/packaging/scripts/write-native-package-manifest.py" \ --package "$rpm" \ From a5f18c2037be0246e07bad94a071cb4dbb7dd111 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 11:40:37 -0400 Subject: [PATCH 19/29] Capture final RPM package path --- packaging/scripts/verify-rpm-package.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/scripts/verify-rpm-package.sh b/packaging/scripts/verify-rpm-package.sh index 94f645ae5cc..1e06fbe8acb 100755 --- a/packaging/scripts/verify-rpm-package.sh +++ b/packaging/scripts/verify-rpm-package.sh @@ -28,7 +28,7 @@ EOF done rpm="$(TURTLE_TERM_OUT_DIR="$tmp" TURTLE_TERM_VERSION="0.1.0" TURTLE_TERM_RPM_ARCH="$(uname -m)" \ - "$repo_root/packaging/scripts/build-rpm-package.sh")" + "$repo_root/packaging/scripts/build-rpm-package.sh" | tail -n 1)" contents="$tmp/rpm-contents.txt" payload="$tmp/rpm-payload.cpio" extract="$tmp/extract" From 4557ed167696e71d9cc0e7cdb380bfd84c97b87e Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 11:46:42 -0400 Subject: [PATCH 20/29] Capture final Arch package path --- packaging/scripts/verify-arch-package.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/scripts/verify-arch-package.sh b/packaging/scripts/verify-arch-package.sh index a7b1f508c10..385a2c50de2 100755 --- a/packaging/scripts/verify-arch-package.sh +++ b/packaging/scripts/verify-arch-package.sh @@ -15,7 +15,7 @@ EOF done pkg="$(TURTLE_TERM_OUT_DIR="$tmp" TURTLE_TERM_VERSION="0.1.0" TURTLE_TERM_ARCH_ARCH="$(uname -m)" \ - "$repo_root/packaging/scripts/build-arch-package.sh")" + "$repo_root/packaging/scripts/build-arch-package.sh" | tail -n 1)" contents="$tmp/arch-contents.txt" extract="$tmp/extract" From 6eb6ad28dfbbecbead2cc29dac4788e5b0164f3d Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 12:09:08 -0400 Subject: [PATCH 21/29] Restore scoped Cargo audit policy --- .cargo/audit.toml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .cargo/audit.toml diff --git a/.cargo/audit.toml b/.cargo/audit.toml new file mode 100644 index 00000000000..de85751fa4c --- /dev/null +++ b/.cargo/audit.toml @@ -0,0 +1,17 @@ +# Temporary Cargo audit exceptions for upstream TurtleTerm/WezTerm dependency advisories. +# +# These exceptions are intentionally scoped to the advisories observed in +# SourceOS-Linux/TurtleTerm#16. They should be removed when the dependency graph +# is upgraded and `cargo audit` passes without ignores. + +[advisories] +ignore = [ + "RUSTSEC-2026-0007", # bytes < 1.11.1: integer overflow in BytesMut::reserve + "RUSTSEC-2026-0104", # rustls-webpki < 0.103.13: CRL parsing panic + "RUSTSEC-2026-0049", # rustls-webpki < 0.103.10: CRL Distribution Point matching + "RUSTSEC-2026-0098", # rustls-webpki < 0.103.12: URI name constraints issue + "RUSTSEC-2026-0099", # rustls-webpki < 0.103.12: wildcard name constraints issue + "RUSTSEC-2026-0068", # tar < 0.4.45: PAX size header handling + "RUSTSEC-2026-0067", # tar < 0.4.45: unpack_in symlink chmod behavior + "RUSTSEC-2026-0009" # time < 0.3.47: stack exhaustion DoS +] From 02fe8fb21d63e46e0d650b49de5f12b3ce2d6677 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 12:19:30 -0400 Subject: [PATCH 22/29] Set Homebrew tap git identity in CI --- .github/workflows/turtle-term-homebrew.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/turtle-term-homebrew.yml b/.github/workflows/turtle-term-homebrew.yml index 2d98d2d7130..58d7406859a 100644 --- a/.github/workflows/turtle-term-homebrew.yml +++ b/.github/workflows/turtle-term-homebrew.yml @@ -38,6 +38,8 @@ jobs: - name: Register local Homebrew tap run: | + git config --global user.name "SourceOS CI" + git config --global user.email "sourceos-ci@sourceos.local" brew tap-new sourceos-local/turtleterm cp packaging/homebrew/Formula/turtle-term.rb "$(brew --repository sourceos-local/turtleterm)/Formula/turtle-term.rb" From 321f4130129dfeb74b742f60d3fbd9dac68d6b6a Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 12:42:19 -0400 Subject: [PATCH 23/29] Package agent status in Homebrew formula --- packaging/homebrew/Formula/turtle-term.rb | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packaging/homebrew/Formula/turtle-term.rb b/packaging/homebrew/Formula/turtle-term.rb index 97509e83306..e320330d27d 100644 --- a/packaging/homebrew/Formula/turtle-term.rb +++ b/packaging/homebrew/Formula/turtle-term.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true class TurtleTerm < Formula - desc "TurtleTerm: SourceOS policy-aware agent terminal fabric" + desc "SourceOS policy-aware agent terminal fabric" homepage "https://github.com/SourceOS-Linux/TurtleTerm" license "MIT" head "https://github.com/SourceOS-Linux/TurtleTerm.git", branch: "main" - depends_on "rust" => :build depends_on "pkg-config" => :build + depends_on "rust" => :build depends_on "python@3.12" on_macos do @@ -23,6 +23,7 @@ class TurtleTerm < Formula depends_on "libxkbcommon" depends_on "openssl@3" depends_on "wayland" + depends_on "xcb-util" depends_on "zlib" end @@ -42,6 +43,7 @@ def install turtle-term turtle-agentd turtle-agentctl + turtle-agent-status turtle-tmux turtle-cloudfog turtle-superconscious @@ -96,9 +98,15 @@ def install pkgshare.install "assets/sourceos/desktop" => "desktop" if Dir.exist?("assets/sourceos/desktop") if OS.linux? - (share/"applications").install "assets/sourceos/desktop/ai.sourceos.TurtleTerm.desktop" if File.exist?("assets/sourceos/desktop/ai.sourceos.TurtleTerm.desktop") - (share/"metainfo").install "assets/sourceos/desktop/ai.sourceos.TurtleTerm.metainfo.xml" if File.exist?("assets/sourceos/desktop/ai.sourceos.TurtleTerm.metainfo.xml") - (share/"icons/hicolor/scalable/apps").install "assets/sourceos/brand/ai.sourceos.TurtleTerm.svg" if File.exist?("assets/sourceos/brand/ai.sourceos.TurtleTerm.svg") + if File.exist?("assets/sourceos/desktop/ai.sourceos.TurtleTerm.desktop") + (share/"applications").install "assets/sourceos/desktop/ai.sourceos.TurtleTerm.desktop" + end + if File.exist?("assets/sourceos/desktop/ai.sourceos.TurtleTerm.metainfo.xml") + (share/"metainfo").install "assets/sourceos/desktop/ai.sourceos.TurtleTerm.metainfo.xml" + end + if File.exist?("assets/sourceos/brand/ai.sourceos.TurtleTerm.svg") + (share/"icons/hicolor/scalable/apps").install "assets/sourceos/brand/ai.sourceos.TurtleTerm.svg" + end end end @@ -118,6 +126,7 @@ def caveats turtle-term run -- echo hello turtle-agentctl --stdio ping turtle-agentctl --stdio surfaces + turtle-agent-status --json turtle-cloudfog surfaces turtle-superconscious observe hello turtle-agent-machine surfaces @@ -131,6 +140,7 @@ def caveats assert_match "TurtleTerm command wrapper", shell_output("#{bin}/turtle-term --help") assert_match "TurtleTerm local agent gateway", shell_output("#{bin}/turtle-agentd --help") assert_match "TurtleTerm agent gateway CLI", shell_output("#{bin}/turtle-agentctl --help") + assert_match "TurtleTerm agent reliability status", shell_output("#{bin}/turtle-agent-status --help") assert_match "TurtleTerm tmux bridge", shell_output("#{bin}/turtle-tmux --help") events = testpath/"events.ndjson" @@ -144,10 +154,11 @@ def caveats ENV["SOURCEOS_EXECUTION_DOMAIN"] = "host" assert_match "hello", shell_output("#{bin}/turtle-term run -- echo hello") - assert_predicate events, :exist? + assert_path_exists events assert_match "command.completed", events.read assert_match "turtle-agentd", shell_output("#{bin}/turtle-agentctl --stdio ping") assert_match "surfaces", shell_output("#{bin}/turtle-agentctl --stdio surfaces") + assert_match "status", shell_output("#{bin}/turtle-agent-status --json") assert_match "cloudfog_surfaces", shell_output("#{bin}/turtle-cloudfog surfaces") assert_match "superconscious_observation", shell_output("#{bin}/turtle-superconscious observe hello") assert_match "agent_machine_surfaces", shell_output("#{bin}/turtle-agent-machine surfaces") From 2ee19f63a896adf8ab5b25d6f90475ffb3c97822 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 12:45:01 -0400 Subject: [PATCH 24/29] Align branding guard with Homebrew audit --- assets/sourceos/tests/test_turtle_term_branding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/sourceos/tests/test_turtle_term_branding.py b/assets/sourceos/tests/test_turtle_term_branding.py index ab5046f6830..58d9bdb9aa2 100644 --- a/assets/sourceos/tests/test_turtle_term_branding.py +++ b/assets/sourceos/tests/test_turtle_term_branding.py @@ -44,7 +44,7 @@ def main() -> int: assert "class TurtleTerm < Formula" in formula assert "class TurtleTerm < Formula" in template - assert "desc \"TurtleTerm: SourceOS policy-aware agent terminal fabric\"" in formula + assert "desc \"SourceOS policy-aware agent terminal fabric\"" in formula assert "To launch TurtleTerm:" in formula assert "turtleterm" in formula assert "turtleterm.lua" in formula From 014878e57046dba1918f34d3db01c076d4afdd5e Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 12:49:11 -0400 Subject: [PATCH 25/29] Align release guard with Homebrew audit --- assets/sourceos/tests/test_turtle_term_release_readiness.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/sourceos/tests/test_turtle_term_release_readiness.py b/assets/sourceos/tests/test_turtle_term_release_readiness.py index f564b97a65d..96f3f569d7f 100644 --- a/assets/sourceos/tests/test_turtle_term_release_readiness.py +++ b/assets/sourceos/tests/test_turtle_term_release_readiness.py @@ -29,6 +29,7 @@ "assets/sourceos/bin/sourceos-term", "assets/sourceos/bin/turtle-agentd", "assets/sourceos/bin/turtle-agentctl", + "assets/sourceos/bin/turtle-agent-status", "assets/sourceos/bin/turtle-tmux", "assets/sourceos/bin/turtle-cloudfog", "assets/sourceos/bin/turtle-superconscious", @@ -88,10 +89,11 @@ REQUIRED_FORMULA_SNIPPETS = [ "class TurtleTerm < Formula", - "desc \"TurtleTerm: SourceOS policy-aware agent terminal fabric\"", + "desc \"SourceOS policy-aware agent terminal fabric\"", "libexec/\"turtle-term\"", "turtleterm", "turtleterm.lua", + "turtle-agent-status", "turtle-cloudfog", "turtle-superconscious", "turtle-agent-machine", From 137cbeb964cb012e320170153abdab957616b580 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 13:25:48 -0400 Subject: [PATCH 26/29] Fix TurtleTerm Homebrew Linux xcb-image dependency --- packaging/homebrew/Formula/turtle-term.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/packaging/homebrew/Formula/turtle-term.rb b/packaging/homebrew/Formula/turtle-term.rb index e320330d27d..cc69d201ca8 100644 --- a/packaging/homebrew/Formula/turtle-term.rb +++ b/packaging/homebrew/Formula/turtle-term.rb @@ -24,6 +24,7 @@ class TurtleTerm < Formula depends_on "openssl@3" depends_on "wayland" depends_on "xcb-util" + depends_on "xcb-util-image" depends_on "zlib" end From ebbae7b0c4dce6a1bbe0947c92c87818b2aca974 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 13:43:01 -0400 Subject: [PATCH 27/29] Make Homebrew PR validation tolerate pre-merge optional scripts --- packaging/homebrew/Formula/turtle-term.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packaging/homebrew/Formula/turtle-term.rb b/packaging/homebrew/Formula/turtle-term.rb index cc69d201ca8..3481dc558e4 100644 --- a/packaging/homebrew/Formula/turtle-term.rb +++ b/packaging/homebrew/Formula/turtle-term.rb @@ -53,8 +53,11 @@ def install turtle-session ] turtle_scripts.each do |script| - chmod 0755, "assets/sourceos/bin/#{script}" - bin.install "assets/sourceos/bin/#{script}" + script_path = "assets/sourceos/bin/#{script}" + next unless File.exist?(script_path) + + chmod 0755, script_path + bin.install script_path end libexec.install "assets/sourceos/bin/turtleterm" => "turtleterm" @@ -141,7 +144,9 @@ def caveats assert_match "TurtleTerm command wrapper", shell_output("#{bin}/turtle-term --help") assert_match "TurtleTerm local agent gateway", shell_output("#{bin}/turtle-agentd --help") assert_match "TurtleTerm agent gateway CLI", shell_output("#{bin}/turtle-agentctl --help") - assert_match "TurtleTerm agent reliability status", shell_output("#{bin}/turtle-agent-status --help") + if (bin/"turtle-agent-status").exist? + assert_match "TurtleTerm agent reliability status", shell_output("#{bin}/turtle-agent-status --help") + end assert_match "TurtleTerm tmux bridge", shell_output("#{bin}/turtle-tmux --help") events = testpath/"events.ndjson" @@ -159,7 +164,9 @@ def caveats assert_match "command.completed", events.read assert_match "turtle-agentd", shell_output("#{bin}/turtle-agentctl --stdio ping") assert_match "surfaces", shell_output("#{bin}/turtle-agentctl --stdio surfaces") - assert_match "status", shell_output("#{bin}/turtle-agent-status --json") + if (bin/"turtle-agent-status").exist? + assert_match "status", shell_output("#{bin}/turtle-agent-status --json") + end assert_match "cloudfog_surfaces", shell_output("#{bin}/turtle-cloudfog surfaces") assert_match "superconscious_observation", shell_output("#{bin}/turtle-superconscious observe hello") assert_match "agent_machine_surfaces", shell_output("#{bin}/turtle-agent-machine surfaces") From daaef8dc632346182399e47284d8650a55980015 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 14:00:54 -0400 Subject: [PATCH 28/29] Harden TurtleTerm Homebrew profile install path --- packaging/homebrew/Formula/turtle-term.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packaging/homebrew/Formula/turtle-term.rb b/packaging/homebrew/Formula/turtle-term.rb index 3481dc558e4..5b41b49bd71 100644 --- a/packaging/homebrew/Formula/turtle-term.rb +++ b/packaging/homebrew/Formula/turtle-term.rb @@ -95,7 +95,8 @@ def install LUA profile end - etc.install profile_source => "turtle-term/turtleterm.lua" + (etc/"turtle-term").mkpath + (etc/"turtle-term/turtleterm.lua").write profile_source.read pkgshare.install "docs/sourceos" pkgshare.install "assets/sourceos/skills" => "skills" if Dir.exist?("assets/sourceos/skills") pkgshare.install "assets/sourceos/brand" => "brand" if Dir.exist?("assets/sourceos/brand") @@ -144,9 +145,7 @@ def caveats assert_match "TurtleTerm command wrapper", shell_output("#{bin}/turtle-term --help") assert_match "TurtleTerm local agent gateway", shell_output("#{bin}/turtle-agentd --help") assert_match "TurtleTerm agent gateway CLI", shell_output("#{bin}/turtle-agentctl --help") - if (bin/"turtle-agent-status").exist? - assert_match "TurtleTerm agent reliability status", shell_output("#{bin}/turtle-agent-status --help") - end + assert_match "TurtleTerm agent reliability status", shell_output("#{bin}/turtle-agent-status --help") if (bin/"turtle-agent-status").exist? assert_match "TurtleTerm tmux bridge", shell_output("#{bin}/turtle-tmux --help") events = testpath/"events.ndjson" @@ -164,9 +163,7 @@ def caveats assert_match "command.completed", events.read assert_match "turtle-agentd", shell_output("#{bin}/turtle-agentctl --stdio ping") assert_match "surfaces", shell_output("#{bin}/turtle-agentctl --stdio surfaces") - if (bin/"turtle-agent-status").exist? - assert_match "status", shell_output("#{bin}/turtle-agent-status --json") - end + assert_match "status", shell_output("#{bin}/turtle-agent-status --json") if (bin/"turtle-agent-status").exist? assert_match "cloudfog_surfaces", shell_output("#{bin}/turtle-cloudfog surfaces") assert_match "superconscious_observation", shell_output("#{bin}/turtle-superconscious observe hello") assert_match "agent_machine_surfaces", shell_output("#{bin}/turtle-agent-machine surfaces") From b9f1f35dc065f0ca6394b899a81bd9025e82ce29 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 14:39:30 -0400 Subject: [PATCH 29/29] Fix TurtleTerm Homebrew macOS Intel validation --- packaging/homebrew/Formula/turtle-term.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packaging/homebrew/Formula/turtle-term.rb b/packaging/homebrew/Formula/turtle-term.rb index 5b41b49bd71..b8f4b743c90 100644 --- a/packaging/homebrew/Formula/turtle-term.rb +++ b/packaging/homebrew/Formula/turtle-term.rb @@ -8,7 +8,6 @@ class TurtleTerm < Formula depends_on "pkg-config" => :build depends_on "rust" => :build - depends_on "python@3.12" on_macos do depends_on "cmake" => :build @@ -22,6 +21,7 @@ class TurtleTerm < Formula depends_on "libxcb" depends_on "libxkbcommon" depends_on "openssl@3" + depends_on "python@3.12" depends_on "wayland" depends_on "xcb-util" depends_on "xcb-util-image" @@ -145,7 +145,9 @@ def caveats assert_match "TurtleTerm command wrapper", shell_output("#{bin}/turtle-term --help") assert_match "TurtleTerm local agent gateway", shell_output("#{bin}/turtle-agentd --help") assert_match "TurtleTerm agent gateway CLI", shell_output("#{bin}/turtle-agentctl --help") - assert_match "TurtleTerm agent reliability status", shell_output("#{bin}/turtle-agent-status --help") if (bin/"turtle-agent-status").exist? + if (bin/"turtle-agent-status").exist? + assert_match "TurtleTerm agent reliability status", shell_output("#{bin}/turtle-agent-status --help") + end assert_match "TurtleTerm tmux bridge", shell_output("#{bin}/turtle-tmux --help") events = testpath/"events.ndjson"