1package tinygit23import (4 "context"5 "errors"6 "net/url"7 "strings"89 event "github.com/FelineStateMachine/tinyrelay/protocol/nostr"10)1112// validateArchiveAnnouncement implements the GRASP-05 admission distinction:13// an archive may announce a repository without clone or relay URLs, but the14// archive profile depends on GRASP-02. A configured service keeps the normal15// GRASP-01 requirement for announcements unless the operator enables both16// extensions. Embedded GitRelay users without a public service URL retain the17// permissive library behavior used by local integrations.18func (g *GitRelay) validateArchiveAnnouncement(r Repository) error {19 if g.serviceURL == "" {20 return nil21 }22 if g.serviceListed(r) {23 return nil24 }25 if g.archiveRelated(context.Background(), r) {26 return nil27 }28 p := g.policyValue()29 if len(r.Clone) == 0 && len(r.Relays) == 0 && p.Features.Grasp02 && p.Features.Grasp05 {30 return nil31 }32 return errors.New("blocked: archive repository admission requires GRASP-02 and GRASP-05")33}3435func (g *GitRelay) serviceListed(r Repository) bool {36 base, err := url.Parse(g.serviceURL)37 if err != nil || base.Host == "" {38 return false39 }40 cloneOK, relayOK := false, false41 for _, raw := range r.Clone {42 u, parseErr := url.Parse(raw)43 if parseErr == nil && (u.Scheme == "http" || u.Scheme == "https") && strings.EqualFold(u.Host, base.Host) && strings.HasSuffix(strings.TrimRight(u.Path, "/"), "/"+r.Identifier+".git") {44 cloneOK = true45 }46 }47 for _, raw := range r.Relays {48 u, parseErr := url.Parse(raw)49 if parseErr == nil && (u.Scheme == "ws" || u.Scheme == "wss") && strings.EqualFold(u.Host, base.Host) {50 relayOK = true51 }52 }53 return cloneOK && relayOK54}5556func (g *GitRelay) archiveRelated(ctx context.Context, r Repository) bool {57 if g.store == nil {58 return false59 }60 q, err := g.store.Query(ctx, event.Filter{Kinds: []int{30617}, Tags: map[string][]string{"d": {r.Identifier}}}, 0, 0)61 if err != nil {62 return false63 }64 for _, candidate := range q {65 parsed, parseErr := g.parseRepository(candidate)66 if parseErr != nil || parsed.Identifier != r.Identifier {67 continue68 }69 if candidate.PubKey == r.Owner || contains(parsed.Maintainers, r.Owner) {70 return true71 }72 }73 return false74}