Tentacle enhancements.
This commit is contained in:
1 parent
4f9b89e38b
commit
9e15f8652d
12 files changed
+619
-22
No files matched your search
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package tentacle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"git.stormux.org/storm/cthulhu/internal/tentaclewire"
|
||||
)
|
||||
|
||||
// RemoteSpeechDelivery is reader-owned work. The owner must check Context and
|
||||
// RemoteContext again at delivery, apply focus/security/output policy, and tag
|
||||
// the result as remote so neither observation nor remote cancel affects local
|
||||
// status. Admission is not evidence of backend delivery or audible speech.
|
||||
type RemoteSpeechDelivery struct {
|
||||
Context context.Context
|
||||
Lease tentaclewire.RemoteLease
|
||||
Speech tentaclewire.RemoteSpeech
|
||||
}
|
||||
|
||||
// EnableRemoteSpeech installs one bounded mailbox, explicitly from reader code.
|
||||
// Without an owner, remote.present is unavailable. No production speech worker
|
||||
// is bound yet. The owner selects Session.Done as well as this channel.
|
||||
func (session *Session) EnableRemoteSpeech() <-chan RemoteSpeechDelivery {
|
||||
session.mutex.Lock()
|
||||
defer session.mutex.Unlock()
|
||||
if session.remoteSpeech == nil {
|
||||
session.remoteSpeech = make(chan RemoteSpeechDelivery, tentaclewire.RemoteSpeechQueueSize)
|
||||
}
|
||||
return session.remoteSpeech
|
||||
}
|
||||
|
||||
func (session *Session) remote_access_locked(lease tentaclewire.RemoteLease, grant string) *remoteAccess {
|
||||
access := session.remote
|
||||
if session.state != Active || access == nil || access.lease == "" || access.lease != lease.Lease || access.epoch != lease.Epoch || !time.Now().Before(access.deadline) || access.ctx.Err() != nil || !slices.Contains(access.grants, grant) {
|
||||
return nil
|
||||
}
|
||||
return access
|
||||
}
|
||||
|
||||
// PublishRemoteSpeech accepts only policy-resolved local output from a reader
|
||||
// owner. The origin is supplied by core, never by child RPC or network input.
|
||||
// Remote output, local status, and sensitive output must use other origins.
|
||||
// The owner must exclude password/secret output before calling this method.
|
||||
func (session *Session) PublishRemoteSpeech(lease tentaclewire.RemoteLease, origin string, speech tentaclewire.RemoteSpeech) error {
|
||||
if origin != "local" || !tentaclewire.ValidRemoteSpeech(speech) {
|
||||
return ErrPermission
|
||||
}
|
||||
session.mutex.Lock()
|
||||
defer session.mutex.Unlock()
|
||||
access := session.remote_access_locked(lease, "output-observe")
|
||||
if access == nil || access.role != "controlled" {
|
||||
return ErrPermission
|
||||
}
|
||||
event := tentaclewire.SessionEvent{Kind: "remote.output", Lease: lease.Lease, Epoch: lease.Epoch, Speech: &speech}
|
||||
if !session.remote_event_locked(&event) {
|
||||
// Shared lifecycle IPC cannot lose revocation or cancel order.
|
||||
session.fail_locked(ErrLimit)
|
||||
return ErrLimit
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (session *Session) remote_present_locked(params json.RawMessage) (any, *RPCError) {
|
||||
var request tentaclewire.RemotePresentation
|
||||
if decode_strict(params, &request) != nil || !tentaclewire.ValidRemoteSpeech(request.Speech) || len(request.Lease) != 32 || request.Epoch == 0 || request.Sequence == 0 {
|
||||
return nil, &RPCError{Code: -32602, Message: "Invalid session parameters"}
|
||||
}
|
||||
lease := tentaclewire.RemoteLease{Lease: request.Lease, Epoch: request.Epoch}
|
||||
access := session.remote_access_locked(lease, "remote-present")
|
||||
if access == nil || access.role != "controller" || request.Sequence <= access.sequence {
|
||||
return nil, &RPCError{Code: -32001, Message: "Session permission denied"}
|
||||
}
|
||||
if session.remoteSpeech == nil {
|
||||
return nil, &RPCError{Code: -32601, Message: "Remote speech owner unavailable"}
|
||||
}
|
||||
select {
|
||||
case session.remoteSpeech <- RemoteSpeechDelivery{Context: access.ctx, Lease: lease, Speech: request.Speech}:
|
||||
access.sequence = request.Sequence
|
||||
return struct{}{}, nil
|
||||
default:
|
||||
// A dropped cancel would let stale speech outlive the intended stream.
|
||||
session.revoke_remote_locked("local", true)
|
||||
return nil, &RPCError{Code: -32002, Message: "Remote speech queue full"}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user