Overview
Start Here
Installation
Tutorial
Architect App Builder
Todo List App
Beacon Beats Audio Store
UCAL App
Authoring
Dynamic Apps
Static Apps
dev_docs
Why Pharos Feels Different
Architect Builder
Framework
Artifacts & Layout
Configuration
Outbound Email
How to Use CLI
Contracts
YAML Authoring
Action Hooks and Middleware
Feature Transaction Primitives
Generated Surfaces
Declarative CRM
Input Validation
Identity and Tenancy
UI Surface Library
Social Connections
Payment Provider Management
Integration Adapters
Reference
Contract, UI, and Surface Reference
Styling UI Elements
Architect Media Artifacts
API Docs
Use Cases
Dynamic SPAs
Generated Admin and APIs
Scheduling and Marketplaces
Multi-App Hosting
Release Notes
Vision
Pharos Framework

← Back

Social account connections

Pharos connects an existing app owner's external account for authorized, read-only content collection through a retained browser session or an optional provider API grant. A connection does not create a local user, replace password authentication or enable the separately licensed OAuth/OIDC app-login feature.

App declaration

Declare contracts.social_connections_contract: data/contracts/social_connections.yml in the source manifest. The contract selects an existing owner capability and an app-owned status component:

enabled: true
connection_mode: browser
owner_capability: owner
collection_owner: connected
status_template: templates/components/social_connections.html
return_path: /owner

A numeric collection_owner explicitly selects the local owner whose consent authorizes background work. Browser mode also accepts connected, which selects a single connected owner for each provider within this app/deployment; zero or multiple connected owners requires an explicit selection rather than guessing. That owner must remain active and retain the declared capability. Browser requests always use the current authenticated owner, regardless of this background selection. The status component receives escaped status, username, action URLs and CSRF values; it receives no tokens or credential references. Architect's component source is apps/architect/templates/components/social_connections.html and can be included in generated source bundles.

Include pharos_runtime_social::schema::entities() in the app's sole authored logical database contract with an additive migration. The pharos_runtime_social example contract emits the portable declarations and their checksum for a supplied app ID. Merge its entities and migration into the existing contract; do not replace prior schema or migration history. Preserve the manifest-selected authoring format; YAML authoring compiles to JSON and the runtime loader remains JSON-only. The entities are social_connection, social_collection and social_observation.

The controller uses the framework's active user, browser session and capability tables. Authority changes commit active-user, session and role assertions in the same generic transaction as credential metadata. The native adapter recognizes the existing user_profile.account_type and store_membership.role role surfaces; an unsupported role representation fails closed. The library accepts host-provided authorization assertions so other framework adapters can supply their own identity representation.

Persistent website sign-in

With connection_mode: browser, owners connect through the actual Facebook, Instagram, X/Twitter, YouTube, SoundCloud or Spotify website rather than an API consent screen. In Event sources, select Connect, sign in directly in the opened browser window, Pharos automatically checkpoints the completed sign-in, closes its browser window and updates the app to Connected. The two icon controls are Reconnect using saved sign-in and Connect/Disconnect; Reconnect is disabled until sign-in is saved and during a pending attempt. Close the provider window to cancel an unfinished attempt. No developer application registration, client secret or extension is needed for this browser transport. Passwords, verification codes and login challenges remain in the provider's own browser UI; Pharos does not read password fields or submit them automatically. Existing passkeys from another profile may require a different initial sign-in method. The connection retains its own profile and keys for subsequent visits.

The profile path is stable for the authenticated owner, application, deployment/preview namespace and provider. Profiles live in a private browser-sessions area beside external runtime state, outside installed app bundles. Browser-managed keys and storage remain in that persistent profile. A saved cookie checkpoint is separately encrypted in the framework credential vault and promoted through revision-guarded generic database metadata. Runtime restart, app artifact replacement, normal window closing and reconnect do not delete the profile. Cancel closes an unfinished sign-in without deleting its profile or changing an accepted connection. For website sessions, Disconnect invalidates collection authority and in-flight revisions and deletes the provider profile and encrypted checkpoint. Reconnect uses the retained sign-in, including when verification is required; it does not require Disconnect first. To use a different external account, Disconnect first, then Connect. Reconnect preserves newer provider cookies already held in the retained profile instead of replaying an older checkpoint over them. A restricted artist page does not mark the whole connection expired unless the provider home also requires sign-in. Other owners, apps, providers and API grants are unaffected.

