···4646- `com.atproto.sync.listReposByCollection`
4747 Present in the published lexicon and covered locally in `t/discovery-surfaces.t`, but not exposed by the current official runtime.
4848- `com.atproto.temp.*`
4949- `requestPhoneVerification`, `revokeAccountCredentials`, `checkSignupQueue`, `dereferenceScope`, and `fetchLabels` are locally covered in `t/temp-endpoints.t`, `t/extended-api.t`, and `t/external-surface.t`, but do not have a like-for-like official public comparison surface.
4949+ `requestPhoneVerification`, `revokeAccountCredentials`, `checkSignupQueue`, and `dereferenceScope` are locally covered in `t/temp-endpoints.t`, while `fetchLabels` is covered in `t/label-rpc-surfaces.t`; these do not have a like-for-like official public comparison surface.
5050- local label RPCs
5151- `com.atproto.label.queryLabels`, `subscribeLabels`, and `com.atproto.temp.fetchLabels` are verified locally; the official runtime does not provide a directly comparable local-labeler implementation.
5151+ `com.atproto.label.queryLabels`, `subscribeLabels`, and `com.atproto.temp.fetchLabels` are verified locally in `t/label-rpc-surfaces.t` and `t/labels.t`; the official runtime does not provide a directly comparable local-labeler implementation.
5252- local appview emulation
5353 `app.bsky.actor.getPreferences`, `putPreferences`, `notification.getPreferences`, `notification.putPreferencesV2`, and the conservative local feed/thread/profile fallback behavior are locally regression-tested rather than compared against an official self-hosted AppView implementation.
5454
+7-6
docs/TEST_AUDIT.md
···1313The current baseline for saying "the audited suite is green" is:
14141515- `prove -lr t`
1616- - latest full green result in the realigned Meridian worktree: `Files=54, Tests=3043`
1616+ - latest full green result in the realigned Meridian worktree: `Files=55, Tests=3047`
1717- `prove -lv t/server-auth.t`
1818- `perl -c script/differential-validate`
1919- `PERLSKY_RUN_REFERENCE_DIFF=1 prove -lv t/reference-differential.t`
···116116Current suite counts by bucket:
117117118118- `direct reference differential`: `5`
119119-- `audited local regression`: `36`
119119+- `audited local regression`: `37`
120120- `local correctness/infrastructure`: `13`
121121122122| Test file | Bucket | Current note |
···138138| `t/email-confirmation.t` | audited local regression | intentionally testing-friendly email flow plus strict missing-email and invalid-email validation semantics |
139139| `t/email-update-helper.t` | audited local regression | shared email-update helper normalization, token revocation, and duplicate-email error semantics |
140140| `t/event-stream.t` | audited local regression | wire-format, malformed frame, and event decoding coverage |
141141-| `t/extended-api.t` | audited local regression | mixes reference-aligned repo/sync/moderation happy paths with still-local label fetch/query smoke and account/invite flows, but is cleaner after splitting reserved-handle and crawl-host checks out |
141141+| `t/extended-api.t` | audited local regression | focused mixed coverage for invite issuance, `applyWrites`, identity refresh/update, email flows, and blob/sync happy paths after the label RPCs were split out |
142142| `t/external-handle-update.t` | audited local regression | external-handle update semantics, including DID-resolution checks and empty-body success for external handle adoption |
143143-| `t/external-surface.t` | audited local regression | focused external-surface coverage for repo/blob/account-status and label behavior after splitting the local discovery surfaces out |
143143+| `t/external-surface.t` | audited local regression | focused external-surface coverage for repo/blob/account-status and missing-blob behavior after splitting discovery and label RPC checks into dedicated suites |
144144| `t/firehose.t` | audited local regression | repo subscription lifecycle, cursor, and CAR behavior |
145145| `t/identity.t` | local correctness/infrastructure | lower-level handle and DID helper coverage, including DNS-over-well-known preference and malformed-handle rejection |
146146| `t/import-repo.t` | audited local regression | focused `importRepo` snapshot-restore and rollback behavior, now cleaner after splitting the disabled-import policy gate into its own suite |
···149149| `t/invite-admin.t` | audited local regression | isolated invite-management coverage for admin listing/disabling and self-service invite-account gating |
150150| `t/ipld-canonical.t` | local correctness/infrastructure | canonical IPLD encoding invariants |
151151| `t/ipld-codecs.t` | local correctness/infrastructure | DAG-CBOR and codec coverage |
152152+| `t/label-rpc-surfaces.t` | audited local regression | isolated local label-RPC coverage for `queryLabels` and `temp.fetchLabels` account/record takedown visibility |
152153| `t/labels.t` | audited local regression | label persistence, replay, negation, and cursor behavior |
153154| `t/local-service-surfaces.t` | audited local regression | isolated local-only coverage for reserved handles and crawler host tracking surfaces |
154155| `t/metrics.t` | audited local regression | metrics endpoint, token-gating smoke, and instrumentation contract for local appview behavior |
···181182The broadest suites are green and audited, but they still mix several categories of behavior inside the same file:
182183183184- `t/extended-api.t`
184184- Carries real conformance value for `applyWrites`, blob/sync flows, and moderation/label visibility, but it still mixes those with some local product behavior such as label fetch/query smoke and invite/account flows.
185185+ Carries real conformance value for `applyWrites`, blob/sync flows, and account/email identity lifecycle behavior, but it still mixes those with local product behavior such as self-service invite flows.
185186- `t/external-surface.t`
186186- Carries strong external-surface coverage for repo export, blob access, account-status behavior, and label visibility. It is cleaner after moving discovery-specific checks into `t/discovery-surfaces.t`, but still remains broader than a single-endpoint conformance file.
187187+ Carries strong external-surface coverage for repo export, blob access, account-status behavior, and missing-blob listing. It is cleaner after moving discovery and label-RPC checks into dedicated suites, but still remains broader than a single-endpoint conformance file.
187188- `t/uncovered-endpoints.t`
188189 Exists specifically to stop a few lesser-used local endpoints from falling out of coverage; it is much narrower now that the temp, invite, and admin-account blocks have moved into dedicated suites, but it should still be read as a pragmatic safety net, not as a pure reference-alignment suite.
189190
+8-75
t/extended-api.t
···1919}
20202121use Test::Mojo;
2222-use Mojo::URL;
2322use ATProto::PDS;
24232524my $root = File::Spec->rel2abs(File::Spec->catdir($Bin, '..'));
···237236 },
238237})->status_is(200);
239238240240-$t->get_ok(Mojo::URL->new('/xrpc/com.atproto.sync.listBlobs')->query(
241241- did => $did,
242242-))->status_is(200)
239239+$t->get_ok('/xrpc/com.atproto.sync.listBlobs?did=' . $did)
240240+ ->status_is(200)
243241 ->json_is('/cids/0', $blob_cid);
244242245245-$t->get_ok(Mojo::URL->new('/xrpc/com.atproto.sync.getBlob')->query(
246246- did => $did,
247247- cid => $blob_cid,
248248-))->status_is(200);
243243+$t->get_ok('/xrpc/com.atproto.sync.getBlob?did=' . $did . '&cid=' . $blob_cid)
244244+ ->status_is(200);
249245is($t->tx->res->body, 'blob-bytes', 'blob bytes are served back');
250246like($t->tx->res->headers->content_type // '', qr{image/png}, 'blob content type preserved');
251247252252-$t->get_ok(Mojo::URL->new('/xrpc/com.atproto.sync.getLatestCommit')->query(
253253- did => $did,
254254-))->status_is(200)
248248+$t->get_ok('/xrpc/com.atproto.sync.getLatestCommit?did=' . $did)
249249+ ->status_is(200)
255250 ->json_has('/cid');
256251257252my $commit_cid = $t->tx->res->json->{cid};
258253259259-$t->get_ok(Mojo::URL->new('/xrpc/com.atproto.sync.getBlocks')->query(
260260- did => $did,
261261- cids => $commit_cid,
262262-))->status_is(200);
263263-like($t->tx->res->headers->content_type // '', qr{application/vnd\.ipld\.car}, 'block export is a CAR');
264264-265265-$t->post_ok('/xrpc/com.atproto.admin.updateSubjectStatus' => {
266266- Authorization => $admin_auth,
267267-} => json => {
268268- subject => { did => $did },
269269- takedown => { applied => JSON::PP::true, ref => 'unit-test' },
270270-})->status_is(200);
271271-272272-$t->get_ok(Mojo::URL->new('/xrpc/com.atproto.label.queryLabels')->query(
273273- uriPatterns => "at://$did*",
274274-))->status_is(200);
275275-ok(
276276- _find_label($t->tx->res->json->{labels}, val => '!hide'),
277277- 'queryLabels includes the takedown label',
278278-);
279279-280280-$t->get_ok('/xrpc/com.atproto.temp.fetchLabels?limit=10')
281281- ->status_is(200);
282282-ok(
283283- _find_label($t->tx->res->json->{labels}, val => '!hide'),
284284- 'fetchLabels includes the takedown label',
285285-);
286286-287287-$t->post_ok('/xrpc/com.atproto.admin.updateSubjectStatus' => {
288288- Authorization => $admin_auth,
289289-} => json => {
290290- subject => { did => $did },
291291- takedown => { applied => JSON::PP::false, ref => 'unit-test' },
292292-})->status_is(200);
293293-294294-$t->get_ok(Mojo::URL->new('/xrpc/com.atproto.label.queryLabels')->query(
295295- uriPatterns => "at://$did*",
296296-))->status_is(200);
297297-ok(
298298- _find_label($t->tx->res->json->{labels}, val => '!hide', neg => JSON::PP::true),
299299- 'queryLabels includes the negated takedown label',
300300-);
301301-302302-$t->get_ok('/xrpc/com.atproto.temp.fetchLabels?limit=10')
254254+$t->get_ok('/xrpc/com.atproto.sync.getBlocks?did=' . $did . '&cids=' . $commit_cid)
303255 ->status_is(200);
304304-ok(
305305- _find_label($t->tx->res->json->{labels}, val => '!hide', neg => JSON::PP::true),
306306- 'fetchLabels includes the negated takedown label',
307307-);
256256+like($t->tx->res->headers->content_type // '', qr{application/vnd\.ipld\.car}, 'block export is a CAR');
308257309258done_testing;
310310-311311-sub _find_label {
312312- my ($labels, %expected) = @_;
313313- return 0 unless ref($labels) eq 'ARRAY';
314314- for my $label (@$labels) {
315315- next unless ref($label) eq 'HASH';
316316- my $matches = 1;
317317- for my $key (keys %expected) {
318318- next if defined($label->{$key}) && "$label->{$key}" eq "$expected{$key}";
319319- $matches = 0;
320320- last;
321321- }
322322- return 1 if $matches;
323323- }
324324- return 0;
325325-}
-31
t/external-surface.t
···278278})->status_is(200)
279279 ->json_is('/handle' => 'alice.example.test');
280280281281-$t->post_ok('/xrpc/com.atproto.admin.updateSubjectStatus' => {
282282- Authorization => $admin_auth,
283283-} => json => {
284284- subject => { uri => $record_uri, cid => $record_cid },
285285- takedown => { applied => JSON::PP::true },
286286-})->status_is(200);
287287-288288-$t->get_ok(Mojo::URL->new('/xrpc/com.atproto.label.queryLabels')->query(
289289- uriPatterns => $record_uri,
290290-))->status_is(200);
291291-ok(
292292- _find_label($t->tx->res->json->{labels}, val => '!hide', uri => $record_uri),
293293- 'queryLabels includes the record takedown label',
294294-);
295295-296281for my $cid ($blob_cid, $nested_blob_cid) {
297282 $app->store->dbh->do(
298283 q{DELETE FROM blob_owners WHERE cid = ?},
···332317 ->json_is('/cursor' => $missing_cids[1]);
333318334319done_testing;
335335-336336-sub _find_label {
337337- my ($labels, %expected) = @_;
338338- return 0 unless ref($labels) eq 'ARRAY';
339339- for my $label (@$labels) {
340340- next unless ref($label) eq 'HASH';
341341- my $matches = 1;
342342- for my $key (keys %expected) {
343343- next if defined($label->{$key}) && "$label->{$key}" eq "$expected{$key}";
344344- $matches = 0;
345345- last;
346346- }
347347- return 1 if $matches;
348348- }
349349- return 0;
350350-}