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.

Align stale record CID semantics

alice adfe5eeb ca860b8b

+65 -4
+1 -1
docs/TEST_AUDIT.md
··· 172 172 | `t/reference-differential-plc.t` | direct reference differential | official runtime comparison in PLC mode | 173 173 | `t/reference-differential.t` | direct reference differential | official runtime comparison in baseline mode | 174 174 | `t/remote-handle-resolution.t` | audited local regression | remote `did:web` DID docs, conservative remote identity handling, external-handle adoption, invalid-handle rejection, and missing remote DID/identity error semantics | 175 - | `t/repo-api.t` | audited local regression | record mutation and read semantics, but still lighter than ideal on some negative/reference edge cases | 175 + | `t/repo-api.t` | audited local regression | record mutation, stale-CID, swap, and missing-delete semantics | 176 176 | `t/repo-firehose-car.t` | audited local regression | repo commit CAR shape and firehose interactions | 177 177 | `t/repo_formats.t` | audited local regression | direct repo wire-format and CAR expectations | 178 178 | `t/self-service-invite-surfaces.t` | audited local regression | isolated self-service invite issuance coverage for invite minting, account scoping, and account invite-code listing |
+13 -2
lib/ATProto/PDS/API/Repo.pm
··· 86 86 return undef; 87 87 } 88 88 assert_repo_readable($c, $account); 89 + my $cid = $c->param('cid'); 89 90 my $row = $c->store->get_record( 90 91 $account->{did}, 91 92 $c->param('collection'), 92 93 $c->param('rkey'), 93 - $c->param('cid'), 94 + $cid, 94 95 ); 95 - xrpc_error(404, 'RecordNotFound', 'Record was not found') unless $row; 96 + unless ($row) { 97 + if (defined $cid && length $cid) { 98 + my $current = $c->store->get_record( 99 + $account->{did}, 100 + $c->param('collection'), 101 + $c->param('rkey'), 102 + ); 103 + xrpc_error(400, 'RecordNotFound', 'Record was not found') if $current; 104 + } 105 + xrpc_error(404, 'RecordNotFound', 'Record was not found'); 106 + } 96 107 assert_record_readable($c, _record_uri($account->{did}, $row->{collection}, $row->{rkey})); 97 108 return _record_view($account->{did}, $row); 98 109 });
+50
script/differential-validate
··· 1337 1337 'putRecord create-or-update semantics match the official reference PDS', 1338 1338 ); 1339 1339 1340 + note('Comparing repo stale-cid and missing-delete semantics'); 1341 + for my $name (sort keys %server) { 1342 + my $stale_get = get_form($server{$name}{origin}, 'com.atproto.repo.getRecord', { 1343 + repo => $server{$name}{did}, 1344 + collection => 'app.bsky.feed.post', 1345 + rkey => 'diffpost', 1346 + cid => $server{$name}{record_cid}, 1347 + }); 1348 + 1349 + my $latest_before_missing_delete = get_form( 1350 + $server{$name}{origin}, 1351 + 'com.atproto.sync.getLatestCommit', 1352 + { did => $server{$name}{did} }, 1353 + ); 1354 + check($latest_before_missing_delete->is_success, "$name getLatestCommit succeeds before missing delete"); 1355 + 1356 + my $missing_delete = post_json($server{$name}{origin}, 'com.atproto.repo.deleteRecord', { 1357 + repo => $server{$name}{did}, 1358 + collection => 'app.bsky.feed.post', 1359 + rkey => 'missing-rkey-ok', 1360 + }, auth_header($server{$name}{access})); 1361 + 1362 + my $latest_after_missing_delete = get_form( 1363 + $server{$name}{origin}, 1364 + 'com.atproto.sync.getLatestCommit', 1365 + { did => $server{$name}{did} }, 1366 + ); 1367 + check($latest_after_missing_delete->is_success, "$name getLatestCommit succeeds after missing delete"); 1368 + 1369 + $server{$name}{repo_read_edges} = { 1370 + stale_get => normalize_xrpc_error($stale_get), 1371 + missing_delete => { 1372 + status => $missing_delete->code // 0, 1373 + body => $missing_delete->body, 1374 + }, 1375 + latest_commit_unchanged => ( 1376 + (($latest_before_missing_delete->json || {})->{cid} // q()) eq (($latest_after_missing_delete->json || {})->{cid} // q()) 1377 + && (($latest_before_missing_delete->json || {})->{rev} // q()) eq (($latest_after_missing_delete->json || {})->{rev} // q()) 1378 + ) ? 1 : 0, 1379 + }; 1380 + } 1381 + 1382 + if (!same_hash($server{reference}{repo_read_edges}, $server{perlsky}{repo_read_edges})) { 1383 + note('reference repo read edges: ' . encode_json($server{reference}{repo_read_edges})); 1384 + note('perlsky repo read edges: ' . encode_json($server{perlsky}{repo_read_edges})); 1385 + fail_check('repo stale-cid reads and missing-delete no-ops match the official reference PDS'); 1386 + } else { 1387 + pass('repo stale-cid reads and missing-delete no-ops match the official reference PDS'); 1388 + } 1389 + 1340 1390 note('Comparing applyWrites'); 1341 1391 for my $name (sort keys %server) { 1342 1392 my $seed_update = post_json($server{$name}{origin}, 'com.atproto.repo.createRecord', {
+1 -1
t/repo-api.t
··· 233 233 ->json_is('/value/text' => 'hello from updated perl'); 234 234 235 235 $t->get_ok("/xrpc/com.atproto.repo.getRecord?repo=$did&collection=app.bsky.feed.post&rkey=first-post&cid=bafyreifakecidmismatch") 236 - ->status_is(404) 236 + ->status_is(400) 237 237 ->json_is('/error' => 'RecordNotFound'); 238 238 239 239 $t->post_ok('/xrpc/com.atproto.repo.putRecord' => { Authorization => "Bearer $access" } => json => {