diff --git a/assets/sourceos/tests/test_cloudshell_fog_receipt_context.py b/assets/sourceos/tests/test_cloudshell_fog_receipt_context.py new file mode 100644 index 00000000000..b4116ae0adf --- /dev/null +++ b/assets/sourceos/tests/test_cloudshell_fog_receipt_context.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +"""Validate TurtleTerm receipt context propagation for CloudShell FOG sessions.""" + +from __future__ import annotations + +import json +import os +import subprocess +import sys +import tempfile +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[3] +TURTLE_WRAPPER = REPO_ROOT / "assets" / "sourceos" / "bin" / "turtle-term" + + +def read_ndjson(path: Path) -> list[dict]: + return [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip()] + + +def main() -> int: + with tempfile.TemporaryDirectory() as tmp: + tmp_path = Path(tmp) + events = tmp_path / "events.ndjson" + receipts = tmp_path / "receipts" + + env = dict(os.environ) + env.update( + { + "SOURCEOS_TERMINAL_SESSION_ID": "csf-session-0001", + "SOURCEOS_WORKSPACE": "workspace:lattice-demo", + "SOURCEOS_TERMINAL_EVENTS": str(events), + "SOURCEOS_TERMINAL_RECEIPTS": str(receipts), + "SOURCEOS_ACTOR_ID": "human:operator@example.com", + "SOURCEOS_POLICY_BUNDLE_ID": "policy:cloudshell-default", + "SOURCEOS_EXECUTION_DOMAIN": "cloudshell-fog/k8s", + } + ) + + result = subprocess.run( + [sys.executable, str(TURTLE_WRAPPER), "run", "--", sys.executable, "-c", "print('cloudshell-fog-ok')"], + cwd=str(REPO_ROOT), + env=env, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + + assert result.returncode == 0, result.stderr + assert "cloudshell-fog-ok" in result.stdout + assert events.exists(), "event stream missing" + + rows = read_ndjson(events) + completed = [row for row in rows if row.get("event_type") == "command.completed"][-1] + receipt_path = Path(completed["receipt_path"]) + assert receipt_path.exists(), f"receipt missing: {receipt_path}" + + receipt = json.loads(receipt_path.read_text(encoding="utf-8")) + assert receipt["schema"] == "sourceos.terminal.receipt.v0" + assert receipt["session_id"] == "csf-session-0001" + assert receipt["workspace_id"] == "workspace:lattice-demo" + assert receipt["actor_id"] == "human:operator@example.com" + assert receipt["policy_bundle_id"] == "policy:cloudshell-default" + assert receipt["execution_domain"] == "cloudshell-fog/k8s" + assert receipt["stdout_digest"].startswith("sha256:") + assert receipt["stderr_digest"].startswith("sha256:") + + print("validated CloudShell FOG receipt context propagation") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/docs/integration/cloudshell-fog.md b/docs/integration/cloudshell-fog.md new file mode 100644 index 00000000000..8567e1a4232 --- /dev/null +++ b/docs/integration/cloudshell-fog.md @@ -0,0 +1,12 @@ +# CloudShell FOG Integration Note + +This document records the TurtleTerm-side integration point for CloudShell FOG. + +TurtleTerm preserves SourceOS terminal receipt context. +CloudShell FOG provides session, placement, runtime, and audit context. + +When both systems are used together, shared identifiers should allow receipts and session records to be correlated. + +Tracked by: +- TurtleTerm issue #1 +- SocioProphet/cloudshell-fog issue #35 diff --git a/packaging/homebrew/Formula/turtle-term.rb b/packaging/homebrew/Formula/turtle-term.rb index b8f4b743c90..f80064b068e 100644 --- a/packaging/homebrew/Formula/turtle-term.rb +++ b/packaging/homebrew/Formula/turtle-term.rb @@ -146,7 +146,7 @@ def caveats 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") + assert_match "SourceOS agent reliability artifacts", shell_output("#{bin}/turtle-agent-status --help") end assert_match "TurtleTerm tmux bridge", shell_output("#{bin}/turtle-tmux --help") diff --git a/packaging/scripts/build-rpm-package.sh b/packaging/scripts/build-rpm-package.sh index 594e6de2233..b2ddd995c59 100755 --- a/packaging/scripts/build-rpm-package.sh +++ b/packaging/scripts/build-rpm-package.sh @@ -40,7 +40,7 @@ session ergonomics, and reproducible operator workflows. %install rm -rf %{buildroot} -TURTLE_TERM_STAGE_PREFIX=%{buildroot}/usr TURTLE_TERM_ETC_DIR=%{buildroot}/etc TURTLE_TERM_RUNTIME_PREFIX=/usr TURTLE_TERM_RUNTIME_ETC_DIR=/etc $repo_root/packaging/scripts/stage-linux-package.sh >/dev/null +TURTLE_TERM_STAGE_PREFIX=%{buildroot}/usr TURTLE_TERM_ETC_DIR=%{buildroot}/etc TURTLE_TERM_RUNTIME_PREFIX=/usr TURTLE_TERM_RUNTIME_ETC_DIR=/etc bash $repo_root/packaging/scripts/stage-linux-package.sh >/dev/null cp $repo_root/LICENSE.md %{buildroot}/LICENSE.md if [ -f $repo_root/THIRD_PARTY_NOTICES.md ]; then cp $repo_root/THIRD_PARTY_NOTICES.md %{buildroot}/THIRD_PARTY_NOTICES.md; fi @@ -83,4 +83,4 @@ python3 "$repo_root/packaging/scripts/write-native-package-manifest.py" \ --arch "$arch" \ --out "$rpm.manifest.json" -echo "$rpm" +echo "$rpm" \ No newline at end of file diff --git a/packaging/scripts/verify-linux-package-layout.sh b/packaging/scripts/verify-linux-package-layout.sh index 9f0fe531aa2..910ec607172 100644 --- a/packaging/scripts/verify-linux-package-layout.sh +++ b/packaging/scripts/verify-linux-package-layout.sh @@ -15,7 +15,7 @@ EOF done prefix="$tmp/prefix" -TURTLE_TERM_STAGE_PREFIX="$prefix" "$repo_root/packaging/scripts/stage-linux-package.sh" >/dev/null +TURTLE_TERM_STAGE_PREFIX="$prefix" bash "$repo_root/packaging/scripts/stage-linux-package.sh" >/dev/null required_paths=( "$prefix/bin/turtleterm" @@ -74,4 +74,4 @@ printf 'def hello():\n return "world"\n' > "$probe" "$prefix/bin/turtle-language" diagnostics "$probe" >/dev/null "$prefix/bin/turtle-language" symbols "$probe" >/dev/null -echo "verified TurtleTerm Linux package layout at $prefix" +echo "verified TurtleTerm Linux package layout at $prefix" \ No newline at end of file