perlsky is a Perl 5 implementation of an AT Protocol Personal Data Server.
13
fork

Configure Feed

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

Cover deleted sync.getRecord proof semantics

alice 00101ee3 d1e34e37

+59
+1
docs/TEST_AUDIT.md
··· 79 79 - Missing-repo read paths now match the official runtime more closely: `describeRepo`, `sync.getLatestCommit`, `sync.getRecord`, `sync.getRepo`, `sync.getCheckout`, `sync.getHead`, `sync.getRepoStatus`, `sync.getBlocks`, `sync.getBlob`, and `sync.listBlobs` report `400 RepoNotFound`, while `listRecords` reports `400 InvalidRequest` / `Could not find repo: ...`. 80 80 - `com.atproto.sync.getBlob` needs two distinct not-found branches to stay reference-compatible: missing repos still report `400 RepoNotFound`, but blobs that are merely unreferenced for that repo report `400 InvalidRequest` / `Blob not found`, while moderation takedowns still hide them behind the moderation-layer `404 BlobNotFound` path. 81 81 - Blob sync visibility is now pinned in three phases instead of one: prereference uploader reads fail with `400 InvalidRequest`, referenced same-repo reads succeed with blob bytes and hardening headers, and deduplicated uploads on another repo still fail until that repo actually references the blob. 82 + - `com.atproto.sync.getRecord` deleted-record proofs are now executable-reference-covered too: existing repos keep returning a CAR rooted at the latest commit, with tree blocks present but the deleted record block omitted. 82 83 - `com.atproto.repo.getRecord` must honor `cid` when present, and `putRecord` / `deleteRecord` must actually enforce `swapRecord`; those negative edges are now covered directly. 83 84 - `com.atproto.repo.createRecord` follows the reference runtime by ignoring a stray `swapRecord` field, and direct reference coverage now pins `putRecord` / `deleteRecord` `swapCommit` and `swapRecord` mismatch semantics explicitly. 84 85 - App-password sessions follow the official runtime more closely than the older local assumptions did: access-token scopes use the `com.atproto.appPass` / `com.atproto.appPassPrivileged` names, standard app-password sessions may list app passwords, privileged-only `getServiceAuth` failures report `InvalidRequest`, and revoked refresh tokens on `refreshSession` fail with `400 ExpiredToken`.
+58
script/differential-validate
··· 1339 1339 'putRecord create-or-update semantics match the official reference PDS', 1340 1340 ); 1341 1341 1342 + note('Comparing sync.getRecord deleted-record proof semantics'); 1343 + for my $name (sort keys %server) { 1344 + my $create_res = post_json($server{$name}{origin}, 'com.atproto.repo.createRecord', { 1345 + repo => $server{$name}{did}, 1346 + collection => 'app.bsky.feed.post', 1347 + rkey => 'sync-delete-proof', 1348 + record => { 1349 + %{$record}, 1350 + text => "sync delete proof validation for $name", 1351 + }, 1352 + }, auth_header($server{$name}{access})); 1353 + check($create_res->is_success, "$name createRecord succeeds for deleted sync proof comparison"); 1354 + next unless $create_res->is_success; 1355 + 1356 + my $deleted_record_cid = ($create_res->json || {})->{cid}; 1357 + my $delete_res = post_json($server{$name}{origin}, 'com.atproto.repo.deleteRecord', { 1358 + repo => $server{$name}{did}, 1359 + collection => 'app.bsky.feed.post', 1360 + rkey => 'sync-delete-proof', 1361 + }, auth_header($server{$name}{access})); 1362 + check($delete_res->is_success, "$name deleteRecord succeeds for deleted sync proof comparison"); 1363 + next unless $delete_res->is_success; 1364 + 1365 + my $latest_commit = get_form( 1366 + $server{$name}{origin}, 1367 + 'com.atproto.sync.getLatestCommit', 1368 + { did => $server{$name}{did} }, 1369 + ); 1370 + check($latest_commit->is_success, "$name getLatestCommit succeeds after deleted sync proof setup"); 1371 + 1372 + my $sync_record = get_form($server{$name}{origin}, 'com.atproto.sync.getRecord', { 1373 + did => $server{$name}{did}, 1374 + collection => 'app.bsky.feed.post', 1375 + rkey => 'sync-delete-proof', 1376 + }); 1377 + my $sync_car = $sync_record->is_success ? read_car($sync_record->body // q()) : undef; 1378 + my $latest_commit_json = $latest_commit->json || {}; 1379 + $server{$name}{deleted_sync_record_proof} = { 1380 + proof_ok => $sync_record->is_success ? 1 : 0, 1381 + proof_is_car => (($sync_record->headers->content_type // q()) =~ m{application/vnd\.ipld\.car}) ? 1 : 0, 1382 + proof_root_matches_head => ( 1383 + $sync_car 1384 + && $sync_car->{roots}[0] 1385 + && (($sync_car->{roots}[0]->to_string // q()) eq (($latest_commit_json->{cid} // q()))) 1386 + ) ? 1 : 0, 1387 + proof_omits_record => ( 1388 + !$sync_car 1389 + || !scalar(grep { $_->{cid}->to_string eq $deleted_record_cid } @{ $sync_car->{blocks} || [] }) 1390 + ) ? 1 : 0, 1391 + proof_has_tree => ($sync_car && @{ $sync_car->{blocks} || [] } >= 2) ? 1 : 0, 1392 + }; 1393 + } 1394 + 1395 + check( 1396 + same_hash($server{reference}{deleted_sync_record_proof}, $server{perlsky}{deleted_sync_record_proof}), 1397 + 'sync.getRecord deleted-record proof semantics match the official reference PDS', 1398 + ); 1399 + 1342 1400 note('Comparing repo stale-cid and missing-delete semantics'); 1343 1401 for my $name (sort keys %server) { 1344 1402 my $stale_get = get_form($server{$name}{origin}, 'com.atproto.repo.getRecord', {