diff --git a/assets/sourceos/bin/turtle-agent-machine b/assets/sourceos/bin/turtle-agent-machine index db703b3383b..b0589cb0759 100644 --- a/assets/sourceos/bin/turtle-agent-machine +++ b/assets/sourceos/bin/turtle-agent-machine @@ -2,21 +2,24 @@ # TurtleTerm Agent Machine bridge. set -eu +bin_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +agentctl="$bin_dir/turtle-agentctl" + cmd="${1:-surfaces}" case "$cmd" in surfaces) shift || true - exec turtle-agentctl --stdio agent-machine-surfaces "$@" + exec "$agentctl" --stdio agent-machine-surfaces "$@" ;; probe) shift || true - exec turtle-agentctl --stdio agent-machine-probe "$@" + exec "$agentctl" --stdio agent-machine-probe "$@" ;; request-execution) shift || true surface="${1:-agent-machine/local-agentpod}" shift || true - exec turtle-agentctl --stdio request-surface-execution "$surface" "$@" + exec "$agentctl" --stdio request-surface-execution "$surface" "$@" ;; *) echo "usage: turtle-agent-machine [surfaces|probe|request-execution -- ]" >&2 diff --git a/assets/sourceos/bin/turtle-cloudfog b/assets/sourceos/bin/turtle-cloudfog index 2d2a3587bf0..45eb5e9de42 100644 --- a/assets/sourceos/bin/turtle-cloudfog +++ b/assets/sourceos/bin/turtle-cloudfog @@ -2,21 +2,24 @@ # TurtleTerm CloudFog surface bridge. set -eu +bin_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +agentctl="$bin_dir/turtle-agentctl" + cmd="${1:-surfaces}" case "$cmd" in surfaces) shift || true - exec turtle-agentctl --stdio cloudfog-surfaces "$@" + exec "$agentctl" --stdio cloudfog-surfaces "$@" ;; inspect) shift || true - exec turtle-agentctl --stdio cloudfog-inspect "$@" + exec "$agentctl" --stdio cloudfog-inspect "$@" ;; request-execution) shift || true surface="${1:-cloudfog/local-devshell}" shift || true - exec turtle-agentctl --stdio request-surface-execution "$surface" "$@" + exec "$agentctl" --stdio request-surface-execution "$surface" "$@" ;; *) echo "usage: turtle-cloudfog [surfaces|inspect |request-execution -- ]" >&2 diff --git a/assets/sourceos/bin/turtle-superconscious b/assets/sourceos/bin/turtle-superconscious index b895d984fb5..91471a1d77c 100644 --- a/assets/sourceos/bin/turtle-superconscious +++ b/assets/sourceos/bin/turtle-superconscious @@ -2,15 +2,18 @@ # TurtleTerm Superconscious bridge. set -eu +bin_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +agentctl="$bin_dir/turtle-agentctl" + cmd="${1:-observe}" case "$cmd" in observe) shift || true - exec turtle-agentctl --stdio superconscious-observe "$@" + exec "$agentctl" --stdio superconscious-observe "$@" ;; propose) shift || true - exec turtle-agentctl --stdio superconscious-propose "$@" + exec "$agentctl" --stdio superconscious-propose "$@" ;; *) echo "usage: turtle-superconscious [observe |propose ]" >&2 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..edd4ebe305c --- /dev/null +++ b/docs/integration/cloudshell-fog.md @@ -0,0 +1,86 @@ +# CloudShell FOG Integration Profile v0 — TurtleTerm + +## Purpose + +TurtleTerm is the SourceOS policy-aware, agent-addressable terminal workbench for trusted command execution, terminal receipts, agent delegation, and reproducible operator workflows. + +CloudShell FOG is the browser/fog/cloud shell execution plane for session lifecycle, placement, PTY attach, runtime allocation, and audit. + +This profile defines how TurtleTerm should integrate with CloudShell FOG while preserving TurtleTerm as the owner of terminal command receipt semantics. + +## Ownership boundary + +### TurtleTerm owns + +- local/operator command lifecycle receipts +- SourceOS terminal session/event/receipt schemas +- stdout/stderr digest capture for command execution +- operator terminal event stream and receipt paths +- local agent gateway and terminal CLI behavior + +### CloudShell FOG owns + +- browser/fog/cloud shell session lifecycle +- WSS PTY attach contract +- fog/cloud placement metadata +- Kubernetes/fog runtime connector behavior +- CloudShell audit events +- runtime allocation and teardown semantics + +## Integration principle + +TurtleTerm should expose or preserve receipt metadata that allows CloudShell FOG sessions and audit events to correlate with local/operator command receipts. + +TurtleTerm should not absorb CloudShell FOG's placement engine or runtime connector responsibilities. + +## Environment propagation + +When a TurtleTerm workflow is launched in the context of a CloudShell FOG session, the launcher MAY set: + +- `SOURCEOS_TERMINAL_SESSION_ID` = CloudShell session ID or derived stable terminal session ID +- `SOURCEOS_WORKSPACE` = CloudShell workspace or project identifier, if known +- `SOURCEOS_ACTOR_ID` = CloudShell authenticated subject or mapped SourceOS actor identity +- `SOURCEOS_POLICY_BUNDLE_ID` = CloudShell policy/profile identifier +- `SOURCEOS_EXECUTION_DOMAIN` = `cloudshell-fog`, `k8s`, `fog`, or a more specific domain + +TurtleTerm should preserve these values in session/event/receipt outputs rather than rewriting them with local-only defaults. + +## Receipt correlation fields + +Where available, CloudShell FOG metadata SHOULD be attached to TurtleTerm receipt context: + +- CloudShell session ID +- CloudShell placement region +- CloudShell placement node ID +- CloudShell trust tier +- CloudShell placement reasons +- runtime image or runtime profile +- runtime namespace/pod identity when applicable + +## Event mapping + +| TurtleTerm / SourceOS terminal concept | CloudShell FOG concept | +|---|---| +| `sourceos.terminal.session.v0` | `session.created` / shell session context | +| `command.started` | command execution within attached shell context | +| `command.completed` | completed command plus receipt pointer | +| command stdout/stderr digests | command-level evidence, not PTY stream replacement | +| `execution_domain` | CloudShell runtime / placement execution domain | +| policy bundle ID | CloudShell policy/profile context | + +## Non-goals + +- TurtleTerm does not replace CloudShell FOG's browser shell or WSS PTY contract. +- TurtleTerm does not own CloudShell FOG's Kubernetes/fog placement engine. +- CloudShell FOG does not need to capture every PTY byte as a TurtleTerm command receipt by default. + +## Open questions + +1. Should SourceOS terminal schemas remain in TurtleTerm or move to a shared terminal-contracts repository? +2. Should TurtleTerm support a `cloudshell-fog` receipt enrichment mode with explicit placement fields? +3. Should AgentPlane become the canonical bridge for launching TurtleTerm-backed workflows from CloudShell FOG? + +## Tracking + +- TurtleTerm: issue #1 +- CloudShell FOG: SocioProphet/cloudshell-fog#35 diff --git a/packaging/scripts/build-rpm-package.sh b/packaging/scripts/build-rpm-package.sh index 1754b7e413d..da39d8df084 100644 --- a/packaging/scripts/build-rpm-package.sh +++ b/packaging/scripts/build-rpm-package.sh @@ -40,7 +40,7 @@ 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