mount public data from the atmosphere to a virtual filesystem (macos only) pdfs.at
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

ref: update handle references

+54 -15
+2 -2
Packages/ATProto/Tests/ATProtoTests/IdentifierTests.swift
··· 35 35 struct HandleTests { 36 36 @Test("accepts valid handles") 37 37 func valid() { 38 - #expect(Handle("nate.natemoo.re")?.rawValue == "nate.natemoo.re") 38 + #expect(Handle("natemoo.re")?.rawValue == "natemoo.re") 39 39 #expect(Handle("alice.bsky.social") != nil) 40 40 #expect(Handle("ALICE.example.COM")?.rawValue == "alice.example.com") // normalized 41 41 } ··· 131 131 132 132 @Test("parses authority-only") 133 133 func authorityOnly() { 134 - let u = ATURI("at://nate.natemoo.re")! 134 + let u = ATURI("at://natemoo.re")! 135 135 #expect(u.collection == nil) 136 136 #expect(u.rkey == nil) 137 137 }
+1 -1
Packages/PDFSShared/Tests/PDFSSharedTests/MountConfigTests.swift
··· 8 8 func roundTrip() throws { 9 9 let config = MountConfig( 10 10 did: "did:plc:abc123", 11 - handle: "nate.natemoo.re", 11 + handle: "natemoo.re", 12 12 pds: URL(string: "https://bsky.social")!, 13 13 xpcServiceName: "at.pdfs.session" 14 14 )
+51 -12
docs/superpowers/plans/2026-04-17-atproto-xrpc-identity.md
··· 18 18 framework. Zero third-party dependencies. Target macOS 14+. 19 19 20 20 **Scope:** 21 + 21 22 - IN: Error model, XRPC GET/POST, typed read endpoints (`describeRepo`, 22 23 `listRecords`, `getRecord`, `listBlobs`, `getBlob`), handle→DID 23 24 resolution, DID→doc resolution, PDS endpoint extraction, TTL cache ··· 83 84 ### Task 1: Error model + POSIX mapping 84 85 85 86 **Files:** 87 + 86 88 - Create: `Sources/ATProto/Errors/ATProtoError.swift` 87 89 - Create: `Sources/ATProto/Errors/ATProtoError+POSIX.swift` 88 90 - Create: `Tests/ATProtoTests/ErrorMappingTests.swift` ··· 158 160 ```bash 159 161 swift test --filter ErrorMappingTests 2>&1 | tail -10 160 162 ``` 163 + 161 164 Expected: FAIL with "cannot find 'ATProtoError' in scope". 162 165 163 166 - [ ] **Step 3: Write the error enum** ··· 233 236 ```bash 234 237 swift test --filter ErrorMappingTests 2>&1 | tail -10 235 238 ``` 239 + 236 240 Expected: 8 tests pass. 237 241 238 242 - [ ] **Step 6: Commit** ··· 247 251 ### Task 2: URLProtocol stub test utility 248 252 249 253 **Files:** 254 + 250 255 - Create: `Tests/ATProtoTests/Support/URLProtocolStub.swift` 251 256 252 257 - [ ] **Step 1: Write the stub utility** ··· 321 326 ```bash 322 327 swift build 2>&1 | tail -5 323 328 ``` 329 + 324 330 Expected: Build complete. 325 331 326 332 - [ ] **Step 3: Commit** ··· 335 341 ### Task 3: XRPCClient GET (unauthenticated) 336 342 337 343 **Files:** 344 + 338 345 - Create: `Sources/ATProto/Support/AuthTokenProvider.swift` 339 346 - Create: `Sources/ATProto/XRPC/XRPCClient.swift` 340 347 - Create: `Tests/ATProtoTests/XRPCClientTests.swift` ··· 429 436 ```bash 430 437 swift test --filter XRPCClientTests 2>&1 | tail -10 431 438 ``` 439 + 432 440 Expected: FAIL with "cannot find 'XRPCClient' in scope". 433 441 434 442 - [ ] **Step 3: Write AuthTokenProvider protocol** ··· 543 551 ```bash 544 552 swift test --filter XRPCClientTests 2>&1 | tail -10 545 553 ``` 554 + 546 555 Expected: 3 tests pass. 547 556 548 557 - [ ] **Step 6: Commit** ··· 557 566 ### Task 4: XRPCClient POST with JSON body 558 567 559 568 **Files:** 569 + 560 570 - Modify: `Sources/ATProto/XRPC/XRPCClient.swift` (add `post<Input, Output>`) 561 571 - Modify: `Tests/ATProtoTests/XRPCClientTests.swift` (add POST tests) 562 572 ··· 597 607 ```bash 598 608 swift test --filter XRPCClientTests/postSuccess 2>&1 | tail -10 599 609 ``` 610 + 600 611 Expected: FAIL with "value of type 'XRPCClient' has no member 'post'". 601 612 602 613 - [ ] **Step 3: Add POST method to XRPCClient** ··· 637 648 ```bash 638 649 swift test --filter XRPCClientTests 2>&1 | tail -10 639 650 ``` 651 + 640 652 Expected: 4 tests pass. 641 653 642 654 - [ ] **Step 5: Commit** ··· 651 663 ### Task 5: Typed endpoint — describeRepo 652 664 653 665 **Files:** 666 + 654 667 - Create: `Sources/ATProto/XRPC/AnyDecodable.swift` 655 668 - Create: `Sources/ATProto/XRPC/DescribeRepoOutput.swift` 656 669 - Create: `Sources/ATProto/XRPC/XRPCEndpoints.swift` ··· 683 696 #expect(request.url?.query?.contains("repo=did:plc:abc") == true) 684 697 return .json(#""" 685 698 { 686 - "handle": "nate.natemoo.re", 699 + "handle": "natemoo.re", 687 700 "did": "did:plc:abc", 688 701 "didDoc": {"id":"did:plc:abc"}, 689 702 "collections": ["app.bsky.feed.post", "app.bsky.actor.profile"], ··· 694 707 defer { URLProtocolStub.reset() } 695 708 696 709 let result = try await client.describeRepo(repo: "did:plc:abc") 697 - #expect(result.handle == "nate.natemoo.re") 710 + #expect(result.handle == "natemoo.re") 698 711 #expect(result.did == "did:plc:abc") 699 712 #expect(result.collections == ["app.bsky.feed.post", "app.bsky.actor.profile"]) 700 713 } ··· 706 719 ```bash 707 720 swift test --filter XRPCEndpointsTests 2>&1 | tail -10 708 721 ``` 722 + 709 723 Expected: FAIL with "value of type 'XRPCClient' has no member 'describeRepo'". 710 724 711 725 - [ ] **Step 3: Create the AnyDecodable helper** ··· 799 813 ```bash 800 814 swift test --filter XRPCEndpointsTests 2>&1 | tail -10 801 815 ``` 816 + 802 817 Expected: 1 test passes. 803 818 804 819 - [ ] **Step 7: Commit** ··· 813 828 ### Task 6: Typed endpoint — listRecords 814 829 815 830 **Files:** 831 + 816 832 - Create: `Sources/ATProto/XRPC/ListRecordsOutput.swift` 817 833 - Modify: `Sources/ATProto/XRPC/XRPCEndpoints.swift` 818 834 - Modify: `Tests/ATProtoTests/XRPCEndpointsTests.swift` ··· 861 877 ```bash 862 878 swift test --filter XRPCEndpointsTests/listRecords 2>&1 | tail -10 863 879 ``` 880 + 864 881 Expected: FAIL. 865 882 866 883 - [ ] **Step 3: Create output type** ··· 920 937 ```bash 921 938 swift test --filter XRPCEndpointsTests 2>&1 | tail -10 922 939 ``` 940 + 923 941 Expected: 2 tests pass. 924 942 925 943 - [ ] **Step 6: Commit** ··· 934 952 ### Task 7: Typed endpoint — getRecord 935 953 936 954 **Files:** 955 + 937 956 - Create: `Sources/ATProto/XRPC/GetRecordOutput.swift` 938 957 - Modify: `Sources/ATProto/XRPC/XRPCEndpoints.swift` 939 958 - Modify: `Tests/ATProtoTests/XRPCEndpointsTests.swift` ··· 974 993 ```bash 975 994 swift test --filter XRPCEndpointsTests/getRecord 2>&1 | tail -10 976 995 ``` 996 + 977 997 Expected: FAIL. 978 998 979 999 - [ ] **Step 3: Create output type** ··· 1024 1044 ```bash 1025 1045 swift test --filter XRPCEndpointsTests 2>&1 | tail -10 1026 1046 ``` 1047 + 1027 1048 Expected: 3 tests pass. 1028 1049 1029 1050 - [ ] **Step 6: Commit** ··· 1038 1059 ### Task 8: Typed endpoint — listBlobs 1039 1060 1040 1061 **Files:** 1062 + 1041 1063 - Create: `Sources/ATProto/XRPC/ListBlobsOutput.swift` 1042 1064 - Modify: `Sources/ATProto/XRPC/XRPCEndpoints.swift` 1043 1065 - Modify: `Tests/ATProtoTests/XRPCEndpointsTests.swift` ··· 1069 1091 ```bash 1070 1092 swift test --filter XRPCEndpointsTests/listBlobs 2>&1 | tail -10 1071 1093 ``` 1094 + 1072 1095 Expected: FAIL. 1073 1096 1074 1097 - [ ] **Step 3: Create output type** ··· 1110 1133 ```bash 1111 1134 swift test --filter XRPCEndpointsTests 2>&1 | tail -10 1112 1135 ``` 1136 + 1113 1137 Expected: 4 tests pass. 1114 1138 1115 1139 - [ ] **Step 6: Commit** ··· 1124 1148 ### Task 9: Typed endpoint — getBlob (raw bytes) 1125 1149 1126 1150 **Files:** 1151 + 1127 1152 - Modify: `Sources/ATProto/XRPC/XRPCEndpoints.swift` 1128 1153 - Modify: `Tests/ATProtoTests/XRPCEndpointsTests.swift` 1129 1154 ··· 1155 1180 ```bash 1156 1181 swift test --filter XRPCEndpointsTests/getBlob 2>&1 | tail -10 1157 1182 ``` 1183 + 1158 1184 Expected: FAIL. 1159 1185 1160 1186 - [ ] **Step 3: Add endpoint + return type** ··· 1184 1210 ```bash 1185 1211 swift test --filter XRPCEndpointsTests 2>&1 | tail -10 1186 1212 ``` 1213 + 1187 1214 Expected: 5 tests pass. 1188 1215 1189 1216 - [ ] **Step 5: Commit** ··· 1198 1225 ### Task 10: Handle → DID resolver 1199 1226 1200 1227 **Files:** 1228 + 1201 1229 - Create: `Sources/ATProto/Identity/HandleResolver.swift` 1202 1230 - Create: `Tests/ATProtoTests/HandleResolverTests.swift` 1203 1231 ··· 1223 1251 func dohSuccess() async throws { 1224 1252 let session = URLProtocolStub.install { request in 1225 1253 let q = request.url?.query ?? "" 1226 - if request.url?.host == "cloudflare-dns.com" && q.contains("name=_atproto.nate.natemoo.re") { 1254 + if request.url?.host == "cloudflare-dns.com" && q.contains("name=_atproto.natemoo.re") { 1227 1255 // Minimal DoH JSON response 1228 1256 return .json(#""" 1229 - {"Status":0,"Answer":[{"name":"_atproto.nate.natemoo.re","type":16,"TTL":300,"data":"\"did=did:plc:abc\""}]} 1257 + {"Status":0,"Answer":[{"name":"_atproto.natemoo.re","type":16,"TTL":300,"data":"\"did=did:plc:abc\""}]} 1230 1258 """#) 1231 1259 } 1232 1260 return URLProtocolStub.Response(statusCode: 500, headers: [:], body: Data()) ··· 1234 1262 defer { URLProtocolStub.reset() } 1235 1263 1236 1264 let resolver = HandleResolver(session: session) 1237 - let did = try await resolver.resolve(handle: Handle("nate.natemoo.re")!) 1265 + let did = try await resolver.resolve(handle: Handle("natemoo.re")!) 1238 1266 #expect(did.rawValue == "did:plc:abc") 1239 1267 } 1240 1268 ··· 1244 1272 if request.url?.host == "cloudflare-dns.com" { 1245 1273 return .json(#"{"Status":0,"Answer":[]}"#) 1246 1274 } 1247 - if request.url?.host == "nate.natemoo.re", request.url?.path == "/.well-known/atproto-did" { 1275 + if request.url?.host == "natemoo.re", request.url?.path == "/.well-known/atproto-did" { 1248 1276 return .text("did:plc:xyz\n") 1249 1277 } 1250 1278 return URLProtocolStub.Response(statusCode: 500, headers: [:], body: Data()) ··· 1252 1280 defer { URLProtocolStub.reset() } 1253 1281 1254 1282 let resolver = HandleResolver(session: session) 1255 - let did = try await resolver.resolve(handle: Handle("nate.natemoo.re")!) 1283 + let did = try await resolver.resolve(handle: Handle("natemoo.re")!) 1256 1284 #expect(did.rawValue == "did:plc:xyz") 1257 1285 } 1258 1286 ··· 1276 1304 ```bash 1277 1305 swift test --filter HandleResolverTests 2>&1 | tail -10 1278 1306 ``` 1307 + 1279 1308 Expected: FAIL with "cannot find 'HandleResolver' in scope". 1280 1309 1281 1310 - [ ] **Step 3: Implement resolver** ··· 1357 1386 ```bash 1358 1387 swift test --filter HandleResolverTests 2>&1 | tail -10 1359 1388 ``` 1389 + 1360 1390 Expected: 3 tests pass. 1361 1391 1362 1392 - [ ] **Step 5: Commit** ··· 1371 1401 ### Task 11: DID → doc resolver 1372 1402 1373 1403 **Files:** 1404 + 1374 1405 - Create: `Sources/ATProto/Identity/DIDDocument.swift` 1375 1406 - Create: `Sources/ATProto/Identity/DIDResolver.swift` 1376 1407 - Create: `Tests/ATProtoTests/DIDResolverTests.swift` ··· 1393 1424 return .json(#""" 1394 1425 { 1395 1426 "id":"did:plc:abc", 1396 - "alsoKnownAs":["at://nate.natemoo.re"], 1427 + "alsoKnownAs":["at://natemoo.re"], 1397 1428 "service":[ 1398 1429 {"id":"#atproto_pds","type":"AtprotoPersonalDataServer","serviceEndpoint":"https://bsky.social"} 1399 1430 ] ··· 1408 1439 let doc = try await resolver.resolve(did: DID("did:plc:abc")!) 1409 1440 #expect(doc.id == "did:plc:abc") 1410 1441 #expect(doc.pdsEndpoint?.absoluteString == "https://bsky.social") 1411 - #expect(doc.handles.first == "nate.natemoo.re") 1442 + #expect(doc.handles.first == "natemoo.re") 1412 1443 } 1413 1444 1414 1445 @Test("resolves did:web via .well-known") ··· 1452 1483 ```bash 1453 1484 swift test --filter DIDResolverTests 2>&1 | tail -10 1454 1485 ``` 1486 + 1455 1487 Expected: FAIL. 1456 1488 1457 1489 - [ ] **Step 3: Implement DIDDocument** ··· 1472 1504 public let serviceEndpoint: String 1473 1505 } 1474 1506 1475 - /// Handles parsed from `alsoKnownAs` entries like `at://nate.natemoo.re`. 1507 + /// Handles parsed from `alsoKnownAs` entries like `at://natemoo.re`. 1476 1508 public var handles: [String] { 1477 1509 alsoKnownAs.compactMap { 1478 1510 $0.hasPrefix("at://") ? String($0.dropFirst("at://".count)) : nil ··· 1562 1594 ```bash 1563 1595 swift test --filter DIDResolverTests 2>&1 | tail -10 1564 1596 ``` 1597 + 1565 1598 Expected: 3 tests pass. 1566 1599 1567 1600 - [ ] **Step 6: Commit** ··· 1576 1609 ### Task 12: IdentityResolver orchestrator + cache 1577 1610 1578 1611 **Files:** 1612 + 1579 1613 - Create: `Sources/ATProto/Identity/IdentityResolver.swift` 1580 1614 - Create: `Tests/ATProtoTests/IdentityResolverTests.swift` 1581 1615 ··· 1600 1634 """#) 1601 1635 case let u? where u.absoluteString == "https://plc.directory/did:plc:abc": 1602 1636 return .json(#""" 1603 - {"id":"did:plc:abc","alsoKnownAs":["at://nate.natemoo.re"], 1637 + {"id":"did:plc:abc","alsoKnownAs":["at://natemoo.re"], 1604 1638 "service":[{"id":"#atproto_pds","type":"AtprotoPersonalDataServer","serviceEndpoint":"https://bsky.social"}]} 1605 1639 """#) 1606 1640 default: ··· 1610 1644 defer { URLProtocolStub.reset() } 1611 1645 1612 1646 let resolver = IdentityResolver(session: session) 1613 - let resolved = try await resolver.resolve(handleOrDID: "nate.natemoo.re") 1647 + let resolved = try await resolver.resolve(handleOrDID: "natemoo.re") 1614 1648 #expect(resolved.did.rawValue == "did:plc:abc") 1615 1649 #expect(resolved.pds?.absoluteString == "https://bsky.social") 1616 1650 } ··· 1638 1672 ```bash 1639 1673 swift test --filter IdentityResolverTests 2>&1 | tail -10 1640 1674 ``` 1675 + 1641 1676 Expected: FAIL. 1642 1677 1643 1678 - [ ] **Step 3: Implement IdentityResolver** ··· 1722 1757 ```bash 1723 1758 swift test --filter IdentityResolverTests 2>&1 | tail -10 1724 1759 ``` 1760 + 1725 1761 Expected: 2 tests pass. 1726 1762 1727 1763 - [ ] **Step 5: Commit** ··· 1743 1779 ```bash 1744 1780 swift test 2>&1 | tail -5 1745 1781 ``` 1782 + 1746 1783 Expected: all tests pass (should be 20+ across Identifiers, Errors, XRPC, 1747 1784 Endpoints, HandleResolver, DIDResolver, IdentityResolver). 1748 1785 ··· 1751 1788 ```bash 1752 1789 swift build 2>&1 | grep -i warning || echo "no warnings" 1753 1790 ``` 1791 + 1754 1792 Expected: "no warnings". 1755 1793 1756 1794 - [ ] **Step 3: (Optional, not in CI) Live smoke test against bsky.social** ··· 1770 1808 ## Self-review notes 1771 1809 1772 1810 Coverage against the parent plan's "atproto client" section: 1811 + 1773 1812 - ✅ Identifiers (already shipped before this plan) 1774 1813 - ✅ Error model + POSIX mapping (Task 1) 1775 1814 - ✅ XRPC GET/POST (Tasks 3–4)