Artifact 614792caead95a76511031a1c7b4af3e604b8fdb71a60c710c19539b4d8b99fa:
- File
internal/sync/sparse_prepare_index.go
— part of check-in
[87f44dbf99]
at
2026-10-21 11:25:00
on branch feat/submanifest-reader
— fix(sync): send the private notice for a prepared sparse gimme
The prepared index left private and shunned RIDs out, so a gimme for an in-cone private artifact failed keeps() and got no reply, where the dense handler sends the private igot. A client never told the artifact is private can keep requesting it. The index now answers cone membership only, as the dense keep-set does; the handler's live private and shun gates decide visibility, so no payload is exposed. (user: [email protected], size: 2430) [annotate] [blame] [check-ins using]
package sync import ( "context" "errors" "fmt" "github.com/danmestas/go-libfossil/internal/cone" "github.com/danmestas/go-libfossil/internal/content" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" "github.com/danmestas/go-libfossil/internal/repo" ) // These deliberately small internal prototype limits reject unsupported // preparation sizes rather than moving unbounded discovery into a request. // Slot accounting includes each RID and all three words of every slice header; // map/parser overhead is separately bounded by artifact/card counts. const ( sparsePrepArtifacts = 1 << 18 sparsePrepAliases = 1 << 20 sparsePrepSlots = 1 << 24 ) func (s *preparedSparseService) prepareIndex(ctx context.Context, bound *repo.Repo) error { budget := content.ReferenceBudget{Artifacts: sparsePrepArtifacts, StoredBytes: 64 << 20, InflatedBytes: 128 << 20, ExpandedBytes: 128 << 20, Cards: sparsePrepAliases} var err error s.facts, s.prepWork, err = content.LoadConeReferenceFactsBounded(ctx, bound.DB(), budget) if err != nil { return err } // Validate all raw root/range/depth relationships off path. This temporary // classification is discarded; no cone-keyed logical facts are retained. validationCone, err := cone.New([]string{"_sparse_validation"}, nil) if err != nil { panic("sync.prepareIndex: invalid constant validation cone") } if _, err := s.facts.Classify(ctx, validationCone); err != nil { return err } // The index answers cone membership only, as the dense keep-set does. // Private and shunned artifacts stay in it: the handler's live gates // withhold their payloads and still send a gimme its ยง8.6 private notice. rows, err := bound.DB().Query(`SELECT rid FROM blob WHERE size>=0 ORDER BY rid`) if err != nil { return err } visible := make([]libfossil.FslID, 0) for rows.Next() { if err := ctx.Err(); err != nil { return errors.Join(err, rows.Close()) } if len(visible) >= sparsePrepArtifacts { return errors.Join(fmt.Errorf("sparse preparation: visibility budget exceeded"), rows.Close()) } var rid libfossil.FslID if err := rows.Scan(&rid); err != nil { return errors.Join(err, rows.Close()) } visible = append(visible, rid) } if err := errors.Join(rows.Err(), rows.Close()); err != nil { return err } s.index, err = content.NewConeSuccessors(ctx, s.facts, visible, sparsePrepAliases, sparsePrepSlots) return err }