package registry import ( "crypto/sha256" "fmt" "time" ) // timestampRecord holds preserved timestamps and hash for an existing entry. type timestampRecord struct { CreatedAt time.Time UpdatedAt time.Time ContentHash string } // ComputeFunctionHash computes a deterministic hash of all content fields of a Function // (excluding created_at, updated_at, and content_hash itself). func ComputeFunctionHash(f *Function) string { h := sha256.New() fmt.Fprintf(h, "%s|%s|%s|%s|%s|%s|%s|%s|%s", f.ID, f.Name, f.Kind, f.Lang, f.Domain, f.Version, f.Purity, f.Signature, f.Description) fmt.Fprintf(h, "|%s", marshalStrings(f.Tags)) fmt.Fprintf(h, "|%s", marshalStrings(f.UsesFunctions)) fmt.Fprintf(h, "|%s", marshalStrings(f.UsesTypes)) fmt.Fprintf(h, "|%s", marshalStrings(f.Returns)) fmt.Fprintf(h, "|%t|%s", f.ReturnsOptional, f.ErrorType) fmt.Fprintf(h, "|%s", marshalStrings(f.Imports)) fmt.Fprintf(h, "|%s|%t", f.Example, f.Tested) fmt.Fprintf(h, "|%s", marshalStrings(f.Tests)) fmt.Fprintf(h, "|%s|%s", f.TestFilePath, f.FilePath) fmt.Fprintf(h, "|%s", marshalProps(f.Props)) fmt.Fprintf(h, "|%s", marshalStrings(f.Emits)) if f.HasState != nil { fmt.Fprintf(h, "|%t", *f.HasState) } fmt.Fprintf(h, "|%s", f.Framework) fmt.Fprintf(h, "|%s", marshalStrings(f.Variant)) fmt.Fprintf(h, "|%s|%s|%s", f.Notes, f.Documentation, f.Code) fmt.Fprintf(h, "|%s|%s|%s", f.SourceRepo, f.SourceLicense, f.SourceFile) fmt.Fprintf(h, "|%s", f.ParamsSchema) return fmt.Sprintf("%x", h.Sum(nil)) } // ComputeTypeHash computes a deterministic hash of all content fields of a Type. func ComputeTypeHash(t *Type) string { h := sha256.New() fmt.Fprintf(h, "%s|%s|%s|%s|%s|%s|%s|%s", t.ID, t.Name, t.Lang, t.Domain, t.Version, t.Algebraic, t.Definition, t.Description) fmt.Fprintf(h, "|%s", marshalStrings(t.Tags)) fmt.Fprintf(h, "|%s", marshalStrings(t.UsesTypes)) fmt.Fprintf(h, "|%s|%s|%s|%s|%s", t.FilePath, t.Examples, t.Notes, t.Documentation, t.Code) fmt.Fprintf(h, "|%s|%s|%s", t.SourceRepo, t.SourceLicense, t.SourceFile) return fmt.Sprintf("%x", h.Sum(nil)) } // ComputeAppHash computes a deterministic hash of all content fields of an App. func ComputeAppHash(a *App) string { h := sha256.New() fmt.Fprintf(h, "%s|%s|%s|%s|%s", a.ID, a.Name, a.Lang, a.Domain, a.Description) fmt.Fprintf(h, "|%s", marshalStrings(a.Tags)) fmt.Fprintf(h, "|%s", marshalStrings(a.UsesFunctions)) fmt.Fprintf(h, "|%s", marshalStrings(a.UsesTypes)) fmt.Fprintf(h, "|%s", marshalStrings(a.UsesModules)) fmt.Fprintf(h, "|%s|%s|%s|%s|%s|%s", a.Framework, a.EntryPoint, a.Documentation, a.Notes, a.DirPath, a.RepoURL) return fmt.Sprintf("%x", h.Sum(nil)) } // ComputeAnalysisHash computes a deterministic hash of all content fields of an Analysis. func ComputeAnalysisHash(a *Analysis) string { h := sha256.New() fmt.Fprintf(h, "%s|%s|%s|%s|%s", a.ID, a.Name, a.Lang, a.Domain, a.Description) fmt.Fprintf(h, "|%s", marshalStrings(a.Tags)) fmt.Fprintf(h, "|%s", marshalStrings(a.UsesFunctions)) fmt.Fprintf(h, "|%s", marshalStrings(a.UsesTypes)) fmt.Fprintf(h, "|%s", marshalStrings(a.UsesModules)) fmt.Fprintf(h, "|%s|%s|%s|%s|%s|%s", a.Framework, a.EntryPoint, a.Documentation, a.Notes, a.DirPath, a.RepoURL) return fmt.Sprintf("%x", h.Sum(nil)) } // ComputeModuleHash computes a deterministic hash of all content fields of a Module. func ComputeModuleHash(m *Module) string { h := sha256.New() fmt.Fprintf(h, "%s|%s|%s|%s|%s", m.ID, m.Name, m.Version, m.Lang, m.Description) fmt.Fprintf(h, "|%s", marshalStrings(m.Members)) fmt.Fprintf(h, "|%s", marshalStrings(m.Tags)) fmt.Fprintf(h, "|%s|%s|%s|%s", m.DirPath, m.RepoURL, m.Documentation, m.Notes) return fmt.Sprintf("%x", h.Sum(nil)) } // ComputeProjectHash computes a deterministic hash of all content fields of a Project. func ComputeProjectHash(p *Project) string { h := sha256.New() fmt.Fprintf(h, "%s|%s|%s", p.ID, p.Name, p.Description) fmt.Fprintf(h, "|%s", marshalStrings(p.Tags)) fmt.Fprintf(h, "|%s|%s|%s|%s", p.RepoURL, p.DirPath, p.Documentation, p.Notes) return fmt.Sprintf("%x", h.Sum(nil)) } // ComputeVaultHash computes a deterministic hash of all content fields of a Vault. func ComputeVaultHash(v *Vault) string { h := sha256.New() fmt.Fprintf(h, "%s|%s|%s|%s|%s|%t", v.ID, v.Name, v.ProjectID, v.Description, v.Path, v.Symlink) fmt.Fprintf(h, "|%s", marshalStrings(v.Tags)) return fmt.Sprintf("%x", h.Sum(nil)) } // LoadTimestamps reads existing id → {created_at, updated_at, content_hash} from all tables. // Called before Purge so we can preserve dates across reindexing. func (db *DB) LoadTimestamps() (funcs, types, apps, analysis, projects, vaults, modules map[string]timestampRecord, err error) { funcs, err = loadTable(db, "functions") if err != nil { return } types, err = loadTable(db, "types") if err != nil { return } apps, err = loadTable(db, "apps") if err != nil { return } analysis, err = loadTable(db, "analysis") if err != nil { return } projects, err = loadTable(db, "projects") if err != nil { return } vaults, err = loadTable(db, "vaults") if err != nil { return } modules, err = loadTable(db, "modules") return } func loadTable(db *DB, table string) (map[string]timestampRecord, error) { rows, err := db.conn.Query(fmt.Sprintf("SELECT id, created_at, updated_at, content_hash FROM %s", table)) if err != nil { return nil, err } defer rows.Close() m := make(map[string]timestampRecord) for rows.Next() { var id, ca, ua, ch string if err := rows.Scan(&id, &ca, &ua, &ch); err != nil { return nil, err } rec := timestampRecord{ContentHash: ch} rec.CreatedAt, _ = time.Parse(time.RFC3339, ca) rec.UpdatedAt, _ = time.Parse(time.RFC3339, ua) m[id] = rec } return m, rows.Err() }