Artifact 51859f3d4232a0f19c72a209072aa4f1a80817ca342abee80ce15e50e4c730c8:
- File
conformance/sparsesync_helpers_test.go
— part of check-in
[dc33bca3a6]
at
2026-10-22 16:12:54
on branch feat/horizon-cones
— feat(sync): serve and take the structural horizon
The origin now answers pragma horizon-structural (draft-fossil-horizon-00 §6.1, §6.2): it keeps only the manifests and nodes the held set reaches, labels every boundary hash at the head of each reply (§6.2.2), mints the seed for C and sends it, pragma ahead of its file card, when the client offers another name (§6.2.3), and advertises the structural level only then; a request it cannot place or seed is served as content. It refuses a seed offered to it and withholds a gimme'd manifest below the horizon.
A structural store's requests carry horizon-structural and the seed it holds. It routes the announced card to seed.Hold after checking its name and shape, never storing or announcing it, records the labels, and applies the seed after each reply and after the closing link. A first reply that does not serve structural with a seed, or a seed failing provenance, makes it a content-horizon store for good, reported in the result, and the manifests below the horizon are fetched (§5.1, §5.2.4, §6.3). (user: [email protected], size: 4013) [annotate] [blame] [check-ins using] [more...]
package conformance import ( "context" "fmt" "path/filepath" "strings" "testing" "time" "github.com/danmestas/go-libfossil/internal/content" "github.com/danmestas/go-libfossil/internal/hash" "github.com/danmestas/go-libfossil/internal/repo" "github.com/danmestas/go-libfossil/internal/sync" "github.com/danmestas/go-libfossil/internal/xfer" "github.com/danmestas/go-libfossil/simio" ) // scSyncPeer records decoded wire transcripts, including requests an old peer // ignores. Both directions cross the actual xfer encoder and decoder. type scSyncPeer struct { r *repo.Repo ignore bool opts sync.HandleOpts requests []*xfer.Message replies []*xfer.Message } func scSyncWire(m *xfer.Message) (*xfer.Message, error) { data, err := m.Encode() if err != nil { return nil, err } return xfer.Decode(data, xfer.ContentTypeCompressed) } func (p *scSyncPeer) Exchange(ctx context.Context, req *xfer.Message) (*xfer.Message, error) { req, err := scSyncWire(req) if err != nil { return nil, err } p.requests = append(p.requests, req) if p.ignore { cards := make([]xfer.Card, 0, len(req.Cards)) for _, card := range req.Cards { if c, ok := card.(*xfer.PragmaCard); ok && (c.Name == "cone" || c.Name == "cone-not" || strings.HasPrefix(c.Name, "horizon")) { continue } cards = append(cards, card) } req = &xfer.Message{Cards: cards} } resp, err := sync.HandleSyncWithOpts(ctx, p.r, req, p.opts) if err != nil { return nil, err } resp, err = scSyncWire(resp) if err != nil { return nil, err } p.replies = append(p.replies, resp) return resp, nil } func newSCSyncRepo(t *testing.T, files map[string]string) *scRepo { t.Helper() r, err := repo.Create(filepath.Join(t.TempDir(), "origin.fossil"), "u", simio.CryptoRand{}, "") if err != nil { t.Fatal(err) } t.Cleanup(func() { r.Close() }) if err := r.SetHashPolicy("sha1"); err != nil { t.Fatal(err) } if _, err := r.DB().Exec("UPDATE user SET cap='gio' WHERE login='nobody'"); err != nil { t.Fatal(err) } f := &scRepo{r: r, when: time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC)} f.c1 = f.commit(t, files, 0) return f } func scSyncFiles() map[string]string { return map[string]string{ "src/a": "inside\n", "src/shared": "shared\n", "zzz/shared": "shared\n", "zzz/b": "outside\n", } } func scSyncMember(value string) []xfer.Card { return []xfer.Card{&xfer.PragmaCard{Name: "cone", Values: []string{value}}} } func scSyncClone( t *testing.T, peer *scSyncPeer, opts sync.CloneOpts, ) (*repo.Repo, *sync.CloneResult) { t.Helper() r, result, err := sync.Clone(context.Background(), filepath.Join(t.TempDir(), "clone.fossil"), peer, opts) if err != nil { t.Fatal(err) } t.Cleanup(func() { r.Close() }) return r, result } func scSyncHolding(r *repo.Repo, text string, want bool) error { uuid := hash.SHA1([]byte(text)) _, got, err := content.CheckAvailableByUUID(r.DB(), uuid) if err != nil { return err } if got != want { return fmt.Errorf("holding %s = %v, want %v", uuid, got, want) } return nil } func scSyncNoGimme(messages []*xfer.Message, uuid string) error { for _, m := range messages { for _, card := range m.Cards { if c, ok := card.(*xfer.GimmeCard); ok && c.UUID == uuid { return fmt.Errorf("requested elided artifact %s", uuid) } } } return nil } func scSyncPayloads(m *xfer.Message) map[string]string { out := map[string]string{} for _, card := range m.Cards { switch c := card.(type) { case *xfer.FileCard: out[c.UUID] = c.DeltaSrc case *xfer.CFileCard: out[c.UUID] = c.DeltaSrc } } return out } func scSyncRefused(m *xfer.Message, want string) error { var errors []string for _, card := range m.Cards { if c, ok := card.(*xfer.ErrorCard); ok { errors = append(errors, c.Message) } } if len(errors) != 1 || errors[0] != want || len(scSyncPayloads(m)) != 0 { return fmt.Errorf("refusal errors=%q payloads=%v; want one %q and no content", errors, scSyncPayloads(m), want) } return nil }