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 moderation subject status responses

alice ac605f7c b5733573

+58 -6
+3 -1
docs/TEST_AUDIT.md
··· 13 13 The current baseline for saying "the audited suite is green" is: 14 14 15 15 - `prove -lr t` 16 - - latest full green result in the realigned Meridian worktree: `Files=48, Tests=3009` 16 + - latest full green result in the realigned Meridian worktree: `Files=48, Tests=3026` 17 17 - `prove -lv t/server-auth.t` 18 18 - `perl -c script/differential-validate` 19 19 - `PERLSKY_RUN_REFERENCE_DIFF=1 prove -lv t/reference-differential.t` ··· 63 63 - `com.atproto.admin.deleteAccount` is now reference-style idempotent for missing DIDs: it succeeds and emits the same deleted account event shape instead of failing locally with `404 AccountNotFound`. 64 64 - `com.atproto.admin.getSubjectStatus` now follows the reference runtime more closely for repo subjects: existing repos return a synthesized active subject status even without a stored moderation row, missing repos use `400 NotFound` / `Subject not found`, blob requests without a DID use `400 InvalidRequest` / `Must provide a did to request blob state`, and entirely missing subject references use `400 InvalidRequest` / `No provided subject`. 65 65 - `com.atproto.admin.updateAccountPassword` and `com.atproto.admin.disableAccountInvites` / `enableAccountInvites` now follow the reference runtime’s missing-account behavior: they are empty-body `200` no-ops instead of returning local `404 AccountNotFound` or JSON `{}` success bodies. 66 + - `com.atproto.admin.updateSubjectStatus` now follows the reference runtime’s narrower response contract: it echoes the normalized `subject`, includes `takedown` only when that field was part of the request, and no longer synthesizes `deactivated` into the response body from stored state. 67 + - Record and blob moderation now have direct coverage for `com.atproto.admin.getSubjectStatus`, so taken-down record/blob subjects are pinned as first-class admin-surface behavior instead of only being inferred from repo and sync visibility. 66 68 - `com.atproto.admin.updateAccountPassword` follows the reference runtime’s looser admin policy: it rejects overlong passwords with `400 InvalidRequest` / `Invalid password length.`, but does not impose the normal user-facing minimum-length gate. 67 69 - `com.atproto.admin.disableAccountInvites` / `enableAccountInvites` now ignore the local `note` field so the visible account state matches the official runtime instead of carrying an extra stored `inviteNote`. 68 70 - `com.atproto.admin.getInviteCodes` now matches the official runtime on sort validation, always-emitted cursor behavior, total `available` counts, and newest-first `uses` ordering.
+1 -2
lib/ATProto/PDS/API/Admin.pm
··· 102 102 } 103 103 return { 104 104 subject => $status->{subject}, 105 - ($status->{takedown} ? (takedown => $status->{takedown}) : ()), 106 - ($status->{deactivated} ? (deactivated => $status->{deactivated}) : ()), 105 + (exists($body->{takedown}) ? (takedown => $body->{takedown}) : ()), 107 106 }; 108 107 }); 109 108
+25
script/differential-validate
··· 1464 1464 ); 1465 1465 check($res->is_success, "$name repo takedown succeeds"); 1466 1466 next unless $res->is_success; 1467 + my $repo_takedown_json = $res->json || {}; 1467 1468 1468 1469 my $blocked_login = post_json($server{$name}{origin}, 'com.atproto.server.createSession', { 1469 1470 identifier => $server{$name}{renamed_handle} || $server{$name}{handle}, ··· 1500 1501 write_error => $blocked_write ? (($blocked_write->json || {})->{error}) : undef, 1501 1502 list_blocked => $blocked_list->is_success ? 0 : 1, 1502 1503 list_error => ($blocked_list->json || {})->{error}, 1504 + response_subject_type => $repo_takedown_json->{subject}{'$type'}, 1505 + response_takedown => $repo_takedown_json->{takedown}{applied} ? 1 : 0, 1506 + response_has_deactivated => exists($repo_takedown_json->{deactivated}) ? 1 : 0, 1503 1507 }; 1504 1508 1505 1509 $res = post_json( ··· 1531 1535 ); 1532 1536 check($res->is_success, "$name record takedown succeeds"); 1533 1537 next unless $res->is_success; 1538 + my $record_takedown_json = $res->json || {}; 1534 1539 1535 1540 my $record_uri = $server{$name}{record_uri}; 1536 1541 my ($collection, $rkey) = $record_uri =~ m{at://[^/]+/([^/]+)/([^/?#]+)\z}; ··· 1546 1551 1547 1552 my $records = $list_records->is_success ? ($list_records->json->{records} || []) : []; 1548 1553 my $record_hidden = !(grep { (($_->{uri} // q()) eq $record_uri) } @$records); 1554 + my $record_subject_status = get_form( 1555 + $server{$name}{origin}, 1556 + 'com.atproto.admin.getSubjectStatus', 1557 + { uri => $record_uri }, 1558 + admin_auth_header($server{$name}{admin_password}), 1559 + ); 1560 + my $record_subject_json = $record_subject_status->json || {}; 1549 1561 $server{$name}{record_takedown} = { 1550 1562 get_blocked => $get_record->is_success ? 0 : 1, 1551 1563 get_error => ($get_record->json || {})->{error}, 1552 1564 list_hidden => $record_hidden ? 1 : 0, 1553 1565 list_success => $list_records->is_success ? 1 : 0, 1566 + response_subject_type => $record_takedown_json->{subject}{'$type'}, 1567 + response_takedown => $record_takedown_json->{takedown}{applied} ? 1 : 0, 1568 + response_has_deactivated => exists($record_takedown_json->{deactivated}) ? 1 : 0, 1569 + subject_status_ok => $record_subject_status->is_success ? 1 : 0, 1570 + subject_status_type => $record_subject_json->{subject}{'$type'}, 1571 + subject_status_takedown => $record_subject_json->{takedown}{applied} ? 1 : 0, 1554 1572 }; 1555 1573 1556 1574 my $sync_record = get_form($server{$name}{origin}, 'com.atproto.sync.getRecord', { ··· 1638 1656 admin_auth_header($server{$name}{admin_password}), 1639 1657 ); 1640 1658 check($reactivate->is_success, "$name repo reactivation succeeds for repo status comparison"); 1659 + my $reactivate_json = $reactivate->json || {}; 1641 1660 1642 1661 my $takedown = post_json( 1643 1662 $server{$name}{origin}, ··· 1684 1703 active => ($deactivated->json || {})->{active} ? 1 : 0, 1685 1704 status => ($deactivated->json || {})->{status}, 1686 1705 has_rev => defined(($deactivated->json || {})->{rev}) ? 1 : 0, 1706 + }, 1707 + reactivate_response => { 1708 + status => $reactivate->code // 0, 1709 + subject_type => $reactivate_json->{subject}{'$type'}, 1710 + has_takedown => exists($reactivate_json->{takedown}) ? 1 : 0, 1711 + has_deactivated => exists($reactivate_json->{deactivated}) ? 1 : 0, 1687 1712 }, 1688 1713 takendown => { 1689 1714 ok => $takendown->is_success ? 1 : 0,
+29 -3
t/moderation.t
··· 74 74 subject => { uri => "at://$did/app.bsky.feed.post/visible-post", cid => $record_cid }, 75 75 takedown => { applied => JSON::PP::true }, 76 76 })->status_is(200) 77 - ->json_is('/subject/uri', "at://$did/app.bsky.feed.post/visible-post"); 77 + ->json_is('/subject/uri', "at://$did/app.bsky.feed.post/visible-post") 78 + ->json_is('/takedown/applied' => JSON::PP::true) 79 + ->json_hasnt('/deactivated'); 80 + 81 + $t->get_ok(Mojo::URL->new('/xrpc/com.atproto.admin.getSubjectStatus')->query( 82 + uri => "at://$did/app.bsky.feed.post/visible-post", 83 + ) => { 84 + Authorization => $admin_auth, 85 + })->status_is(200) 86 + ->json_is('/subject/$type' => 'com.atproto.repo.strongRef') 87 + ->json_is('/subject/cid' => $record_cid) 88 + ->json_is('/takedown/applied' => JSON::PP::true); 78 89 79 90 $t->get_ok("/xrpc/com.atproto.repo.getRecord?repo=$did&collection=app.bsky.feed.post&rkey=visible-post") 80 91 ->status_is(404) ··· 103 114 } => json => { 104 115 subject => { did => $did }, 105 116 takedown => { applied => JSON::PP::true }, 106 - })->status_is(200); 117 + })->status_is(200) 118 + ->json_is('/subject/did' => $did) 119 + ->json_is('/takedown/applied' => JSON::PP::true) 120 + ->json_hasnt('/deactivated'); 107 121 108 122 $t->get_ok("/xrpc/com.atproto.sync.getRepoStatus?did=$did") 109 123 ->status_is(200) ··· 189 203 subject => { did => $did, cid => $blob_cid }, 190 204 takedown => { applied => JSON::PP::true }, 191 205 })->status_is(200) 192 - ->json_is('/subject/cid', $blob_cid); 206 + ->json_is('/subject/cid', $blob_cid) 207 + ->json_is('/takedown/applied' => JSON::PP::true) 208 + ->json_hasnt('/deactivated'); 209 + 210 + $t->get_ok(Mojo::URL->new('/xrpc/com.atproto.admin.getSubjectStatus')->query( 211 + did => $did, 212 + blob => $blob_cid, 213 + ) => { 214 + Authorization => $admin_auth, 215 + })->status_is(200) 216 + ->json_is('/subject/$type' => 'com.atproto.admin.defs#repoBlobRef') 217 + ->json_is('/subject/cid' => $blob_cid) 218 + ->json_is('/takedown/applied' => JSON::PP::true); 193 219 194 220 $t->get_ok(Mojo::URL->new('/xrpc/com.atproto.sync.getBlob')->query( 195 221 did => $did,