refactor(errors): consolidate domain validation errors onto a shared type master
internal/core/errors was dead code — zero importers — while six domain packages each reimplemented the same ValidationError with a slightly different message format. Because those were distinct Go types with identical shapes, a handler could not ask "is this a validation failure?" in one place: it had to call posts.IsValidationError, then communities.IsValidationError, then aggregators.IsValidationError, and any domain it forgot fell through to a 500. The six packages now alias the shared type rather than redefining it: type ValidationError = coreerrors.ValidationError A Go type alias is the *same* type, not a similar one, so a single errors.As at the API boundary matches validation failures from every aliasing domain while each package keeps its own constructors and predicates. Existing call sites compile unchanged. Two latent bugs surfaced and are fixed: - timeline.IsValidationError and discover.IsValidationError used a bare type assertion rather than errors.As, so a validation error wrapped anywhere in the stack with %w stopped being recognised and returned 500 instead of 400. - posts.IsNotFound compared with == rather than errors.Is, which breaks the moment any layer adds context. Changes: - Rewrite internal/core/errors as the canonical ValidationError, NotFoundError, and ConflictError, with Is methods bridging the latter two to shared sentinels - Alias the shared types in posts, communities, aggregators, communityFeeds, discover, and timeline - Switch timeline/discover predicates to errors.As so they unwrap - Switch posts.IsNotFound to errors.Is - Replace a hand-rolled contains/anySubstring reimplementation of strings.Contains in posts with the stdlib - Add errors_test.go asserting the cross-domain property the whole change exists for: reverting any alias to a local struct still compiles and still passes each domain's own tests, so only this test catches it Note for clients: ValidationError.Error() is now uniformly "field: message". Previously posts returned "validation error (field): msg", aggregators "validation error: field - msg", and timeline/discover the bare message with no field prefix. These strings reach clients as the XRPC error *message*; the error *code* is unchanged, so the lexicon contract holds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>