Artifact 9ded06d953d34c5c74a426db74f6bd23d6c5bd21d6ef293f2ae78c4adc7ad904:
- File
internal/content/horizon_labels.go
— part of check-in
[c61a17d9ab]
at
2026-10-22 16:25:41
on branch feat/horizon-cones
— feat: expose the structural horizon and name it where history ends
Horizon gains Structural, whose zero value is the content horizon, so a Horizon built before it means what it meant; clone and sync results say how many replies served the structural level and whether the store fell back. repo clone and repo cone store take --structural with --horizon.
A structural store's history ends at its horizon, which the commands now say rather than leave bare phantoms (draft-fossil-horizon-00 §5.4, §5.5): repo info names the horizon and the check-ins below it, repo verify counts them apart from damage, and checkout, diff and annotate of a check-in below C fail naming the horizon and the deepen that reaches it. (user: [email protected], size: 4200) [annotate] [blame] [check-ins using] [more...]
package content import ( "context" "database/sql" "errors" "fmt" "github.com/danmestas/go-libfossil/db" "github.com/danmestas/go-libfossil/internal/cone" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" "github.com/danmestas/go-libfossil/internal/hash" ) // belowLabelTable holds the boundary labels of a structural horizon // (draft-fossil-horizon-00 §5.3, §6.2.2): the names an origin said lie below // the horizon. They live beside the store cone, as store-adjacent state no // peer is sent, and like it in a table of their own that `fossil rebuild` // drops; losing them makes the boundary parents transit again, which costs a // request the origin answers by labelling them anew. const belowLabelTable = "horizonbelow" const belowLabelSchema = `CREATE TABLE IF NOT EXISTS ` + belowLabelTable + `( uuid TEXT PRIMARY KEY ) WITHOUT ROWID` // maxBelowLabels bounds the labels a store keeps. An origin labels the // parents its held set names below it, so the bound is the held-set walk's. const maxBelowLabels = coneReferenceNodes // horizonOf is c's horizon member, the zero Horizon when it has none. func horizonOf(c *cone.Cone) cone.Horizon { if c == nil { return cone.Horizon{} } h, _ := c.Horizon() return h } // LabelBelow records names an origin labelled below the store's structural // horizon. A name that is no artifact hash is ignored, as a malformed pragma // is; labelling is idempotent. func LabelBelow(q db.Querier, names []string) error { if q == nil { panic("content.LabelBelow: q must not be nil") } if len(names) == 0 { return nil } if _, err := q.Exec(belowLabelSchema); err != nil { return fmt.Errorf("content.LabelBelow: %w", err) } var n int if err := q.QueryRow("SELECT count(*) FROM " + belowLabelTable).Scan(&n); err != nil { return fmt.Errorf("content.LabelBelow: %w", err) } for _, name := range names { if !hash.IsValidHash(name) { continue } if n >= maxBelowLabels { return fmt.Errorf("content.LabelBelow: more than %d labels", maxBelowLabels) } res, err := q.Exec("INSERT OR IGNORE INTO "+belowLabelTable+"(uuid) VALUES(?)", name) if err != nil { return fmt.Errorf("content.LabelBelow: %w", err) } if added, err := res.RowsAffected(); err == nil { n += int(added) } } return nil } // BelowHorizon reports rid as a check-in a structural store does not hold // because it lies below the store's horizon (draft-fossil-horizon-00 §5.5), // with that horizon. An operation that needs the check-in's tree names the // horizon in its diagnostic rather than reporting a missing artifact. func BelowHorizon(ctx context.Context, q db.Querier, rid libfossil.FslID) (cone.Horizon, bool, error) { if ctx == nil || q == nil { panic("content.BelowHorizon: nil context or querier") } c, err := StoreCone(q) if err != nil { return cone.Horizon{}, false, err } h := horizonOf(c) if h.Depth() != cone.DepthStructural || h.Hash() == "" { return cone.Horizon{}, false, nil } var size int64 err = q.QueryRow("SELECT size FROM blob WHERE rid=?", rid).Scan(&size) if errors.Is(err, sql.ErrNoRows) || err == nil && size >= 0 { return cone.Horizon{}, false, nil } if err != nil { return cone.Horizon{}, false, fmt.Errorf("content.BelowHorizon: %w", err) } a, err := LoadAbsencesContext(ctx, q) if err != nil { return cone.Horizon{}, false, err } return h, a.Below(rid), nil } // BelowLabels returns the boundary labels the store holds. func BelowLabels(q db.Querier) (map[string]bool, error) { if q == nil { panic("content.BelowLabels: q must not be nil") } var n int if err := q.QueryRow( "SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?", belowLabelTable, ).Scan(&n); err != nil { return nil, fmt.Errorf("content.BelowLabels: %w", err) } out := map[string]bool{} if n == 0 { return out, nil } rows, err := q.Query("SELECT uuid FROM " + belowLabelTable) if err != nil { return nil, fmt.Errorf("content.BelowLabels: %w", err) } for rows.Next() { var uuid string if err := rows.Scan(&uuid); err != nil { return nil, errors.Join(err, rows.Close()) } out[uuid] = true } return out, errors.Join(rows.Err(), rows.Close()) }