// Package callmemory implements CALL-MEMORY-1.1. Provider capabilities are // evidence-backed release gates, never inferred from a successful configuration write. package callmemory import ( "bytes" "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "errors" "fmt" "io" "sort" "strings" "time" "unicode/utf8" ) const ContractVersion = "CALL-MEMORY-1.1" const MaxBody = 8 << 10 const MaxMemoryBody = 64 << 10 const LeaseMS int64 = 45000 const ManifestMS int64 = 30000 type Error struct { Status int Code, Message, Recovery string } func (e *Error) Error() string { return e.Code } func fault(status int, code string) *Error { return &Error{status, code, code, "NONE"} } func hash(b []byte) string { h := sha256.Sum256(b); return hex.EncodeToString(h[:]) } func jsonBytes(v any) []byte { b, _ := json.Marshal(v); return b } // StrictJSON rejects ambiguous duplicate keys, trailing values and unknown struct fields. func StrictJSON(b []byte, v any) error { if !utf8.Valid(b) { return fault(400, "INVALID_JSON") } d := json.NewDecoder(bytes.NewReader(b)) d.UseNumber() var walk func() error walk = func() error { t, e := d.Token() if e != nil { return e } delim, ok := t.(json.Delim) if !ok { return nil } switch delim { case '{': keys := map[string]bool{} for d.More() { k, e := d.Token() if e != nil { return e } s, ok := k.(string) if !ok || keys[s] { return errors.New("duplicate key") } keys[s] = true if e = walk(); e != nil { return e } } case '[': for d.More() { if e := walk(); e != nil { return e } } default: return errors.New("invalid delimiter") } _, e = d.Token() return e } if e := walk(); e != nil { return fault(400, "INVALID_JSON") } if _, e := d.Token(); e != io.EOF { return fault(400, "INVALID_JSON") } d = json.NewDecoder(bytes.NewReader(b)) d.DisallowUnknownFields() if e := d.Decode(v); e != nil { return fault(400, "INVALID_JSON") } return nil } func ManifestMAC(key []byte, b64 string) string { h := hmac.New(sha256.New, key) h.Write([]byte("CALLCFG1\n" + b64)) return hex.EncodeToString(h.Sum(nil)) } func EncodeManifest(key []byte, m Manifest) (string, string, error) { b, e := json.Marshal(m) if e != nil || len(key) != 32 || len(b) > 24<<10 { return "", "", fault(400, "INVALID_MANIFEST") } encoded := base64.RawURLEncoding.EncodeToString(b) return encoded, ManifestMAC(key, encoded), nil } func DecodeManifest(key []byte, b64, mac string) (Manifest, error) { var m Manifest if len(key) != 32 || len(b64) > 32<<10 || len(mac) != 64 || !hmac.Equal([]byte(ManifestMAC(key, b64)), []byte(mac)) { return m, fault(400, "MANIFEST_INVALID") } b, e := base64.RawURLEncoding.DecodeString(b64) if e != nil || base64.RawURLEncoding.EncodeToString(b) != b64 { return m, fault(400, "MANIFEST_INVALID") } e = StrictJSON(b, &m) if e == nil { e = m.Validate() } return m, e } // Validate is also used by device clients after authenticating the original bytes. func (m Manifest) Validate() error { if m.ContractVersion != ContractVersion || !validUUID(m.ManifestID) || !validUUID(m.SessionID) || !short(m.DeviceID) || !validUUID(m.BootID) || !short(m.HookID) || m.CallGeneration < 0 || m.BindingGeneration < 0 || m.Mode != "RTC" || !short(m.BotID) || !short(m.ProfileID) || !short(m.AudioProfileID) || !oneOf(m.Purpose, "NORMAL", "FIRST_TEST") || !oneOf(m.FirstTurnMode, "AI_WELCOME", "USER_FIRST") || !oneOf(m.MemoryMode, "OFF", "HOT_ONLY", "NATIVE_VIKING") || m.MemoryRevision < 0 || m.VisibleRevision < 0 || m.VisibleRevision > m.MemoryRevision || m.PrivacyEpoch < 0 || m.ContextVersion < 0 || m.IssuedAt <= 0 || m.ExpiresAt <= m.IssuedAt || m.ExpiresAt-m.IssuedAt > ManifestMS || m.LeaseMS <= 0 || m.LeaseMS > LeaseMS || m.LeaseExpiresAt <= m.IssuedAt || m.LeaseExpiresAt-m.IssuedAt > m.LeaseMS || m.MaxCallMS <= 0 || m.MaxCallMS > 3600000 || len(m.VendorParams) > 16<<10 || !json.Valid([]byte(m.VendorParams)) || (m.LocalNotice && !short(m.NoticeProfileID)) { return fault(400, "MANIFEST_INVALID") } return nil } type Manifest struct { ContractVersion string `json:"contract_version"` ManifestID string `json:"manifest_id"` SessionID string `json:"business_session_id"` DeviceID string `json:"device_id"` BootID string `json:"boot_id"` HookID string `json:"hook_event_id"` CallGeneration int64 `json:"call_generation"` BindingGeneration int64 `json:"binding_generation"` IssuedAt int64 `json:"issued_at_ms"` ExpiresAt int64 `json:"expires_at_ms"` Mode string `json:"mode"` BotID string `json:"bot_id"` ProfileID string `json:"provider_profile_id"` Purpose string `json:"purpose"` FirstTurnMode string `json:"first_turn_mode"` MemoryMode string `json:"memory_mode"` MemoryRevision int64 `json:"memory_revision"` VisibleRevision int64 `json:"viking_visible_revision"` PrivacyEpoch int64 `json:"privacy_epoch"` ContextVersion int64 `json:"context_version"` AudioProfileID string `json:"audio_profile_id"` VendorParams string `json:"vendor_params_json"` LeaseMS int64 `json:"auth_lease_ms"` LeaseExpiresAt int64 `json:"auth_lease_expires_at_ms"` MaxCallMS int64 `json:"max_call_ms"` LocalNotice bool `json:"local_notice_enabled"` NoticeProfileID string `json:"notice_profile_id"` } type Profile struct { ID, SDKVersion, BotID, ModelVersion, Transport string Audio map[string]any Template map[string]any Mapping map[string]string Capabilities map[string]bool Evidence map[string]string } func (p Profile) Check(mode string) error { if !oneOf(mode, "OFF", "HOT_ONLY", "NATIVE_VIKING") || p.ID == "" || p.SDKVersion == "" || p.BotID == "" || p.ModelVersion == "" || p.Transport != "RTC" { return fault(409, "PROVIDER_PROFILE_UNVERIFIED") } for _, g := range []string{"H01", "H02", "H04", "H05", "H06", "H07", "H09", "H11"} { if strings.TrimSpace(p.Evidence[g]) == "" || !p.Capabilities[g] { return fault(409, "PROVIDER_PROFILE_UNVERIFIED") } } if mode == "NATIVE_VIKING" { for _, g := range []string{"H03", "H08", "H12"} { if p.Evidence[g] == "" || !p.Capabilities[g] { return fault(409, "PROVIDER_PROFILE_UNVERIFIED") } } } return nil } // Compile changes only explicitly reviewed JSON pointer paths. OFF clears the // exact same fields used by native memory; no guessed provider field names. func (p Profile) Compile(mode, context, welcome, user, assistant, dialog string) (string, error) { if e := p.Check(mode); e != nil { return "", e } var out map[string]any if e := json.Unmarshal(jsonBytes(p.Template), &out); e != nil { return "", e } paths := []string{} for _, name := range []string{"memory_enabled", "hot_context", "welcome_text", "user_id", "assistant_id", "dialog_id"} { path := p.Mapping[name] for _, prior := range paths { if path == prior || strings.HasPrefix(path, prior+"/") || strings.HasPrefix(prior, path+"/") { return "", fault(409, "PROVIDER_MAPPING_UNVERIFIED") } } paths = append(paths, path) } values := map[string]any{"memory_enabled": mode == "NATIVE_VIKING", "hot_context": context, "welcome_text": welcome, "user_id": user, "assistant_id": assistant, "dialog_id": dialog} if mode == "OFF" { values["hot_context"] = "" values["welcome_text"] = "" } if mode != "NATIVE_VIKING" { values["user_id"] = "" values["assistant_id"] = "" } for name, v := range values { path := p.Mapping[name] if path == "" { return "", fault(409, "PROVIDER_MAPPING_UNVERIFIED") } if e := setPointer(out, path, v); e != nil { return "", e } } b, e := json.Marshal(out) if e != nil || len(b) > 16<<10 { return "", fault(413, "VENDOR_PARAMS_TOO_LARGE") } return string(b), nil } func setPointer(m map[string]any, path string, value any) error { if !strings.HasPrefix(path, "/") { return fault(409, "PROVIDER_MAPPING_UNVERIFIED") } parts := strings.Split(path[1:], "/") for i, raw := range parts { k := strings.ReplaceAll(strings.ReplaceAll(raw, "~1", "/"), "~0", "~") if k == "" { return fault(409, "PROVIDER_MAPPING_UNVERIFIED") } if i == len(parts)-1 { m[k] = value return nil } n, ok := m[k].(map[string]any) if !ok { return fault(409, "PROVIDER_MAPPING_UNVERIFIED") } m = n } return nil } type ContextFact struct { ID, EntityKey, Text, Type, ItemStatus, Policy string Version int64 SourceTime time.Time } func BuildContext(facts []ContextFact, limit int) string { // Only the latest valid record for one entity participates. Keep complete facts // so truncation cannot strip a negation, a date, or an uncertainty qualifier. sort.SliceStable(facts, func(i, j int) bool { if !facts[i].SourceTime.Equal(facts[j].SourceTime) { return facts[i].SourceTime.After(facts[j].SourceTime) } if facts[i].Version != facts[j].Version { return facts[i].Version > facts[j].Version } return facts[i].ID < facts[j].ID }) seen := map[string]bool{} parts := []string{} used := 0 for _, f := range facts { key := f.EntityKey if key == "" { key = f.ID } if seen[key] { continue } seen[key] = true if f.Policy != "ALLOWED" || strings.TrimSpace(f.Text) == "" { continue } if f.Type == "OPEN_ITEM" && f.ItemStatus != "OPEN" { continue } text := strings.TrimSpace(f.Text) n := utf8.RuneCountInString(text) if used+n+1 > limit { continue } used += n + 1 parts = append(parts, text) } return strings.Join(parts, "\n") } func SelectMemoryMode(revision, visible int64, critical, consent, offVerified bool) (string, error) { if !consent { if !offVerified { return "", fault(409, "MEMORY_OFF_UNVERIFIED") } return "OFF", nil } if critical { if !offVerified { return "", fault(409, "MEMORY_CRITICAL_LAG") } return "HOT_ONLY", nil } if revision == 0 { return "HOT_ONLY", nil } if visible <= revision { return "NATIVE_VIKING", nil } return "", fmt.Errorf("invalid memory watermark") }