Files
Agent-System-Backend/internal/settings/project_metadata.go
T
weichen f1302ed5b3 Enable canonical versioned Go module consumption
Why: Allow consumers to download an immutable module revision without nested source checkouts or local path replacements.

Evidence: SELF package tests and the prior canonical-path rejection are recorded in the Dev dependency-workspace task card; remote consumption is replayed against the committed candidate.

Impact: Change the module identity and its Go imports while preserving API behavior. Existing historical submodule revisions remain buildable.

Refs: Versioned dependencies U1/U3; feature/dependency-workspace
2026-10-13 15:47:16 +00:00

109 lines
2.9 KiB
Go

package settings
import (
core "git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git"
"strings"
)
// MaskedProjectMetadata returns a detached credential-safe view without changing
// stored values or dotted service/configuration key shapes. The builtin and
// plugin secret schema plus the existing secret-leaf fallback cover legacy data.
func (s *Service) MaskedProjectMetadata(values map[string]any) map[string]any {
if values == nil {
return map[string]any{}
}
schema := Schema()
if s != nil {
schema = append(schema, s.extraSchema...)
}
secrets := map[string]bool{}
for _, d := range schema {
if d.Type == core.SettingSecret || d.Secret {
secrets[d.Key] = true
}
}
return maskedProjectValue(values, "", secrets).(map[string]any)
}
func maskedProjectValue(value any, path string, secrets map[string]bool) any {
leaf := path
if i := strings.LastIndexByte(leaf, '.'); i >= 0 {
leaf = leaf[i+1:]
}
if secrets[path] || isSecretKey(leaf) {
if value == nil {
return nil
}
if text, ok := value.(string); ok && text == "" {
return text
}
return MaskPlaceholder
}
switch v := value.(type) {
case map[string]any:
if v == nil {
return v
}
out := make(map[string]any, len(v))
for key, item := range v {
child := key
if path != "" {
child = path + "." + key
}
out[key] = maskedProjectValue(item, child, secrets)
}
return out
case []any:
if v == nil {
return v
}
out := make([]any, len(v))
for i, item := range v {
out[i] = maskedProjectValue(item, path, secrets)
}
return out
default:
return value
}
}
// PrepareProjectMetadataWrite applies the same managed-path and mask-placeholder
// rules as project settings, while preserving the metadata store's key shapes.
// Project metadata lives in projects.settings, not the settings scope table.
func (s *Service) PrepareProjectMetadataWrite(values map[string]any) (map[string]any, error) {
incoming := dropProjectMaskPlaceholders(values)
if err := s.checkManagedWrite(normalizeKeys(incoming)); err != nil {
return nil, err
}
return incoming, nil
}
// dropProjectMaskPlaceholders prunes a parent only when a previously nonempty
// object became empty through mask removal. Explicit empty objects still express
// an overwrite and must reach the managed-path guard unchanged.
func dropProjectMaskPlaceholders(values map[string]any) map[string]any {
out := make(map[string]any, len(values))
for key, value := range values {
if child, ok := value.(map[string]any); ok {
prepared := dropProjectMaskPlaceholders(child)
if len(child) > 0 && len(prepared) == 0 {
continue
}
out[key] = prepared
continue
}
if text, ok := value.(string); ok && text == MaskPlaceholder {
continue
}
out[key] = value
}
return out
}
func (s *Service) checkManagedWrite(incoming map[string]any) error {
if hits := core.TouchesManagedSettings(incoming, s.ManagedSettingPaths()); len(hits) > 0 {
return &ErrManagedPath{Paths: hits}
}
return nil
}