···5353- perlsky intentionally still allows test-friendly no-email local accounts, but once an email is supplied `com.atproto.server.createAccount` now follows the same unsupported-syntax rejection shape as `updateEmail` and the official runtime.
5454- `com.atproto.server.createAccount` must not turn duplicate-email requests into a `500`; it now follows the official client-visible `400 InvalidRequest` / `Email already taken: ...` shape instead.
5555- Local handle-conflict flows now use the reference runtime’s client-visible `400 InvalidRequest` / `Handle already taken: ...` shape on `createAccount`, `com.atproto.identity.updateHandle`, and `com.atproto.admin.updateAccountHandle`, instead of the older local `HandleNotAvailable` variant.
5656+- The executable differential harness now proves that handle-conflict shape directly for both user and admin handle-update flows, not just local regression tests.
5657- `app.bsky.actor.putPreferences` and `app.bsky.notification.putPreferencesV2` now have explicit shape validation plus focused regression coverage, turning an earlier hardening concern into a pinned contract.
5758- `com.atproto.identity.resolveHandle` should reject malformed handles with `400 InvalidRequest`, not quietly treat them as misses or return a local `InvalidHandle` variant.
5859- `com.atproto.identity.resolveHandle` should treat well-formed but unresolved handles as `400 InvalidRequest` with `Unable to resolve handle`, matching the official runtime instead of returning a local `404 HandleNotFound`.
+46
script/differential-validate
···560560 if $diff_account_did_method eq 'did:plc';
561561}
562562563563+note('Creating secondary accounts for conflict checks');
564564+for my $name (sort keys %server) {
565565+ my $res = post_json($server{$name}{origin}, 'com.atproto.server.createAccount', {
566566+ handle => $name eq 'reference' ? 'bob-ref.test' : 'bob-perl.test',
567567+ email => $name eq 'reference' ? 'bob-ref@test.com' : 'bob-perl@test.com',
568568+ password => 'hunter22',
569569+ });
570570+ check($res->is_success, "$name secondary createAccount succeeds");
571571+ next unless $res->is_success;
572572+ my $json = $res->json || {};
573573+ $server{$name}{secondary_did} = $json->{did};
574574+ $server{$name}{secondary_handle} = $json->{handle};
575575+}
576576+563577note('Comparing account password boundary semantics');
564578for my $name (sort keys %server) {
565579 my $too_long_create = post_json($server{$name}{origin}, 'com.atproto.server.createAccount', {
···610624 fail_check('account password boundary and reset-email semantics match the official reference PDS');
611625} else {
612626 pass('account password boundary and reset-email semantics match the official reference PDS');
627627+}
628628+629629+note('Comparing handle conflict semantics');
630630+for my $name (sort keys %server) {
631631+ my $duplicate_identity_handle = post_json(
632632+ $server{$name}{origin},
633633+ 'com.atproto.identity.updateHandle',
634634+ { handle => $server{$name}{secondary_handle} },
635635+ auth_header($server{$name}{access}),
636636+ );
637637+ my $duplicate_admin_handle = post_json(
638638+ $server{$name}{origin},
639639+ 'com.atproto.admin.updateAccountHandle',
640640+ {
641641+ did => $server{$name}{did},
642642+ handle => $server{$name}{secondary_handle},
643643+ },
644644+ admin_auth_header($server{$name}{admin_password}),
645645+ );
646646+647647+ $server{$name}{handle_conflicts} = {
648648+ identity_update_dup => normalize_xrpc_error($duplicate_identity_handle),
649649+ admin_update_dup => normalize_xrpc_error($duplicate_admin_handle),
650650+ };
651651+}
652652+653653+if (!same_hash($server{reference}{handle_conflicts}, $server{perlsky}{handle_conflicts})) {
654654+ note('reference handle conflicts: ' . encode_json($server{reference}{handle_conflicts}));
655655+ note('perlsky handle conflicts: ' . encode_json($server{perlsky}{handle_conflicts}));
656656+ fail_check('handle conflict update semantics match the official reference PDS');
657657+} else {
658658+ pass('handle conflict update semantics match the official reference PDS');
613659}
614660615661note('Comparing resolveHandle');