package config import ( "fmt" "strings" ) // OwnerIdentityInjectionEnv 是「主人身份说明书注入范围」开关的环境变量名 // (design/11 §0 红线;见 cmd/cyrene/persona_identity.go 的消费点)。 const OwnerIdentityInjectionEnv = "OWNER_IDENTITY_INJECTION" // 注入范围的三档取值。默认(也是唯一安全的一档)是 OwnerIdentityOwnerOnly: // 只有主人会话能拿到那段知识,群聊与陌生人私聊的上下文里根本不出现。 const ( // OwnerIdentityOwnerOnly 只在主人会话注入(默认)。 OwnerIdentityOwnerOnly = "owner_only" // OwnerIdentityAlways 所有会话都注入,含群聊与陌生人私聊。 // 它显式开启红线例外,只应在本地自测/排障时短暂使用。 OwnerIdentityAlways = "always" // OwnerIdentityOff 完全关闭注入:连主人会话也没有这段知识。 OwnerIdentityOff = "off" ) // LoadOwnerIdentityInjection 读取注入范围开关,优先级与其余配置完全一致: // 进程环境 > .env > 默认 owner_only。返回值已去空白(空串按默认处理), // 由调用方用 ParseOwnerIdentityInjection 判定合法性 —— 非法值必须被显式 // 报告(记日志 + 回退默认),绝不静默生效或静默失效。 // // 为什么需要这个读取函数,而不是给 Config 加一个字段:Config 的反射解码器 // 只做标量赋值,承载不了「三档 + 非法值判定」的语义;而直接 os.Getenv 会 // 让只写在 .env 里的声明被静默忽略(.env 由 parseDotEnv 读进独立 map, // 并不会写回进程环境)。这里复用同一套优先级与同一份 .env 解析,因此两种 // 写法都真实生效。envPath 为空表示不看 .env(与 Load 的约定一致)。 func LoadOwnerIdentityInjection(envPath string) (string, error) { fromEnv := map[string]string{} if envPath != "" { m, err := parseDotEnv(envPath) if err != nil { return "", fmt.Errorf("config: read %s: %w", envPath, err) } fromEnv = m } raw := strings.TrimSpace(lookupRaw(OwnerIdentityInjectionEnv, OwnerIdentityOwnerOnly, fromEnv)) if raw == "" { raw = OwnerIdentityOwnerOnly } return raw, nil } // ParseOwnerIdentityInjection 把开关取值判定成三档之一(大小写不敏感, // 允许首尾空白)。空串按默认 owner_only 处理;其余无法识别的取值返回错误, // 由调用方明确回退到 owner_only 并把非法值记进日志。 func ParseOwnerIdentityInjection(raw string) (string, error) { switch strings.ToLower(strings.TrimSpace(raw)) { case "", OwnerIdentityOwnerOnly: return OwnerIdentityOwnerOnly, nil case OwnerIdentityAlways: return OwnerIdentityAlways, nil case OwnerIdentityOff: return OwnerIdentityOff, nil default: return OwnerIdentityOwnerOnly, fmt.Errorf( "config: %s=%q is not one of %s|%s|%s", OwnerIdentityInjectionEnv, raw, OwnerIdentityOwnerOnly, OwnerIdentityAlways, OwnerIdentityOff) } }