···11111212The immediate goal is a PDS that is pleasant to hack on and interoperable enough to be exercised with real AT Protocol clients and repo sync tooling.
13131414+Developer-oriented notes about the current facade/module split live in `docs/CODE_STRUCTURE.md`.
1515+1416Reference differential validation:
15171618- Run `script/differential-validate` to compare `perlsky` against the official published `@atproto/pds` on a focused set of account, repo, moderation, sync, firehose, and `importRepo` snapshot-restore behaviors.
+110
docs/CODE_STRUCTURE.md
···11+# Code Structure
22+33+This repo is intentionally keeping a few stable public facades while moving dense implementation details into smaller internal modules. The current split points are:
44+55+## Store
66+77+`lib/ATProto/PDS/Store/SQLite.pm` remains the public SQLite store facade.
88+99+Internal slices currently live in:
1010+1111+- `lib/ATProto/PDS/Store/SQLite/ActionTokens.pm`
1212+- `lib/ATProto/PDS/Store/SQLite/Events.pm`
1313+- `lib/ATProto/PDS/Store/SQLite/Invites.pm`
1414+- `lib/ATProto/PDS/Store/SQLite/Labels.pm`
1515+- `lib/ATProto/PDS/Store/SQLite/Moderation.pm`
1616+- `lib/ATProto/PDS/Store/SQLite/Operations.pm`
1717+- `lib/ATProto/PDS/Store/SQLite/Preferences.pm`
1818+- `lib/ATProto/PDS/Store/SQLite/Reservations.pm`
1919+2020+Labels and event persistence are treated as a high-sensitivity area. Prefer small behavior-preserving edits there, with direct coverage in `t/labels.t`, `t/firehose.t`, and `t/store-sqlite.t`.
2121+2222+## Repo Manager
2323+2424+`lib/ATProto/PDS/Repo/Manager.pm` is still the entry point for repo initialization and normal write flows.
2525+2626+Internal helper slices:
2727+2828+- `lib/ATProto/PDS/Repo/Manager/Artifacts.pm`
2929+ - snapshot CAR generation
3030+ - commit artifact assembly
3131+ - firehose path shaping
3232+- `lib/ATProto/PDS/Repo/Manager/Import.pm`
3333+ - `importRepo`
3434+ - invalid TID repair
3535+ - imported-record rewriting and diffing
3636+3737+If you add more helpers here, keep normal write-path semantics easy to audit from the facade and avoid mixing import/repair logic back into the hot path.
3838+3939+## Service Proxy
4040+4141+`lib/ATProto/PDS/ServiceProxy.pm` is now a slim facade that keeps:
4242+4343+- request entrypoint
4444+- local-handler dispatch
4545+- service-auth JWT forwarding
4646+4747+Internal helper slices:
4848+4949+- `lib/ATProto/PDS/ServiceProxy/Upstream.pm`
5050+ - upstream request retries
5151+ - target selection
5252+ - proxy-header parsing
5353+ - config lookup
5454+- `lib/ATProto/PDS/ServiceProxy/Preferences.pm`
5555+ - actor preferences
5656+ - notification preferences
5757+- `lib/ATProto/PDS/ServiceProxy/Profile.pm`
5858+ - local profile views
5959+ - blob URL shaping
6060+- `lib/ATProto/PDS/ServiceProxy/Posts.pm`
6161+ - local post URI parsing
6262+ - post/thread helper primitives
6363+- `lib/ATProto/PDS/ServiceProxy/Threads.pm`
6464+ - author feed
6565+ - local posts/threads
6666+ - request-scoped post index
6767+ - embed rendering
6868+6969+The important contract is unchanged: local handlers can return `undef` to fall through to upstream proxying. Keep that behavior exact.
7070+7171+## Reference Differential Harness
7272+7373+`script/differential-validate` still owns:
7474+7575+- scenario sequencing
7676+- semantic normalizers
7777+- reference/perlsky state mutation
7878+- final mismatch reporting
7979+8080+Low-level harness mechanics now live in:
8181+8282+- `lib/ATProto/PDS/Test/Differential/HTTP.pm`
8383+ - authenticated XRPC request helpers
8484+ - request body variants
8585+- `lib/ATProto/PDS/Test/Differential/Firehose.pm`
8686+ - websocket quiet checks
8787+ - commit-frame capture
8888+ - frame draining helpers
8989+9090+The current rule of thumb is:
9191+9292+- okay to extract transport/plumbing helpers
9393+- okay to extract scenario wrappers when they do not hide important state transitions
9494+- keep normalizers explicit unless a refactor is obviously behavior-preserving
9595+9696+The differential harness is validated by:
9797+9898+- `PERLSKY_RUN_REFERENCE_DIFF=1 prove -lv t/reference-differential.t`
9999+- `PERLSKY_RUN_REFERENCE_DIFF=1 prove -lv t/reference-differential-plc.t`
100100+101101+## Refactor Pattern
102102+103103+The pattern used for these splits is:
104104+105105+1. keep the public facade stable
106106+2. extract one cohesive internal slice at a time
107107+3. verify the most relevant focused suites first
108108+4. only then move to the next slice
109109+110110+That is the safest way to keep simplifying the codebase without losing reference parity or label/moderation correctness.