Artifact 51611e6384ebe9106d33648da180116a8d046906bba5da7d126cf48924d0d88a:
- File
internal/manifest/read_missing.go
— part of check-in
[8821596379]
at
2026-10-28 21:55:57
on branch feat/demand-fetch-reads
— feat(horizon): targeted demand fetch for reads below the horizon
ReadFileContext, ReadFileAtContext, AnnotateContext and DiffContext take RequiredContentOpts and, when a read needs content a horizon store does not hold, fetch exactly the artifacts it names from the peer and read again (draft-fossil-horizon-00 4.4): the payload, and on a structural store first the check-in's manifest, baseline and nodes. The store cone and its horizon do not move; fetched artifacts are held content.
Reads now refuse with content.MissingBelowHorizon, which matches ErrBelowHorizon and names the horizon. Annotate gathers every missing version along its walk into one round and Diff both sides; a read over one round's gimme budget is refused naming DeepenHorizon.
Closes #489 (user: [email protected], size: 2850) [annotate] [blame] [check-ins using]
package manifest import ( "context" "errors" "github.com/danmestas/go-libfossil/db" "github.com/danmestas/go-libfossil/internal/content" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" "github.com/danmestas/go-libfossil/internal/repo" ) // ReadFileAt reads filePath as check-in rid names it, serving the manifest and // the payload through cache (nil reads uncached); found is false when rid // does not name the file. When the read needs an artifact the store does not // hold and the store cone has a horizon, it fails with a // *content.MissingBelowHorizon naming the check-in, when its structure is // what is missing, or else the payload (draft-fossil-horizon-00 ยง4.4). func ReadFileAt( ctx context.Context, r *repo.Repo, rid libfossil.FslID, filePath string, cache *content.Cache, ) (data []byte, found bool, err error) { if ctx == nil || r == nil { panic("manifest.ReadFileAt: nil context or repo") } if filePath == "" { panic("manifest.ReadFileAt: filePath must not be empty") } f, found, err := LookupFileCached(r, rid, filePath, cache) if err != nil { missing, merr := MissingStructure(ctx, r.DB(), []libfossil.FslID{rid}) return nil, false, missingOr(missing, merr, err) } if !found { return nil, false, nil } fileRID, err := content.RequireAvailableByUUID(r.DB(), f.UUID) if err != nil { missing, merr := content.MissingAtHorizon(r.DB(), nil, []string{f.UUID}) return nil, false, missingOr(missing, merr, err) } data, err = cache.Expand(r.DB(), fileRID) if err != nil { return nil, false, err } return data, true, nil } // MissingStructure is the refusal naming each check-in among rids whose // manifest, baseline or nodes the store does not hold, when its cone has a // horizon; nil when every one is held or the cone has none. func MissingStructure( ctx context.Context, q db.Querier, rids []libfossil.FslID, ) (*content.MissingBelowHorizon, error) { if ctx == nil || q == nil { panic("manifest.MissingStructure: nil context or querier") } var checkins []string for _, rid := range rids { err := RequireParentContent(ctx, q, []libfossil.FslID{rid}) var required *RequiredContentError if errors.As(err, &required) { checkins = append(checkins, required.Roots...) } else if err != nil { return nil, err } } if len(checkins) == 0 { return nil, nil } return content.MissingAtHorizon(q, checkins, nil) } // missingOr is the refusal when the read's failure is content missing below // the horizon, else the read's own error: a classification that cannot // decide leaves that error as the diagnostic. func missingOr(missing *content.MissingBelowHorizon, classifyErr, readErr error) error { if readErr == nil { panic("manifest.missingOr: no read error to classify") } if classifyErr == nil && missing != nil { return missing } return readErr }