YouTube, SoundCloud and Spotify use the same two controls and isolated persistent browser profiles. Automatic completion requires a first-party authenticated cookie and the current account identifier from that provider's account UI. Only a provider-scoped fingerprint of that identifier is stored; visitor cookies, public artist profiles and editable login fields cannot establish a connection. Reconnect verifies the account again before promoting credentials and rejects account changes. These three are website sign-in connectors; they do not enable OAuth API grants, add a social-feed collector, or claim event extraction from those services. Public source fetching keeps its existing separate policy.

YouTube account settings, SoundCloud sign-in and Spotify login methods describe the providers' own account flows. Google, Facebook and Apple sign-in choices use explicit, bounded interactive authentication hosts; provider cookies and content reads remain separately scoped.

Post publication dates come from artist-associated headers or semantic timestamps tied to the post's permalink. A timestamp may follow the caption in DOM order. Comment/reply links and nested comment containers are excluded; dates in body prose and artwork do not establish posting time. Bounded date-tooltip reads remain associated with one post. A failed optional detail does not discard already verified profile evidence, and failed reads release only their own durable lease.

Provider session expiry or a login challenge changes collection to authorization required; credentials are retained for reconnection, never treated as a successful empty feed. Cookies cannot be posted through automatic completion: the native controller reads only its own authorized browser. Saved browser sessions use independent provider keys and never create Pharos users or enable OAuth/OIDC account-login features.

The current desktop transport is enabled by the trusted serving configuration, not imported app source:

SOCIAL_BROWSER_MODE=local
SOCIAL_BROWSER_EXECUTABLE=/absolute/path/to/installed/Chrome-or-Chromium

An empty mode disables desktop browser admission. An empty executable selects an installed browser from the framework's fixed platform locations. Local mode opens a window on the serving computer and is suitable for a local desktop server; it does not present a remote server's desktop to another device. Unsupported hosts report the capability as unavailable. No browser is downloaded or installed by a connection request.

Browser control uses inherited private pipes without an exposed debugging port. HTTPS browser traffic is limited to provider-owned roots through a public-address-pinned proxy. Browser sandboxing and certificate validation stay enabled. Snapshot intake excludes forms, passwords, drafts, editable content and inline scripts. It retains a bounded partial rendered view and links, never claiming to have scraped a complete feed. The label consumer retains observations privately and passes external public event links to its existing event-validation pipeline; private social prose cannot directly publish gigs. Consumers request their own cadence; event discovery uses twelve hours.

Optional API connection UI

Set connection_mode: api (or omit it for existing API declarations) to select the separate OAuth/API adapter. setup_capability optionally exposes its administrator registration form; it is irrelevant to browser-mode sign-in. Unknown modes fail closed.

Open the app as its owner, expand CRM and select Event sources. Select Connect beside Facebook, Instagram or X. The platform opens in the current browser, where the owner can use their existing sign-in or saved passkey, approve access, and return to the app automatically. No extension, browser installation, personal-profile copy or cookie import is required. Owners do not enter developer credentials, callback URLs or numeric account identifiers. Disconnect revokes the app's authorization independently of the owner's normal website login.

Instagram discovers the professional account authorized through Meta consent. Select one linked professional account in Meta's account permissions. If the permitted list is ambiguous or incomplete, Pharos asks for a narrower selection instead of silently choosing an account. This API requires a professional Instagram account linked to a Facebook Page; it does not support personal Instagram profiles.

For API mode, Pharos must have an operator-owned application registered with each provider and the exact callback URL approved before owners can connect. This setup is separate from each owner's sign-in. If it is missing, the owner sees server setup required; the app does not substitute a fake client ID or promise unavailable access. An app can explicitly expose administrator provisioning with setup_capability. The administrator opens Application setup in the provider card, registers the displayed exact callback with the provider and saves the provider-issued client ID and secret in the app. This encrypted application registration is shared across owners only within that app and deployment. Each owner then authorizes an independent account connection. Initial setup refuses replacement of an existing registration; it cannot silently change identities or rotate registrations underneath existing grants. A provider-issued registration and permission approval are external requirements; the setup form cannot issue them.

