Files
fn_registry/registry/hash.go
egutierrez 34ecadf5a4 feat: add params_schema column for function composability
Nueva columna params_schema en functions con migración 009. Almacena JSON
con descripción semántica de inputs/outputs para que agentes razonen sobre
composabilidad de funciones. Incluye: campo en modelo Go, parsing de params/output
del frontmatter YAML, serialización a JSON, FTS5 rebuild con nueva columna,
hash de contenido actualizado, y warning en indexer cuando faltan params.
2026-04-05 18:45:01 +02:00

119 lines
4.2 KiB
Go

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|%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|%s|%s|%s|%s|%s", a.Framework, a.EntryPoint, a.Documentation, a.Notes, a.DirPath, a.RepoURL)
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 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")
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()
}