···163163 log.Printf("Warning: failed to resolve handle for DID %s: %v", did, err)
164164 // Continue checking explicit DIDs even if handle resolution fails
165165 handleResolved = true // Mark as attempted (don't retry)
166166- handle = "" // Empty handle won't match patterns
166166+ handle = "" // Empty handle won't match patterns
167167 } else {
168168 handleResolved = true
169169 }
+295
pkg/hold/pds/cbor_gen.go
···11+// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT.
22+33+package pds
44+55+import (
66+ "fmt"
77+ "io"
88+ "math"
99+ "sort"
1010+1111+ cid "github.com/ipfs/go-cid"
1212+ cbg "github.com/whyrusleeping/cbor-gen"
1313+ xerrors "golang.org/x/xerrors"
1414+)
1515+1616+var _ = xerrors.Errorf
1717+var _ = cid.Undef
1818+var _ = math.E
1919+var _ = sort.Sort
2020+2121+func (t *CrewRecord) MarshalCBOR(w io.Writer) error {
2222+ if t == nil {
2323+ _, err := w.Write(cbg.CborNull)
2424+ return err
2525+ }
2626+2727+ cw := cbg.NewCborWriter(w)
2828+2929+ if _, err := cw.Write([]byte{165}); err != nil {
3030+ return err
3131+ }
3232+3333+ // t.Role (string) (string)
3434+ if len("role") > 8192 {
3535+ return xerrors.Errorf("Value in field \"role\" was too long")
3636+ }
3737+3838+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("role"))); err != nil {
3939+ return err
4040+ }
4141+ if _, err := cw.WriteString(string("role")); err != nil {
4242+ return err
4343+ }
4444+4545+ if len(t.Role) > 8192 {
4646+ return xerrors.Errorf("Value in field t.Role was too long")
4747+ }
4848+4949+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Role))); err != nil {
5050+ return err
5151+ }
5252+ if _, err := cw.WriteString(string(t.Role)); err != nil {
5353+ return err
5454+ }
5555+5656+ // t.Type (string) (string)
5757+ if len("$type") > 8192 {
5858+ return xerrors.Errorf("Value in field \"$type\" was too long")
5959+ }
6060+6161+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil {
6262+ return err
6363+ }
6464+ if _, err := cw.WriteString(string("$type")); err != nil {
6565+ return err
6666+ }
6767+6868+ if len(t.Type) > 8192 {
6969+ return xerrors.Errorf("Value in field t.Type was too long")
7070+ }
7171+7272+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Type))); err != nil {
7373+ return err
7474+ }
7575+ if _, err := cw.WriteString(string(t.Type)); err != nil {
7676+ return err
7777+ }
7878+7979+ // t.Member (string) (string)
8080+ if len("member") > 8192 {
8181+ return xerrors.Errorf("Value in field \"member\" was too long")
8282+ }
8383+8484+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("member"))); err != nil {
8585+ return err
8686+ }
8787+ if _, err := cw.WriteString(string("member")); err != nil {
8888+ return err
8989+ }
9090+9191+ if len(t.Member) > 8192 {
9292+ return xerrors.Errorf("Value in field t.Member was too long")
9393+ }
9494+9595+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Member))); err != nil {
9696+ return err
9797+ }
9898+ if _, err := cw.WriteString(string(t.Member)); err != nil {
9999+ return err
100100+ }
101101+102102+ // t.AddedAt (string) (string)
103103+ if len("addedAt") > 8192 {
104104+ return xerrors.Errorf("Value in field \"addedAt\" was too long")
105105+ }
106106+107107+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("addedAt"))); err != nil {
108108+ return err
109109+ }
110110+ if _, err := cw.WriteString(string("addedAt")); err != nil {
111111+ return err
112112+ }
113113+114114+ if len(t.AddedAt) > 8192 {
115115+ return xerrors.Errorf("Value in field t.AddedAt was too long")
116116+ }
117117+118118+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.AddedAt))); err != nil {
119119+ return err
120120+ }
121121+ if _, err := cw.WriteString(string(t.AddedAt)); err != nil {
122122+ return err
123123+ }
124124+125125+ // t.Permissions ([]string) (slice)
126126+ if len("permissions") > 8192 {
127127+ return xerrors.Errorf("Value in field \"permissions\" was too long")
128128+ }
129129+130130+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("permissions"))); err != nil {
131131+ return err
132132+ }
133133+ if _, err := cw.WriteString(string("permissions")); err != nil {
134134+ return err
135135+ }
136136+137137+ if len(t.Permissions) > 8192 {
138138+ return xerrors.Errorf("Slice value in field t.Permissions was too long")
139139+ }
140140+141141+ if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Permissions))); err != nil {
142142+ return err
143143+ }
144144+ for _, v := range t.Permissions {
145145+ if len(v) > 8192 {
146146+ return xerrors.Errorf("Value in field v was too long")
147147+ }
148148+149149+ if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil {
150150+ return err
151151+ }
152152+ if _, err := cw.WriteString(string(v)); err != nil {
153153+ return err
154154+ }
155155+156156+ }
157157+ return nil
158158+}
159159+160160+func (t *CrewRecord) UnmarshalCBOR(r io.Reader) (err error) {
161161+ *t = CrewRecord{}
162162+163163+ cr := cbg.NewCborReader(r)
164164+165165+ maj, extra, err := cr.ReadHeader()
166166+ if err != nil {
167167+ return err
168168+ }
169169+ defer func() {
170170+ if err == io.EOF {
171171+ err = io.ErrUnexpectedEOF
172172+ }
173173+ }()
174174+175175+ if maj != cbg.MajMap {
176176+ return fmt.Errorf("cbor input should be of type map")
177177+ }
178178+179179+ if extra > cbg.MaxLength {
180180+ return fmt.Errorf("CrewRecord: map struct too large (%d)", extra)
181181+ }
182182+183183+ n := extra
184184+185185+ nameBuf := make([]byte, 11)
186186+ for i := uint64(0); i < n; i++ {
187187+ nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 8192)
188188+ if err != nil {
189189+ return err
190190+ }
191191+192192+ if !ok {
193193+ // Field doesn't exist on this type, so ignore it
194194+ if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
195195+ return err
196196+ }
197197+ continue
198198+ }
199199+200200+ switch string(nameBuf[:nameLen]) {
201201+ // t.Role (string) (string)
202202+ case "role":
203203+204204+ {
205205+ sval, err := cbg.ReadStringWithMax(cr, 8192)
206206+ if err != nil {
207207+ return err
208208+ }
209209+210210+ t.Role = string(sval)
211211+ }
212212+ // t.Type (string) (string)
213213+ case "$type":
214214+215215+ {
216216+ sval, err := cbg.ReadStringWithMax(cr, 8192)
217217+ if err != nil {
218218+ return err
219219+ }
220220+221221+ t.Type = string(sval)
222222+ }
223223+ // t.Member (string) (string)
224224+ case "member":
225225+226226+ {
227227+ sval, err := cbg.ReadStringWithMax(cr, 8192)
228228+ if err != nil {
229229+ return err
230230+ }
231231+232232+ t.Member = string(sval)
233233+ }
234234+ // t.AddedAt (string) (string)
235235+ case "addedAt":
236236+237237+ {
238238+ sval, err := cbg.ReadStringWithMax(cr, 8192)
239239+ if err != nil {
240240+ return err
241241+ }
242242+243243+ t.AddedAt = string(sval)
244244+ }
245245+ // t.Permissions ([]string) (slice)
246246+ case "permissions":
247247+248248+ maj, extra, err = cr.ReadHeader()
249249+ if err != nil {
250250+ return err
251251+ }
252252+253253+ if extra > 8192 {
254254+ return fmt.Errorf("t.Permissions: array too large (%d)", extra)
255255+ }
256256+257257+ if maj != cbg.MajArray {
258258+ return fmt.Errorf("expected cbor array")
259259+ }
260260+261261+ if extra > 0 {
262262+ t.Permissions = make([]string, extra)
263263+ }
264264+265265+ for i := 0; i < int(extra); i++ {
266266+ {
267267+ var maj byte
268268+ var extra uint64
269269+ var err error
270270+ _ = maj
271271+ _ = extra
272272+ _ = err
273273+274274+ {
275275+ sval, err := cbg.ReadStringWithMax(cr, 8192)
276276+ if err != nil {
277277+ return err
278278+ }
279279+280280+ t.Permissions[i] = string(sval)
281281+ }
282282+283283+ }
284284+ }
285285+286286+ default:
287287+ // Field doesn't exist on this type, so ignore it
288288+ if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
289289+ return err
290290+ }
291291+ }
292292+ }
293293+294294+ return nil
295295+}
+2-26
pkg/hold/pds/crew.go
···33import (
44 "context"
55 "fmt"
66- "io"
76 "time"
8798 "github.com/ipfs/go-cid"
109)
11101212-// CrewRecord represents a crew member in the hold
1313-type CrewRecord struct {
1414- Member string `json:"member" cborgen:"member"` // DID of the crew member
1515- Role string `json:"role" cborgen:"role"` // "admin" or "member"
1616- Permissions []string `json:"permissions" cborgen:"permissions"` // e.g., ["blob:read", "blob:write"]
1717- AddedAt time.Time `json:"addedAt" cborgen:"addedAt"`
1818-}
1919-2020-// MarshalCBOR implements cbg.CBORMarshaler
2121-func (c *CrewRecord) MarshalCBOR(w io.Writer) error {
2222- // TODO: Implement proper CBOR marshaling
2323- return fmt.Errorf("CBOR marshaling not yet implemented")
2424-}
2525-2626-// UnmarshalCBOR implements cbg.CBORUnmarshaler
2727-func (c *CrewRecord) UnmarshalCBOR(r io.Reader) error {
2828- // TODO: Implement proper CBOR unmarshaling
2929- return fmt.Errorf("CBOR unmarshaling not yet implemented")
3030-}
3131-3232-const (
3333- CrewCollection = "io.atcr.hold.crew"
3434-)
3535-3611// AddCrewMember adds a new crew member to the hold and commits to carstore
3712func (p *HoldPDS) AddCrewMember(ctx context.Context, memberDID, role string, permissions []string) (cid.Cid, error) {
3813 crewRecord := &CrewRecord{
1414+ Type: CrewCollection,
3915 Member: memberDID,
4016 Role: role,
4117 Permissions: permissions,
4242- AddedAt: time.Now(),
1818+ AddedAt: time.Now().Format(time.RFC3339),
4319 }
44204521 // Create record in repo (using memberDID as rkey for easy lookup)
+7-8
pkg/hold/pds/did.go
···991010// DIDDocument represents a did:web document
1111type DIDDocument struct {
1212- Context []string `json:"@context"`
1313- ID string `json:"id"`
1414- AlsoKnownAs []string `json:"alsoKnownAs,omitempty"`
1515- VerificationMethod []VerificationMethod `json:"verificationMethod"`
1616- Authentication []string `json:"authentication,omitempty"`
1717- AssertionMethod []string `json:"assertionMethod,omitempty"`
1818- Service []Service `json:"service,omitempty"`
1212+ Context []string `json:"@context"`
1313+ ID string `json:"id"`
1414+ AlsoKnownAs []string `json:"alsoKnownAs,omitempty"`
1515+ VerificationMethod []VerificationMethod `json:"verificationMethod"`
1616+ Authentication []string `json:"authentication,omitempty"`
1717+ AssertionMethod []string `json:"assertionMethod,omitempty"`
1818+ Service []Service `json:"service,omitempty"`
1919}
20202121// VerificationMethod represents a public key in a DID document
···82828383 return doc, nil
8484}
8585-86858786// MarshalDIDDocument converts a DID document to JSON using the stored public URL
8887func (p *HoldPDS) MarshalDIDDocument() ([]byte, error) {
+9-4
pkg/hold/pds/server.go
···101101// Bootstrap initializes the hold with the owner as the first crew member
102102func (p *HoldPDS) Bootstrap(ctx context.Context, ownerDID string) error {
103103 if ownerDID == "" {
104104- // No owner specified, skip bootstrap
105104 return nil
106105 }
107106108107 // Check if repo already has commits
109109- _, err := p.carstore.GetUserRepoHead(ctx, p.uid)
108108+ head, err := p.carstore.GetUserRepoHead(ctx, p.uid)
110109 if err == nil {
111111- // Repo already has commits, skip bootstrap
112112- return nil
110110+ // Repo exists - check if we need to re-bootstrap due to key change
111111+ // If the repo exists but is empty/invalid, we should re-bootstrap
112112+ if head.String() == "" || head.String() == "b" {
113113+ fmt.Printf("⚠️ Detected invalid repo state, re-bootstrapping...\n")
114114+ } else {
115115+ fmt.Printf("⏭️ Skipping PDS bootstrap: repo already initialized (head: %s)\n", head.String()[:16])
116116+ return nil
117117+ }
113118 }
114119115120 // Add hold owner as first crew member with admin role
+16
pkg/hold/pds/types.go
···11+package pds
22+33+// ATProto record types for the hold service
44+55+// CrewRecord represents a crew member in the hold
66+type CrewRecord struct {
77+ Type string `cborgen:"$type"`
88+ Member string `cborgen:"member"`
99+ Role string `cborgen:"role"`
1010+ Permissions []string `cborgen:"permissions"`
1111+ AddedAt string `cborgen:"addedAt"` // RFC3339 timestamp
1212+}
1313+1414+const (
1515+ CrewCollection = "io.atcr.hold.crew"
1616+)
+3-3
pkg/hold/pds/xrpc.go
···11111212// XRPCHandler handles XRPC requests for the embedded PDS
1313type XRPCHandler struct {
1414- pds *HoldPDS
1515- publicURL string
1616- blobStore BlobStore
1414+ pds *HoldPDS
1515+ publicURL string
1616+ blobStore BlobStore
1717}
18181919// BlobStore interface wraps the existing hold service storage operations