| Provider | Read permissions | Resource boundary | | --- | --- | --- | | Facebook | public_profile, pages_show_list, pages_read_engagement | Permitted Pages and posts; managed Page tokens are derived per read. Other Pages can require approved Page Public Content Access. | | Instagram | Facebook permissions above plus instagram_basic | A provider-verified linked professional account and permitted professional artist profiles through business discovery. | | X | tweet.read, users.read, offline.access | Explicit API-resolved accounts and bounded timeline pages allowed by the grant and API product. |

Provider references: Meta Page token flow, Meta Instagram API, X authorization-code flow.

Application setup and account ownership

Application setup selects the provider client and exact callback for an app/deployment, not a human account. The optional setup_capability must name a privileged application role capability, and both it and owner_capability are required for the setup action. Client secrets are write-only and stored in the existing external encrypted vault. Browser input cannot select an application, namespace, owner, secret reference or callback. The server derives callbacks from its mount, including isolated Architect preview routes. Setup is initial-only; a configured registration cannot be overwritten by another setup submission.

A connection belongs to one authenticated local owner, application, deployment namespace, provider and verified external account. Disconnect affects that owner’s grant, not shared application setup or another owner’s connection. collection_owner selects whose grant background collection uses. Preview and installed connections never share namespaces.

Operator registration

Set SOCIAL_CONNECTIONS_CONFIG in the shared runtime configuration to an external JSON registration file. This setting is read from the operator runtime file, never an imported app's source or app.conf. An empty setting disables operator-file registrations. This optional operator file is an alternative to in-app administrator provisioning. Previously saved encrypted owner registrations remain readable for compatibility. With no setup_capability, the app exposes no administrator provisioning operation. With no social contract or enabled: false, it exposes no social connection capability. On Unix the registration file must be owner-only, regular, not a symlink, and at most 64 KiB.

The file contains an array of registrations:

[
  {
    "application": "my_app",
    "namespace": "namespace returned by the authenticated status endpoint with ?format=json",
    "provider": "x",
    "client_id": "operator-registered-client-id",
    "callback": "http://localhost:8084/my-app/__pharos/social/x/callback",
    "revision": "registration-1",
    "client_secret_env": null
  }
]

For Architect previews, the registered callback uses its existing isolated route, for example http://localhost:8084/architect/apps/view?task_id=306&path=%2F__pharos%2Fsocial%2Fx%2Fcallback. Fixed routing query fields remain part of the exact registered URI through consent and token exchange; OAuth response fields and duplicate query keys cannot be prefilled. This follows the registered-query preservation rule in RFC 6749 section 3.1.2.

Register the exact callback URL with X. A confidential client can name an operator-managed environment variable in client_secret_env; do not put the secret itself in the registration file, app source or context. Changing the client, callback or revision invalidates older grants. Each installed or isolated preview namespace needs its own explicit registration entry. An installed app's connection is never borrowed by its preview. Preview access requires the task and project creator; its connection namespace also includes the immutable project identity, so changing accepted source versions preserves connections while recreating an app name does not inherit them.

An operator registers the Pharos application and callback once for the deployment and provider; individual owners authorize their own accounts through that registration. Instagram account IDs need not be configured: the service discovers the authorized professional account during consent. The optional legacy instagram_account_id remains supported for existing operator configurations.

The native mount exposes these relative routes for facebook, instagram and x:

