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.
115 lines
3.8 KiB
Go
115 lines
3.8 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/tentacle"
|
|
"git.stormux.org/storm/cthulhu/internal/tentaclewire"
|
|
sdk "git.stormux.org/storm/cthulhu/sdk/go/tentacle"
|
|
)
|
|
|
|
func TestRemoteManagementPeer(t *testing.T) {
|
|
if len(os.Args) < 2 || os.Args[len(os.Args)-1] != "remote-management-peer" {
|
|
return
|
|
}
|
|
client := sdk.NewSessionClient()
|
|
command := sdk.Command{ID: "open", Title: "Open", Handle: func(ctx context.Context, _ json.RawMessage) (any, error) {
|
|
var event sdk.SessionEvent
|
|
select {
|
|
case event = <-client.Events():
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
}
|
|
var lease sdk.RemoteLease
|
|
err := client.Call(ctx, "remote.open", sdk.RemoteOpen{Action: event.Action, Epoch: event.Epoch, Role: event.Role, Grants: event.Grants}, &lease)
|
|
return lease, err
|
|
}}
|
|
if sdk.ServeSession(os.Stdin, os.Stdout, []sdk.Command{command}, nil, client) != nil {
|
|
os.Exit(1)
|
|
}
|
|
os.Exit(0)
|
|
}
|
|
|
|
func TestRemoteLeaseRevokedBeforeFailedUpdatePreparation(t *testing.T) {
|
|
executable, err := os.Executable()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
manager, err := tentacle.NewManager(tentacle.DefaultLimits(), nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
if err := manager.Shutdown(ctx); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}()
|
|
repository := tentacle.Repository{URL: "https://example.org/tentacles", ID: "example.source", Tentacles: []tentacle.Manifest{{ID: "probe", Version: "2.0"}}}
|
|
id := tentacle.PackageID(repository, "probe")
|
|
session, err := manager.Start(t.Context(), tentacle.Spec{ID: id, Program: []string{executable, "-test.run=^TestRemoteManagementPeer$", "--", "remote-management-peer"}, SessionEvents: true, RemoteGrants: []string{"output-observe"}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
select {
|
|
case <-session.Ready():
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("peer not ready")
|
|
}
|
|
if err := session.Activate(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := session.AuthorizeRemote("controlled", []string{"output-observe"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
pending, err := session.Invoke(t.Context(), "open", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var lease tentaclewire.RemoteLease
|
|
select {
|
|
case result := <-pending:
|
|
if result.Err != nil || json.Unmarshal(result.Value, &lease) != nil {
|
|
t.Fatal("open failed", result.Err)
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("open timed out")
|
|
}
|
|
delivery, err := session.RemoteContext(lease, "output-observe")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
owner := &tentacleRuntime{registry: &tentacle.Registry{Path: filepath.Join(t.TempDir(), "registry.toml")}, lifecycleContext: t.Context(), sessions: []*tentacle.Session{session},
|
|
snapshot: tentacle.Snapshot{Repositories: []tentacle.Repository{repository}, Entries: []tentacle.EntryStatus{{Entry: tentacle.Entry{ID: id, Version: "1.0", Enabled: true}}}}}
|
|
failure := errors.New("fixture preparation failure")
|
|
called := false
|
|
owner.preparePackage = func(context.Context, string, tentacle.Repository, string) (*tentacle.PreparedPackage, error) {
|
|
called = true
|
|
if delivery.Err() == nil {
|
|
t.Error("preparation began with live remote access")
|
|
}
|
|
return nil, failure
|
|
}
|
|
if err := owner.Manage(t.Context(), tentacle.ManagementRequest{Action: "upgrade", URL: repository.URL, ID: "probe"}); !errors.Is(err, failure) {
|
|
t.Fatal("wrong update result", err)
|
|
}
|
|
if !called || delivery.Err() == nil {
|
|
t.Fatal("failed update retained access")
|
|
}
|
|
if _, err := session.RemoteContext(lease, "output-observe"); err == nil {
|
|
t.Fatal("failed update restored consent")
|
|
}
|
|
if state, err := session.Status(); state != tentacle.Active || err != nil {
|
|
t.Fatal("failed preparation unnecessarily stopped ordinary commands", state, err)
|
|
}
|
|
}
|