package domain import ( "crypto/rand" "crypto/sha256" "encoding/base32" "encoding/json" "fmt" "sort" "strings" "time" ) const ConfirmationTTL = 120 * time.Second type ConfirmationState string const ( ConfirmationPending ConfirmationState = "pending" ConfirmationConsumed ConfirmationState = "consumed" ConfirmationExpired ConfirmationState = "expired" ConfirmationRejected ConfirmationState = "rejected" ) type Confirmation struct { ID string `json:"id"` CapabilityID string `json:"capability_id"` CapabilityVersion int64 `json:"capability_version"` TargetEntityID string `json:"target_entity_id"` ArgsNormalized string `json:"args_normalized"` ArgsHash string `json:"args_hash"` Requester string `json:"requester"` CreatedAt time.Time `json:"created_at"` ExpiresAt time.Time `json:"expires_at"` State ConfirmationState `json:"state"` } func NewConfirmationID() string { b := make([]byte, 10) rand.Read(b) return "conf_" + strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(b)) } // NormalizeArgs produces a stable JSON encoding of an arguments map (sorted // keys) so args_hash is comparable across requests that differ only in key // order. func NormalizeArgs(args map[string]any) (string, error) { if args == nil { args = map[string]any{} } keys := make([]string, 0, len(args)) for k := range args { keys = append(keys, k) } sort.Strings(keys) var b strings.Builder b.WriteByte('{') for i, k := range keys { if i > 0 { b.WriteByte(',') } kb, err := json.Marshal(k) if err != nil { return "", err } vb, err := json.Marshal(args[k]) if err != nil { return "", err } b.Write(kb) b.WriteByte(':') b.Write(vb) } b.WriteByte('}') return b.String(), nil } func HashArgs(normalized string) string { sum := sha256.Sum256([]byte(normalized)) return fmt.Sprintf("%x", sum) }