Artifact 6002402a6ec30170a58508d744f8281a937164545c09f745a009bc189c90e934:
- File
internal/content/cone_references.go
— part of check-in
[2ea6479ba9]
at
2026-10-22 00:04:54
on branch feat/horizon-cones
— feat(content): classify blobs below a content horizon as elided
A store cone may now carry a content horizon (draft-fossil-horizon-00 §3.2, §4.1-§4.3). Classification computes the held set K from the store's own check-in graph and walks K's full trees -- F cards directly, through B baselines and through S sub-manifest nodes, with one visited set over structural names -- because mlink records a file version only where it changed, and a blob introduced below C and unchanged through C is named by C's tree alone. Nothing about K is stored: every classification recomputes it, so moving C or losing the cone is a reload, never a migration.
Blobs no K check-in names are elided; with path members as well a blob is kept only when it is in both keep sets. Under a horizon nothing structural is elided, by the horizon or by path members, and delta bases of held artifacts and shunned names keep their classes. A C the store does not hold elides nothing, as zit's classification does: eliding everything would leave the pull that brings a deepened C nothing to ask for. A structural horizon is refused by SetStoreCone until it is implemented.
internal/content now imports internal/horizon, so the held-set tests, which commit through internal/manifest, move to an external test package to avoid an import cycle. (user: [email protected], size: 5741) [annotate] [blame] [check-ins using] [more...]
package content import ( "context" "fmt" "github.com/danmestas/go-libfossil/db" "github.com/danmestas/go-libfossil/internal/cone" "github.com/danmestas/go-libfossil/internal/deck" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" ) const coneReferenceDepth = 16 const coneReferenceNodes = 1 << 20 // ConeReferences is inferred from raw artifacts, not crosslink tables: a sparse // tree can be healthy before its elided children permit whole-tree crosslinking. // Unknown artifacts stay wanted. A covered alias or structural reference wins // over every outside reference. type ConeReferences struct { names map[libfossil.FslID]string outside, inside, required map[string]bool tree map[string]bool held map[string]libfossil.FslID nodes map[libfossil.FslID]*deck.Deck } // Outside reports only proven elision candidates. Delta dependency closure is // a separate operation: a sender can instead expand a retained delta in full. func (p *ConeReferences) Outside(rid libfossil.FslID) bool { if p == nil { return false } uuid := p.names[rid] return p.outside[uuid] && !p.inside[uuid] && !p.required[uuid] } // Required reports a covered alias or a structural reference, including ones // which have not been crosslinked yet. func (p *ConeReferences) Required(rid libfossil.FslID) bool { if p == nil { return false } uuid := p.names[rid] return p.inside[uuid] || p.required[uuid] } // structure reports a structural reference or a sub-manifest node: an // artifact a content horizon holds at every age. func (p *ConeReferences) structure(rid libfossil.FslID) bool { uuid := p.names[rid] return p.required[uuid] || p.tree[uuid] } // file reports a name some held F card gives: a blob, which a horizon may // place below it. A phantom no F card names is not proven to be one. func (p *ConeReferences) file(rid libfossil.FslID) bool { uuid := p.names[rid] return p.inside[uuid] || p.outside[uuid] } // Artifacts counts the artifact names the classification read. func (p *ConeReferences) Artifacts() int { if p == nil { return 0 } return len(p.names) } // LoadConeReferences classifies both held and phantom artifact names. Traversal // shares the single-name lookup depth and node limits across all roots. func LoadConeReferences(ctx context.Context, q db.Querier, c *cone.Cone) (*ConeReferences, error) { if ctx == nil || q == nil { panic("content.LoadConeReferences: nil context or querier") } p := &ConeReferences{names: map[libfossil.FslID]string{}, outside: map[string]bool{}, inside: map[string]bool{}, required: map[string]bool{}, tree: map[string]bool{}, held: map[string]libfossil.FslID{}, nodes: map[libfossil.FslID]*deck.Deck{}} if err := ctx.Err(); err != nil { return nil, err } if c == nil { return p, nil } facts, err := LoadConeReferenceFacts(ctx, q) if err != nil { return nil, err } return facts.Classify(ctx, c) } // RetainProvenance removes every held sub-manifest node from a proposed drop // set. Sparse-cone Sections 5.4 and 6.3 keep manifests and sub-manifest nodes, // in cone or not: a leaf's F cards are what classify the blobs it names as // elided rather than transit phantoms, and an interior is the range proof that // reaches the leaf. Only blobs are ever dropped. Check-ins never enter a drop // set, since no S card names them. func (p *ConeReferences) RetainProvenance(ctx context.Context, drop map[libfossil.FslID]bool) error { for rid := range p.nodes { if err := ctx.Err(); err != nil { return err } delete(drop, rid) } return ctx.Err() } func (p *ConeReferences) structural(d *deck.Deck) { for _, uuid := range []string{d.B, d.G, d.I} { if uuid != "" { p.required[uuid] = true } } for _, uuid := range d.P { p.required[uuid] = true } for _, q := range d.Q { p.required[q.Target] = true if q.Baseline != "" { p.required[q.Baseline] = true } } for _, t := range d.T { if t.UUID != "" && t.UUID != "*" { p.required[t.UUID] = true } } if d.A != nil && d.A.Source != "" { p.required[d.A.Source] = true } // Cluster inventories are not dependencies; otherwise every clustered file // would be immune to sparse transfer and explicit compaction. } type coneReferenceWalk struct { ctx context.Context cone *cone.Cone refs *ConeReferences nodes map[string]*deck.Deck path map[string]bool visited int } func (w *coneReferenceWalk) walk(cards []deck.SubmanifestCard, upper string, depth int, parentWanted bool) error { for i, card := range cards { if err := w.ctx.Err(); err != nil { return err } if depth > coneReferenceDepth || w.visited >= coneReferenceNodes { return fmt.Errorf("sparse cone: tree traversal bound exceeded") } w.visited++ w.refs.tree[card.UUID] = true limit := upper if i+1 < len(cards) { limit = cards[i+1].Name } wanted := parentWanted && w.cone.MayCover(card.Name, limit) if wanted { w.refs.required[card.UUID] = true } else { w.refs.outside[card.UUID] = true } d := w.nodes[card.UUID] if d == nil { continue } if w.path[card.UUID] { return fmt.Errorf("sparse cone: cyclic sub-manifest %s", card.UUID) } if len(d.F) > 0 { if d.F[0].Name != card.Name || limit != "" && d.F[len(d.F)-1].Name >= limit { return fmt.Errorf("sparse cone: invalid sub-manifest range") } continue } if len(d.S) == 0 || d.S[0].Name != card.Name || limit != "" && d.S[len(d.S)-1].Name >= limit { return fmt.Errorf("sparse cone: invalid sub-manifest range") } w.path[card.UUID] = true err := w.walk(d.S, limit, depth+1, wanted) delete(w.path, card.UUID) if err != nil { return err } } return nil }