···13131414Reference differential validation:
15151616-- Run `script/differential-validate` to compare `perlds` against the official published `@atproto/pds` on a focused set of account, repo, sync, firehose, and `importRepo` snapshot-restore behaviors.
1717-- Run `PERLDS_DIFF_ACCOUNT_DID_METHOD=did:plc script/differential-validate` to exercise the same harness in PLC-account mode, including recommended DID credentials, PLC signature requests, PLC handle updates, and token-gated PLC signing behavior.
1616+- Run `script/differential-validate` to compare `perlds` against the official published `@atproto/pds` on a focused set of account, repo, moderation, sync, firehose, and `importRepo` snapshot-restore behaviors.
1717+- Run `PERLDS_DIFF_ACCOUNT_DID_METHOD=did:plc script/differential-validate` to exercise the same harness in PLC-account mode, including recommended DID credentials, PLC signature requests, PLC handle updates, token-gated PLC signing behavior, and moderation checks after PLC handle changes.
1818- The helper installs the reference runtime into `.tools/reference-runtime` with Node 20 via `fnm`.
1919- Run `PERLDS_RUN_REFERENCE_DIFF=1 prove -lv t/reference-differential.t` to exercise the same harness from the test suite.
2020- Run `PERLDS_RUN_REFERENCE_DIFF=1 prove -lv t/reference-differential-plc.t` to run the PLC-specific reference comparison from the test suite.
2121+2222+Moderation and labels:
2323+2424+- `com.atproto.admin.updateSubjectStatus` now enforces repo, record, and blob takedowns as real behavior instead of passive metadata.
2525+- Repo takedowns block ordinary login, repo writes, and public repo reads. `allowTakendown` sessions are accepted for parity with the reference PDS, but those sessions still cannot write.
2626+- Record takedowns hide records from `com.atproto.repo.getRecord` and `com.atproto.repo.listRecords`.
2727+- Blob takedowns quarantine blob reads for the public while still permitting authenticated self/admin recovery access, and they block both duplicate blob uploads and new record writes that reference quarantined blobs.
2828+- `com.atproto.label.queryLabels`, `com.atproto.label.subscribeLabels`, and `com.atproto.temp.fetchLabels` are backed by persisted local labels rather than synthesized snapshots. Admin takedowns emit `!hide` labels and restores emit negation events.
2929+- Label query/stream behavior is covered by local regression tests in `t/labels.t`. The official reference PDS does not provide a like-for-like local labeler implementation to diff against, so direct upstream differential checks are focused on moderation semantics rather than label RPC parity.
21302231Interop fixtures:
2332
+7-1
lib/ATProto/PDS/API/Repo.pm
···120120 $registry->register('com.atproto.repo.listRecords', sub ($c, $endpoint) {
121121 my $account = _resolve_repo($c, $c->param('repo'));
122122 _xrpc_error(404, 'RepoNotFound', 'Repository was not found') unless $account;
123123- assert_repo_readable($c, $account);
123123+ assert_repo_readable(
124124+ $c,
125125+ $account,
126126+ status => 400,
127127+ error => 'InvalidRequest',
128128+ message => 'Could not find repo: ' . ($c->param('repo') // q()),
129129+ );
124130 my $page = _list_visible_records(
125131 $c,
126132 $account->{did},
+3-3
lib/ATProto/PDS/Moderation.pm
···9191sub assert_login_allowed ($c, $account, %opts) {
9292 xrpc_error(403, 'AccountDeleted', 'Account has been deleted') if defined $account->{deleted_at};
9393 if (is_repo_takedown($c, $account->{did}) && !$opts{allow_takedown}) {
9494- xrpc_error(403, 'AccountTakedown', 'Account has been taken down');
9494+ xrpc_error(401, 'AccountTakedown', 'Account has been taken down');
9595 }
9696 if (defined $account->{deactivated_at} && !$opts{allow_deactivated}) {
9797- xrpc_error(403, 'AccountDeactivated', 'Account is deactivated');
9797+ xrpc_error(401, 'AccountDeactivated', 'Account is deactivated');
9898 }
9999 return 1;
100100}
···124124125125sub assert_repo_writable ($c, $account) {
126126 if (is_repo_takedown($c, $account->{did}) || defined $account->{deactivated_at}) {
127127- xrpc_error(401, 'InvalidToken', 'Bad token scope');
127127+ xrpc_error(400, 'InvalidToken', 'Bad token scope');
128128 }
129129 return 1;
130130}