A community based topic aggregation platform built on atproto

feat(ingestion): GREEN cycle 2 — queue driver, decider, factory, sweep master

All 42 cycle-2 reds pass at T0/T1. The three T2 contracts pass only with a one-line fix to the RED helper they share — see BLOCKER below; the file is committed exactly as RED wrote it. (a) ListPendingSubjects + its index Two INNER JOINs, both exclusions in SQL: posts (deleted_at IS NULL) and communities (pds_refresh_token_encrypted IS NOT NULL — credential presence, never hosted_by_did, which any repo can claim about itself). Migration 036 gains the partial index on (created_at) over the two undecided statuses and is renamed to match its widened scope; a 037 would have broken admission_repo_schema_test's asserted 36→35→34 chain. (b) CommunityRepoFactory + credential refresher Credential presence is the hosting test; absence is ErrCommunityNotHosted (permanent), while an unindexed community stays an ordinary error because it may simply not have arrived. Token renewed before the client is built, since a client is bound to the token it was constructed with. (c) AdmissionEngineDecider Post read first; tombstoned or absent returns UNDECIDED wrapping the new ErrSubjectGone rather than a code — the engine turns a code on a pending_reacceptance row into a REMOVAL record, and an author deleting their own post is not the community removing it. Actor classification checks the trusted set first (no lookup), and every uncertain path falls to ActorUser. TrustedAggregatorDIDs is now one helper shared with CreatePost so the two paths cannot disagree about who is privileged. (d) QueueDriver One goroutine, grouped by community (the partition a future worker pool must shard on), per-subject exponential backoff on deferral keyed by (community, post) — NOT by PendingSubject, whose time.Time field makes map equality fragile. Errors checked before outcomes, since a failing engine returns EngineDeferred alongside them. Snapshot is mutex-guarded: the health handler reads it while the job writes. (e) DeleteAcceptance + the tombstone sweep State-shaped single-op applyWrites, swapCommit-guarded, absence reported as a skip. The consumer sweeps only when the admission row says an acceptance stands, only when the tombstone actually applied (not on every redelivery), and never lets a failed sweep hold the local tombstone hostage. (f) Wiring: engine + driver behind ACCEPTANCE_QUEUE_INTERVAL (0 disables, and a nil driver is what omits the health block); acceptanceQueue in /health/consumers via an option, since an existing test pins the handler's arity. TWO PRODUCTION BUGS the T2 contracts caught, both in the READ path: - post.get refused every postv2 URI ("invalid collection in URI"), making author-owned posts unfetchable by the endpoint that hydrates every feed. parsePostURIParts now accepts both collections; the DELETE path narrows back to the community-repo one, because it uses the authority as a community DID and would otherwise try to delete from the AUTHOR's repo. - the synthesized `record` map hardcoded $type community.post and an `author` field, so every postv2 read handed back exactly the field whose removal makes authorship unforgeable. The shape now follows the URI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>