# Unified Publishing Center ## Architecture The Publishing Center is a composition layer over `app/social`. It does not own provider connections, tokens, media files, publishing jobs, retries, or provider payload translation. The authoritative flow is: 1. A canonical `SocialPost` references a workspace project and Social media asset. 2. Each exact connected account creates one `SocialPostTarget`. 3. `PublishingService` validates account state, authorization scopes, provider capabilities, canonical asset ownership, project provenance, and adapter media constraints. 4. Publish-now creates idempotent `SocialJob` records. Scheduling creates one durable `SocialSchedule` stored in UTC with its IANA display timezone. 5. Existing Social workers claim jobs atomically and call the registered provider adapter. 6. Provider results and reconciliation update each target independently. Post reconciliation reports `published`, `failed`, or `partial_success`. Provider adapters remain the only components that translate canonical post data into provider API payloads. An adapter that is registered but does not advertise publishing remains unavailable and must raise `SOCIAL_PROVIDER_NOT_IMPLEMENTED` or `SOCIAL_CAPABILITY_UNSUPPORTED`; neither condition is converted to success. ## Canonical post and targets `SocialPost` now records optional immutable project provenance, a bounded canonical caption, and bounded canonical hashtags. Platform overrides remain on targets and never mutate the canonical copy. Target input is strict. YouTube, TikTok, X, and LinkedIn use their existing typed provider metadata models. Providers without implemented publishing contracts are capability-gated instead of accepting arbitrary payloads. No binary media or credentials are stored in Social records. A Social media asset binds an existing canonical MediaRouter asset to a validated immutable output locator. ## Validation `POST /v1/social/posts/{post_id}/validate` returns a structured result for every target. Validation reuses: - connected-account and OAuth scope checks; - provider capability metadata; - canonical Social media resolution; - existing FFprobe/media validation; - each provider adapter's `validate_media` implementation. The publish and schedule paths revalidate server-side. Frontend validation is advisory and cannot authorize publishing. ## Scheduling and jobs Scheduling uses `SocialSchedule`; timestamps must be offset-aware and are stored as UTC. The supplied IANA timezone is retained for display. The existing scheduler claims due records using row locks and `SKIP LOCKED`. Publishing jobs use deterministic per-target idempotency keys and bounded worker concurrency. Provider-native scheduling is used only by an adapter when explicitly supported. The Publishing Center otherwise relies on MediaRouter's durable scheduler. The separate Calendar interface is outside this phase. ## Retries, reconciliation, and partial success The existing worker retry classifier applies exponential backoff and jitter to transient errors. Invalid credentials, invalid media, permission denial, unsupported operations, cancellation, and non-retryable provider responses are not retried indefinitely. OAuth 401 handling refreshes once through `OAuthService`. The Publishing Center also exposes a target-scoped retry endpoint. It only accepts a failed target with no external provider ID, rejects permanent configuration/auth/media failures, and derives a deterministic retry job key. It never requeues successful or uncertain provider operations. Async provider acceptance is reconciled through the adapter's status or recovery contract. MediaRouter does not claim exactly-once external delivery where the provider cannot support lookup or idempotency; local idempotency prevents duplicate internal operations. Targets reconcile independently. A mixture of published and failed targets produces `partial_success`, never `published`. ## Cancellation Draft, scheduled, queued, and retrying jobs can transition to `cancelled`. Preparing, processing, uploading, and publishing jobs instead record `cancellation_requested_at`. Their target remains in its actual state until the worker/provider result is reconciled. This prevents MediaRouter from claiming cancellation after a provider may have accepted a post. ## Security, permissions, and RLS The API uses existing Social scopes: - `social:accounts:read` / `social:accounts:write` - `social:posts:read` / `social:posts:write` - `social:posts:publish` - `social:schedules:write` Project and asset ownership is checked server-side. The additive migration adds a project foreign key, a same-workspace integrity trigger, indexes, and retains forced RLS on posts, targets, and jobs. Social accounts, media references, targets, schedules, and jobs continue using the existing workspace policies. Runtime PostgreSQL RLS certification remains deferred. Tokens are available only through `TokenService`. REST, SDK, MCP, n8n, logs, audits, and job payloads never expose access tokens, refresh tokens, provider secrets, authorization headers, or signed upload/session URLs. ## Audit and retention The Publishing Center uses existing bounded Social audit records, including post creation, validation, scheduling, publish start, completion/failure, and cancellation. Audit and application logs contain opaque resource identifiers, provider, status, duration, and error categories only. Raw provider payloads are not retained by the composer. Provider recovery state that must survive restarts remains encrypted in the existing dedicated job field. Public result metadata is filtered before transport. ## Frontend Routes: - `/publish` - `/projects/[projectId]/publish` TanStack Query owns providers, accounts, assets, posts, jobs, and validation responses. The composer keeps only draft selections in local UI state. It requires exact account selection, capability-gates unavailable adapters, registers an existing project output as a Social media reference when needed, and requires an accessible confirmation dialog before external publishing. Status uses the existing Social job polling rather than a second realtime system. Project actions link to the project-aware route. Content Studio, AI Studio, and template-created projects use the same project/asset provenance and publishing APIs; there is no specialized publishing path. ## Copilot The shared Copilot action registry contains: - `publishing.validate` - `publishing.create_post` - `publishing.schedule` - `publishing.publish` - `publishing.cancel` Publish, schedule, and cancellation are registered as external side effects and always require confirmation. Copilot uses exact post/account identifiers and the canonical Social services; it cannot invoke an adapter or arbitrary function directly. ## SDK, MCP, and n8n TypeScript and Python SDKs extend the existing Social resource with structured validation and the new canonical fields. Existing methods remain compatible. MCP exposes typed validation and job listing alongside existing create, publish, schedule, cancel, and status tools. Authorization remains enforced by the MCP registry. The existing n8n Social node adds List Posts, Validate Post, and List Publishing Jobs. Operations use the official SDK, preserve idempotency and Continue On Fail, and return real per-target backend state. ## Provider certification boundary Unit tests may mock provider boundaries. Live provider certification, live OAuth credentials, Docker, PostgreSQL runtime/RLS verification, FFmpeg runtime verification, and final production hardening remain deferred. An `implementation_status` of `registered` or a false publishing capability is shown as unavailable and is never simulated.