Enable isolated Remote speech and protect reading exports

Attach incoming software speech to reader sessions with lease checks, local output priority and isolated cancellation. Preserve every document fragment source for privacy checks and export reviewed browse current-line reading, spelling and phonetic output.

Verified focused reading and Remote race tests, complete review and external Remote race suites, private relay integration, speech regressions, affected-package vet, reader build and Tentacles make check. Audible speech, physical input/Braille and live interoperability remain unverified.
This commit is contained in:
ak47 committed 2026-11-05 15:41:08 -05:00
1 parent ac67236eca
commit 49be8309fe
27 files changed
+986 -54

No files matched your search

+148 -16
View File
@@ -32,7 +32,7 @@ import (
// This uses the source-built external executable and real host/SDK IPC. Only
// OpenLocal submissions are synthetic; it cannot prove physical-source input.
func remote_command_fixture(t *testing.T) (*tentacleRuntime, *consentTestForms, input.Command) {
func remote_command_fixture(t *testing.T, setup ...func(*tentacleRuntime)) (*tentacleRuntime, *consentTestForms, input.Command) {
t.Helper()
program := os.Getenv("CTHULHU_REMOTE_BINARY")
if program == "" {
@@ -53,6 +53,9 @@ func remote_command_fixture(t *testing.T) (*tentacleRuntime, *consentTestForms,
runtime.newManager = func() (*tentacle.Manager, error) { return tentacle.NewManager(tentacle.DefaultLimits(), nil) }
forms := &consentTestForms{requests: make(chan consentFormRequest, 1)}
runtime.forms = forms
for _, configure := range setup {
configure(runtime)
}
ctx, cancel := context.WithCancel(t.Context())
t.Cleanup(func() {
cancel()
@@ -224,15 +227,25 @@ func TestRemoteConnectionCommandCancelsInspectionOnHandoff(t *testing.T) {
}
func TestRemoteConnectionCommandPrivateRelay(t *testing.T) {
for _, test := range []struct{ role, ending string }{
{"controller", "disconnect"}, {"controlled", "disconnect"},
{"controller", "lock"}, {"controlled", "lock"},
{"controller", "handoff"}, {"controlled", "handoff"},
{"controller", "disable"}, {"controlled", "disable"},
for _, test := range []struct {
role, ending string
playback bool
}{
{"controller", "disconnect", false}, {"controlled", "disconnect", false},
{"controller", "lock", false}, {"controlled", "lock", false},
{"controller", "handoff", false}, {"controlled", "handoff", false},
{"controller", "disable", false}, {"controlled", "disable", false},
{"controller", "disconnect", true}, {"controller", "lock", true},
{"controller", "handoff", true}, {"controller", "disable", true},
{"controller", "backend-failure", true},
} {
role, ending := test.role, test.ending
t.Run(role+"/"+ending, func(t *testing.T) {
remoteText := " " + strings.Repeat("é\x00世界 ", 1500) + " "
name := role + "/" + ending
if test.playback {
name += "/production-speech"
}
t.Run(name, func(t *testing.T) {
remoteText := " " + strings.Repeat("é\t世界 ", 1500) + " "
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
@@ -248,6 +261,7 @@ func TestRemoteConnectionCommandPrivateRelay(t *testing.T) {
}
defer listener.Close()
probed, joined, closed := make(chan struct{}), make(chan struct{}), make(chan struct{})
speechReady := make(chan struct{})
relayOutput := make(chan []byte, 64)
go func() {
probe, err := listener.Accept()
@@ -290,14 +304,19 @@ func TestRemoteConnectionCommandPrivateRelay(t *testing.T) {
peerRole = "slave"
}
io.WriteString(connection, `{"type":"channel_joined","channel":"synthetic-fixture","clients":[{"id":7,"connection_type":"`+peerRole+`"}]}`+"\n")
close(joined)
if role == "controller" {
select {
case <-speechReady:
case <-t.Context().Done():
return
}
json.NewEncoder(connection).Encode(map[string]any{"type": "speak", "origin": 7, "priority": 2, "sequence": []any{
[]any{"CharacterModeCommand", map[string]any{"state": true}},
[]any{"LangChangeCommand", map[string]any{"lang": "fr"}}, remoteText,
[]any{"EndUtteranceCommand", map[string]any{}},
}})
}
close(joined)
for {
frame, err := reader.ReadBytes('\n')
if err != nil {
@@ -307,7 +326,29 @@ func TestRemoteConnectionCommandPrivateRelay(t *testing.T) {
}
close(closed)
}()
runtime, forms, shortcut := remote_command_fixture(t)
var server *isolatedVoiceServer
var voice *remoteSpeechVoice
var rendered isolatedVoiceUtterance
var configure []func(*tentacleRuntime)
if test.playback {
speaker, fixture := isolated_voice_fixture(t)
server = fixture
configure = append(configure, func(runtime *tentacleRuntime) {
worker := runtime.output.speech
worker.speaker = speaker
if err := worker.Start(t.Context()); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
if err := worker.Stop(ctx); err != nil {
t.Error(err)
}
})
})
}
runtime, forms, shortcut := remote_command_fixture(t, configure...)
if role == "controlled" {
worker := runtime.output.speech
worker.speaker = &observationSpeaker{fakeSpeaker{spoken: make(chan string, 16)}}
@@ -323,10 +364,10 @@ func TestRemoteConnectionCommandPrivateRelay(t *testing.T) {
}
})
}
// Incoming speech still needs a test owner; production observation
// is attached through makeManager on the controlled side.
// Keep the explicit mailbox assembly fixture alongside production
// playback, which is attached by the actual runtime startup path.
var deliveries <-chan tentacle.RemoteSpeechDelivery
if role == "controller" {
if role == "controller" && !test.playback {
runtime.mutex.Lock()
for _, command := range runtime.commands {
if command.id == "remote" {
@@ -371,13 +412,74 @@ func TestRemoteConnectionCommandPrivateRelay(t *testing.T) {
if status.ctx.Err() == nil {
t.Fatal("background connection retained its form context")
}
if test.playback {
waitTentacle(t, func() bool {
worker := runtime.output.speech
worker.mutex.Lock()
defer worker.mutex.Unlock()
return worker.activeDone == nil && len(worker.pending) == 0
})
if ending == "backend-failure" {
server.mutex.Lock()
server.queryReplies = map[string]string{"GET LANGUAGE": "400 Rejected\r\n"}
server.mutex.Unlock()
}
}
close(speechReady)
if ending == "backend-failure" {
remote_stream_wait(t, closed)
// Backend failure retires the lease, while the process and
// mailbox owner remain usable for an explicit new connection.
runtime.Invoke(t.Context(), shortcut)
selection := next_remote_form(t, forms)
if selection.form.Title == "Remote relay status" {
// An ended attempt remains inspectable until explicitly
// closed, as with ordinary transport failure.
selection.reply <- internalUIResult{Activated: "cancel"}
waitTentacle(t, func() bool { return len(runtime.resultSlots) == 0 })
runtime.Invoke(t.Context(), shortcut)
selection = next_remote_form(t, forms)
}
if !strings.Contains(selection.form.Title, "Remote relay and role") {
t.Fatal("backend failure retained a connected workflow")
}
selection.reply <- internalUIResult{Cancelled: true}
waitTentacle(t, func() bool { return len(runtime.resultSlots) == 0 })
server.mutex.Lock()
for _, connection := range server.connections {
if connection.priority == "TEXT" && connection.pending != "" {
t.Error("failed rendering exposed a speech prefix")
}
}
server.mutex.Unlock()
return
}
select {
case <-closed:
t.Fatal("return to desktop disconnected the relay")
default:
}
var delivery tentacle.RemoteSpeechDelivery
if role == "controller" {
if test.playback {
utterance := server.next_utterance(t)
for utterance.priority == "MESSAGE" {
// Returning to desktop produces local status on its ordinary
// client. The production Remote voice must stay separate.
utterance = server.next_utterance(t)
}
rendered = utterance
if utterance.text != remoteText || utterance.language != "fr" || utterance.spelling != "on" ||
utterance.ssml != "off" || utterance.priority != "TEXT" {
t.Fatal("production owner lost literal speech, metadata or local priority")
}
speaker := runtime.output.speech.speaker.(*configuredSpeaker)
speaker.mu.RLock()
voice = speaker.remote
speaker.mu.RUnlock()
if voice == nil {
t.Fatal("production owner did not acquire isolated synthesis")
}
} else if role == "controller" {
select {
case delivery = <-deliveries:
case <-time.After(5 * time.Second):
@@ -448,6 +550,28 @@ func TestRemoteConnectionCommandPrivateRelay(t *testing.T) {
if err := worker.EnqueueAndWait(t.Context(), request); err != nil {
t.Fatal(err)
}
request = current_line_observation_request(t, review.Result{CurrentLineBrowse: true,
DocumentCaretText: "Current colour.", DocumentPosition: review.DocumentPosition{Snapshot: atspi.Snapshot{Role: "static"}},
DocumentCaretContext: []atspi.Snapshot{{Role: "static"}, {Role: "paragraph"}, {Role: "document web"}}})
if err := worker.EnqueueAndWait(t.Context(), request); err != nil {
t.Fatal(err)
}
// A joined line can contain a private neighboring fragment even
// when its caret source and the line's presentation are ordinary.
caret := atspi.Snapshot{Role: "static"}
privateLine := atspi.DocumentLine{Contents: []atspi.DocumentContent{{Snapshot: caret, Text: "private joined text"}},
SourceContexts: [][]atspi.Snapshot{{caret}, {{Role: "static"}, {Role: "password text"}}}}
for _, kind := range []string{"caret", "read-all", "current-line"} {
request := document_observation_request(t, kind, review.Result{
Moved: true, DocumentCaretCommand: review.CaretNextLine,
DocumentPosition: review.DocumentPosition{Snapshot: caret}, DocumentCaretText: "private joined text",
DocumentCaretContext: []atspi.Snapshot{caret, {Role: "document web"}},
DocumentCaretLine: &privateLine, ReadAllContext: t.Context(), ReadAllLines: []atspi.DocumentLine{privateLine},
})
if err := worker.EnqueueAndWait(t.Context(), request); err != nil {
t.Fatal(err)
}
}
worker.InterruptForUser()
var received strings.Builder
ends, frames := 0, 0
@@ -483,7 +607,7 @@ func TestRemoteConnectionCommandPrivateRelay(t *testing.T) {
t.Fatal("reader output did not reach the relay in order")
}
}
if received.String() != "color "+remoteText+"First color.Second color." || frames < 4 || ends != 3 {
if received.String() != "color "+remoteText+"First color.Second color.Current color." || frames < 5 || ends != 4 {
t.Fatal("observation leaked private text or lost resolved output/boundaries")
}
}
@@ -512,7 +636,15 @@ func TestRemoteConnectionCommandPrivateRelay(t *testing.T) {
t.Fatal("background connection retained the relay after " + ending)
}
waitTentacle(t, func() bool { return len(runtime.resultSlots) == 0 })
if role == "controller" {
if test.playback {
remote_stream_wait(t, voice.closed)
server.mutex.Lock()
pending := server.connections[rendered.id].pending
server.mutex.Unlock()
if pending != "" {
t.Fatal("revoked production speech survived backend cleanup")
}
} else if role == "controller" {
// Socket cancellation precedes the final sequenced lease close.
// A reopened status interaction did not issue the original consent,
// so its result-slot completion is not the lease-close barrier.