Add Remote session foundations and feasibility checks

Add opt-in Tentacle protocol 1.1, explicit remote grants, lifecycle IPC and expiring core leases in both SDKs. Revoke access before update preparation, including failed updates.

Add transient secret entries, shared Braille cell export and isolated Xorg recovery experiments with focused tests and documented limitations. Production consent, desktop services and live interoperability remain pending.
This commit is contained in:
ak47 committed 2026-11-03 22:33:26 -05:00
1 parent a47253d697
commit 573d15d7b4
45 files changed
+3634 -80

No files matched your search

+418
View File
@@ -0,0 +1,418 @@
// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package tentacle
import (
"bufio"
"context"
"encoding/json"
"errors"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
"git.stormux.org/storm/cthulhu/internal/tentaclewire"
sdk "git.stormux.org/storm/cthulhu/sdk/go/tentacle"
)
func TestSessionSDKChild(t *testing.T) {
if len(os.Args) < 2 || os.Args[len(os.Args)-1] != "session-sdk-child" {
return
}
client := sdk.NewSessionClient()
commands := []sdk.Command{
{ID: "large", Title: "Large", Handle: func(ctx context.Context, args json.RawMessage) (any, error) { return strings.Repeat("x", 48*1024), nil }},
{ID: "request", Title: "Request", Handle: func(ctx context.Context, args json.RawMessage) (any, error) {
var request struct {
Method string `json:"method"`
Params json.RawMessage `json:"params"`
}
if err := json.Unmarshal(args, &request); err != nil {
return nil, err
}
var value json.RawMessage
err := client.Call(ctx, request.Method, request.Params, &value)
var failure *sdk.SessionError
if errors.As(err, &failure) {
return map[string]any{"code": failure.Code}, nil
}
return map[string]any{"value": value}, err
}},
{ID: "event", Title: "Event", Handle: func(ctx context.Context, args json.RawMessage) (any, error) {
select {
case event := <-client.Events():
return event, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}},
{ID: "wait", Title: "Wait", Handle: func(ctx context.Context, args json.RawMessage) (any, error) { <-ctx.Done(); return nil, ctx.Err() }},
}
if sdk.ServeSession(os.Stdin, os.Stdout, commands, nil, client) != nil {
os.Exit(1)
}
os.Exit(0)
}
func sessionSDKProgram(t *testing.T, language string) []string {
t.Helper()
if language == "go" {
executable, err := os.Executable()
if err != nil {
t.Fatal(err)
}
return []string{executable, "-test.run=^TestSessionSDKChild$", "--", "session-sdk-child"}
}
python, err := exec.LookPath("python3")
if err != nil {
t.Fatal("Python is required for session SDK conformance")
}
peer, err := filepath.Abs("testdata/session_peer.py")
if err != nil {
t.Fatal(err)
}
return []string{python, "-B", peer}
}
func startSessionSDK(t *testing.T, manager *Manager, language, id string) *Session {
t.Helper()
session, err := manager.Start(t.Context(), Spec{ID: id, Program: sessionSDKProgram(t, language), SessionEvents: true, RemoteGrants: []string{"output-observe", "remote-present", "clipboard-exchange"}})
if err != nil {
t.Fatal(err)
}
wait_signal(t, session.Ready())
if err := session.Activate(); err != nil {
state, reason := session.Status()
t.Fatalf("activate: %v, %s %v", err, state, reason)
}
return session
}
func invokeSessionSDK(t *testing.T, session *Session, command string, params any) json.RawMessage {
t.Helper()
data, err := json.Marshal(params)
if err != nil {
t.Fatal(err)
}
pending, err := session.Invoke(t.Context(), command, data)
if err != nil {
t.Fatal(err)
}
result := wait_result(t, pending)
if result.Err != nil {
t.Fatal(result.Err)
}
return result.Value
}
func remoteCall(t *testing.T, session *Session, method string, params any) (json.RawMessage, int) {
t.Helper()
raw := invokeSessionSDK(t, session, "request", map[string]any{"method": method, "params": params})
var result struct {
Value json.RawMessage `json:"value"`
Code int `json:"code"`
}
if err := json.Unmarshal(raw, &result); err != nil {
t.Fatal(err)
}
return result.Value, result.Code
}
func openRemote(t *testing.T, session *Session) tentaclewire.RemoteLease {
t.Helper()
action, err := session.AuthorizeRemote("controlled", []string{"output-observe"})
if err != nil {
t.Fatal(err)
}
raw := invokeSessionSDK(t, session, "event", nil)
var event tentaclewire.SessionEvent
if json.Unmarshal(raw, &event) != nil || event.Action != action.Action || event.Kind != "remote.action" {
t.Fatal("lost local action", string(raw))
}
value, code := remoteCall(t, session, "remote.open", tentaclewire.RemoteOpen{Action: event.Action, Epoch: event.Epoch, Role: event.Role, Grants: event.Grants})
var lease tentaclewire.RemoteLease
if code != 0 || json.Unmarshal(value, &lease) != nil || lease.Lease == "" || lease.Epoch != event.Epoch {
t.Fatal("open", code, string(value))
}
return lease
}
func TestSessionSDKLifecycleConformance(t *testing.T) {
for _, language := range []string{"go", "python"} {
t.Run(language, func(t *testing.T) {
limits := DefaultLimits()
limits.RequestTimeout = 5 * time.Second
manager := test_manager(t, limits, nil)
session := startSessionSDK(t, manager, language, "session-sdk")
if _, code := remoteCall(t, session, "remote.open", tentaclewire.RemoteOpen{Action: strings.Repeat("a", 32), Epoch: 1, Role: "controlled", Grants: []string{"output-observe"}}); code != -32001 {
t.Fatal("child minted consent", code)
}
if _, err := session.AuthorizeRemote("controller", []string{"keyboard-capture"}); !errors.Is(err, ErrPermission) {
t.Fatal("ungranted approval", err)
}
if _, err := session.AuthorizeRemote("controller", []string{"output-observe"}); !errors.Is(err, ErrPermission) {
t.Fatal("wrong role", err)
}
lease := openRemote(t, session)
delivery, err := session.RemoteContext(lease, "output-observe")
if err != nil {
t.Fatal(err)
}
if _, err := session.RemoteContext(lease, "clipboard-exchange"); !errors.Is(err, ErrPermission) {
t.Fatal("install grant became session grant")
}
if _, code := remoteCall(t, session, "remote.key", map[string]any{}); code != -32601 {
t.Fatal("unimplemented desktop operation accepted")
}
// One blocked handler cannot block the endpoint receive loop or lifecycle calls.
waiting, err := session.Invoke(t.Context(), "wait", nil)
if err != nil {
t.Fatal(err)
}
heartbeat := tentaclewire.RemoteOperation{Lease: lease.Lease, Epoch: lease.Epoch, Sequence: 1}
if _, code := remoteCall(t, session, "remote.heartbeat", heartbeat); code != 0 {
t.Fatal("heartbeat", code)
}
if _, code := remoteCall(t, session, "remote.heartbeat", heartbeat); code != -32001 {
t.Fatal("duplicate operation accepted")
}
heartbeat.Sequence++
if _, code := remoteCall(t, session, "remote.close", heartbeat); code != 0 {
t.Fatal("close", code)
}
if delivery.Err() == nil {
t.Fatal("buffered delivery survived close")
}
var event tentaclewire.SessionEvent
raw := invokeSessionSDK(t, session, "event", nil)
if json.Unmarshal(raw, &event) != nil || event.Kind != "remote.revoked" || event.Reason != "closed" {
t.Fatal("missing revocation", string(raw))
}
if _, code := remoteCall(t, session, "remote.close", heartbeat); code != 0 {
t.Fatal("close not idempotent")
}
newLease := openRemote(t, session)
if newLease.Epoch <= lease.Epoch {
t.Fatal("epoch reused")
}
if _, code := remoteCall(t, session, "remote.heartbeat", heartbeat); code != -32001 {
t.Fatal("stale heartbeat accepted")
}
if _, code := remoteCall(t, session, "remote.close", heartbeat); code != 0 {
t.Fatal("stale close failed")
}
current, err := session.RemoteContext(newLease, "output-observe")
if err != nil {
t.Fatal("stale close revoked new lease")
}
session.Disable()
if current.Err() == nil {
t.Fatal("disable left lease live")
}
if result := wait_result(t, waiting); result.Err == nil {
t.Fatal("blocked handler survived disable")
}
wait_signal(t, session.Done())
})
}
}
func TestSessionLeaseExpiryAndExclusiveOwner(t *testing.T) {
limits := DefaultLimits()
limits.RequestTimeout = 5 * time.Second
manager := test_manager(t, limits, nil)
first := startSessionSDK(t, manager, "go", "first")
second := startSessionSDK(t, manager, "python", "second")
lease := openRemote(t, first)
delivery, err := first.RemoteContext(lease, "output-observe")
if err != nil {
t.Fatal(err)
}
if _, err := second.AuthorizeRemote("controlled", []string{"output-observe"}); !errors.Is(err, ErrBusy) {
t.Fatal("two owners", err)
}
select {
case <-delivery.Done():
case <-time.After(RemoteHeartbeatTimeout + time.Second):
t.Fatal("heartbeat did not expire")
}
if _, code := remoteCall(t, first, "remote.heartbeat", tentaclewire.RemoteOperation{Lease: lease.Lease, Epoch: lease.Epoch, Sequence: 1}); code != -32001 {
t.Fatal("expired lease revived")
}
openRemote(t, second)
}
func TestSessionSDKCallTimeout(t *testing.T) {
for _, blockedWriter := range []bool{false, true} {
for _, language := range []string{"go", "python"} {
t.Run(language, func(t *testing.T) {
program := sessionSDKProgram(t, language)
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
command := exec.CommandContext(ctx, program[0], program[1:]...)
input, err := command.StdinPipe()
if err != nil {
t.Fatal(err)
}
output, err := command.StdoutPipe()
if err != nil {
t.Fatal(err)
}
if err := command.Start(); err != nil {
t.Fatal(err)
}
defer input.Close()
writer := json.NewEncoder(input)
reader := bufio.NewReader(output)
writer.Encode(map[string]any{"jsonrpc": "2.0", "id": "1", "method": "initialize", "params": Initialization{Protocol: Version{1, 1}, Session: "test", APIs: []string{"commands", "session-events"}, Capabilities: []string{}}})
if _, err := read_frame(reader); err != nil {
t.Fatal(err)
}
writer.Encode(map[string]any{"jsonrpc": "2.0", "method": "initialized", "params": map[string]string{"session": "test"}})
requestID := "2"
if blockedWriter {
writer.Encode(map[string]any{"jsonrpc": "2.0", "id": "2", "method": "command.invoke", "params": map[string]any{"command": "large", "arguments": nil}})
requestID = "3"
}
writer.Encode(map[string]any{"jsonrpc": "2.0", "id": requestID, "method": "command.invoke", "params": map[string]any{"command": "request", "arguments": map[string]any{"method": "remote.heartbeat", "params": map[string]any{}}}})
if !blockedWriter {
frame, err := read_frame(reader)
if err != nil || !strings.Contains(string(frame), `"id":"c:1"`) {
t.Fatal("missing child request", string(frame), err)
}
}
// Keep stdin open and never reply: SDK must wake a blocked receive loop.
_ = command.Wait()
if ctx.Err() != nil {
t.Fatal("SDK stayed blocked after request timeout")
}
})
}
}
}
func TestSessionQueueOverflowRevokesLease(t *testing.T) {
manager := test_manager(t, DefaultLimits(), nil)
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
output, revoke := context.WithCancel(ctx)
session := &Session{manager: manager, spec: Spec{SessionEvents: true, RemoteGrants: []string{"output-observe"}}, ctx: ctx, cancel: cancel, outputContext: output, revokeOutput: revoke, state: Active, outbound: make(chan outgoing, 1), revoked: make(chan struct{})}
action, err := session.AuthorizeRemote("controlled", []string{"output-observe"})
if err != nil {
t.Fatal(err)
}
params, _ := json.Marshal(tentaclewire.RemoteOpen{Action: action.Action, Epoch: action.Epoch, Role: action.Role, Grants: action.Grants})
session.mutex.Lock()
value, failure := session.remote_call_locked("remote.open", params)
session.mutex.Unlock()
if failure != nil {
t.Fatal(failure)
}
delivery, err := session.RemoteContext(value.(tentaclewire.RemoteLease), "output-observe")
if err != nil {
t.Fatal(err)
}
session.RevokeRemote() // Revocation cannot be queued behind the unread action.
if delivery.Err() == nil {
t.Fatal("queue blocked immediate revocation")
}
if state, _ := session.Status(); state != Failed {
t.Fatal("overflow did not retire session", state)
}
}
func TestSessionHostRejectsMalformedChildRequests(t *testing.T) {
for _, frame := range []string{
`{"jsonrpc":"2.0","id":"1","method":"remote.close","params":{}}`,
`{"jsonrpc":"2.0","id":"c:01","method":"remote.close","params":{}}`,
`{"jsonrpc":"2.0","id":"c:0","method":"remote.close","params":{}}`,
`{"jsonrpc":"2.0","id":null,"method":"remote.close","params":{}}`,
`{"jsonrpc":"2.0","id":"c:1","method":"remote.close","method":"remote.open","params":{}}`,
`{"jsonrpc":"2.0","id":"c:1","method":"remote.close","params":{},"result":{}}`,
`{"jsonrpc":"2.0","id":"c:1","method":"remote.close"}`,
} {
t.Run(frame, func(t *testing.T) {
session := &Session{spec: Spec{SessionEvents: true}, ctx: t.Context(), state: Active, outbound: make(chan outgoing, 32)}
if err := session.accept_child_request([]byte(frame)); err == nil {
t.Fatal("malformed request accepted")
}
})
}
session := &Session{spec: Spec{SessionEvents: true}, ctx: t.Context(), state: Active, outbound: make(chan outgoing, 32)}
frame := []byte(`{"jsonrpc":"2.0","id":"c:1","method":"remote.unknown","params":{}}`)
if err := session.accept_child_request(frame); err != nil {
t.Fatal(err)
}
reply, err := decode_response((<-session.outbound).frame)
if err != nil || reply.Error == nil || reply.Error.Code != -32601 {
t.Fatal("unknown method response", reply, err)
}
if err := session.accept_child_request(frame); err == nil {
t.Fatal("duplicate child ID accepted")
}
session.spec.SessionEvents = false
if err := session.accept_child_request([]byte(`{"jsonrpc":"2.0","id":"c:2","method":"remote.close","params":{}}`)); err == nil {
t.Fatal("1.0 accepted child request")
}
}
func TestSessionSDKRejectsMalformedHostMessages(t *testing.T) {
initialize := `{"jsonrpc":"2.0","id":"1","method":"initialize","params":{"protocol":{"major":1,"minor":1},"session":"test","apis":["commands","session-events"],"capabilities":[]}}` + "\n"
activate := `{"jsonrpc":"2.0","method":"initialized","params":{"session":"test"}}` + "\n"
event := `{"jsonrpc":"2.0","method":"session.event","params":{"sequence":1,"kind":"remote.action","action":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","epoch":1,"role":"controlled","grants":["output-observe"]}}` + "\n"
cases := map[string]string{
"null capabilities": strings.Replace(initialize, `"capabilities":[]`, `"capabilities":null`, 1),
"null event field": initialize + activate + strings.Replace(event, `"epoch":1`, `"epoch":1,"lease":null`, 1),
"old host": strings.Replace(initialize, `"minor":1`, `"minor":0`, 1),
"missing event API": strings.Replace(initialize, `"commands","session-events"`, `"commands"`, 1),
"legacy capabilities": strings.Replace(initialize, `"capabilities":[]`, `"capabilities":["commands"]`, 1),
"unrequested response": initialize + activate + `{"jsonrpc":"2.0","id":"c:1","result":null}` + "\n",
"event before activation": initialize + event,
"event with request ID": initialize + activate + strings.Replace(event, `"method":`, `"id":"2","method":`, 1),
"duplicate event": initialize + activate + event + event,
"skipped sequence": initialize + activate + strings.Replace(event, `"sequence":1`, `"sequence":2`, 1),
"event role mismatch": initialize + activate + strings.Replace(event, `"controlled"`, `"controller"`, 1),
"unknown event": initialize + activate + strings.Replace(event, `"remote.action"`, `"remote.execute"`, 1),
}
overflow := initialize + activate
for sequence := 1; sequence <= 33; sequence++ {
overflow += strings.Replace(event, `"sequence":1`, `"sequence":`+strconv.Itoa(sequence), 1)
}
cases["event overflow"] = overflow
for _, language := range []string{"go", "python"} {
for name, input := range cases {
t.Run(language+"/"+name, func(t *testing.T) {
program := sessionSDKProgram(t, language)
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
command := exec.CommandContext(ctx, program[0], program[1:]...)
command.Stdin = strings.NewReader(input)
if err := command.Run(); err == nil {
t.Fatal("invalid host message accepted")
}
if ctx.Err() != nil {
t.Fatal("SDK hung on invalid message")
}
})
}
}
}
func TestSessionAggregateQueueBound(t *testing.T) {
session := &Session{spec: Spec{SessionEvents: true}, outbound: make(chan outgoing, 32)}
frame := make([]byte, MaxFrameBytes)
for range 4 {
if !session.enqueue_locked(outgoing{frame: frame}) {
t.Fatal("queue rejected boundary")
}
}
if session.enqueue_locked(outgoing{frame: []byte{1}}) {
t.Fatal("aggregate byte ceiling exceeded")
}
}