| Method | Path | Purpose | | --- | --- | --- | | GET | /__pharos/social/{provider}/status | Owner-only nonsecret status or the declared component | | POST | /__pharos/social/{provider}/finish | Browser mode only: CSRF-protected automatic completion polling; wait for authentication, checkpoint the framework-owned session and ignore posted cookies | | POST | /__pharos/social/{provider}/setup | Explicit setup capability plus owner/session/origin/CSRF gates; initial encrypted app registration | | POST | /__pharos/social/{provider}/connect | Owner, trusted-origin and CSRF-checked consent | | POST | /__pharos/social/{provider}/cancel | Cancels only the initiating session's pending approval | | GET | /__pharos/social/{provider}/callback | Single-use state/PKCE completion in the initiating session | | POST | /__pharos/social/{provider}/disconnect | Invalidates local authority, cancels collection and revokes the provider grant |

Consent uses top-level navigation in the current browser. Pending consent expires after five minutes and is invalidated by runtime restart or cancellation. A declined or failed consent returns to the app with fixed, session-scoped feedback rather than provider payloads. Feedback remains across refreshes for fifteen minutes or until the next attempt. Completed connections survive restart in the configured database and an external encrypted vault beside runtime state. Access and refresh tokens have no general serialization surface; explicit vault buffers are erased after use. Vault paths, root keys and credentials are excluded from app source exports and builder context.

Disconnect invalidates the durable connection revision before calling the provider. If remote revocation fails, local collection remains disabled and the encrypted credential is retained only for an explicit revocation retry. A concurrent refresh cannot restore the disconnected revision. Reauthorization resumes retained collection cursors only after a successful verified connection.

API collection and consumers

X intake resolves explicit profile handles through its API and reads bounded timeline pages using the connected owner's grant. OAuth permissions are fixed to tweet.read, users.read and offline.access. API authorization to a target resource is required; a local owner login does not grant access to every artist's account.

The generic queue uses durable leases, immutable connection revisions, cancellation guards, retained pagination/high-water marks, rate-limit backoff and idempotent observations. A page's observations and cursor commit together. An incomplete page sequence does not advance the high-water mark. Failed, unavailable, rate-limited and successful-empty feeds remain distinct. Consumers choose their cadence; the event-discovery consumer requests 12 hours.

The label/event-discovery adapter adds public links found in authorized posts to its existing source queue. Its protected public transport enforces HTTPS, public DNS, response/time limits, no redirects and conservative robots policy. Social prose cannot directly create or publish a gig. Public linked pages must supply the existing validated event evidence, performer identity, timezone, date and venue; manually locked gigs remain protected. Artist name, event date and event link remain the public gig projection.

Facebook and Instagram use the shared Meta Facebook Login adapter, fixed Graph API v25.0 endpoints, verified permissions and account identity, long-lived token exchange/renewal and remote permission revocation. Instagram retains business-discovery account identity and opaque media cursors. Exact registered callbacks and approved resource permissions remain necessary for live reads; controlled tests do not certify operator accounts. Live certification is tracked in the implementation plan. Pharos never stores provider passwords or bypasses CAPTCHA/login challenges. Website collection uses the separately scoped browser transport described above.

Federated website sign-in and event evidence

The interactive X/Twitter connection browser permits exact Google sign-in and required asset hosts so its Google sign-in button can open the provider flow. These transport permissions do not expand X cookie export, provider navigation or headless collection authority. Google session state stays inside the retained owner-specific browser profile. Google may require its own verification.

Authorized browser collection waits for hydrated content and samples three bounded rendered views. It prioritizes post links over page navigation and retains structured declarations and post snippets as inert evidence. Concrete Facebook event links observed in the profile can supply up to two additional bounded event views. This remains a partial view rather than a complete feed. Domain consumers determine whether evidence describes events; they must verify public publication rights and complete event facts before publishing it.

Rendered post evidence is accepted only when the loaded profile and the post author match the requested source. Account panels, owner feeds, comments and unattributed cards cannot supply event evidence. Post timestamps are distinct from advertised event dates, including dates read from attached artwork. Poster extraction uses the configured local media helper with bounded image counts, input bytes and execution deadlines; unavailable extraction leaves dates unverified. Old or retrospective posts are excluded from current event review, and incomplete/nonpublic facts retain the existing publication checks.

Connection status fragments use the same application mount scoping as full pages. Embedded HTMX views preserve the initiating app and preview session; a server-root request must not substitute another app namespace. A forbidden request reports a neutral permission failure rather than asserting an ownership mismatch without evidence.