Documentation
Architecture
How Astro, Phoenix, Neon, Oban, and provider adapters fit together in the current TextTree alpha.
Architecture
The public site and the application runtime are deliberately split.
Public surface
Astro owns:
- landing pages
- docs
- blog
- SEO metadata, RSS, and sitemap output
Application surface
Phoenix owns:
- login and authenticated UX
/api/v1/*- signed provider webhooks
/mcp/*
Core state
Neon-backed Postgres is the system of record for:
- Phoenix-native users and TextTree auth identities
- username-backed headless agent accounts
- users and workspaces
- legacy consent/contact metadata
- workspace and campaign suppressions
- messages
- ledger entries and spend limits
- provider events
- funding sessions
- MCP execution audits
Worker ownership
Oban owns side effects and retries.
- outbound messages are queued before provider execution
- provider events are stored before reconciliation
- replay and retry paths stay out of controllers and LiveViews
Execution rules
Outbound messaging follows a strict path:
- workspace suppression check
- spend reservation and ledger write
- Oban enqueue
- provider execution in a worker
That keeps external side effects out of controllers and LiveViews.
Webhook rules
Provider webhooks follow a parallel boundary:
- signature verification
- normalized event persistence
- duplicate detection
- Oban enqueue
- reconciliation into message, suppression, or funding state
MCP rules
The MCP surface is intentionally narrow in alpha:
- public health and headless account bootstrap
- TextTree bearer-token authentication for tool routes
- route-level scope enforcement
- server-registry and allowlist checks
- quota and timeout controls
- persisted execution audit records
Headless account bootstrap through /api/v1/accounts or /mcp/accounts creates
a username/password agent account, one-time backup codes, and a TextTree bearer
token. The bootstrap throttle stores a hashed IP address and allows only one
successful account creation per IP every five minutes.
Provider boundaries
Controllers and LiveViews do not call vendors directly.
- messaging routes talk to the messaging context
- funding routes talk to the funding context
- provider-specific HTTP semantics stay inside provider adapters
- webhook normalization happens before business-state reconciliation
Alpha constraints
This architecture is already usable, but still intentionally incomplete:
- repo-local ADRs capture the current implementation baseline while the canonical canvas import is still pending
- some product surfaces are operator-only rather than public API contracts
- MCP tooling is server-configured, not tenant-configured
- docs describe the implemented alpha contract, not future roadmap promises