Make delivery and integration failures explicit
Persist reminder presentations and retry state, atomically complete collapsed deliveries, fall back across away reaches, and block permanent failures visibly (V-715, V-678). Fail closed when enabled integrations lack credentials and keep remote arms explicitly dark (V-691). Give mavweb one sanitized, request-correlated error contract (V-689). Owner explicitly requested direct commits to master.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -18,6 +20,13 @@ import (
|
||||
// Only the prompt crosses the LAN, and the workstation is not "the box". The
|
||||
// rules in CLAUDE.md about what may leave still apply.
|
||||
type WorkstationConfig struct {
|
||||
// Disabled keeps both written workpc arms explicitly dark. ModelDisabled is
|
||||
// the narrower switch: CW2 STT may remain live while the large-model
|
||||
// supervisor has no provisioned client token.
|
||||
Disabled bool `json:"disabled,omitempty"`
|
||||
|
||||
ModelDisabled bool `json:"model_disabled,omitempty"`
|
||||
|
||||
// URL — where mavgpud listens, e.g. "http://192.168.1.105:8080". Empty ⇒
|
||||
// the whole block is normalised to nil and nothing probes anything.
|
||||
URL string `json:"url,omitempty"`
|
||||
@@ -30,8 +39,8 @@ type WorkstationConfig struct {
|
||||
// Token — the bearer credential mavgpud requires, expanded from the
|
||||
// environment like every other secret here. It is what stops anything on
|
||||
// the LAN spending the card, so a URL that is not loopback needs one.
|
||||
// Wrong or missing reads as a workstation that is down, and Maven falls
|
||||
// back to the resident model.
|
||||
// A missing token on a LAN URL fails config validation. Loopback development
|
||||
// endpoints may omit it.
|
||||
Token string `json:"token,omitempty"`
|
||||
|
||||
// Probe — how often admission is re-checked. 0 ⇒ DefaultWorkstationProbe.
|
||||
@@ -60,6 +69,10 @@ type WorkstationConfig struct {
|
||||
// the ggml-small.bin homesrv loads
|
||||
// (docs/evals/2026-08-09-crisperwhisper2-russian-wer.md).
|
||||
type WorkstationSttConfig struct {
|
||||
// Disabled keeps the written CW2 endpoint dark without also disabling the
|
||||
// independently authenticated model supervisor.
|
||||
Disabled bool `json:"disabled,omitempty"`
|
||||
|
||||
// URL — the transcribe endpoint, e.g.
|
||||
// "http://192.168.1.105:8081/transcribe". Empty ⇒ the block is normalised
|
||||
// to nil and mavsttd takes every turn.
|
||||
@@ -97,29 +110,85 @@ const (
|
||||
// model: an unconfigured workstation is the default deploy and must be
|
||||
// indistinguishable from today.
|
||||
func (c *Config) normaliseWorkstation() {
|
||||
if c.Workstation != nil && strings.TrimSpace(c.Workstation.URL) == "" {
|
||||
if c.Workstation != nil && c.Workstation.Disabled {
|
||||
c.Workstation = nil
|
||||
}
|
||||
if c.Workstation == nil {
|
||||
return
|
||||
}
|
||||
w := c.Workstation
|
||||
if strings.TrimSpace(w.Health) == "" {
|
||||
w.Health = strings.TrimRight(w.URL, "/") + "/health"
|
||||
// Preserve the historical empty-block meaning. A live STT sub-block makes
|
||||
// the parent non-empty; in that case omitting the model URL is an error
|
||||
// unless model_disabled states the operator's intent.
|
||||
if !w.ModelDisabled && strings.TrimSpace(w.URL) == "" &&
|
||||
(w.Stt == nil || w.Stt.Disabled || strings.TrimSpace(w.Stt.URL) == "") {
|
||||
c.Workstation = nil
|
||||
return
|
||||
}
|
||||
if w.Probe <= 0 {
|
||||
w.Probe = Duration(DefaultWorkstationProbe)
|
||||
}
|
||||
if w.Timeout <= 0 {
|
||||
w.Timeout = Duration(DefaultWorkstationTimeout)
|
||||
if !w.ModelDisabled {
|
||||
if strings.TrimSpace(w.Health) == "" {
|
||||
w.Health = strings.TrimRight(w.URL, "/") + "/health"
|
||||
}
|
||||
if w.Probe <= 0 {
|
||||
w.Probe = Duration(DefaultWorkstationProbe)
|
||||
}
|
||||
if w.Timeout <= 0 {
|
||||
w.Timeout = Duration(DefaultWorkstationTimeout)
|
||||
}
|
||||
}
|
||||
normaliseWorkstationStt(w)
|
||||
if w.ModelDisabled && w.Stt == nil {
|
||||
c.Workstation = nil
|
||||
}
|
||||
}
|
||||
|
||||
// validateWorkstation rejects a live LAN endpoint without its bearer secret.
|
||||
// Loopback remains useful for local development without manufacturing a secret;
|
||||
// malformed or non-HTTP endpoints are rejected before any probe starts.
|
||||
func (c *Config) validateWorkstation() error {
|
||||
if c.Workstation == nil {
|
||||
return nil
|
||||
}
|
||||
w := c.Workstation
|
||||
if !w.ModelDisabled {
|
||||
if err := validateWorkstationEndpoint("workstation.url", w.URL, w.Token); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateWorkstationEndpoint("workstation.health", w.Health, w.Token); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if w.Stt != nil {
|
||||
if err := validateWorkstationEndpoint("workstation.stt.url", w.Stt.URL, w.Stt.Token); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateWorkstationEndpoint("workstation.stt.health", w.Stt.Health, w.Stt.Token); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateWorkstationEndpoint(name, raw, token string) error {
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil || u.Host == "" || (u.Scheme != "http" && u.Scheme != "https") {
|
||||
return fmt.Errorf("%s must be an absolute http(s) URL", name)
|
||||
}
|
||||
host := strings.TrimSpace(u.Hostname())
|
||||
loopback := strings.EqualFold(host, "localhost")
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
loopback = ip.IsLoopback()
|
||||
}
|
||||
if !loopback && strings.TrimSpace(token) == "" {
|
||||
return fmt.Errorf("%s is not loopback, so its token is required while enabled", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// normaliseWorkstationStt applies the speech-to-text block's defaults. No
|
||||
// address, no remote: mavsttd then takes every utterance, which is today.
|
||||
func normaliseWorkstationStt(w *WorkstationConfig) {
|
||||
if w.Stt != nil && strings.TrimSpace(w.Stt.URL) == "" {
|
||||
if w.Stt != nil && (w.Stt.Disabled || strings.TrimSpace(w.Stt.URL) == "") {
|
||||
w.Stt = nil
|
||||
}
|
||||
if w.Stt == nil {
|
||||
|
||||
Reference in New Issue
Block a user